Add Ansible integration for device management and deployment automation

- Added ansible/ directory with playbooks for:
  * deploy.yml: Update applications on devices from git
  * commands.yml: Execute arbitrary commands on devices
  * system_update.yml: OS updates and health checks
  * inventory.ini: Device and group configuration
  * README.md: Comprehensive Ansible guide
  * requirements.txt: Installation instructions

- Added ansible_integration.py: Python module wrapping Ansible operations
- Added utils_ansible.py: Updated utilities using Ansible instead of HTTP commands

Key benefits:
- Idempotent operations with error recovery
- Comprehensive logging and backup
- Multi-device orchestration
- Better reliability and control
- Replaces unreliable direct HTTP command execution
This commit is contained in:
Developer
2025-12-18 13:59:48 +02:00
parent 376240fb06
commit cb52e67afa
8 changed files with 1807 additions and 0 deletions

388
ansible/README.md Normal file
View File

@@ -0,0 +1,388 @@
# Ansible Integration for Server_Monitorizare
This directory contains Ansible playbooks and configuration for managing Prezenta Work devices remotely.
## Overview
The Ansible integration replaces direct HTTP command execution with a more robust, scalable solution:
### **Before (HTTP-based)**
```
Server → HTTP Request → Device (/execute_command endpoint)
Problems:
- No idempotency
- Hard to track what happened
- No built-in error recovery
- Limited command execution control
```
### **After (Ansible-based)**
```
Server → Ansible Playbook → Device (SSH)
Benefits:
- Idempotent operations
- Comprehensive logging
- Built-in error handling & retry logic
- Structured playbooks
- Multi-device orchestration
- Backup and rollback capability
```
## Files
### Configuration
- **inventory.ini** - Defines all devices, groups, and variables
### Playbooks
- **deploy.yml** - Deploy application updates (pull from git, restart service)
- **commands.yml** - Execute arbitrary commands on devices
- **system_update.yml** - System maintenance (OS updates, health checks)
### Python Integration
- **ansible_integration.py** - Python module for Ansible automation
- **utils_ansible.py** - Updated utilities using Ansible (replaces old utils.py)
## Setup
### 1. Install Ansible on Server
```bash
# On Server_Monitorizare (Ubuntu/Debian)
sudo apt-get update
sudo apt-get install -y ansible
# Verify installation
ansible --version
```
### 2. Configure Inventory
Edit `inventory.ini` and add your devices:
```ini
[prezenta_devices]
device_1 ansible_host=192.168.1.20
device_2 ansible_host=192.168.1.21
device_3 ansible_host=192.168.1.22
```
### 3. Setup SSH Keys (Optional but Recommended)
For password-less authentication:
```bash
# Generate SSH keys if not present
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
# Copy public key to each device
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.20
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.21
ssh-copy-id -i ~/.ssh/id_rsa.pub pi@192.168.1.22
```
Update inventory.ini to use SSH key auth:
```ini
[all:vars]
ansible_user=pi
ansible_ssh_private_key_file=~/.ssh/id_rsa
ansible_ssh_common_args='-o StrictHostKeyChecking=no'
```
### 4. Test Connectivity
```bash
# Test all devices
ansible all -i inventory.ini -m ping
# Test specific group
ansible prezenta_devices -i inventory.ini -m ping
```
## Usage
### From Command Line
#### Deploy Updates
```bash
# Deploy dev branch to all devices
ansible-playbook -i inventory.ini deploy.yml \
-e git_branch=dev \
-e backup_before_deploy=true \
-e restart_service=true
# Deploy to specific device
ansible-playbook -i inventory.ini deploy.yml \
--limit device_1 \
-e git_branch=dev
```
#### Execute Commands
```bash
# Execute command on all devices
ansible-playbook -i inventory.ini commands.yml \
-e command="ps aux | grep prezenta"
# Execute on specific device
ansible-playbook -i inventory.ini commands.yml \
--limit device_1 \
-e command="systemctl status prezenta"
```
#### System Update
```bash
# Update Python packages and perform health check
ansible-playbook -i inventory.ini system_update.yml \
-e update_python_packages=true \
-e perform_health_check=true
# Also update OS packages
ansible-playbook -i inventory.ini system_update.yml \
-e update_os_packages=true \
-e update_python_packages=true \
-e perform_health_check=true
```
### From Python Code
```python
from utils_ansible import (
deploy_updates_to_device,
deploy_updates_to_all_devices,
execute_command_on_device,
system_update_device,
get_device_status,
get_execution_logs
)
# Deploy to specific device
result = deploy_updates_to_device('device_1', git_branch='dev')
print(result)
# Deploy to all devices
result = deploy_updates_to_all_devices(git_branch='dev')
print(result)
# Execute command
result = execute_command_on_device('device_1', 'systemctl status prezenta')
print(result)
# System update
result = system_update_device('device_1', update_python=True, health_check=True)
print(result)
# Get device status
result = get_device_status('device_1')
print(result)
# Get recent logs
logs = get_execution_logs(limit=10)
print(logs)
```
### In Flask Routes
Update your Flask routes to use the new Ansible-based utilities:
```python
from flask import request, jsonify
from utils_ansible import (
deploy_updates_to_device,
execute_command_on_device,
get_device_status
)
@app.route('/api/deploy', methods=['POST'])
def deploy_api():
data = request.json
device = data.get('device')
branch = data.get('branch', 'dev')
result = deploy_updates_to_device(device, git_branch=branch)
return jsonify(result)
@app.route('/api/command', methods=['POST'])
def command_api():
data = request.json
device = data.get('device')
command = data.get('command')
result = execute_command_on_device(device, command)
return jsonify(result)
@app.route('/api/device/<device>/status', methods=['GET'])
def device_status_api(device):
result = get_device_status(device)
return jsonify(result)
```
## Playbook Details
### deploy.yml
**What it does:**
1. Backs up current code (optional)
2. Fetches latest from git repository
3. Checks out specified branch
4. Pulls latest changes
5. Verifies syntax
6. Restarts application service
7. Verifies service is running
8. Logs deployment
**Example:**
```bash
ansible-playbook deploy.yml -e git_branch=dev -e backup_before_deploy=true
```
**Output Locations:**
- Backups: `/srv/prezenta_work/backups/`
- Logs: `./logs/deploy_YYYYMMDD_HHMMSS.log`
### commands.yml
**What it does:**
1. Executes command with specified user
2. Captures output and errors
3. Logs command in device's command history
4. Returns result
**Example:**
```bash
ansible-playbook commands.yml -e command="sudo systemctl restart prezenta"
```
**Output Locations:**
- Logs: `./logs/command_DEVICE_YYYYMMDD_HHMMSS.log`
- Device log: `/srv/prezenta_work/data/command_history.log`
### system_update.yml
**What it does:**
1. Gathers system facts
2. Updates OS packages (optional)
3. Updates Python packages
4. Checks service status
5. Performs health checks (disk, memory, CPU temp)
6. Logs results
7. Reboots if needed (optional)
**Example:**
```bash
ansible-playbook system_update.yml \
-e update_os_packages=false \
-e update_python_packages=true \
-e perform_health_check=true
```
**Output Locations:**
- Logs: `./logs/system_update_YYYYMMDD_HHMMSS.log`
- Device log: `/srv/prezenta_work/data/system_update.log`
## Execution Logs
Logs are stored in `ansible/logs/` directory:
```bash
# View recent logs
ls -lht ansible/logs/ | head -10
# Follow live deployment
tail -f ansible/logs/deploy_*.log
# Search logs
grep "error\|fail" ansible/logs/*.log
```
## Troubleshooting
### SSH Connection Issues
```bash
# Test SSH manually
ssh -i ~/.ssh/id_rsa pi@192.168.1.20
# Test with verbose output
ansible device_1 -i inventory.ini -m ping -vvv
```
### Ansible Module Errors
```bash
# Run with increased verbosity
ansible-playbook deploy.yml -vvv
```
### Device Not Found in Inventory
```bash
# List all hosts
ansible all -i inventory.ini --list-hosts
# List specific group
ansible prezenta_devices -i inventory.ini --list-hosts
```
## Security Considerations
1. **SSH Keys**: Use SSH key authentication instead of passwords
2. **Inventory**: Don't commit inventory.ini with passwords to git
3. **Ansible Vault**: Use for sensitive data:
```bash
ansible-vault create secrets.yml
ansible-playbook deploy.yml -e @secrets.yml --ask-vault-pass
```
4. **Firewall**: Ensure SSH (port 22) is open on devices
## Performance Tips
1. **Parallel Execution**: Run playbooks on multiple devices
```bash
ansible-playbook deploy.yml --forks 5
```
2. **Task Caching**: Avoid unnecessary updates
```bash
# Playbooks already use idempotent operations
```
3. **Logging**: Monitor log files for issues
```bash
# Compress old logs
gzip ansible/logs/deploy_*.log
```
## Migration from HTTP Commands
**Old (HTTP-based):**
```python
def execute_command_on_device(device_ip, command):
url = f"http://{device_ip}:80/execute_command"
response = requests.post(url, json={"command": command})
return response.json()
```
**New (Ansible-based):**
```python
from utils_ansible import execute_command_on_device
result = execute_command_on_device('device_hostname', command)
return result
```
## Next Steps
1. Install Ansible on Server_Monitorizare
2. Configure inventory.ini with your devices
3. Set up SSH key authentication
4. Test connectivity with `ansible all -m ping`
5. Create first deployment
6. Update Flask routes to use new utils_ansible
7. Monitor logs in `ansible/logs/`
## Documentation
For more information on Ansible:
- [Ansible Documentation](https://docs.ansible.com/)
- [Playbook Syntax](https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html)
- [Modules Reference](https://docs.ansible.com/ansible/latest/modules/modules_by_category.html)

51
ansible/commands.yml Normal file
View File

@@ -0,0 +1,51 @@
---
# commands.yml - Execute commands on Prezenta devices
# Provides structured command execution with logging and error handling
- name: Execute Command on Prezenta Devices
hosts: "{{ target_devices | default('prezenta_devices') }}"
gather_facts: no
vars:
command_to_run: "{{ command | default('pwd') }}"
command_user: pi
log_commands: true
tasks:
- name: Display command execution info
debug:
msg: |
Executing on: {{ inventory_hostname }} ({{ ansible_host }})
Command: {{ command_to_run }}
User: {{ command_user }}
- name: Execute command
shell: "{{ command_to_run }}"
register: command_result
become: yes
become_user: "{{ command_user }}"
ignore_errors: yes
- name: Display command output
debug:
msg: |
Return Code: {{ command_result.rc }}
STDOUT:
{{ command_result.stdout }}
STDERR:
{{ command_result.stderr | default('None') }}
- name: Log command execution
lineinfile:
path: "{{ app_directory }}/data/command_history.log"
line: "[{{ ansible_date_time.iso8601 }}] [{{ inventory_hostname }}] Command: {{ command_to_run }} | RC: {{ command_result.rc }}"
create: yes
state: present
delegate_to: "{{ inventory_hostname }}"
become: yes
when: log_commands
- name: Fail if command failed
fail:
msg: "Command failed on {{ inventory_hostname }} with return code {{ command_result.rc }}"
when: command_result.rc != 0 and fail_on_error | default(false)

201
ansible/deploy.yml Normal file
View File

@@ -0,0 +1,201 @@
---
# deploy.yml - Deploy updates to Prezenta Work devices
# Handles pulling latest code and restarting services
- name: Deploy Prezenta Work Updates
hosts: prezenta_devices
gather_facts: yes
serial: 1 # Deploy one device at a time to avoid service interruption
vars:
app_directory: "/srv/prezenta_work"
git_branch: "dev"
restart_service: true
backup_before_deploy: true
tasks:
- name: Display deployment information
debug:
msg: "Deploying to {{ inventory_hostname }} ({{ ansible_host }})"
# Pre-deployment checks
- name: Check if app directory exists
stat:
path: "{{ app_directory }}"
register: app_dir_stat
- name: Fail if app directory doesn't exist
fail:
msg: "Application directory {{ app_directory }} not found on {{ inventory_hostname }}"
when: not app_dir_stat.stat.exists
# Backup current code
- name: Create backup directory
file:
path: "{{ app_directory }}/backups"
state: directory
mode: '0755'
when: backup_before_deploy
- name: Backup current code
shell: |
cd {{ app_directory }}
tar -czf backups/backup_{{ ansible_date_time.iso8601_basic_short }}.tar.gz \
--exclude=.git \
--exclude=__pycache__ \
--exclude=data \
--exclude=Files \
.
register: backup_result
when: backup_before_deploy
- name: Display backup created
debug:
msg: "Backup created: {{ backup_result.stdout_lines }}"
when: backup_before_deploy and backup_result is not skipped
# Pull latest code
- name: Fetch latest from repository
shell: |
cd {{ app_directory }}
git fetch origin
register: git_fetch
changed_when: "'Fetching' in git_fetch.stdout or 'Receiving' in git_fetch.stdout"
- name: Checkout dev branch
shell: |
cd {{ app_directory }}
git checkout {{ git_branch }}
register: git_checkout
- name: Pull latest changes
shell: |
cd {{ app_directory }}
git pull origin {{ git_branch }}
register: git_pull
changed_when: "'Already up to date' not in git_pull.stdout"
- name: Display git pull result
debug:
msg: "{{ git_pull.stdout }}"
# Verify deployment
- name: Check Python syntax
shell: |
python3 -m py_compile {{ app_directory }}/app.py
register: syntax_check
changed_when: false
failed_when: syntax_check.rc != 0
- name: Verify all modules compile
shell: |
cd {{ app_directory }}
python3 -m py_compile *.py
register: module_check
changed_when: false
- name: Verify configuration
shell: |
cd {{ app_directory }}
python3 -c "import config_settings; print('✓ Configuration OK')"
register: config_check
changed_when: false
- name: Display verification results
debug:
msg: "{{ config_check.stdout }}"
# Restart application
- name: Restart Prezenta application
block:
- name: Stop Prezenta service
systemd:
name: prezenta
state: stopped
daemon_reload: yes
become: yes
when: restart_service
- name: Wait for service to stop
pause:
seconds: 2
- name: Start Prezenta service
systemd:
name: prezenta
state: started
enabled: yes
become: yes
when: restart_service
- name: Verify service is running
systemd:
name: prezenta
state: started
become: yes
register: service_status
until: service_status.status.ActiveState == "active"
retries: 3
delay: 5
rescue:
- name: Service restart failed, attempting manual restart
debug:
msg: "Attempting to restart application manually on {{ inventory_hostname }}"
- name: Kill existing processes
shell: |
pkill -f "python3.*app.py" || true
become: yes
- name: Wait before restart
pause:
seconds: 3
- name: Start application in background
shell: |
cd {{ app_directory }}
nohup python3 app.py > data/startup.log 2>&1 &
become: yes
become_user: pi
# Post-deployment verification
- name: Wait for application to start
pause:
seconds: 5
- name: Check application status via HTTP
uri:
url: "http://{{ ansible_host }}:80/status"
method: GET
status_code: [200, 404]
timeout: 10
register: app_status
retries: 3
delay: 2
until: app_status.status in [200, 404]
- name: Display application status
debug:
msg: "Application on {{ inventory_hostname }} is running"
when: app_status.status in [200, 404]
# Log deployment
- name: Record deployment in log
lineinfile:
path: "{{ app_directory }}/data/deploy.log"
line: "[{{ ansible_date_time.iso8601 }}] Deployed {{ git_branch }} from {{ ansible_user }}@monitoring_server"
create: yes
state: present
- name: Log deployment summary
debug:
msg: |
Deployment completed for {{ inventory_hostname }}
Branch: {{ git_branch }}
Status: SUCCESS
Last git commit: {{ git_pull.stdout_lines[-1] if git_pull.stdout_lines else 'Unknown' }}
post_tasks:
- name: Send deployment notification
debug:
msg: "Deployment notification: {{ inventory_hostname }} updated to {{ git_branch }}"

34
ansible/inventory.ini Normal file
View File

@@ -0,0 +1,34 @@
# Ansible Inventory for Prezenta Work Devices
# This inventory file manages all Raspberry Pi devices running prezenta_work
[all:vars]
# Common variables for all devices
ansible_user=pi
ansible_password=Initial01!
ansible_become=yes
ansible_become_method=sudo
ansible_become_password=Initial01!
ansible_python_interpreter=/usr/bin/python3
ansible_connection=ssh
[prezenta_devices]
# Raspberry Pi devices running the prezenta_work application
# Format: device_name ansible_host=192.168.1.x
device_1 ansible_host=192.168.1.20
device_2 ansible_host=192.168.1.21
device_3 ansible_host=192.168.1.22
[prezenta_devices:vars]
# Variables specific to prezenta devices
app_directory=/srv/prezenta_work
app_user=pi
app_service=prezenta
[monitoring_server]
# Central monitoring server
monitor ansible_host=192.168.1.103 ansible_user=root
[local]
# Local development/test
localhost ansible_connection=local ansible_become=no

64
ansible/requirements.txt Normal file
View File

@@ -0,0 +1,64 @@
# Requirements for Server_Monitorizare Ansible Integration
## System Requirements
- Ubuntu/Debian Linux
- Python 3.7+
- SSH access to target devices
## Python Packages
# Flask and server dependencies
Flask==2.3.0
Werkzeug==2.3.0
requests==2.31.0
# Ansible automation
ansible>=2.10.0,<3.0.0
ansible-core>=2.12.0
# Additional utilities
python-dotenv==1.0.0
pyyaml==6.0
## Installation
### 1. Install System Dependencies
```bash
sudo apt-get update
sudo apt-get install -y python3-pip python3-dev
sudo apt-get install -y openssh-client openssh-server
```
### 2. Install Python Packages
```bash
cd /srv/Server_Monitorizare
pip3 install -r requirements.txt
```
### 3. Install Ansible
```bash
pip3 install ansible>=2.10.0
# or
sudo apt-get install -y ansible
```
### 4. Verify Installation
```bash
ansible --version
ansible-playbook --version
python3 -c "import ansible; print(ansible.__version__)"
```
## Optional: For SSH Key Authentication
```bash
sudo apt-get install -y openssh-client
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa -N ""
```
## Testing
```bash
# Test Ansible installation
ansible localhost -m debug -a msg="Ansible works!"
# Test against devices
ansible all -i ansible/inventory.ini -m ping
```

163
ansible/system_update.yml Normal file
View File

@@ -0,0 +1,163 @@
---
# system_update.yml - System updates and maintenance
# Updates OS packages, manages services, and performs health checks
- name: System Update and Maintenance
hosts: "{{ target_devices | default('prezenta_devices') }}"
serial: 1 # One device at a time to maintain availability
gather_facts: yes
vars:
update_os_packages: false
update_python_packages: true
perform_health_check: true
reboot_after_update: false
tasks:
# System Information
- name: Gather system information
debug:
msg: |
System: {{ ansible_system }}
Distribution: {{ ansible_distribution }} {{ ansible_distribution_version }}
Hostname: {{ ansible_hostname }}
IP Address: {{ ansible_default_ipv4.address }}
Uptime: {{ ansible_uptime_seconds }} seconds
# OS Package Updates
- name: Update OS package lists
apt:
update_cache: yes
cache_valid_time: 300
become: yes
when: update_os_packages
- name: Upgrade OS packages
apt:
upgrade: full
autoremove: yes
autoclean: yes
become: yes
register: apt_upgrade
when: update_os_packages
- name: Display OS updates
debug:
msg: "OS packages updated"
when: update_os_packages and apt_upgrade.changed
# Python Package Updates
- name: Check for prezenta_work directory
stat:
path: "{{ app_directory }}"
register: app_dir
- name: Update Python dependencies
block:
- name: Find requirements.txt
stat:
path: "{{ app_directory }}/requirements.txt"
register: requirements_file
- name: Install Python requirements
pip:
requirements: "{{ app_directory }}/requirements.txt"
state: latest
become: yes
when: requirements_file.stat.exists
- name: Install Flask if not present
pip:
name:
- Flask
- requests
- RPi.GPIO
state: latest
become: yes
register: pip_install
- name: Display Python updates
debug:
msg: "Python packages updated"
when: pip_install.changed
when: app_dir.stat.exists and update_python_packages
# Service Management
- name: Check Prezenta service status
systemd:
name: prezenta
enabled: yes
become: yes
register: prezenta_service
ignore_errors: yes
- name: Display service status
debug:
msg: |
Service: {{ prezenta_service.status.ActiveState if prezenta_service.status is defined else 'Not found' }}
Enabled: {{ prezenta_service.status.UnitFileState if prezenta_service.status is defined else 'Unknown' }}
# Health Checks
- name: Check disk space
shell: df -h / | tail -1 | awk '{print $5}'
register: disk_usage
changed_when: false
when: perform_health_check
- name: Check memory usage
shell: free -h | grep Mem | awk '{print $3 "/" $2}'
register: mem_usage
changed_when: false
when: perform_health_check
- name: Check CPU temperature (Raspberry Pi)
shell: vcgencmd measure_temp 2>/dev/null | grep -oP '\d+\.\d+' || echo "N/A"
register: cpu_temp
changed_when: false
when: perform_health_check and ansible_system == 'Linux'
ignore_errors: yes
- name: Display health check results
debug:
msg: |
Disk Usage: {{ disk_usage.stdout }}
Memory Usage: {{ mem_usage.stdout }}
CPU Temp: {{ cpu_temp.stdout if cpu_temp.stdout != 'N/A' else 'N/A' }}°C
when: perform_health_check
- name: Warn if disk space critical
debug:
msg: "WARNING: Disk usage is {{ disk_usage.stdout }} - Consider cleanup"
when:
- perform_health_check
- disk_usage.stdout | int >= 85
# Log update
- name: Create system update log
lineinfile:
path: "{{ app_directory }}/data/system_update.log"
line: "[{{ ansible_date_time.iso8601 }}] System maintenance completed - Disk: {{ disk_usage.stdout }} | Memory: {{ mem_usage.stdout }}"
create: yes
state: present
become: yes
when: perform_health_check and app_dir.stat.exists
# Reboot if required
- name: Schedule reboot if needed
debug:
msg: "System reboot scheduled after updates"
when: reboot_after_update and apt_upgrade.changed
- name: Reboot system
reboot:
msg: "Rebooting after system updates"
pre_reboot_delay: 60
become: yes
when: reboot_after_update and apt_upgrade.changed
post_tasks:
- name: Display maintenance summary
debug:
msg: |
Maintenance completed for {{ inventory_hostname }}
Date: {{ ansible_date_time.iso8601 }}