deploy hermes
This commit is contained in:
@@ -1,34 +0,0 @@
|
||||
# file: group_vars/prometheus_server/vars
|
||||
# Ansible vars template for Prometheus nodes created via Ansible\.
|
||||
|
||||
# Version of Prometheus to install. All available versions can be found at: https://prometheus.io/download/
|
||||
|
||||
#prometheus_version: "3.5.0"
|
||||
#alertmanager_version: "0.28.1"
|
||||
|
||||
# prometheus_nodes:
|
||||
# # - '192.168.1.207' # Worker node IP
|
||||
# - cinderella-castle # Master node IP
|
||||
# - 10.1.71.21
|
||||
# - cinderella-castle.local.mk-labs.cloud
|
||||
|
||||
# Static Variables
|
||||
# prometheus_installer_file: "prometheus-{{ prometheus_version }}.linux-amd64.tar.gz"
|
||||
# prometheus_installer_download_url: "https://github.com/prometheus/prometheus/releases/download/v{{ prometheus_version }}/{{ prometheus_installer_file }}"
|
||||
|
||||
# Grafana Variables
|
||||
|
||||
#grafana_port: "3000"
|
||||
#grafana_url: "{{ inventory_hostname }}.{{ base_domain }}:3000"
|
||||
|
||||
# prometheus_targets:
|
||||
# node:
|
||||
# - targets:
|
||||
# - localhost:9100
|
||||
# labels:
|
||||
# env: mk-labs
|
||||
|
||||
grafana_ini:
|
||||
security:
|
||||
admin_user: admin
|
||||
admin_password: admin
|
||||
@@ -1,77 +0,0 @@
|
||||
---
|
||||
# Variables for Unbound https://github.com/aruhier/ansible-role-unbound
|
||||
|
||||
### Server ###
|
||||
|
||||
unbound_chroot: '""'
|
||||
unbound_num_threads: 2
|
||||
unbound_interfaces:
|
||||
- "{{ ip_address }}"
|
||||
#unbound_do_ip4: "yes"
|
||||
unbound_do_ip6: "no"
|
||||
#unbound_do_udp: "yes"
|
||||
#unbound_do_tcp: "yes"
|
||||
|
||||
# list of strings, as the order impact how policies are interpreted
|
||||
unbound_outgoing_policies: []
|
||||
|
||||
unbound_access_control:
|
||||
- '192.168.1.0/24 allow'
|
||||
- '192.168.3.0/24 allow'
|
||||
- '192.168.5.0/24 allow'
|
||||
- '192.168.10.0/24 allow'
|
||||
- '10.1.71.0/24 allow'
|
||||
|
||||
unbound_access_control_tag: []
|
||||
unbound_access_control_tag_action: []
|
||||
unbound_access_control_tag_data: []
|
||||
unbound_access_control_view: []
|
||||
|
||||
unbound_private_addresses:
|
||||
- '192.168.1.0/24'
|
||||
- '192.168.3.0/24'
|
||||
- '192.168.5.0/24'
|
||||
- '192.168.10.0/24'
|
||||
- '10.1.71.0/24'
|
||||
|
||||
unbound_private_domains:
|
||||
- 'mk-labs.cloud'
|
||||
|
||||
unbound_domains_insecure: []
|
||||
unbound_do_not_query_addresses: []
|
||||
unbound_local_zones: []
|
||||
unbound_local_datas: []
|
||||
unbound_local_data_ptrs: []
|
||||
unbound_local_zone_tags: []
|
||||
unbound_local_zone_overrides: []
|
||||
|
||||
unbound_trust_anchors: []
|
||||
unbound_trusted_keys_files: []
|
||||
|
||||
### Remote Control ###
|
||||
|
||||
unbound_control_enable: "no"
|
||||
unbound_control_interface: []
|
||||
|
||||
### Stub, forward zones and others ###
|
||||
|
||||
unbound_rpz_zones: [] # list of dicts
|
||||
unbound_stub_zones: [] # list of dicts
|
||||
unbound_forward_zones:
|
||||
- name: 'int.mk-labs.cloud'
|
||||
forward-addr:
|
||||
- '10.1.71.5'
|
||||
- name: '.'
|
||||
forward-addr:
|
||||
- '1.0.0.1@53#one.one.one.one'
|
||||
- '1.1.1.1@53#one.one.one.one'
|
||||
|
||||
# For stub and forward zones, if a key inside one of the dict is an iterable,
|
||||
# it will iterate inside it to duplicate the option with all the contained
|
||||
# values.
|
||||
# Can be useful in case of multiple forward-addr for a same zone:
|
||||
# Example:
|
||||
# unbound_forward_zones:
|
||||
# - {name: "test.tld", "forward-addr": ["192.0.2.5", "192.0.2.6"]}
|
||||
|
||||
unbound_views: [] # list of strings, as options can be multiples
|
||||
25
ansible/host_vars/astro-orbiter/vars.yml
Normal file
25
ansible/host_vars/astro-orbiter/vars.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/host_vars/astro_orbiter/vars.yml
|
||||
# HOST: astro-orbiter (10.1.71.130)
|
||||
# ROLE: Ollama inference host with AMD RX 5700 GPU passthrough
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
ansible_host: 10.1.71.130
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
# LVM root expansion — xlarge template uses sda3 partition, standard VG/LV names
|
||||
common_expand_root_lvm: true
|
||||
common_root_pv: /dev/sda3
|
||||
common_root_vg: ubuntu-vg
|
||||
common_root_lv: ubuntu-lv
|
||||
|
||||
# Ollama — all defaults apply; explicitly documented here for visibility
|
||||
ollama_rocm_version: "6.2"
|
||||
ollama_default_model: "qwen3:8b"
|
||||
ollama_hsa_override_gfx_version: "10.1.0"
|
||||
ollama_data_disk: /dev/sdb
|
||||
ollama_data_vg: ollama-vg
|
||||
ollama_data_lv: ollama-lv
|
||||
ollama_data_dir: /var/lib/ollama
|
||||
@@ -1,49 +0,0 @@
|
||||
---
|
||||
# file: host_vars/vm_template/vars
|
||||
# Ansible vars template for hosts created via cloning.
|
||||
|
||||
# Supported hypervisors:
|
||||
# - Proxmox
|
||||
|
||||
platform: "proxmox"
|
||||
|
||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
||||
|
||||
proxmox_clone_node: "fantasyland"
|
||||
|
||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
||||
# Current OS offerings are:
|
||||
# - Fedora (42)
|
||||
# Current VM sizes are:
|
||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
||||
# - Xlarge Plus: 4 cores, 8GB memory, 128 GiB virtual disk
|
||||
|
||||
vm_clone_source: "fedora-42-xlarge-plus"
|
||||
|
||||
# Proxmox storage target.
|
||||
|
||||
vm_storage: "general"
|
||||
|
||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
||||
|
||||
ha_group: "fantasyland"
|
||||
proxmox_host_target: "fantasyland"
|
||||
|
||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
||||
|
||||
ip_address: 10.1.71.21
|
||||
|
||||
# Software
|
||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
||||
# the VM after creation.
|
||||
|
||||
#terraform_version: "1.11.3"
|
||||
|
||||
# ---
|
||||
|
||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
||||
@@ -1,17 +0,0 @@
|
||||
---
|
||||
# file: host_vars/monitor/vars
|
||||
|
||||
# VM Definition
|
||||
platform: "proxmox"
|
||||
proxmox_clone_node: "pve03"
|
||||
vm_clone_source: "fedora-42-large"
|
||||
vm_storage: "pve-general"
|
||||
ha_group: "pve03"
|
||||
|
||||
# Networking
|
||||
ip_address: 10.1.71.249
|
||||
|
||||
# Software
|
||||
#terraform_version: "1.11.3"
|
||||
|
||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" #change variable to fqdn
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
# file: host_vars/unbound01/vars
|
||||
|
||||
# Supported hypervisors:
|
||||
# - Proxmox
|
||||
|
||||
platform: "proxmox"
|
||||
|
||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
||||
|
||||
proxmox_clone_node: "pve03"
|
||||
|
||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
||||
# Current OS offerings are:
|
||||
# - Fedora (42)
|
||||
# Current VM sizes are:
|
||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
||||
|
||||
vm_clone_source: "fedora-42-small"
|
||||
|
||||
# Proxmox storage target.
|
||||
|
||||
vm_storage: "pve-general"
|
||||
|
||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
||||
|
||||
ha_group: "pve03"
|
||||
proxmox_host_target: "pve03"
|
||||
|
||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
||||
|
||||
ip_address: 10.1.71.251
|
||||
|
||||
# Software
|
||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
||||
# the VM after creation.
|
||||
|
||||
#terraform_version: "1.11.3"
|
||||
|
||||
# ---
|
||||
|
||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
||||
@@ -1,47 +0,0 @@
|
||||
---
|
||||
# file: host_vars/unbound02/vars
|
||||
|
||||
# Supported hypervisors:
|
||||
# - Proxmox
|
||||
|
||||
platform: "proxmox"
|
||||
|
||||
# This is the Proxmox node where all the VM templates are stored. (Templates are not global.)
|
||||
|
||||
proxmox_clone_node: "pve03"
|
||||
|
||||
# Templates are named in the following format (all lower case): <OS Distribution>-<OS Version>-<VM Size>
|
||||
# Current OS offerings are:
|
||||
# - Fedora (42)
|
||||
# Current VM sizes are:
|
||||
# - Small: 2 cores, 2GB memory, 8 GiB virtual disk
|
||||
# - Medium: 2 cores, 4GB memory, 16 GiB virtual disk
|
||||
# - Large: 4 cores, 4GB memory, 32 GiB virtual disk
|
||||
# - Xlarge: 4 cores, 8GB memory, 64 GiB virtual disk
|
||||
|
||||
vm_clone_source: "fedora-42-small"
|
||||
|
||||
# Proxmox storage target.
|
||||
|
||||
vm_storage: "pve-general"
|
||||
|
||||
# Proxmox does not yet do dynamic load balancing, the host target sets the target for HA groups
|
||||
# and backup groups. (ha_group will be factored out in the next functionality update.)
|
||||
|
||||
ha_group: "pve03"
|
||||
proxmox_host_target: "pve03"
|
||||
|
||||
# Currently, only single NIC VMs using IPv4 are supported via cloning. The IP address also
|
||||
# sets the Proxmox VMID. The VMID is a combination of the 3rd and 4th octet of the IPv4 address.
|
||||
|
||||
ip_address: 10.1.71.252
|
||||
|
||||
# Software
|
||||
# Future enhancement will allow specification of additional software to automatically deploy to
|
||||
# the VM after creation.
|
||||
|
||||
#terraform_version: "1.11.3"
|
||||
|
||||
# ---
|
||||
|
||||
hostname: "{{ inventory_hostname }}.{{ base_domain }}" # Change variable to fqdn
|
||||
@@ -58,25 +58,13 @@ n8n_server:
|
||||
hosts:
|
||||
tiki-room:
|
||||
|
||||
monitoring_server:
|
||||
ollama_server:
|
||||
hosts:
|
||||
cinderella-castle:
|
||||
ansible_host: 10.1.71.31
|
||||
astro-orbiter:
|
||||
ansible_host: 10.1.71.130
|
||||
ansible_user: wed
|
||||
ansible_become: true
|
||||
|
||||
# node_explorer:
|
||||
# hosts:
|
||||
# cinderella-castle:
|
||||
|
||||
# alertmanager:
|
||||
# hosts:
|
||||
# cinderella-castle:
|
||||
|
||||
# grafana:
|
||||
# hosts:
|
||||
# cinderella-castle:
|
||||
|
||||
papermc_server:
|
||||
# ansible-galaxy role install engonzal.papermc
|
||||
hosts:
|
||||
|
||||
400
ansible/playbooks/day1_configure_netbox_catalog.yml
Normal file
400
ansible/playbooks/day1_configure_netbox_catalog.yml
Normal file
@@ -0,0 +1,400 @@
|
||||
---
|
||||
# ansible/playbooks/day1_configure_netbox_catalog.yml
|
||||
#
|
||||
# Creates the tag taxonomy and custom fields in NetBox for service catalog
|
||||
# documentation. Run once (idempotent — uses name-based checks).
|
||||
#
|
||||
# Usage (from ansible/ directory):
|
||||
# ansible-playbook playbooks/day1_configure_netbox_catalog.yml
|
||||
#
|
||||
# Requires:
|
||||
# - vault_netbox_token in Ansible Vault
|
||||
# - netbox reachable at http://fire-station.local.mk-labs.cloud
|
||||
|
||||
- name: Configure NetBox service catalog taxonomy
|
||||
hosts: localhost
|
||||
gather_facts: false
|
||||
|
||||
vars_files:
|
||||
- "{{ playbook_dir }}/../group_vars/all/vault"
|
||||
|
||||
vars:
|
||||
netbox_url: "http://fire-station.local.mk-labs.cloud"
|
||||
netbox_token: "{{ vault_netbox_token }}"
|
||||
netbox_api: "{{ netbox_url }}/api"
|
||||
headers:
|
||||
Authorization: "Token {{ netbox_token }}"
|
||||
Content-Type: "application/json"
|
||||
Accept: "application/json"
|
||||
|
||||
# ── Tag definitions ───────────────────────────────────────────────────
|
||||
tags:
|
||||
# Infrastructure type
|
||||
- name: k8s
|
||||
slug: k8s
|
||||
color: "2196f3" # blue
|
||||
description: "Workload running in the fastpass Kubernetes cluster"
|
||||
- name: vm
|
||||
slug: vm
|
||||
color: "4caf50" # green
|
||||
description: "Traditional VM or LXC on Proxmox"
|
||||
# Service tier
|
||||
- name: platform
|
||||
slug: platform
|
||||
color: "9c27b0" # purple
|
||||
description: "Platform/infrastructure service (not user-facing)"
|
||||
- name: application
|
||||
slug: application
|
||||
color: "ff9800" # orange
|
||||
description: "User-facing application workload"
|
||||
# Service categories
|
||||
- name: monitoring
|
||||
slug: monitoring
|
||||
color: "607d8b" # grey
|
||||
description: "Metrics, logging, alerting"
|
||||
- name: auth
|
||||
slug: auth
|
||||
color: "607d8b"
|
||||
description: "Authentication and SSO"
|
||||
- name: gitops
|
||||
slug: gitops
|
||||
color: "607d8b"
|
||||
description: "GitOps and CI/CD"
|
||||
- name: dashboard
|
||||
slug: dashboard
|
||||
color: "607d8b"
|
||||
description: "Dashboard and portal services"
|
||||
- name: storage
|
||||
slug: storage
|
||||
color: "607d8b"
|
||||
description: "Storage and file services"
|
||||
- name: dns
|
||||
slug: dns
|
||||
color: "607d8b"
|
||||
description: "DNS and name resolution"
|
||||
- name: automation
|
||||
slug: automation
|
||||
color: "607d8b"
|
||||
description: "Automation and orchestration"
|
||||
- name: networking
|
||||
slug: networking
|
||||
color: "607d8b"
|
||||
description: "Network infrastructure services"
|
||||
- name: inference
|
||||
slug: inference
|
||||
color: "607d8b"
|
||||
description: "AI/ML inference workloads"
|
||||
|
||||
# ── Custom field definitions ──────────────────────────────────────────
|
||||
# object_types use app_label.model format
|
||||
custom_fields:
|
||||
- name: hostnames
|
||||
label: Hostnames
|
||||
type: longtext
|
||||
object_types:
|
||||
- ipam.ipaddress
|
||||
- virtualization.virtualmachine
|
||||
description: "All DNS names that resolve to this service (comma-separated)"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
|
||||
- name: namespace
|
||||
label: Namespace
|
||||
type: text
|
||||
object_types:
|
||||
- ipam.ipaddress
|
||||
- virtualization.virtualmachine
|
||||
description: "Kubernetes namespace (blank for VM-based services)"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
|
||||
- name: managed_by
|
||||
label: Managed By
|
||||
type: select
|
||||
object_types:
|
||||
- ipam.ipaddress
|
||||
- virtualization.virtualmachine
|
||||
description: "How this service is managed"
|
||||
choices:
|
||||
- ArgoCD
|
||||
- Ansible
|
||||
- Manual
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
|
||||
- name: thematic_name
|
||||
label: Thematic Name
|
||||
type: text
|
||||
object_types:
|
||||
- ipam.ipaddress
|
||||
- virtualization.virtualmachine
|
||||
description: "Disney/Magic Kingdom thematic hostname for this service"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
|
||||
tasks:
|
||||
|
||||
# ── Tags ─────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Fetch existing tags
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/tags/?limit=200"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: existing_tags_response
|
||||
|
||||
- name: Set existing tag slugs fact
|
||||
ansible.builtin.set_fact:
|
||||
existing_tag_slugs: "{{ existing_tags_response.json.results | map(attribute='slug') | list }}"
|
||||
|
||||
- name: Create tags
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/tags/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: "{{ item.name }}"
|
||||
slug: "{{ item.slug }}"
|
||||
color: "{{ item.color }}"
|
||||
description: "{{ item.description }}"
|
||||
status_code: 201
|
||||
loop: "{{ tags }}"
|
||||
when: item.slug not in existing_tag_slugs
|
||||
register: tag_creation
|
||||
changed_when: tag_creation.status == 201
|
||||
|
||||
# ── Custom Fields ─────────────────────────────────────────────────────
|
||||
|
||||
- name: Fetch existing custom fields
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/?limit=200"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: existing_cf_response
|
||||
|
||||
- name: Set existing custom field names fact
|
||||
ansible.builtin.set_fact:
|
||||
existing_cf_names: "{{ existing_cf_response.json.results | map(attribute='name') | list }}"
|
||||
|
||||
- name: Create custom field — hostnames
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: hostnames
|
||||
label: Hostnames
|
||||
type: longtext
|
||||
object_types: "{{ custom_fields[0].object_types }}"
|
||||
description: "{{ custom_fields[0].description }}"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'hostnames' not in existing_cf_names"
|
||||
register: cf_hostnames
|
||||
changed_when: cf_hostnames.status == 201
|
||||
|
||||
- name: Create custom field — namespace
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: namespace
|
||||
label: Namespace
|
||||
type: text
|
||||
object_types: "{{ custom_fields[1].object_types }}"
|
||||
description: "{{ custom_fields[1].description }}"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'namespace' not in existing_cf_names"
|
||||
register: cf_namespace
|
||||
changed_when: cf_namespace.status == 201
|
||||
|
||||
- name: Create choice set for managed_by field
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-field-choice-sets/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: managed-by-choices
|
||||
extra_choices:
|
||||
- - ArgoCD
|
||||
- ArgoCD
|
||||
- - Ansible
|
||||
- Ansible
|
||||
- - Manual
|
||||
- Manual
|
||||
status_code: [201, 400]
|
||||
register: choice_set
|
||||
changed_when: choice_set.status == 201
|
||||
|
||||
- name: Fetch choice set ID
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=managed-by-choices"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: choice_set_response
|
||||
|
||||
- name: Create custom field — managed_by
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: managed_by
|
||||
label: Managed By
|
||||
type: select
|
||||
object_types: "{{ custom_fields[2].object_types }}"
|
||||
description: "{{ custom_fields[2].description }}"
|
||||
choice_set: "{{ choice_set_response.json.results[0].id }}"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'managed_by' not in existing_cf_names"
|
||||
register: cf_managed_by
|
||||
changed_when: cf_managed_by.status == 201
|
||||
|
||||
- name: Create custom field — thematic_name
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: thematic_name
|
||||
label: Thematic Name
|
||||
type: text
|
||||
object_types: "{{ custom_fields[3].object_types }}"
|
||||
description: "{{ custom_fields[3].description }}"
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'thematic_name' not in existing_cf_names"
|
||||
register: cf_thematic_name
|
||||
changed_when: cf_thematic_name.status == 201
|
||||
|
||||
# ── Provisioning Pipeline Fields ──────────────────────────────────────
|
||||
# These fields drive the NetBox → n8n → Terraform pipeline.
|
||||
# proxmox_datastore already exists — PATCH it to add utilidor choice.
|
||||
# data_disk_enabled and data_disk_size_gb are new POSTs.
|
||||
|
||||
- name: Fetch proxmox_datastore field ID
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/?name=proxmox_datastore"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: proxmox_datastore_cf_response
|
||||
|
||||
- name: Fetch proxmox-datastore-choices choice set ID
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=proxmox-datastore-choices"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: datastore_choice_set_response
|
||||
|
||||
- name: Create proxmox-datastore-choices choice set if missing
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-field-choice-sets/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: proxmox-datastore-choices
|
||||
extra_choices:
|
||||
- - liberty-tree
|
||||
- liberty-tree
|
||||
- - utilidor
|
||||
- utilidor
|
||||
status_code: [201, 400]
|
||||
register: datastore_choice_set_create
|
||||
changed_when: datastore_choice_set_create.status == 201
|
||||
when: datastore_choice_set_response.json.count == 0
|
||||
|
||||
- name: Re-fetch proxmox-datastore-choices choice set ID after possible creation
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-field-choice-sets/?name=proxmox-datastore-choices"
|
||||
method: GET
|
||||
headers: "{{ headers }}"
|
||||
return_content: true
|
||||
status_code: 200
|
||||
register: datastore_choice_set_response
|
||||
|
||||
- name: Patch proxmox_datastore field to use choice set with utilidor
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/{{ proxmox_datastore_cf_response.json.results[0].id }}/"
|
||||
method: PATCH
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
choice_set: "{{ datastore_choice_set_response.json.results[0].id }}"
|
||||
status_code: 200
|
||||
when: proxmox_datastore_cf_response.json.count > 0
|
||||
register: cf_datastore_patch
|
||||
changed_when: cf_datastore_patch.status == 200
|
||||
|
||||
- name: Create custom field — data_disk_enabled
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: data_disk_enabled
|
||||
label: Data Disk Enabled
|
||||
type: boolean
|
||||
object_types:
|
||||
- virtualization.virtualmachine
|
||||
description: "When true, Terraform provisions a second disk for application data storage."
|
||||
default: false
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'data_disk_enabled' not in existing_cf_names"
|
||||
register: cf_data_disk_enabled
|
||||
changed_when: cf_data_disk_enabled.status == 201
|
||||
|
||||
- name: Create custom field — data_disk_size_gb
|
||||
ansible.builtin.uri:
|
||||
url: "{{ netbox_api }}/extras/custom-fields/"
|
||||
method: POST
|
||||
headers: "{{ headers }}"
|
||||
body_format: json
|
||||
body:
|
||||
name: data_disk_size_gb
|
||||
label: Data Disk Size (GB)
|
||||
type: integer
|
||||
object_types:
|
||||
- virtualization.virtualmachine
|
||||
description: "Size in GB for the optional second data disk. Only used when data_disk_enabled is true."
|
||||
ui_visible: always
|
||||
ui_editable: yes
|
||||
status_code: 201
|
||||
when: "'data_disk_size_gb' not in existing_cf_names"
|
||||
register: cf_data_disk_size
|
||||
changed_when: cf_data_disk_size.status == 201
|
||||
|
||||
# ── Summary ───────────────────────────────────────────────────────────
|
||||
|
||||
- name: Summary
|
||||
ansible.builtin.debug:
|
||||
msg:
|
||||
- "Tags created: {{ tag_creation.results | selectattr('status', 'equalto', 201) | list | length }}"
|
||||
- "Tags skipped (already exist): {{ tag_creation.results | selectattr('skipped', 'defined') | list | length }}"
|
||||
- "Custom fields configured: hostnames, namespace, managed_by, thematic_name, proxmox_datastore (patched), data_disk_enabled, data_disk_size_gb"
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: playbooks/day1_deploy_monitoring.yml
|
||||
# DESCRIPTION: Deploys Prometheus + Grafana + Node Exporter + Alertmanager
|
||||
# on cinderella-castle (10.1.71.31)
|
||||
# Runs: common → docker-host → monitoring
|
||||
#
|
||||
# USAGE:
|
||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_monitoring.yml
|
||||
# ansible-playbook -i inventory.yml playbooks/day1_deploy_monitoring.yml --limit cinderella_castle
|
||||
#
|
||||
# SECRETS REQUIRED IN VAULT (group_vars/all/vault):
|
||||
# vault_grafana_admin_password
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy monitoring stack on cinderella-castle
|
||||
hosts: cinderella-castle
|
||||
become: true
|
||||
|
||||
vars:
|
||||
grafana_admin_password: "{{ vault_grafana_admin_password }}"
|
||||
|
||||
roles:
|
||||
- role: common
|
||||
- role: docker-host
|
||||
- role: monitoring
|
||||
35
ansible/playbooks/day1_deploy_ollama.yml
Normal file
35
ansible/playbooks/day1_deploy_ollama.yml
Normal file
@@ -0,0 +1,35 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/day1_deploy_ollama.yml
|
||||
# DESCRIPTION: Deploys Ollama with ROCm GPU acceleration on astro-orbiter.
|
||||
# Assumes day0_baseline.yml has already run (common role complete).
|
||||
# PCIe passthrough for the RX 5700 must be configured in Proxmox
|
||||
# and the GPU must be visible to the VM before running this playbook.
|
||||
#
|
||||
# Pre-flight check:
|
||||
# ssh wed@astro-orbiter 'lspci | grep -i amd'
|
||||
# Should show the RX 5700 before proceeding.
|
||||
#
|
||||
# Usage (from ansible/ directory):
|
||||
# ansible-playbook playbooks/day1_deploy_ollama.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Deploy Ollama with ROCm on astro-orbiter
|
||||
hosts: astro-orbiter
|
||||
become: true
|
||||
|
||||
pre_tasks:
|
||||
- name: Verify AMD GPU is visible to the VM
|
||||
command: lspci
|
||||
register: lspci_output
|
||||
changed_when: false
|
||||
|
||||
- name: Fail if no AMD GPU detected
|
||||
fail:
|
||||
msg: >
|
||||
No AMD GPU detected via lspci. Verify PCIe passthrough is configured
|
||||
in Proxmox and the RX 5700 is visible to the VM before proceeding.
|
||||
when: "'AMD' not in lspci_output.stdout and 'Radeon' not in lspci_output.stdout"
|
||||
|
||||
roles:
|
||||
- ollama
|
||||
7
ansible/playbooks/day1_docker.yml
Normal file
7
ansible/playbooks/day1_docker.yml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
- name: Apply common role
|
||||
hosts: "{{ target | default('all') }}"
|
||||
become: true
|
||||
roles:
|
||||
- common
|
||||
- docker-host
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
common_timezone: America/Chicago
|
||||
|
||||
common_ntp_server: 10.1.71.33 # sundial
|
||||
common_ntp_server: "sundial.local.mk-labs.cloud"
|
||||
|
||||
common_packages:
|
||||
- curl
|
||||
@@ -20,3 +20,15 @@ common_packages:
|
||||
- lsb-release
|
||||
- net-tools
|
||||
- dnsutils
|
||||
- lvm2
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# LVM root volume expansion
|
||||
# Override common_root_pv/vg/lv per host if the template uses different names.
|
||||
# Ubuntu 24.04 cloud templates use ubuntu-vg/ubuntu-lv by default.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
common_expand_root_lvm: true
|
||||
common_root_pv: /dev/sda3 # Ubuntu cloud-init partition layout
|
||||
common_root_vg: ubuntu-vg
|
||||
common_root_lv: ubuntu-lv
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart chrony
|
||||
systemd:
|
||||
name: chrony
|
||||
state: restarted
|
||||
|
||||
- name: restart sshd
|
||||
systemd:
|
||||
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
|
||||
state: restarted
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/common/tasks/main.yml
|
||||
# DESCRIPTION: Baseline configuration applied to all managed hosts.
|
||||
# Handles hostname, timezone, core packages, NTP, and
|
||||
# ansible user setup.
|
||||
# DESCRIPTION: Baseline configuration applied to all managed Ubuntu hosts.
|
||||
# Handles hostname, timezone, core packages, NTP, ansible user,
|
||||
# and optional LVM root volume expansion.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Set hostname
|
||||
@@ -10,7 +11,7 @@
|
||||
name: "{{ inventory_hostname | replace('_', '-') }}"
|
||||
|
||||
- name: Set timezone
|
||||
community.general.timezone:
|
||||
timezone:
|
||||
name: "{{ common_timezone }}"
|
||||
|
||||
- name: Update apt cache
|
||||
@@ -41,7 +42,7 @@
|
||||
state: started
|
||||
enabled: yes
|
||||
|
||||
- name: Ensure ansible user has sudo without password
|
||||
- name: Ensure ansible user has passwordless sudo
|
||||
lineinfile:
|
||||
path: /etc/sudoers.d/{{ ansible_user }}
|
||||
line: "{{ ansible_user }} ALL=(ALL) NOPASSWD:ALL"
|
||||
@@ -49,33 +50,28 @@
|
||||
mode: '0440'
|
||||
validate: 'visudo -cf %s'
|
||||
|
||||
# --- Step-CA SSH certificate enrollment ---
|
||||
- name: Enroll host in step-ca SSH CA
|
||||
include_tasks: step_ca_client.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
# LVM root volume expansion
|
||||
# Extends the root PV to the full disk size and grows the LV + filesystem.
|
||||
# This codifies what was previously done manually after provisioning.
|
||||
# Runs only when common_expand_root_lvm is true (default: true).
|
||||
# Safe to re-run — pvresize and lvextend are idempotent when already at max.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Create authorized principals directory
|
||||
ansible.builtin.file:
|
||||
path: /etc/ssh/auth_principals
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0755'
|
||||
- name: Expand root PV to full disk size
|
||||
command: pvresize {{ common_root_pv }}
|
||||
register: pvresize_result
|
||||
changed_when: "'changed' in pvresize_result.stdout or pvresize_result.rc == 0"
|
||||
when: common_expand_root_lvm | bool
|
||||
|
||||
- name: Create authorized principals files
|
||||
ansible.builtin.copy:
|
||||
content: "{{ item.principals | join('\n') }}\n"
|
||||
dest: "/etc/ssh/auth_principals/{{ item.local_user }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: '0644'
|
||||
loop: "{{ step_ca_principal_mappings }}"
|
||||
notify: restart sshd
|
||||
|
||||
- name: Configure sshd AuthorizedPrincipalsFile
|
||||
ansible.builtin.lineinfile:
|
||||
path: /etc/ssh/sshd_config
|
||||
line: "AuthorizedPrincipalsFile /etc/ssh/auth_principals/%u"
|
||||
regexp: "^#?AuthorizedPrincipalsFile"
|
||||
state: present
|
||||
notify: restart sshd
|
||||
|
||||
- name: Extend root LV to 100% of free VG space
|
||||
lvol:
|
||||
vg: "{{ common_root_vg }}"
|
||||
lv: "{{ common_root_lv }}"
|
||||
size: +100%FREE
|
||||
resizefs: yes
|
||||
when:
|
||||
- common_expand_root_lvm | bool
|
||||
ignore_errors: yes
|
||||
# ignore_errors because lvextend returns non-zero when already at max size.
|
||||
# resizefs: yes handles the resize2fs call inline — no separate task needed.
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# Directory layout on cinderella-castle
|
||||
monitoring_base_dir: /opt/docker/monitoring
|
||||
|
||||
# Data disk — LVM on /dev/sdb, mounted at this path for Prometheus TSDB
|
||||
prometheus_data_dir: /var/lib/prometheus
|
||||
prometheus_vg: prometheus-vg
|
||||
prometheus_lv: prometheus-lv
|
||||
|
||||
# Prometheus retention
|
||||
prometheus_retention_time: 15d
|
||||
prometheus_retention_size: 58GB # 64GB disk, leaving headroom
|
||||
|
||||
# Scrape interval
|
||||
prometheus_scrape_interval: 30s
|
||||
|
||||
# Grafana
|
||||
grafana_port: 3000
|
||||
grafana_admin_user: admin
|
||||
|
||||
# Service DNS (via Traefik)
|
||||
grafana_hostname: grafana.local.mk-labs.cloud
|
||||
|
||||
# Trusted proxy (lightning-lane)
|
||||
trusted_proxy_ip: 10.1.71.35
|
||||
|
||||
# Prometheus port (internal, not exposed via Traefik)
|
||||
prometheus_port: 9090
|
||||
|
||||
# Alertmanager port (internal)
|
||||
alertmanager_port: 9093
|
||||
|
||||
# Node exporter port
|
||||
node_exporter_port: 9100
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: restart monitoring
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ monitoring_base_dir }}"
|
||||
state: present
|
||||
recreate: always
|
||||
@@ -1,203 +0,0 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/tasks/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ── Data disk (LVM) ───────────────────────────────────────────────────────────
|
||||
|
||||
- name: Install LVM2
|
||||
ansible.builtin.apt:
|
||||
name: lvm2
|
||||
state: present
|
||||
update_cache: true
|
||||
|
||||
- name: Check if PV already exists on /dev/sdb
|
||||
ansible.builtin.command: pvs /dev/sdb
|
||||
register: pvs_result
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Check if disk has a partition table signature
|
||||
ansible.builtin.command: wipefs /dev/sdb
|
||||
register: wipefs_check
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
when: pvs_result.rc != 0
|
||||
|
||||
- name: Wipe GPT protective partition signature from /dev/sdb
|
||||
ansible.builtin.command: wipefs -a /dev/sdb
|
||||
when:
|
||||
- pvs_result.rc != 0
|
||||
- wipefs_check.stdout | length > 0
|
||||
|
||||
- name: Create LVM volume group on /dev/sdb
|
||||
community.general.lvg:
|
||||
vg: prometheus-vg
|
||||
pvs: /dev/sdb
|
||||
state: present
|
||||
when: pvs_result.rc != 0
|
||||
|
||||
- name: Check if logical volume already exists
|
||||
ansible.builtin.command: lvs /dev/prometheus-vg/prometheus-lv
|
||||
register: lvs_result
|
||||
changed_when: false
|
||||
failed_when: false
|
||||
|
||||
- name: Create logical volume for Prometheus TSDB
|
||||
community.general.lvol:
|
||||
vg: prometheus-vg
|
||||
lv: prometheus-lv
|
||||
size: 100%FREE
|
||||
state: present
|
||||
shrink: false
|
||||
when: lvs_result.rc != 0
|
||||
|
||||
- name: Format logical volume as ext4
|
||||
community.general.filesystem:
|
||||
fstype: ext4
|
||||
dev: /dev/prometheus-vg/prometheus-lv
|
||||
|
||||
- name: Create Prometheus data directory mount point
|
||||
ansible.builtin.file:
|
||||
path: "{{ prometheus_data_dir }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Mount Prometheus logical volume
|
||||
ansible.posix.mount:
|
||||
path: "{{ prometheus_data_dir }}"
|
||||
src: /dev/prometheus-vg/prometheus-lv
|
||||
fstype: ext4
|
||||
opts: defaults
|
||||
state: mounted
|
||||
|
||||
- name: Set ownership of Prometheus data directory
|
||||
ansible.builtin.file:
|
||||
path: "{{ prometheus_data_dir }}"
|
||||
state: directory
|
||||
owner: "65534" # nobody — Prometheus container runs as this UID
|
||||
group: "65534"
|
||||
mode: "0755"
|
||||
|
||||
# ── Directory layout ───────────────────────────────────────────────────────────
|
||||
|
||||
- name: Create monitoring directory structure
|
||||
ansible.builtin.file:
|
||||
path: "{{ item }}"
|
||||
state: directory
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0775"
|
||||
loop:
|
||||
- "{{ monitoring_base_dir }}"
|
||||
- "{{ monitoring_base_dir }}/prometheus"
|
||||
- "{{ monitoring_base_dir }}/grafana"
|
||||
- "{{ monitoring_base_dir }}/alertmanager"
|
||||
- "{{ monitoring_base_dir }}/pve-exporter"
|
||||
- "{{ monitoring_base_dir }}/snmp-exporter"
|
||||
|
||||
# ── Config files ───────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Deploy Prometheus configuration
|
||||
ansible.builtin.template:
|
||||
src: prometheus.yaml.j2
|
||||
dest: "{{ monitoring_base_dir }}/prometheus/prometheus.yaml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: restart monitoring
|
||||
|
||||
- name: Deploy Alertmanager configuration
|
||||
ansible.builtin.template:
|
||||
src: alertmanager.yml.j2
|
||||
dest: "{{ monitoring_base_dir }}/alertmanager/alertmanager.yml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: restart monitoring
|
||||
|
||||
- name: Deploy Grafana provisioning datasource
|
||||
ansible.builtin.template:
|
||||
src: grafana-datasource.yml.j2
|
||||
dest: "{{ monitoring_base_dir }}/grafana/datasource.yml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: restart monitoring
|
||||
|
||||
- name: Deploy PVE exporter credentials file
|
||||
ansible.builtin.template:
|
||||
src: pve.yml.j2
|
||||
dest: "{{ monitoring_base_dir }}/pve-exporter/pve.yml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0644"
|
||||
no_log: true
|
||||
notify: restart monitoring
|
||||
|
||||
# ── SNMP exporter config ───────────────────────────────────────────────────────
|
||||
# Extract the default snmp.yml from the image (contains full if_mib metrics
|
||||
# mapping) then inject the mk-labs community auth into the auths section.
|
||||
|
||||
- name: Extract default snmp.yml from snmp-exporter image
|
||||
ansible.builtin.shell:
|
||||
cmd: >
|
||||
docker run --rm --entrypoint cat prom/snmp-exporter:latest
|
||||
/etc/snmp_exporter/snmp.yml
|
||||
> {{ monitoring_base_dir }}/snmp-exporter/snmp.yml
|
||||
changed_when: true
|
||||
|
||||
- name: Inject mk_labs_v2c auth into snmp.yml
|
||||
ansible.builtin.shell:
|
||||
cmd: |
|
||||
python3 - << 'EOF'
|
||||
path = '{{ monitoring_base_dir }}/snmp-exporter/snmp.yml'
|
||||
with open(path, 'r') as f:
|
||||
content = f.read()
|
||||
auth = (
|
||||
' mk_labs_v2c:\n'
|
||||
' community: {{ vault_snmp_community }}\n'
|
||||
' security_level: noAuthNoPriv\n'
|
||||
' auth_protocol: MD5\n'
|
||||
' priv_protocol: DES\n'
|
||||
' version: 2\n'
|
||||
)
|
||||
if 'mk_labs_v2c' not in content:
|
||||
content = content.replace('\nmodules:\n', '\n' + auth + 'modules:\n', 1)
|
||||
with open(path, 'w') as f:
|
||||
f.write(content)
|
||||
print('injected')
|
||||
else:
|
||||
print('already present')
|
||||
EOF
|
||||
register: snmp_inject
|
||||
changed_when: "'injected' in snmp_inject.stdout"
|
||||
no_log: true
|
||||
notify: restart monitoring
|
||||
|
||||
# ── Compose stack ──────────────────────────────────────────────────────────────
|
||||
|
||||
- name: Deploy monitoring compose file from boilerplate
|
||||
ansible.builtin.copy:
|
||||
src: "{{ playbook_dir }}/../../boilerplates/monitoring/compose.yaml"
|
||||
dest: "{{ monitoring_base_dir }}/compose.yaml"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0644"
|
||||
notify: restart monitoring
|
||||
|
||||
- name: Deploy .env with secrets from vault
|
||||
ansible.builtin.template:
|
||||
src: env.j2
|
||||
dest: "{{ monitoring_base_dir }}/.env"
|
||||
owner: "{{ ansible_user }}"
|
||||
group: docker
|
||||
mode: "0600"
|
||||
notify: restart monitoring
|
||||
|
||||
- name: Start monitoring stack
|
||||
community.docker.docker_compose_v2:
|
||||
project_src: "{{ monitoring_base_dir }}"
|
||||
state: present
|
||||
@@ -1,13 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/templates/alertmanager.yml.j2
|
||||
# DESCRIPTION: Alertmanager configuration — routing deferred (P.XX)
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
route:
|
||||
receiver: blackhole
|
||||
group_wait: 30s
|
||||
group_interval: 5m
|
||||
repeat_interval: 4h
|
||||
|
||||
receivers:
|
||||
- name: blackhole
|
||||
@@ -1,11 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/templates/env.j2
|
||||
# DESCRIPTION: Environment file for monitoring compose stack
|
||||
# Deployed to {{ monitoring_base_dir }}/.env
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
GRAFANA_ADMIN_USER={{ grafana_admin_user }}
|
||||
GRAFANA_ADMIN_PASSWORD={{ grafana_admin_password }}
|
||||
PROMETHEUS_RETENTION_TIME={{ prometheus_retention_time }}
|
||||
PROMETHEUS_RETENTION_SIZE={{ prometheus_retention_size }}
|
||||
TRUSTED_PROXIES={{ trusted_proxy_ip }}
|
||||
@@ -1,14 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/monitoring/templates/grafana-datasource.yml.j2
|
||||
# DESCRIPTION: Auto-provisions Prometheus as Grafana default datasource
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
apiVersion: 1
|
||||
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://prometheus:9090
|
||||
isDefault: true
|
||||
editable: false
|
||||
@@ -1,205 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/monitoring/templates/prometheus.yaml.j2
|
||||
# DESCRIPTION: Prometheus scrape configuration
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
global:
|
||||
scrape_interval: 30s
|
||||
evaluation_interval: 30s
|
||||
|
||||
alerting:
|
||||
alertmanagers:
|
||||
- static_configs:
|
||||
- targets:
|
||||
- alertmanager:9093
|
||||
|
||||
rule_files: []
|
||||
|
||||
scrape_configs:
|
||||
|
||||
# Prometheus self-monitoring
|
||||
- job_name: prometheus
|
||||
static_configs:
|
||||
- targets:
|
||||
- localhost:9090
|
||||
|
||||
# cinderella-castle node exporter (this host)
|
||||
- job_name: node-cinderella-castle
|
||||
static_configs:
|
||||
- targets:
|
||||
- 10.1.71.31:9100
|
||||
labels:
|
||||
hostname: cinderella-castle
|
||||
|
||||
# Proxmox nodes (node exporter on each hypervisor)
|
||||
- job_name: proxmox-nodes
|
||||
static_configs:
|
||||
- targets:
|
||||
- 10.1.71.11:9100
|
||||
- 10.1.71.12:9100
|
||||
- 10.1.71.13:9100
|
||||
labels:
|
||||
job: proxmox
|
||||
|
||||
# Traefik metrics
|
||||
- job_name: traefik
|
||||
static_configs:
|
||||
- targets:
|
||||
- 10.1.71.35:8080
|
||||
labels:
|
||||
hostname: lightning-lane
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Proxmox VE Exporter — cluster-level metrics (VM states, storage, CPU)
|
||||
# Uses the multi-target pattern: Prometheus passes the cluster address as a
|
||||
# query parameter to the exporter, which proxies to the Proxmox API.
|
||||
# One scrape job covers all three nodes via the cluster API endpoint.
|
||||
# --------------------------------------------------------------------------
|
||||
- job_name: proxmox-cluster
|
||||
metrics_path: /pve
|
||||
params:
|
||||
module: [default]
|
||||
cluster: ["1"]
|
||||
node: [""]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 10.1.71.11 # main-street-usa — cluster API endpoint
|
||||
labels:
|
||||
cluster: magic-kingdom
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: pve-exporter:9221
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# Pure Storage FlashArray -- utilidor (DEFERRED until Purity 6.7+)
|
||||
#
|
||||
# When utilidor is upgraded to Purity 6.7+, uncomment this job and remove
|
||||
# the legacy exporter container. No container needed -- Prometheus scrapes
|
||||
# the native OpenMetrics endpoint on the array directly over HTTPS.
|
||||
#
|
||||
# Also add vault_pure_utilidor_api_token to group_vars/all/vault and create
|
||||
# a read-only API token on utilidor under System -> Users -> API Tokens.
|
||||
# --------------------------------------------------------------------------
|
||||
# - job_name: pure-flasharray-utilidor
|
||||
# metrics_path: /metrics/array
|
||||
# scheme: https
|
||||
# tls_config:
|
||||
# insecure_skip_verify: true
|
||||
# authorization:
|
||||
# credentials: {{ vault_pure_utilidor_api_token }}
|
||||
# params:
|
||||
# namespace: ["purefa"]
|
||||
# static_configs:
|
||||
# - targets:
|
||||
# - utilidor.local.mk-labs.cloud
|
||||
# labels:
|
||||
# array: utilidor
|
||||
# array_type: physical
|
||||
|
||||
# --------------------------------------------------------------------------
|
||||
# UniFi Network — SNMP scrape jobs (one per device for clean Grafana filtering)
|
||||
# All targets use the if_mib module for interface throughput, errors, and
|
||||
# up/down state. Auth uses the mk_labs_v2c community string from vault.
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
- job_name: snmp-udm-pro
|
||||
metrics_path: /snmp
|
||||
params:
|
||||
auth: [mk_labs_v2c]
|
||||
module: [if_mib]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.1
|
||||
labels:
|
||||
device: world-drive
|
||||
model: UDM-Pro
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: 10.1.71.31:9116
|
||||
|
||||
- job_name: snmp-usw-pro-aggregation
|
||||
metrics_path: /snmp
|
||||
params:
|
||||
auth: [mk_labs_v2c]
|
||||
module: [if_mib]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.123
|
||||
labels:
|
||||
device: usw-pro-aggregation
|
||||
model: USW-Pro-Aggregation
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: 10.1.71.31:9116
|
||||
|
||||
- job_name: snmp-usw-pro-max-24
|
||||
metrics_path: /snmp
|
||||
scrape_interval: 60s
|
||||
scrape_timeout: 55s
|
||||
params:
|
||||
auth: [mk_labs_v2c]
|
||||
module: [if_mib]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.226
|
||||
labels:
|
||||
device: usw-pro-max-24
|
||||
model: USW-Pro-Max-24
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: 10.1.71.31:9116
|
||||
|
||||
- job_name: snmp-usw-lite-16-poe
|
||||
metrics_path: /snmp
|
||||
params:
|
||||
auth: [mk_labs_v2c]
|
||||
module: [if_mib]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.136
|
||||
labels:
|
||||
device: usw-lite-16-poe
|
||||
model: USW-Lite-16-PoE
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: 10.1.71.31:9116
|
||||
|
||||
- job_name: snmp-ups-tower
|
||||
metrics_path: /snmp
|
||||
params:
|
||||
auth: [mk_labs_v2c]
|
||||
module: [if_mib]
|
||||
static_configs:
|
||||
- targets:
|
||||
- 192.168.1.233
|
||||
labels:
|
||||
device: ups-tower
|
||||
model: UPS-Tower
|
||||
relabel_configs:
|
||||
- source_labels: [__address__]
|
||||
target_label: __param_target
|
||||
- source_labels: [__param_target]
|
||||
target_label: instance
|
||||
- target_label: __address__
|
||||
replacement: 10.1.71.31:9116
|
||||
@@ -1,11 +0,0 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: ansible/playbooks/roles/monitoring/templates/pve.yml.j2
|
||||
# DESCRIPTION: Proxmox VE Exporter credentials file.
|
||||
# Mounted read-only into the pve-exporter container.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
default:
|
||||
user: prometheus@pve
|
||||
token_name: prometheus-token
|
||||
token_value: {{ vault_proxmox_prometheus_token }}
|
||||
verify_ssl: false
|
||||
28
ansible/playbooks/roles/ollama/defaults/main.yml
Normal file
28
ansible/playbooks/roles/ollama/defaults/main.yml
Normal file
@@ -0,0 +1,28 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/defaults/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ROCm
|
||||
ollama_rocm_version: "6.2" # ROCm release stream — update when upgrading
|
||||
ollama_rocm_packages:
|
||||
- hip-runtime-amd # Pulls in full ROCm stack
|
||||
- rocminfo # GPU visibility verification tool
|
||||
- rocm-smi-lib # ROCm system management interface
|
||||
|
||||
# Ollama
|
||||
ollama_version: "latest"
|
||||
ollama_port: 11434
|
||||
ollama_host: "0.0.0.0" # Listen on all interfaces for cluster access
|
||||
ollama_default_model: "qwen3:8b"
|
||||
|
||||
# HSA override — required for RX 5000-series (gfx1010 / RDNA 1)
|
||||
# Tells ROCm to treat the GPU as gfx1010 which has supported LLVM targets.
|
||||
ollama_hsa_override_gfx_version: "10.1.0"
|
||||
|
||||
# Data disk
|
||||
ollama_setup_data_disk: true
|
||||
ollama_data_disk: /dev/sdb
|
||||
ollama_data_dir: /var/lib/ollama # Ollama default models path
|
||||
ollama_data_vg: ollama-vg
|
||||
ollama_data_lv: ollama-lv
|
||||
20
ansible/playbooks/roles/ollama/handlers/main.yml
Normal file
20
ansible/playbooks/roles/ollama/handlers/main.yml
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/handlers/main.yml
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: update apt cache
|
||||
apt:
|
||||
update_cache: yes
|
||||
|
||||
- name: update initramfs
|
||||
command: update-initramfs -u
|
||||
|
||||
- name: reload systemd
|
||||
systemd:
|
||||
daemon_reload: yes
|
||||
|
||||
- name: restart ollama
|
||||
systemd:
|
||||
name: ollama
|
||||
state: restarted
|
||||
229
ansible/playbooks/roles/ollama/tasks/main.yml
Normal file
229
ansible/playbooks/roles/ollama/tasks/main.yml
Normal file
@@ -0,0 +1,229 @@
|
||||
---
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/tasks/main.yml
|
||||
# DESCRIPTION: Deploys Ollama with AMD ROCm GPU acceleration on Ubuntu 24.04.
|
||||
# Handles:
|
||||
# - ROCm repository and driver stack installation
|
||||
# - Data disk preparation (LVM, ext4, persistent mount)
|
||||
# - Ollama installation and systemd service configuration
|
||||
# - HSA override for RX 5000-series (gfx1010) compatibility
|
||||
# - Initial model pull
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ROCm prerequisites
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install ROCm prerequisite packages
|
||||
apt:
|
||||
name:
|
||||
- wget
|
||||
- gnupg
|
||||
- ca-certificates
|
||||
- lvm2
|
||||
state: present
|
||||
update_cache: yes
|
||||
|
||||
- name: Add ROCm apt repository signing key
|
||||
apt_key:
|
||||
url: https://repo.radeon.com/rocm/rocm.gpg.key
|
||||
state: present
|
||||
|
||||
- name: Add ROCm apt repository
|
||||
apt_repository:
|
||||
repo: "deb [arch=amd64] https://repo.radeon.com/rocm/apt/{{ ollama_rocm_version }} {{ ansible_distribution_release }} main"
|
||||
state: present
|
||||
filename: rocm
|
||||
notify: update apt cache
|
||||
|
||||
- name: Flush handlers to update apt cache before ROCm install
|
||||
meta: flush_handlers
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# ROCm driver stack
|
||||
# hip-runtime-amd pulls in the full ROCm stack as a dependency.
|
||||
# rocminfo is used to verify GPU visibility after install.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Install ROCm packages
|
||||
apt:
|
||||
name: "{{ ollama_rocm_packages }}"
|
||||
state: present
|
||||
notify: update initramfs
|
||||
|
||||
- name: Create /opt/rocm symlink to versioned directory
|
||||
file:
|
||||
src: "/opt/rocm-{{ ollama_rocm_version }}.0"
|
||||
dest: /opt/rocm
|
||||
state: link
|
||||
force: false
|
||||
|
||||
- name: Add ROCm binaries to system PATH
|
||||
copy:
|
||||
dest: /etc/profile.d/rocm.sh
|
||||
content: |
|
||||
export PATH=$PATH:/opt/rocm/bin
|
||||
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/rocm/lib
|
||||
mode: '0644'
|
||||
|
||||
- name: Add wed user to render and video groups for GPU access
|
||||
user:
|
||||
name: "{{ ansible_user }}"
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
|
||||
- name: Add ollama user to render and video groups (created by Ollama installer)
|
||||
user:
|
||||
name: ollama
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
ignore_errors: yes
|
||||
# ollama user does not exist yet at this point — Ollama installer creates it.
|
||||
# This task re-runs after install via the post-install handler.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Data disk — LVM setup on /dev/sdb
|
||||
# Follows the same pattern as the monitoring role (cinderella-castle).
|
||||
# Wipes any stale GPT/partition signatures before creating the PV to avoid
|
||||
# the "GPT protective partition" error encountered during monitoring deploy.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Wipe any existing partition signatures from data disk
|
||||
command: wipefs -a {{ ollama_data_disk }}
|
||||
args:
|
||||
creates: /dev/{{ ollama_data_vg }}
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM physical volume on data disk
|
||||
command: pvcreate {{ ollama_data_disk }}
|
||||
args:
|
||||
creates: /dev/{{ ollama_data_vg }}
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM volume group
|
||||
lvg:
|
||||
vg: "{{ ollama_data_vg }}"
|
||||
pvs: "{{ ollama_data_disk }}"
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create LVM logical volume using 100% of VG
|
||||
lvol:
|
||||
vg: "{{ ollama_data_vg }}"
|
||||
lv: "{{ ollama_data_lv }}"
|
||||
size: 100%FREE
|
||||
shrink: false
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Format logical volume as ext4
|
||||
filesystem:
|
||||
fstype: ext4
|
||||
dev: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||
opts: "-L ollama-data"
|
||||
when: ollama_setup_data_disk | bool
|
||||
|
||||
- name: Create Ollama model directory mount point
|
||||
file:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Mount Ollama data volume and add fstab entry
|
||||
mount:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
src: "/dev/{{ ollama_data_vg }}/{{ ollama_data_lv }}"
|
||||
fstype: ext4
|
||||
opts: defaults,noatime
|
||||
state: mounted
|
||||
when: ollama_setup_data_disk | bool
|
||||
# noatime: eliminates inode access time writes on every model file read.
|
||||
# Meaningful win for large sequential reads like model loading.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Ollama installation
|
||||
# Uses the official install script which creates the ollama user and
|
||||
# systemd service unit. We override the service with our own config.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Download Ollama install script
|
||||
get_url:
|
||||
url: https://ollama.ai/install.sh
|
||||
dest: /tmp/ollama_install.sh
|
||||
mode: '0755'
|
||||
|
||||
- name: Run Ollama install script
|
||||
command: /tmp/ollama_install.sh
|
||||
environment: "{{ {} if ollama_version == 'latest' else {'OLLAMA_VERSION': ollama_version} }}"
|
||||
args:
|
||||
creates: /usr/local/bin/ollama
|
||||
|
||||
- name: Add ollama user to render and video groups (post-install)
|
||||
user:
|
||||
name: ollama
|
||||
groups:
|
||||
- render
|
||||
- video
|
||||
append: yes
|
||||
|
||||
- name: Set ownership of Ollama data directory to ollama user
|
||||
file:
|
||||
path: "{{ ollama_data_dir }}"
|
||||
owner: ollama
|
||||
group: ollama
|
||||
recurse: yes
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Systemd service override
|
||||
# Injects HSA_OVERRIDE_GFX_VERSION for RX 5000-series compatibility and
|
||||
# sets OLLAMA_MODELS to the data disk mount point.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Create systemd override directory for ollama service
|
||||
file:
|
||||
path: /etc/systemd/system/ollama.service.d
|
||||
state: directory
|
||||
mode: '0755'
|
||||
|
||||
- name: Deploy ollama systemd service override
|
||||
template:
|
||||
src: ollama-override.conf.j2
|
||||
dest: /etc/systemd/system/ollama.service.d/override.conf
|
||||
mode: '0644'
|
||||
notify:
|
||||
- reload systemd
|
||||
- restart ollama
|
||||
|
||||
- name: Flush handlers to apply service config before model pull
|
||||
meta: flush_handlers
|
||||
|
||||
- name: Enable and start Ollama service
|
||||
systemd:
|
||||
name: ollama
|
||||
state: started
|
||||
enabled: yes
|
||||
daemon_reload: yes
|
||||
|
||||
- name: Wait for Ollama API to become available
|
||||
uri:
|
||||
url: "http://localhost:{{ ollama_port }}/api/tags"
|
||||
status_code: 200
|
||||
register: ollama_health
|
||||
retries: 12
|
||||
delay: 5
|
||||
until: ollama_health.status == 200
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Initial model pull
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
- name: Pull initial model
|
||||
command: ollama pull {{ ollama_default_model }}
|
||||
environment:
|
||||
OLLAMA_HOST: "http://localhost:{{ ollama_port }}"
|
||||
register: model_pull
|
||||
changed_when: "'pulled' in model_pull.stdout or model_pull.rc == 0"
|
||||
timeout: 600
|
||||
# Qwen3 8B Q4_K_M is ~5GB — allow 10 minutes on first pull.
|
||||
@@ -0,0 +1,13 @@
|
||||
# ------------------------------------------------------------------------------
|
||||
# FILE: roles/ollama/templates/ollama-override.conf.j2
|
||||
# DESCRIPTION: Systemd drop-in for the ollama service.
|
||||
# Sets environment variables required for AMD GPU operation
|
||||
# and configures the model storage path.
|
||||
# Managed by Ansible — do not edit manually.
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
[Service]
|
||||
Environment="OLLAMA_HOST={{ ollama_host }}:{{ ollama_port }}"
|
||||
Environment="OLLAMA_MODELS={{ ollama_data_dir }}"
|
||||
Environment="HSA_OVERRIDE_GFX_VERSION={{ ollama_hsa_override_gfx_version }}"
|
||||
Environment="ROCR_VISIBLE_DEVICES=0"
|
||||
Reference in New Issue
Block a user