Major updates:

* Rejig of playbook to load roles from ansible-galaxy
* Remove ansible-nas role and move tasks to tasks directory
* Add InfluxDB/Telegraf/Grafana for monitoring & dashboards
* Automatically configure InfluxDB as a datasource in Grafana
* Switch to ZFS Docker driver
* Add note about switching to overlay2 Docker driver if required
This commit is contained in:
David Stephens 2018-04-08 23:29:25 +01:00
parent 9d589811b6
commit 3da19e5708
30 changed files with 372 additions and 165 deletions

6
.gitmodules vendored
View file

@ -1,6 +0,0 @@
[submodule "roles/samba"]
path = roles/samba
url = https://github.com/bertvv/ansible-role-samba.git
[submodule "roles/docker"]
path = roles/docker
url = https://github.com/geerlingguy/ansible-role-docker.git

25
.vscode/tasks.json vendored Normal file
View file

@ -0,0 +1,25 @@
{
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
"version": "2.0.0",
"tasks": [
{
"label": "Run Playbook",
"type": "shell",
"command": "ansible-playbook -i inventory nas.yml -b -K",
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "Run Playbook Syntax Check",
"type": "shell",
"command": "ansible-playbook -i inventory nas.yml --syntax-check",
"group": {
"kind": "test",
"isDefault": true
}
}
]
}

View file

@ -3,16 +3,31 @@
After getting burned by broken FreeNAS updates one too many times, I figured I could do a much better job myself using
just a stock Ubuntu install, some clever Ansible config and a bunch of docker containers.
## What This Provides
## What This Sets Up
* Any number of Samba shares for you to store your stuff
* Via Docker:
- [Duplicati](https://www.duplicati.com/) for backing up your stuff
- [Transmission](https://transmissionbt.com/) BitTorrent client (with OpenVPN if you have a supported VPN provider)
- [Sonarr](https://sonarr.tv/) for downloading and managing TV episodes
* A BitTorrent client
* Various media management tools - Sonarr, Sickrage, CouchPotato
* A Docker host with Portainer for image and container management
* Various ways to see stats about your NAS - Glances, dashboards in Grafana
* A backup tool - allows scheduled backups to Amazon S3, OneDrive, Dropbox etc
* An IRC bouncer
### Docker Containers Used
- [CouchPotato](https://couchpota.to/) for downloading and managing movies
- [Portainer](https://portainer.io/) for managing Docker and running custom images
- [Duplicati](https://www.duplicati.com/) for backing up your stuff
- [Glances](https://nicolargo.github.io/glances/) for seeing the state of your system via a web browser
- [Grafana](https://github.com/grafana/grafana) - Dashboarding tool
- [InfluxDB](https://github.com/influxdata/influxdb) - Time series database used for stats collection
- [Portainer](https://portainer.io/) for managing Docker and running custom images
- [Sickrage](https://sickrage.github.io/) for managing TV episodes
- [Sonarr](https://sonarr.tv/) for downloading and managing TV episodes
- [Telegraf](https://github.com/influxdata/telegraf) - Metrics collection agent
- [Transmission](https://transmissionbt.com/) BitTorrent client (with OpenVPN if you have a supported VPN provider)
- [ZNC](https://wiki.znc.in/ZNC) - IRC bouncer to stay connected to favourite IRC networks and channels
## What This Doesn't Do
@ -20,19 +35,25 @@ Ansible NAS doesn't set up your disk partitions, primarily because getting it wr
That aside, configuring partitions is usually a one-time (or very infrequent) event, so there's not much to be
gained by automating it.
## Hardware
Ansible NAS should work on any recent Ubuntu box. Development was done on Ubuntu 16.04.3 LTS.
**TODO:** Test against a Raspberry Pi!
## How To Use
1. `git clone https://www.github.com/davestephens/ansible-nas && cd ansible-nas`
2. Copy `group_vars/all.yml.dist` to `group_vars/all.yml`.
3. Open up `group_vars/all.yml` and follow the instructions there for configuring your Ansible NAS.
3. If you plan to use Transmission with OpenVPN, also copy `group_vars/vpn_credentials.yml.dist` to
`group_vars/vpn_credentials.yml` and input your settings.
`group_vars/vpn_credentials.yml` and fill in your settings.
4. Modify `inventory` and update it with the hostname of your NAS box.
5. Run the playbook - something like `ansible-playbook -i inventory nas.yml` should do you nicely.
5. Install the dependent roles: `sudo ansible-galaxy install -r requirements.yml`
5. Run the playbook - something like `ansible-playbook -i inventory nas.yml -b -K` should do you nicely.
## Migrating from FreeNAS
@ -41,13 +62,16 @@ Assuming that your Ubuntu system disk is separate from your storage (it should b
1. Disconnect your drives.
2. Run Ansible NAS against your server.
3. Reconnect your drives.
4. SSH to the server and run `zpool import` to determine available ZFS pools.
4. SSH to the server and run `zpool list` to determine available ZFS pools.
5. `zpool import <pool_name>` against the pools you want to attach.
6. `chown -R root:root /mnt/<volume>` to fix the ownership of the data
6. `chown -R root:root /mnt/<pool_name>` to fix the ownership of the data
## TODO
1. Handle Docker containers being enabled then subsequently disabled (i.e clean up afterwards)
2. SMART disk monitoring
## Contributing
Contributions welcome!
Contributions welcome, please feel free to raise a PR!

View file

@ -11,11 +11,13 @@ transmission_enabled: false
# Media Sourcing
sonarr_enabled: true
sickrage_enabled: false
couchpotato_enabled: false
# System Management
portainer_enabled: true
glances_enabled: true
stats_enabled: true
# Backup & Restore
crashplan_enabled: true
@ -37,9 +39,15 @@ keep_packages_updated: false
# Will be added to the docker group to give user command line access to docker
ansible_nas_user: david
###
### Docker
###
# Where you want Docker to store its images and container data.
docker_home: /mnt/Volume2/docker
# Docker storage driver, see https://docs.docker.com/storage/storagedriver/select-storage-driver/#supported-backing-filesystems
# You'll need to change this if your docker_home isn't on zfs and Docker won't start (you'll probably want overlay2 instead)
docker_storage_driver: zfs
###
### Samba
@ -178,6 +186,7 @@ portainer_data_directory: "{{ docker_home }}/portainer/config"
crashplan_user_id: 0
crashplan_group_id: 0
###
### ZNC
###
@ -189,6 +198,7 @@ znc_group_id: 0
###
### Stats
###
telegraf_config_directory: "{{ docker_home }}/telegraf"
telegraf_data_directory: "{{ docker_home }}/telegraf"
influxdb_data_directory: "{{ docker_home }}/influxdb"
grafana_data_directory: "{{ docker_home }}/grafana"
stat_collection_interval: 15s

54
nas.yml
View file

@ -1,6 +1,54 @@
---
- hosts: all
tasks:
- import_tasks: tasks/general.yml
- import_tasks: tasks/docker.yml
- import_tasks: tasks/portainer.yml
when: portainer_enabled == true
tags: portainer
- import_tasks: tasks/transmission.yml
when: transmission_enabled == true
tags: transmission
- import_tasks: tasks/transmission_with_openvpn.yml
when: transmission_with_openvpn_enabled == true
tags: transmission
- import_tasks: tasks/sonarr.yml
when: sonarr_enabled == true
tags: sonarr
- import_tasks: tasks/glances.yml
when: glances_enabled == true
tags: glances
- import_tasks: tasks/duplicati.yml
when: duplicati_enabled == true
tags: duplicati
- import_tasks: tasks/crashplan.yml
when: crashplan_enabled == true
tags: crashplan
- import_tasks: tasks/couchpotato.yml
when: couchpotato_enabled == true
tags: couchpotato
- import_tasks: tasks/sickrage.yml
when: sickrage_enabled == true
tags: sickrage
- import_tasks: tasks/znc.yml
when: znc_enabled == true
tags: znc
- import_tasks: tasks/stats.yml
when: stats_enabled == true
tags: stats
roles:
- samba
- docker
- ansible-nas
- bertvv.samba
- geerlingguy.docker

6
requirements.yml Normal file
View file

@ -0,0 +1,6 @@
---
- name: geerlingguy.docker
version: 2.1.0
- name: bertvv.samba
version: v2.5.0

View file

@ -1,48 +0,0 @@
---
- include: general.yml
- include: docker.yml
- include: portainer.yml
when: portainer_enabled == true
tags: portainer
- include: transmission.yml
when: transmission_enabled == true
tags: transmission
- include: transmission_with_openvpn.yml
when: transmission_with_openvpn_enabled == true
tags: transmission
- include: sonarr.yml
when: sonarr_enabled == true
tags: sonarr
- include: glances.yml
when: glances_enabled == true
tags: glances
- include: duplicati.yml
when: duplicati_enabled == true
tags: duplicati
- include: crashplan.yml
when: crashplan_enabled == true
tags: crashplan
- include: couchpotato.yml
when: couchpotato_enabled == true
tags: couchpotato
- include: sickrage.yml
when: sickrage_enabled == true
tags: sickrage
- include: znc.yml
when: znc_enabled == true
tags: znc
- include: stats.yml
when: stats_enabled == true
tags: stats

View file

@ -1,35 +0,0 @@
---
- name: Create Directories
file:
path: "{{ item }}"
state: directory
with_items:
- "{{ influxdb_data_directory }}"
- "{{ telegraf_config_directory }}"
- name: influxdb
docker_container:
name: influxdb
image: influxdb
pull: true
volumes:
- "{{ influxdb_data_directory }}:/var/lib/influxdb:rw"
- "8086:8086"
restart_policy: unless-stopped
memory: 1g
- name: Template telegraf.conf
template:
src: telegraf.conf
dest: "{{ telegraf_config_directory }}/telegraf.conf"
- name: telegraf
docker_container:
name: telegraf
image: telegraf
pull: true
volumes:
- "{{ telegraf_config_directory }}/telegraf.conf:/etc/telegraf/telegraf.conf:ro"
restart_policy: unless-stopped
memory: 1g

View file

@ -1,4 +0,0 @@
{
"graph": "{{ docker_home }}/data",
"storage-driver": "overlay"
}

@ -1 +0,0 @@
Subproject commit 0960f85f784fbaaee9d2e3a606db7899542ae2fb

@ -1 +0,0 @@
Subproject commit b63ca1385ee8e186b6b4060778b780512c6a37d7

View file

@ -23,7 +23,7 @@
- name: update docker home from install default
template:
src: daemon.json
src: docker/daemon.json
dest: /etc/docker/daemon.json
register: docker_config

View file

@ -15,7 +15,8 @@
- "8200:8200"
volumes:
- "{{ duplicati_data_directory }}:/config:rw"
- "{{ samba_shares_root }}:/source:ro"
- "{{ samba_shares_root }}:/source/shares:ro"
- "{{ docker_home }}:/source/docker:ro"
- "/etc/timezone:/etc/timezone:ro"
restart_policy: unless-stopped
memory: 4g
memory: 1g

View file

@ -10,6 +10,8 @@
#- "/glances.conf:/glances/conf/glances.conf"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/etc/timezone:/etc/timezone:ro"
pid_mode: host
network_mode: host
env:
GLANCES_OPT: "-w"
restart_policy: unless-stopped

59
tasks/stats.yml Normal file
View file

@ -0,0 +1,59 @@
---
- name: Create Directories
file:
path: "{{ item }}"
state: directory
with_items:
- "{{ influxdb_data_directory }}"
- "{{ grafana_data_directory }}"
- "{{ grafana_data_directory }}/data"
- "{{ grafana_data_directory }}/config/"
- "{{ grafana_data_directory }}/config/provisioning"
- "{{ grafana_data_directory }}/config/provisioning/datasources"
- "{{ telegraf_data_directory }}"
- name: InfluxDB
docker_container:
name: influxdb
image: influxdb
pull: true
volumes:
- "{{ influxdb_data_directory }}:/var/lib/influxdb:rw"
ports:
- "8086:8086"
restart_policy: unless-stopped
memory: 1g
- name: Template telegraf.conf
template:
src: telegraf/telegraf.conf
dest: "{{ telegraf_data_directory }}/telegraf.conf"
- name: Telegraf
docker_container:
name: telegraf
image: telegraf
pull: true
volumes:
- "{{ telegraf_data_directory }}/telegraf.conf:/etc/telegraf/telegraf.conf:ro"
- "/var/run/docker.sock:/var/run/docker.sock:ro"
restart_policy: unless-stopped
memory: 1g
- name: Template Grafana data source
template:
src: grafana/provisioning/datasources/ansible-nas.yml
dest: "{{ grafana_data_directory }}/config/provisioning/datasources/ansible-nas.yml"
- name: Grafana
docker_container:
name: grafana
image: grafana/grafana
pull: true
volumes:
- "{{ grafana_data_directory }}/data:/var/lib/grafana:rw"
- "{{ grafana_data_directory }}/config/provisioning:/etc/grafana/provisioning:ro"
ports:
- "3000:3000"
restart_policy: unless-stopped
memory: 1g

View file

@ -0,0 +1,4 @@
{
"data-root": "{{ docker_home }}/data",
"storage-driver": "{{ docker_storage_driver }}"
}

View file

@ -0,0 +1,43 @@
# config file version
apiVersion: 1
# list of datasources to insert/update depending
# whats available in the database
datasources:
# <string, required> name of the datasource. Required
- name: InfluxDB
# <string, required> datasource type. Required
type: influxdb
# <string, required> access mode. direct or proxy. Required
access: direct
# <int> org id. will default to orgId 1 if not specified
orgId: 1
# <string> url
url: http://{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}:8086
# <string> database password, if used
password:
# <string> database user, if used
user:
# <string> database name, if used
database: telegraf
# <bool> enable/disable basic auth
basicAuth:
# <string> basic auth username
basicAuthUser:
# <string> basic auth password
basicAuthPassword:
# <bool> enable/disable with credentials headers
withCredentials:
# <bool> mark as default datasource. Max one per org
isDefault: true
# <map> fields that will be converted to json and stored in json_data
jsonData:
timeInterval: "15s"
# <string> json object of data that will be encrypted.
secureJsonData:
tlsCACert: "..."
tlsClientCert: "..."
tlsClientKey: "..."
version: 1
# <bool> allow users to edit datasources from the UI.
editable: false

View file

@ -88,12 +88,12 @@
##
## Multiple URLs can be specified for a single cluster, only ONE of the
## urls will be written to each interval.
urls = ["unix:///var/run/influxdb.sock"]
# urls = ["unix:///var/run/influxdb.sock"]
# urls = ["udp://127.0.0.1:8089"]
urls = ["http://{{ ansible_nas_hostname }}:8086"]
urls = ["http://{{ hostvars[inventory_hostname]['ansible_default_ipv4']['address'] }}:8086"]
## The target database for metrics; will be created as needed.
# database = "telegraf"
database = "telegraf"
## If true, no CREATE DATABASE queries will be sent. Set to true when using
## Telegraf with a user without permissions to create databases or when the
@ -224,53 +224,51 @@
[[inputs.system]]
# no configuration
# Read metrics about docker containers
[[inputs.docker]]
## Docker Endpoint
## To use TCP, set endpoint = "tcp://[ip]:[port]"
## To use environment variables (ie, docker-machine), set endpoint = "ENV"
endpoint = "unix:///var/run/docker.sock"
## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
gather_services = false
# # Read metrics about docker containers
# [[inputs.docker]]
# ## Docker Endpoint
# ## To use TCP, set endpoint = "tcp://[ip]:[port]"
# ## To use environment variables (ie, docker-machine), set endpoint = "ENV"
# endpoint = "unix:///var/run/docker.sock"
#
# ## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
# gather_services = false
#
# ## Only collect metrics for these containers, collect all if empty
# container_names = []
#
# ## Containers to include and exclude. Globs accepted.
# ## Note that an empty array for both will include all containers
# container_name_include = []
# container_name_exclude = []
#
# ## Container states to include and exclude. Globs accepted.
# ## When empty only containers in the "running" state will be captured.
# # container_state_include = []
# # container_state_exclude = []
#
# ## Timeout for docker list, info, and stats commands
# timeout = "5s"
#
# ## Whether to report for each container per-device blkio (8:0, 8:1...) and
# ## network (eth0, eth1, ...) stats or not
# perdevice = true
# ## Whether to report for each container total blkio and network stats or not
# total = false
# ## Which environment variables should we use as a tag
# ##tag_env = ["JAVA_HOME", "HEAP_SIZE"]
#
# ## docker labels to include and exclude as tags. Globs accepted.
# ## Note that an empty array for both will include all labels as tags
# docker_label_include = []
# docker_label_exclude = []
#
# ## Optional SSL Config
# # ssl_ca = "/etc/telegraf/ca.pem"
# # ssl_cert = "/etc/telegraf/cert.pem"
# # ssl_key = "/etc/telegraf/key.pem"
# ## Use SSL but skip chain & host verification
# # insecure_skip_verify = false
## Only collect metrics for these containers, collect all if empty
container_names = []
## Containers to include and exclude. Globs accepted.
## Note that an empty array for both will include all containers
container_name_include = []
container_name_exclude = []
## Container states to include and exclude. Globs accepted.
## When empty only containers in the "running" state will be captured.
# container_state_include = []
# container_state_exclude = []
## Timeout for docker list, info, and stats commands
timeout = "5s"
## Whether to report for each container per-device blkio (8:0, 8:1...) and
## network (eth0, eth1, ...) stats or not
perdevice = true
## Whether to report for each container total blkio and network stats or not
total = false
## Which environment variables should we use as a tag
##tag_env = ["JAVA_HOME", "HEAP_SIZE"]
## docker labels to include and exclude as tags. Globs accepted.
## Note that an empty array for both will include all labels as tags
docker_label_include = []
docker_label_exclude = []
## Optional SSL Config
# ssl_ca = "/etc/telegraf/ca.pem"
# ssl_cert = "/etc/telegraf/cert.pem"
# ssl_key = "/etc/telegraf/key.pem"
## Use SSL but skip chain & host verification
# insecure_skip_verify = false
# # Monitor disks' temperatures using hddtemp

82
tests/test.sh Executable file
View file

@ -0,0 +1,82 @@
#!/bin/bash
#
# Ansible playbook tester.
# Based on geerlingguy's Ansible role tester.
#
# Usage: [OPTIONS] .tests/test.sh
# - distro: a supported Docker distro version (default = "ubuntu1604")
# - playbook: a playbook in the tests directory (default = "test.yml")
# - cleanup: whether to remove the Docker container (default = true)
# - container_id: the --name to set for the container (default = timestamp)
# - test_idempotence: whether to test playbook's idempotence (default = true)
#
# License: MIT
# Exit on any individual command failure.
set -e
# Pretty colors.
red='\033[0;31m'
green='\033[0;32m'
neutral='\033[0m'
timestamp=$(date +%s)
# Allow environment variables to override defaults.
distro=${distro:-"ubuntu1604"}
docker_owner=${docker_owner:-"geerlingguy"}
playbook=${playbook:-"nas.yml"}
cleanup=${cleanup:-"true"}
container_id=${container_id:-$timestamp}
test_idempotence=${test_idempotence:-"false"}
init="/lib/systemd/systemd"
opts="--privileged --volume=/sys/fs/cgroup:/sys/fs/cgroup:ro"
skip_tags="ruby,packages"
# Run the container using the supplied OS.
printf ${green}"Starting Docker container: $docker_owner/docker-$distro-ansible."${neutral}"\n"
docker pull $docker_owner/docker-$distro-ansible:latest
docker run --detach --volume="$PWD":/etc/ansible/playbooks/playbook_under_test:rw --name $container_id $opts $docker_owner/docker-$distro-ansible:latest $init
printf "\n"
# Install requirements if `requirements.yml` is present.
if [ -f "$PWD/requirements.yml" ]; then
printf ${green}"Requirements file detected; installing dependencies."${neutral}"\n"
docker exec --tty $container_id env TERM=xterm ansible-galaxy install -r /etc/ansible/playbooks/playbook_under_test/requirements.yml
fi
printf "\n"
# Output Ansible version
printf ${green}"Checking Ansible version."${neutral}"\n"
docker exec --tty $container_id env TERM=xterm ansible-playbook --version
printf "\n"
# Test Ansible syntax.
printf ${green}"Checking Ansible playbook syntax."${neutral}"\n"
docker exec --tty $container_id env TERM=xterm ansible-playbook /etc/ansible/playbooks/playbook_under_test/$playbook --syntax-check
printf "\n"
# Run Ansible playbook.
printf ${green}"Running command: docker exec $container_id env TERM=xterm ansible-playbook /etc/ansible/playbooks/playbook_under_test/$playbook"${neutral}"\n"
docker exec $container_id env TERM=xterm env ANSIBLE_FORCE_COLOR=1 ansible-playbook /etc/ansible/playbooks/playbook_under_test/$playbook --skip-tags $skip_tags
if [ "$test_idempotence" = true ]; then
# Run Ansible playbook again (idempotence test).
printf ${green}"Running playbook again: idempotence test"${neutral}
idempotence=$(mktemp)
docker exec $container_id ansible-playbook /etc/ansible/playbooks/playbook_under_test/$playbook --skip-tags $skip_tags | tee -a $idempotence
tail $idempotence \
| grep -q 'changed=0.*failed=0' \
&& (printf ${green}'Idempotence test: pass'${neutral}"\n") \
|| (printf ${red}'Idempotence test: fail'${neutral}"\n" && exit 1)
fi
# Remove the Docker container (if configured).
if [ "$cleanup" = true ]; then
printf "Removing Docker container...\n"
docker rm -f $container_id
fi