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 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. 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 * Any number of Samba shares for you to store your stuff
* Via Docker: * A BitTorrent client
- [Duplicati](https://www.duplicati.com/) for backing up your stuff * Various media management tools - Sonarr, Sickrage, CouchPotato
- [Transmission](https://transmissionbt.com/) BitTorrent client (with OpenVPN if you have a supported VPN provider) * A Docker host with Portainer for image and container management
- [Sonarr](https://sonarr.tv/) for downloading and managing TV episodes * Various ways to see stats about your NAS - Glances, dashboards in Grafana
- [CouchPotato](https://couchpota.to/) for downloading and managing movies * A backup tool - allows scheduled backups to Amazon S3, OneDrive, Dropbox etc
- [Portainer](https://portainer.io/) for managing Docker and running custom images * An IRC bouncer
- [Glances](https://nicolargo.github.io/glances/) for seeing the state of your system via a web browser
### Docker Containers Used
- [CouchPotato](https://couchpota.to/) for downloading and managing movies
- [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 ## 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 That aside, configuring partitions is usually a one-time (or very infrequent) event, so there's not much to be
gained by automating it. gained by automating it.
## Hardware ## Hardware
Ansible NAS should work on any recent Ubuntu box. Development was done on Ubuntu 16.04.3 LTS. 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 ## How To Use
1. `git clone https://www.github.com/davestephens/ansible-nas && cd ansible-nas` 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`. 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. 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 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. 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 ## 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. 1. Disconnect your drives.
2. Run Ansible NAS against your server. 2. Run Ansible NAS against your server.
3. Reconnect your drives. 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. 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 ## TODO
1. Handle Docker containers being enabled then subsequently disabled (i.e clean up afterwards) 1. Handle Docker containers being enabled then subsequently disabled (i.e clean up afterwards)
2. SMART disk monitoring 2. SMART disk monitoring
## Contributing ## Contributing
Contributions welcome!
Contributions welcome, please feel free to raise a PR!

View file

@ -11,11 +11,13 @@ transmission_enabled: false
# Media Sourcing # Media Sourcing
sonarr_enabled: true sonarr_enabled: true
sickrage_enabled: false
couchpotato_enabled: false couchpotato_enabled: false
# System Management # System Management
portainer_enabled: true portainer_enabled: true
glances_enabled: true glances_enabled: true
stats_enabled: true
# Backup & Restore # Backup & Restore
crashplan_enabled: true 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 # Will be added to the docker group to give user command line access to docker
ansible_nas_user: david ansible_nas_user: david
###
### Docker
###
# Where you want Docker to store its images and container data. # Where you want Docker to store its images and container data.
docker_home: /mnt/Volume2/docker 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 ### Samba
@ -178,6 +186,7 @@ portainer_data_directory: "{{ docker_home }}/portainer/config"
crashplan_user_id: 0 crashplan_user_id: 0
crashplan_group_id: 0 crashplan_group_id: 0
### ###
### ZNC ### ZNC
### ###
@ -189,6 +198,7 @@ znc_group_id: 0
### ###
### Stats ### Stats
### ###
telegraf_config_directory: "{{ docker_home }}/telegraf" telegraf_data_directory: "{{ docker_home }}/telegraf"
influxdb_data_directory: "{{ docker_home }}/influxdb" influxdb_data_directory: "{{ docker_home }}/influxdb"
grafana_data_directory: "{{ docker_home }}/grafana"
stat_collection_interval: 15s stat_collection_interval: 15s

54
nas.yml
View file

@ -1,6 +1,54 @@
--- ---
- hosts: all - 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: roles:
- samba - bertvv.samba
- docker - geerlingguy.docker
- ansible-nas

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 - name: update docker home from install default
template: template:
src: daemon.json src: docker/daemon.json
dest: /etc/docker/daemon.json dest: /etc/docker/daemon.json
register: docker_config register: docker_config

View file

@ -15,7 +15,8 @@
- "8200:8200" - "8200:8200"
volumes: volumes:
- "{{ duplicati_data_directory }}:/config:rw" - "{{ 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" - "/etc/timezone:/etc/timezone:ro"
restart_policy: unless-stopped restart_policy: unless-stopped
memory: 4g memory: 1g

View file

@ -10,6 +10,8 @@
#- "/glances.conf:/glances/conf/glances.conf" #- "/glances.conf:/glances/conf/glances.conf"
- "/var/run/docker.sock:/var/run/docker.sock:ro" - "/var/run/docker.sock:/var/run/docker.sock:ro"
- "/etc/timezone:/etc/timezone:ro" - "/etc/timezone:/etc/timezone:ro"
pid_mode: host
network_mode: host
env: env:
GLANCES_OPT: "-w" GLANCES_OPT: "-w"
restart_policy: unless-stopped 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 ## Multiple URLs can be specified for a single cluster, only ONE of the
## urls will be written to each interval. ## 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 = ["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. ## 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 ## 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 ## Telegraf with a user without permissions to create databases or when the
@ -224,53 +224,51 @@
[[inputs.system]] [[inputs.system]]
# no configuration # 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 ## Only collect metrics for these containers, collect all if empty
# [[inputs.docker]] container_names = []
# ## Docker Endpoint
# ## To use TCP, set endpoint = "tcp://[ip]:[port]" ## Containers to include and exclude. Globs accepted.
# ## To use environment variables (ie, docker-machine), set endpoint = "ENV" ## Note that an empty array for both will include all containers
# endpoint = "unix:///var/run/docker.sock" container_name_include = []
# container_name_exclude = []
# ## Set to true to collect Swarm metrics(desired_replicas, running_replicas)
# gather_services = false ## Container states to include and exclude. Globs accepted.
# ## When empty only containers in the "running" state will be captured.
# ## Only collect metrics for these containers, collect all if empty # container_state_include = []
# container_names = [] # container_state_exclude = []
#
# ## Containers to include and exclude. Globs accepted. ## Timeout for docker list, info, and stats commands
# ## Note that an empty array for both will include all containers timeout = "5s"
# container_name_include = []
# container_name_exclude = [] ## Whether to report for each container per-device blkio (8:0, 8:1...) and
# ## network (eth0, eth1, ...) stats or not
# ## Container states to include and exclude. Globs accepted. perdevice = true
# ## When empty only containers in the "running" state will be captured. ## Whether to report for each container total blkio and network stats or not
# # container_state_include = [] total = false
# # container_state_exclude = [] ## Which environment variables should we use as a tag
# ##tag_env = ["JAVA_HOME", "HEAP_SIZE"]
# ## Timeout for docker list, info, and stats commands
# timeout = "5s" ## docker labels to include and exclude as tags. Globs accepted.
# ## Note that an empty array for both will include all labels as tags
# ## Whether to report for each container per-device blkio (8:0, 8:1...) and docker_label_include = []
# ## network (eth0, eth1, ...) stats or not docker_label_exclude = []
# perdevice = true
# ## Whether to report for each container total blkio and network stats or not ## Optional SSL Config
# total = false # ssl_ca = "/etc/telegraf/ca.pem"
# ## Which environment variables should we use as a tag # ssl_cert = "/etc/telegraf/cert.pem"
# ##tag_env = ["JAVA_HOME", "HEAP_SIZE"] # ssl_key = "/etc/telegraf/key.pem"
# ## Use SSL but skip chain & host verification
# ## docker labels to include and exclude as tags. Globs accepted. # insecure_skip_verify = false
# ## 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 # # 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