> Привет
> Я — студент, изучаю Ansible на реальных одноплатниках через Tailscale. Делюсь полным путем от первой ошибки до работающих веб-серверов. Код + выводы + уроки. Репозиторий на GitHub.
Оглавление
День 1: Подготовка лаборатории
Инфраструктура
Я решил работать с реальным оборудованием:
Основная машина мой ноут(Ubuntu с Ansible)
pi1: Raspberry Pi
pi2: Orange Pi Lite
На каждое устройство установлен Tailscale — это VPN, который создает приватную сеть со статическими IP для всех хостов.
inventory.ini — сердце Ansible
Файл inventory.ini определяет, с какими хостами работает Ansible. Вот мой:
[mal]
malina ansible_host=100.64.0.XXX ansible_user=root ansible_python_interpreter=/usr/bin/python3
[ap]
orange ansible_host=100.64.0.XXX ansible_user=root ansible_python_interpreter=/usr/bin/python3
[servers:children]
mal
ap
Пояснение строк:
[mal]— группа для Raspberry Pimalina— имя хоста в Ansible (произвольное, удобное для работы)ansible_host=100.94.xx.xx— реальный IP в Tailscale-сетиansible_user=root— подключаться под пользователемrootansible_python_interpreter=/usr/bin/python3— путь к Python на целевом хосте[servers:children]— мета-группа, объединяющаяmalиap
Проверка:
ansible all -i inventory.ini --list-hosts
Вывод:
hosts (2): 100.94.xx 100.74.xx
Отлично! Ansible видит оба хоста.
Первая проблема: SSH-доступ
Попытался пингануть хосты:
ansible servers -i inventory.ini -m ping
Ошибка:
"msg": "Failed to connect to the host via ssh"
Решение: Настройка SSH на Pi
На каждой из Pi (через dwservice терминал) отредактировал SSH-конфигурацию:
sudo nano /etc/ssh/sshd_config
Расскоментировал три строки:
PasswordAuthentication yes # Разрешить пароли
PubkeyAuthentication yes # Разрешить ключи
PermitRootLogin yes # Разрешить root-доступ
Перезапустил SSH:
sudo systemctl restart ssh
На основной машине добавил SSH-ключи:
ssh-copy-id root@100.94.xx
ssh-copy-id root@100.74.xx
Финальный тест:
ansible servers -i inventory.ini -m ping
Вывод:
pi1 | SUCCESS => { "changed": false, "ping": "pong"
}
pi2 | SUCCESS => { "changed": false, "ping": "pong"
}
SSH работает!
День 2: Первый Nginx (простой плейбук)
Решил установить Nginx на orange (Orange Pi Lite) с помощью простого плейбука.
Файл nginx-server-pi2.yml
---
- name: Install Nginx on pi2
hosts: ap
become: yes
tasks:
- name: Update apt package cache
apt:
update_cache: yes
- name: Install Nginx package
apt:
name: nginx
state: latest
- name: Start and enable Nginx service
systemd:
name: nginx
state: started
enabled: yes
- name: Copy custom index.html
copy:
content: "<h1>Hello from Ansible! Host: {{ inventory_hostname }}</h1>"
dest: /var/www/html/index.html
mode: '0644'
notify: Restart Nginx
handlers:
- name: Restart Nginx
systemd:
name: nginx
state: restarted
Разберемся с tasks
1. Update apt package cache
- name: Update apt package cache apt: update_cache: yes
Обновляет кэш пакетов. Эквивалент apt update на машине.
2. Install Nginx package
- name: Install Nginx package
apt:
name: nginx
state: latest
Устанавливает Nginx в последней версии. Если уже установлен — обновляет.
3. Start and enable Nginx service
- name: Start and enable Nginx service
systemd:
name: nginx
state: started
enabled: yes
state: started— запускает Nginx сейчасenabled: yes— добавляет в автозапуск (systemd enable)
4. Copy custom index.html
- name: Copy custom index.html
copy:
content: "<h1>Hello from Ansible! Host: {{ inventory_hostname }}</h1>"
dest: /var/www/html/index.html
mode: '0644'
notify: Restart Nginx
Создает файл
/var/www/html/index.htmlс HTML{{ inventory_hostname }}— переменная Ansible (подставитorange)notify: Restart Nginx— вызовет handler только если файл изменился
Handler: Restart Nginx
handlers:
- name: Restart Nginx
systemd:
name: nginx
state: restarted
Перезапускает Nginx только после изменений (не каждый раз).
Запуск плейбука
ansible-playbook -i inventory.ini nginx-server-pi2.yml
Вывод:
PLAY [Install Nginx on pi2] ****
TASK [Gathering Facts] ****
pi2 | SUCCESS
TASK [Update apt package cache] ****
pi2 | CHANGED
TASK [Install Nginx package] ****
pi2 | CHANGED
TASK [Start and enable Nginx service] ****
pi2 | CHANGED
TASK [Copy custom index.html] ****
pi2 | CHANGED
RUNNING HANDLER [Restart Nginx] ****
pi2 | CHANGED
PLAY RECAP ****
pi2 : ok=6 changed=5 unreachable=0 failed=0
Результат
Открыл браузер и вбил адрес: http://100.74.xx.xx
На экране:
Hello from Ansible! Host: orange
День 3: Nginx с Jinja2
Понял, что нужно попробовать разные конфигурации для каждой Pi, переменные, шаблоны. Решил использовать Jinja2 — систему шаблонов Ansible.
Структура проекта
mkdir -p ~/malina_ansible/{templates,group_vars,host_vars}
cd ~/malina_ansible
Итоговая стру��тура:
~/malina_ansible/
├── ansible.cfg
├── inventory.ini
├── group_vars/
│ └── servers.yml
├── host_vars/
│ ├── pi1.yml
│ └── pi2.yml
├── templates/
│ ├── nginx-site.j2
│ └── index.html.j2
└── nginx-advanced.yml
Переменные конфигурации
group_vars/servers.yml (общие для всех)
nano group_vars/servers.yml
---
# Общие настройки для всех серверов
nginx_user: www-data
nginx_worker_processes: auto
nginx_worker_connections: 1024
Описание:
nginx_user— пользователь, под которым работает Nginxnginx_worker_processes— кол-во worker-процессов (auto = по количеству CPU)nginx_worker_connections— макс. соединений на worker
host_vars/pi1.yml (только для Raspberry Pi)
nano host_vars/pi1.yml
---
# Настройки только для malina
nginx_port: 80
nginx_root: /var/www/pi1
nginx_server_name: pi1.local
nginx_index: index.html index.htm
host_vars/pi2.yml (только для Orange Pi Lite)
nano host_vars/pi2.yml
---
# Настройки только для orange
nginx_port: 8080
nginx_root: /var/www/pi2
nginx_server_name: pi2.local
nginx_index: index.html index.htm
Разница: malina на 80-м порту, orange на 8080-м. Каждая система имеет свою корневую папку.
Jinja2 шаблоны
templates/nginx-site.j2
nano templates/nginx-site.j2
server { listen {{ nginx_port }};
server_name {{ nginx_server_name }};
root {{ nginx_root }};
index {{ nginx_index }};
location /
{
try_files $uri $uri/ =404; }
location ~ \.php$ {
return 404; }
access_log /var/log/nginx/{{ nginx_server_name }}-access.log;
error_log /var/log/nginx/{{ nginx_server_name }}-error.log;
}
Что происходит:
{{ nginx_port }}— подставляется значение изhost_vars(80 или 8080){{ nginx_root }}— подставляется своя папка для каждой Pi{{ nginx_server_name }}— уникальное имя для каждого хостаДля pi1: получится конфиг на порту 80 с
/var/www/pi1Для pi2: получится конфиг на порту 8080 с
/var/www/pi2
templates/index.html.j2
nano templates/index.html.j2
<title>{{ inventory_hostname }} - Ansible Nginx</title>
<style>
body {
font-family: Arial;
text-align: center;
margin-top: 100px;
background: #f0f8ff;
}
h1
{ color: #2c3e50;
}
.info
{
margin: 20px;
padding: 20px;
background: white;
border-radius: 10px;
display: inline-block;
}
</style>
<div class="info">
<h1>? Hello from {{ inventory_hostname }}!</h1>
<p><strong>Port:</strong> {{ nginx_port }}</p>
<p><strong>Root:</strong> {{ nginx_root }}</p>
<p><strong>OS:</strong> {{ ansible_distribution }}
{{ ansible_distribution_version }}</p>
<p><strong>Uptime:</strong> {{ ansible_uptime_seconds }} seconds</p>
</div>
Динам��ческие переменные:
{{ inventory_hostname }}— имя хоста из inventory (pi1,pi2){{ ansible_distribution }}— ОС (Ubuntu, Debian и т.д.){{ ansible_uptime_seconds }}— время работы системы в секундах
? Главный плейбук nginx-advanced.yml
nano nginx-with-jinja.yml
---
- name: Deploy Advanced Nginx with Jinja2
hosts: ap
become: yes
vars:
nginx_config_dir: /etc/nginx/sites-available
tasks:
- name: Update apt cache
apt:
update_cache: yes
- name: Install nginx
apt:
name: nginx
state: latest
- name: Create web roots
file:
path: "{{ nginx_root }}"
state: directory
mode: '0755'
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
- name: Deploy custom index.html
template:
src: index.html.j2
dest: "{{ nginx_root }}/index.html"
mode: '0644'
owner: "{{ nginx_user }}"
group: "{{ nginx_user }}"
notify:
- Test nginx config
- Restart nginx
- name: Deploy nginx site config from template
template:
src: nginx-site.j2
dest: "{{ nginx_config_dir }}/{{ inventory_hostname }}"
mode: '0644'
notify:
- Test nginx config
- Restart nginx
- name: Enable site (symlink)
file:
src: "{{ nginx_config_dir }}/{{ inventory_hostname }}"
dest: /etc/nginx/sites-enabled/{{ inventory_hostname }}
state: link
notify:
- Test nginx config
- Restart nginx
- name: Disable default site
file:
path: /etc/nginx/sites-enabled/default
state: absent
notify:
- Test nginx config
- Restart nginx
- name: Start and enable nginx
systemd:
name: nginx
state: started
enabled: yes
handlers:
- name: Test nginx config
systemd:
name: nginx
state: started
check_mode: yes
- name: Restart nginx
systemd:
name: nginx
state: restarted
? Разберемся с tasks
Task |
Описание |
|---|---|
|
Очищает проблемные репозитории APT |
|
Обновляет список пакето�� |
|
Устанавливает Nginx |
|
Создает папки |
|
Генерирует HTML из шаблона с динамическими данными |
|
Создает конфиги |
|
Создает симлинк в |
|
Удаляет дефолтный сайт Nginx |
|
Запускает и включает автозапуск |
Handlers:
Test nginx config— проверяет синтаксисnginx -tRestart nginx— перезапускает (срабатывает только при изменениях)
Запуск плейбука
ansible-playbook -i inventory.ini nginx-advanced.yml
Вывод:
PLAY [Deploy Advanced Nginx with Jinja2] ****
TASK [Gathering Facts] ****
pi1 | SUCCESS
pi2 | SUCCESS
TASK [Clean previous repos] ****
pi1 | CHANGED
pi2 | CHANGED
... (остальные tasks)
PLAY RECAP ****
pi1 : ok=11 changed=10 unreachable=0 failed=0
pi2 : ok=11 changed=10 unreachable=0 failed=0
Результат в браузере
Адрес |
Результат |
|---|---|
? Hello from pi1! Port: 80 |
|
? Hello from pi2! Port: 8080 |
Каждая Pi имеет свою конфигурацию, свой порт, свою корневую папку. Всё автоматизировано!
День 4: Диагностика и ansible.cfg
Проблема: Спящий режим Raspberry Pi
На следующий день проверил доступность:
ansible servers -m ping -i inventory.ini
Результат:
pi2 | SUCCESS => {"changed": false, "ping": "pong"}
pi1 | FAILED - Host is not reachable
Причина: Raspberry Pi переходит в спящий режим, Orange Pi Lite остается активной (из-за работающего Nginx).
Решение: Подключился к malina через SSH в dwservice — "разбудил" систему, и она ответила.
Вторая проблема: Забытый ansible.cfg
Попытался управлять Nginx без -i inventory.ini:
ansible servers -m systemd -a "name=nginx"
Ошибка:
[WARNING]: No inventory was parsed, only implicit localhost is available
[WARNING]: provided hosts list is empty, only localhost is available
[WARNING]: Could not match supplied host pattern, ignoring: servers
Причина: Ansible не нашел файл inventory.ini — искал его в дефолтных местах, не нашел и использовал только localhost.
Решение: Создание ansible.cfg
Создал конфигурационный файл в корне проекта:
cat > ansible.cfg << 'EOF'
[defaults]
inventory = inventory.ini
host_key_checking = False
[privilege_escalation]
become = True
EOF
Параметры ansible.cfg
Параметр |
Значение |
Описание |
|---|---|---|
|
|
Путь к inventory (автоматический, без |
|
|
Отключить проверку SSH host keys (удобно для lab/тестирования) |
|
|
Автоматически использовать |
Результат после ansible.cfg
Было:
ansible servers -i inventory.ini -m ping
ansible-playbook -i inventory.ini nginx-advanced.yml
ansible servers -i inventory.ini -m systemd -a "name=nginx state=stopped"
Стало:
ansible servers -m ping
ansible-playbook nginx-advanced.yml
ansible servers -m systemd -a "name=nginx state=stopped"
Намного удобнее! Теперь не нужно писать флаг -i inventory.ini в каждой команде.
Финальные проверки
# Список хостов
ansible servers --list-hosts
# Пинг обеих машин
ansible servers -m ping
# Статус Nginx
ansible servers -m systemd -a "name=nginx"
# Тест конфигурации
ansible servers -m shell -a "nginx -t"
# Проверка сайтов
curl http://100.94.38.85 # pi1: порт 80
curl http://100.74.146.7:8080 # pi2: порт 8080
Итоговая архитектура
┌────────────────────────────────────────────────��────┐
│ Основная машина (Ubuntu) │
│ ├─ Ansible │
│ ├─ inventory.ini │
│ └─ ansible.cfg │
└────────┬──────────────────────────────┬─────────────┘
│ Tailscale VPN │
│ │
┌────▼────┐ ┌────▼────┐
│ pi1 │ │ pi2 │
│ (RPi) │ │ (OPi) │
│ │ │ │
│ Nginx │ │ Nginx │
│ Port 80 │ │ Port 80→8080
│ │ │ │
│ Index │ │ Index │
│ HTML │ │ HTML │
└─────────┘ └─────────┘
Репозиторий
Весь код доступен на GitHub:
git clone https://github.com/doub1ess/ansible-lab
cd ansible-lab
ansible-playbook nginx-with-jinja.yml
Если у вас есть замечания, вопросы или советы по Ansible — пишите в комментариях. Буду благодарен за фидбек.