Files
cnbugs c161c4487a feat: 主机变量支持 - 每台主机独立BMC/IPMI配置
- 后端: inventory生成时写入主机自定义变量(host_vars)
- 前端: 主机编辑表单新增'主机变量'输入区(key=value格式)
- 模板: ipmitool-bmc改为读取主机变量,未配置时用默认值
- 示例: 每台主机设置 bmc_ip=10.10.10.x,Playbook自动区分
- 变量写入inventory: web ansible_host=... bmc_ip=10.10.10.1
2026-07-30 23:07:10 +08:00

2671 lines
76 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package services
// PlaybookTemplate Playbook模板定义
type PlaybookTemplate struct {
ID string `json:"id"`
Name string `json:"name"`
Category string `json:"category"`
Description string `json:"description"`
Icon string `json:"icon"`
Content string `json:"content"`
Tags []string `json:"tags"`
}
// TemplateCategory 模板分类
type TemplateCategory struct {
ID string `json:"id"`
Name string `json:"name"`
Icon string `json:"icon"`
Count int `json:"count"`
}
// GetTemplateCategories 获取所有模板分类
func GetTemplateCategories() []TemplateCategory {
cats := map[string]TemplateCategory{}
for _, t := range GetPlaybookTemplates() {
c, ok := cats[t.Category]
if !ok {
c = TemplateCategory{ID: t.Category, Name: categoryNames[t.Category], Icon: categoryIcons[t.Category]}
}
c.Count++
cats[t.Category] = c
}
var result []TemplateCategory
// 按固定顺序输出
order := []string{"system", "web", "database", "container", "cluster", "devops", "security", "monitoring"}
for _, id := range order {
if c, ok := cats[id]; ok {
result = append(result, c)
}
}
return result
}
var categoryNames = map[string]string{
"system": "系统管理",
"web": "Web服务",
"database": "数据库",
"container": "容器化",
"cluster": "集群部署",
"devops": "DevOps",
"security": "安全加固",
"monitoring": "监控告警",
}
var categoryIcons = map[string]string{
"system": "🖥️",
"web": "🌐",
"database": "🗄️",
"container": "🐳",
"cluster": "☸️",
"devops": "🔧",
"security": "🔒",
"monitoring": "📊",
}
// GetPlaybookTemplates 获取所有Playbook模板
func GetPlaybookTemplates() []PlaybookTemplate {
return []PlaybookTemplate{
// ===== 系统管理 =====
{
ID: "update-packages", Name: "系统包更新", Category: "system",
Description: "自动更新系统软件包,支持Debian/RedHat系列",
Icon: "📦", Tags: []string{"update", "apt", "yum"},
Content: `---
- name: 系统包更新
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
update_cache: yes
upgrade_type: dist # dist(完整升级) 或 safe(安全升级)
tasks:
- name: 更新apt缓存
apt:
update_cache: yes
when: ansible_os_family == "Debian"
- name: 升级所有包(Debian)
apt:
upgrade: "{{ upgrade_type }}"
autoremove: yes
autoclean: yes
when: ansible_os_family == "Debian"
- name: 更新yum缓存
yum:
update_cache: yes
when: ansible_os_family == "RedHat"
- name: 升级所有包(RedHat)
yum:
name: "*"
state: latest
when: ansible_os_family == "RedHat"
- name: 检查是否需要重启
stat:
path: /var/run/reboot-required
register: reboot_required
when: ansible_os_family == "Debian"
- name: 提示重启
debug:
msg: "⚠️ 系统需要重启以完成更新"
when: ansible_os_family == "Debian" and reboot_required.stat.exists
`,
},
{
ID: "check-system", Name: "系统信息采集", Category: "system",
Description: "采集操作系统、硬件、网络等详细信息",
Icon: "🔍", Tags: []string{"info", "facts", "inventory"},
Content: `---
- name: 系统信息采集
hosts: "{{ target_hosts | default('all') }}"
gather_facts: yes
tasks:
- name: 操作系统信息
debug:
msg: |
══════════ 系统信息 ══════════
主机名: {{ ansible_facts['hostname'] }}
系统: {{ ansible_facts['distribution'] }} {{ ansible_facts['distribution_version'] }}
内核: {{ ansible_facts['kernel'] }}
架构: {{ ansible_facts['architecture'] }}
CPU: {{ ansible_facts['processor_vcpus'] }} vCPUs
内存: {{ (ansible_facts['memtotal_mb'] / 1024) | round(2) }} GB
IP: {{ ansible_facts['default_ipv4']['address'] | default('N/A') }}
═══════════════════════════════
- name: 磁盘使用
shell: df -h | grep -E '^/dev/'
register: disk_info
- name: 显示磁盘
debug:
msg: "{{ disk_info.stdout_lines }}"
- name: 运行时间
shell: uptime -p
register: uptime_info
- name: 显示运行时间
debug:
msg: "运行时间: {{ uptime_info.stdout }}"
`,
},
{
ID: "check-resources", Name: "资源监控检查", Category: "system",
Description: "检查CPU/内存/磁盘使用率,超阈值告警",
Icon: "📈", Tags: []string{"monitor", "cpu", "memory", "disk"},
Content: `---
- name: 资源监控检查
hosts: "{{ target_hosts | default('all') }}"
gather_facts: yes
vars:
warn_threshold: 80
crit_threshold: 90
tasks:
- name: 获取CPU使用率
shell: top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1
register: cpu_result
changed_when: false
- name: 获取内存使用率
shell: free | grep Mem | awk '{printf "%.0f", $3/$2*100}'
register: mem_result
changed_when: false
- name: 获取磁盘使用率
shell: df -h / | tail -1 | awk '{print $5}' | tr -d '%'
register: disk_result
changed_when: false
- name: 显示资源报告
debug:
msg: |
═══════ {{ inventory_hostname }} 资源报告 ═══════
CPU 使用率: {{ cpu_result.stdout }}%
内存使用率: {{ mem_result.stdout }}%
磁盘使用率: {{ disk_result.stdout }}%
告警阈值: {{ warn_threshold }}% / 严重: {{ crit_threshold }}%
═══════════════════════════════════════
- name: 严重告警
fail:
msg: "🚨 严重: {{ inventory_hostname }} 资源使用率超过 {{ crit_threshold }}%"
when: >
(cpu_result.stdout | int >= crit_threshold) or
(mem_result.stdout | int >= crit_threshold) or
(disk_result.stdout | int >= crit_threshold)
`,
},
{
ID: "log-cleanup", Name: "日志清理", Category: "system",
Description: "清理系统日志、临时文件,释放磁盘空间",
Icon: "🧹", Tags: []string{"cleanup", "log", "disk"},
Content: `---
- name: 日志清理
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
log_retention_days: 30
tmp_retention_days: 7
tasks:
- name: 清理journal日志
shell: journalctl --vacuum-time={{ log_retention_days }}d
register: journal_result
changed_when: "'Vacuuming' in journal_result.stdout"
- name: 清理旧日志文件
find:
paths: /var/log
patterns: "*.gz,*.old,*.[0-9]"
age: "{{ log_retention_days }}d"
register: old_logs
- name: 删除旧日志
file:
path: "{{ item.path }}"
state: absent
loop: "{{ old_logs.files }}"
- name: 清理临时文件
find:
paths: /tmp
age: "{{ tmp_retention_days }}d"
register: tmp_files
- name: 删除临时文件
file:
path: "{{ item.path }}"
state: absent
loop: "{{ tmp_files.files }}"
ignore_errors: yes
- name: 清理apt缓存
apt:
autoclean: yes
when: ansible_os_family == "Debian"
- name: 显示磁盘空间
shell: df -h /
register: disk_after
- name: 清理完成
debug:
msg: "{{ disk_after.stdout_lines }}"
`,
},
{
ID: "time-sync", Name: "时间同步(NTP)", Category: "system",
Description: "配置NTP时间同步,确保集群时间一致",
Icon: "⏰", Tags: []string{"ntp", "time", "chrony"},
Content: `---
- name: 时间同步配置
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
ntp_servers:
- ntp.aliyun.com
- ntp1.aliyun.com
- pool.ntp.org
tasks:
- name: 安装chrony
package:
name: chrony
state: present
- name: 配置NTP服务器
lineinfile:
path: /etc/chrony.conf
regexp: "^server "
line: "server {{ item }} iburst"
create: yes
loop: "{{ ntp_servers }}"
- name: 启动chrony
service:
name: chronyd
state: started
enabled: yes
- name: 验证时间同步
shell: chronyc tracking
register: chrony_status
changed_when: false
- name: 显示同步状态
debug:
msg: "{{ chrony_status.stdout_lines }}"
`,
},
// ===== Web服务 =====
{
ID: "deploy-nginx", Name: "部署Nginx", Category: "web",
Description: "安装配置Nginx,支持自定义站点和反向代理",
Icon: "🌐", Tags: []string{"nginx", "web", "proxy"},
Content: `---
- name: 部署Nginx
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
nginx_worker_processes: auto
nginx_worker_connections: 1024
server_name: localhost
listen_port: 80
proxy_pass: ""
root_dir: /var/www/html
tasks:
- name: 安装Nginx
package:
name: nginx
state: present
- name: 创建网站目录
file:
path: "{{ root_dir }}"
state: directory
owner: www-data
group: www-data
mode: '0755'
when: ansible_os_family == "Debian"
- name: 创建网站目录(RedHat)
file:
path: "{{ root_dir }}"
state: directory
owner: nginx
group: nginx
mode: '0755'
when: ansible_os_family == "RedHat"
- name: 部署默认页面
copy:
content: |
<!DOCTYPE html>
<html><head><title>{{ server_name }}</title></head>
<body><h1>Deployed by Ansible Deploy</h1>
<p>Server: {{ inventory_hostname }}</p></body></html>
dest: "{{ root_dir }}/index.html"
when: proxy_pass == ""
- name: 配置Nginx站点
copy:
content: |
server {
listen {{ listen_port }};
server_name {{ server_name }};
{% if proxy_pass != "" %}
location / {
proxy_pass {{ proxy_pass }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
{% else %}
root {{ root_dir }};
index index.html;
location / {
try_files $uri $uri/ =404;
}
{% endif %}
}
dest: /etc/nginx/conf.d/{{ server_name }}.conf
notify: Reload Nginx
- name: 启动Nginx
service:
name: nginx
state: started
enabled: yes
handlers:
- name: Reload Nginx
service:
name: nginx
state: reloaded
`,
},
{
ID: "deploy-apache", Name: "部署Apache", Category: "web",
Description: "安装配置Apache HTTP Server",
Icon: "🪶", Tags: []string{"apache", "httpd", "web"},
Content: `---
- name: 部署Apache
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
server_name: localhost
listen_port: 80
document_root: /var/www/html
tasks:
- name: 安装Apache(Debian)
apt:
name: apache2
state: present
when: ansible_os_family == "Debian"
- name: 安装Apache(RedHat)
yum:
name: httpd
state: present
when: ansible_os_family == "RedHat"
- name: 创建网站目录
file:
path: "{{ document_root }}"
state: directory
mode: '0755'
- name: 部署测试页面
copy:
content: "<h1>Apache on {{ inventory_hostname }}</h1>"
dest: "{{ document_root }}/index.html"
- name: 启动Apache(Debian)
service:
name: apache2
state: started
enabled: yes
when: ansible_os_family == "Debian"
- name: 启动Apache(RedHat)
service:
name: httpd
state: started
enabled: yes
when: ansible_os_family == "RedHat"
`,
},
// ===== 数据库 =====
{
ID: "deploy-mysql", Name: "部署MySQL", Category: "database",
Description: "安装MySQL/MariaDB,配置root密码和基础优化",
Icon: "🐬", Tags: []string{"mysql", "mariadb", "database"},
Content: `---
- name: 部署MySQL
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
mysql_root_password: "ChangeMe@2024"
mysql_port: 3306
mysql_max_connections: 500
mysql_datadir: /var/lib/mysql
tasks:
- name: 安装MySQL(Debian)
apt:
name:
- mysql-server
- mysql-client
- python3-pymysql
state: present
when: ansible_os_family == "Debian"
- name: 安装MariaDB(RedHat)
yum:
name:
- mariadb-server
- mariadb
- python3-PyMySQL
state: present
when: ansible_os_family == "RedHat"
- name: 启动MySQL
service:
name: "{{ 'mysql' if ansible_os_family == 'Debian' else 'mariadb' }}"
state: started
enabled: yes
- name: 设置root密码
shell: |
mysql -u root -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_root_password }}'; FLUSH PRIVILEGES;"
ignore_errors: yes
no_log: true
- name: 优化配置
copy:
content: |
[mysqld]
port = {{ mysql_port }}
max_connections = {{ mysql_max_connections }}
datadir = {{ mysql_datadir }}
innodb_buffer_pool_size = 256M
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 2
dest: /etc/mysql/conf.d/optimize.cnf
when: ansible_os_family == "Debian"
notify: Restart MySQL
- name: 验证MySQL
shell: mysql -u root -p{{ mysql_root_password }} -e "SELECT VERSION();"
register: mysql_version
changed_when: false
no_log: true
- name: 显示版本
debug:
msg: "MySQL版本: {{ mysql_version.stdout_lines[-1] | default('unknown') }}"
handlers:
- name: Restart MySQL
service:
name: "{{ 'mysql' if ansible_os_family == 'Debian' else 'mariadb' }}"
state: restarted
`,
},
{
ID: "deploy-redis", Name: "部署Redis", Category: "database",
Description: "安装Redis,配置密码、内存限制和持久化",
Icon: "🔴", Tags: []string{"redis", "cache", "database"},
Content: `---
- name: 部署Redis
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
redis_port: 6379
redis_password: "ChangeMe@2024"
redis_maxmemory: "512mb"
redis_maxmemory_policy: allkeys-lru
redis_bind: "0.0.0.0"
tasks:
- name: 安装Redis
package:
name: redis-server
state: present
when: ansible_os_family == "Debian"
- name: 安装Redis(RedHat)
yum:
name: redis
state: present
when: ansible_os_family == "RedHat"
- name: 配置Redis
lineinfile:
path: /etc/redis/redis.conf
regexp: "{{ item.regexp }}"
line: "{{ item.line }}"
loop:
- { regexp: '^bind ', line: 'bind {{ redis_bind }}' }
- { regexp: '^port ', line: 'port {{ redis_port }}' }
- { regexp: '^requirepass ', line: 'requirepass {{ redis_password }}' }
- { regexp: '^maxmemory ', line: 'maxmemory {{ redis_maxmemory }}' }
- { regexp: '^maxmemory-policy ', line: 'maxmemory-policy {{ redis_maxmemory_policy }}' }
notify: Restart Redis
- name: 启动Redis
service:
name: "{{ 'redis-server' if ansible_os_family == 'Debian' else 'redis' }}"
state: started
enabled: yes
- name: 验证Redis
shell: redis-cli -a {{ redis_password }} ping
register: redis_ping
changed_when: false
no_log: true
- name: 显示状态
debug:
msg: "Redis状态: {{ redis_ping.stdout }}"
handlers:
- name: Restart Redis
service:
name: "{{ 'redis-server' if ansible_os_family == 'Debian' else 'redis' }}"
state: restarted
`,
},
{
ID: "deploy-postgresql", Name: "部署PostgreSQL", Category: "database",
Description: "安装PostgreSQL,配置用户和基础优化",
Icon: "🐘", Tags: []string{"postgresql", "postgres", "database"},
Content: `---
- name: 部署PostgreSQL
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
postgres_version: "15"
postgres_password: "ChangeMe@2024"
postgres_port: 5432
postgres_max_connections: 200
tasks:
- name: 安装PostgreSQL(Debian)
apt:
name:
- postgresql
- postgresql-contrib
- python3-psycopg2
state: present
when: ansible_os_family == "Debian"
- name: 安装PostgreSQL(RedHat)
yum:
name:
- postgresql-server
- postgresql-contrib
- python3-psycopg2
state: present
when: ansible_os_family == "RedHat"
- name: 初始化数据库(RedHat)
shell: postgresql-setup --initdb
when: ansible_os_family == "RedHat"
ignore_errors: yes
- name: 启动PostgreSQL
service:
name: postgresql
state: started
enabled: yes
- name: 设置postgres密码
become_user: postgres
shell: psql -c "ALTER USER postgres PASSWORD '{{ postgres_password }}';"
no_log: true
- name: 验证安装
become_user: postgres
shell: psql -c "SELECT version();"
register: pg_version
changed_when: false
- name: 显示版本
debug:
msg: "{{ pg_version.stdout_lines[0] | default('PostgreSQL installed') }}"
`,
},
// ===== 容器化 =====
{
ID: "deploy-docker", Name: "部署Docker", Category: "container",
Description: "安装Docker CE + Docker Compose,配置镜像加速",
Icon: "🐳", Tags: []string{"docker", "container", "compose"},
Content: `---
- name: 部署Docker
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
docker_mirror: "https://mirror.ccs.tencentyun.com"
docker_data_root: /var/lib/docker
docker_log_max_size: "100m"
docker_log_max_file: "3"
tasks:
- name: 安装依赖(Debian)
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
- lsb-release
state: present
when: ansible_os_family == "Debian"
- name: 添加Docker GPG密钥
shell: |
curl -fsSL https://download.docker.com/linux/{{ ansible_distribution | lower }}/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
when: ansible_os_family == "Debian"
ignore_errors: yes
- name: 添加Docker仓库
shell: |
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/{{ ansible_distribution | lower }} {{ ansible_distribution_release }} stable" > /etc/apt/sources.list.d/docker.list
when: ansible_os_family == "Debian"
- name: 安装Docker(Debian)
apt:
name:
- docker-ce
- docker-ce-cli
- containerd.io
- docker-compose-plugin
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: 安装Docker(RedHat)
yum:
name:
- docker
- docker-compose-plugin
state: present
when: ansible_os_family == "RedHat"
- name: 配置Docker
copy:
content: |
{
"data-root": "{{ docker_data_root }}",
"registry-mirrors": ["{{ docker_mirror }}"],
"log-driver": "json-file",
"log-opts": {
"max-size": "{{ docker_log_max_size }}",
"max-file": "{{ docker_log_max_file }}"
},
"storage-driver": "overlay2",
"live-restore": true
}
dest: /etc/docker/daemon.json
notify: Restart Docker
- name: 启动Docker
service:
name: docker
state: started
enabled: yes
- name: 添加用户到docker组
user:
name: "{{ ansible_user }}"
groups: docker
append: yes
ignore_errors: yes
- name: 验证Docker
shell: docker version --format '{{.Server.Version}}'
register: docker_ver
changed_when: false
- name: 显示版本
debug:
msg: "Docker版本: {{ docker_ver.stdout }}"
handlers:
- name: Restart Docker
service:
name: docker
state: restarted
`,
},
{
ID: "deploy-portainer", Name: "部署Portainer", Category: "container",
Description: "部署Portainer CE容器管理面板",
Icon: "🎛️", Tags: []string{"portainer", "docker", "ui"},
Content: `---
- name: 部署Portainer
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
portainer_port: 9443
portainer_data: /opt/portainer
tasks:
- name: 创建数据目录
file:
path: "{{ portainer_data }}"
state: directory
- name: 拉取并启动Portainer
shell: |
docker volume create portainer_data
docker run -d \
-p 8000:8000 \
-p {{ portainer_port }}:9443 \
--name portainer \
--restart=always \
-v /var/run/docker.sock:/var/run/docker.sock \
-v portainer_data:/data \
portainer/portainer-ce:latest
register: portainer_result
ignore_errors: yes
- name: 显示访问地址
debug:
msg: "Portainer已部署: https://{{ ansible_host | default(inventory_hostname) }}:{{ portainer_port }}"
`,
},
// ===== DevOps =====
{
ID: "deploy-nodejs", Name: "部署Node.js", Category: "devops",
Description: "安装Node.js + PM2进程管理器",
Icon: "💚", Tags: []string{"nodejs", "npm", "pm2"},
Content: `---
- name: 部署Node.js
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
node_version: "20"
install_pm2: true
tasks:
- name: 安装NodeSource仓库
shell: |
curl -fsSL https://deb.nodesource.com/setup_{{ node_version }}.x | bash -
when: ansible_os_family == "Debian"
- name: 安装Node.js(Debian)
apt:
name: nodejs
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: 安装Node.js(RedHat)
yum:
name: nodejs
state: present
when: ansible_os_family == "RedHat"
- name: 安装PM2
npm:
name: pm2
global: yes
when: install_pm2
- name: 设置PM2开机启动
shell: pm2 startup systemd -u {{ ansible_user }} --hp /home/{{ ansible_user }}
when: install_pm2
ignore_errors: yes
- name: 验证安装
shell: node --version && npm --version
register: node_ver
changed_when: false
- name: 显示版本
debug:
msg: "{{ node_ver.stdout_lines }}"
`,
},
{
ID: "deploy-python-app", Name: "部署Python应用", Category: "devops",
Description: "配置Python环境 + Gunicorn + Systemd服务",
Icon: "🐍", Tags: []string{"python", "gunicorn", "flask", "django"},
Content: `---
- name: 部署Python应用
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
app_name: myapp
app_dir: /opt/myapp
python_version: python3
venv_dir: /opt/myapp/venv
gunicorn_port: 8000
gunicorn_workers: 4
app_user: www-data
tasks:
- name: 安装Python和依赖
package:
name:
- "{{ python_version }}"
- "{{ python_version }}-venv"
- python3-pip
state: present
- name: 创建应用目录
file:
path: "{{ app_dir }}"
state: directory
owner: "{{ app_user }}"
mode: '0755'
- name: 创建虚拟环境
shell: "{{ python_version }} -m venv {{ venv_dir }}"
args:
creates: "{{ venv_dir }}/bin/activate"
- name: 安装Gunicorn
pip:
name: gunicorn
virtualenv: "{{ venv_dir }}"
- name: 创建Systemd服务
copy:
content: |
[Unit]
Description={{ app_name }} Gunicorn Service
After=network.target
[Service]
User={{ app_user }}
WorkingDirectory={{ app_dir }}
ExecStart={{ venv_dir }}/bin/gunicorn --workers {{ gunicorn_workers }} --bind 0.0.0.0:{{ gunicorn_port }} app:app
Restart=always
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/{{ app_name }}.service
notify: Restart App
- name: 启动服务
systemd:
name: "{{ app_name }}"
state: started
enabled: yes
daemon_reload: yes
handlers:
- name: Restart App
systemd:
name: "{{ app_name }}"
state: restarted
daemon_reload: yes
`,
},
{
ID: "backup-data", Name: "数据备份", Category: "devops",
Description: "定时备份指定目录到本地或远程",
Icon: "💾", Tags: []string{"backup", "cron", "archive"},
Content: `---
- name: 数据备份
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
backup_source: /data
backup_dest: /backup
backup_retention_days: 7
backup_hour: "2"
backup_minute: "0"
tasks:
- name: 创建备份目录
file:
path: "{{ backup_dest }}"
state: directory
mode: '0700'
- name: 创建备份脚本
copy:
content: |
#!/bin/bash
DATE=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="{{ backup_dest }}/backup_${DATE}.tar.gz"
tar -czf "$BACKUP_FILE" {{ backup_source }} 2>/dev/null
find {{ backup_dest }} -name "backup_*.tar.gz" -mtime +{{ backup_retention_days }} -delete
echo "Backup completed: $BACKUP_FILE"
dest: /usr/local/bin/backup.sh
mode: '0755'
- name: 配置定时任务
cron:
name: "daily-backup"
hour: "{{ backup_hour }}"
minute: "{{ backup_minute }}"
job: "/usr/local/bin/backup.sh >> /var/log/backup.log 2>&1"
- name: 立即执行一次备份
shell: /usr/local/bin/backup.sh
register: backup_result
- name: 备份结果
debug:
msg: "{{ backup_result.stdout }}"
`,
},
// ===== 安全加固 =====
{
ID: "security-hardening", Name: "安全加固", Category: "security",
Description: "SSH加固、防火墙配置、禁用root远程登录",
Icon: "🔒", Tags: []string{"security", "ssh", "firewall", "hardening"},
Content: `---
- name: 安全加固
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
ssh_port: 22
disable_root_login: true
enable_firewall: true
allowed_ssh_users: []
tasks:
- name: 修改SSH端口
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?Port "
line: "Port {{ ssh_port }}"
notify: Restart SSH
- name: 禁用root登录
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PermitRootLogin "
line: "PermitRootLogin no"
when: disable_root_login
notify: Restart SSH
- name: 禁用密码认证(仅密钥)
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?PasswordAuthentication "
line: "PasswordAuthentication no"
notify: Restart SSH
- name: 设置SSH超时
lineinfile:
path: /etc/ssh/sshd_config
regexp: "^#?ClientAliveInterval "
line: "ClientAliveInterval 300"
notify: Restart SSH
- name: 安装UFW(Debian)
apt:
name: ufw
state: present
when: ansible_os_family == "Debian" and enable_firewall
- name: 配置UFW规则
shell: |
ufw allow {{ ssh_port }}/tcp
ufw allow 80/tcp
ufw allow 443/tcp
ufw --force enable
when: ansible_os_family == "Debian" and enable_firewall
- name: 安装fail2ban
package:
name: fail2ban
state: present
ignore_errors: yes
- name: 启动fail2ban
service:
name: fail2ban
state: started
enabled: yes
ignore_errors: yes
- name: 加固完成
debug:
msg: "✅ 安全加固完成 - SSH端口: {{ ssh_port }}, Root登录: {{ '禁用' if disable_root_login else '允许' }}"
handlers:
- name: Restart SSH
service:
name: "{{ 'ssh' if ansible_os_family == 'Debian' else 'sshd' }}"
state: restarted
`,
},
{
ID: "ssl-cert", Name: "SSL证书部署", Category: "security",
Description: "使用Certbot自动申请Let's Encrypt证书",
Icon: "📜", Tags: []string{"ssl", "https", "certbot", "letsencrypt"},
Content: `---
- name: SSL证书部署
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
domain: example.com
email: admin@example.com
webroot: /var/www/html
tasks:
- name: 安装Certbot
package:
name:
- certbot
- python3-certbot-nginx
state: present
- name: 申请证书
shell: |
certbot certonly --webroot -w {{ webroot }} \
-d {{ domain }} \
--email {{ email }} \
--agree-tos --non-interactive
args:
creates: /etc/letsencrypt/live/{{ domain }}/fullchain.pem
- name: 配置自动续期
cron:
name: "certbot-renew"
hour: "3"
minute: "30"
job: "certbot renew --quiet --post-hook 'systemctl reload nginx'"
- name: 证书信息
shell: openssl x509 -in /etc/letsencrypt/live/{{ domain }}/fullchain.pem -noout -dates
register: cert_info
changed_when: false
ignore_errors: yes
- name: 显示证书
debug:
msg: "{{ cert_info.stdout_lines | default(['证书已申请']) }}"
`,
},
// ===== 监控告警 =====
{
ID: "deploy-node-exporter", Name: "部署Node Exporter", Category: "monitoring",
Description: "部署Prometheus Node Exporter采集主机指标",
Icon: "📡", Tags: []string{"prometheus", "monitoring", "exporter"},
Content: `---
- name: 部署Node Exporter
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
exporter_version: "1.7.0"
exporter_port: 9100
tasks:
- name: 创建用户
user:
name: node_exporter
system: yes
shell: /usr/sbin/nologin
create_home: no
- name: 下载Node Exporter
get_url:
url: "https://github.com/prometheus/node_exporter/releases/download/v{{ exporter_version }}/node_exporter-{{ exporter_version }}.linux-amd64.tar.gz"
dest: /tmp/node_exporter.tar.gz
- name: 解压
unarchive:
src: /tmp/node_exporter.tar.gz
dest: /tmp
remote_src: yes
- name: 安装二进制
copy:
src: "/tmp/node_exporter-{{ exporter_version }}.linux-amd64/node_exporter"
dest: /usr/local/bin/node_exporter
mode: '0755'
remote_src: yes
- name: 创建Systemd服务
copy:
content: |
[Unit]
Description=Node Exporter
After=network.target
[Service]
User=node_exporter
ExecStart=/usr/local/bin/node_exporter --web.listen-address=:{{ exporter_port }}
Restart=always
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/node_exporter.service
- name: 启动服务
systemd:
name: node_exporter
state: started
enabled: yes
daemon_reload: yes
- name: 验证
shell: curl -s http://localhost:{{ exporter_port }}/metrics | head -5
register: metrics
changed_when: false
- name: 显示状态
debug:
msg: "Node Exporter运行在端口 {{ exporter_port }}"
`,
},
{
ID: "deploy-grafana", Name: "部署Grafana", Category: "monitoring",
Description: "部署Grafana可视化监控面板",
Icon: "📊", Tags: []string{"grafana", "dashboard", "visualization"},
Content: `---
- name: 部署Grafana
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
grafana_port: 3000
grafana_admin_password: "admin123"
tasks:
- name: 安装依赖
apt:
name:
- apt-transport-https
- software-properties-common
state: present
when: ansible_os_family == "Debian"
- name: 添加Grafana仓库
shell: |
curl -fsSL https://apt.grafana.com/gpg.key | gpg --dearmor -o /usr/share/keyrings/grafana.gpg
echo "deb [signed-by=/usr/share/keyrings/grafana.gpg] https://apt.grafana.com stable main" > /etc/apt/sources.list.d/grafana.list
when: ansible_os_family == "Debian"
- name: 安装Grafana
apt:
name: grafana
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: 配置端口
lineinfile:
path: /etc/grafana/grafana.ini
regexp: "^;?http_port"
line: "http_port = {{ grafana_port }}"
- name: 启动Grafana
service:
name: grafana-server
state: started
enabled: yes
- name: 显示访问信息
debug:
msg: "Grafana: http://{{ ansible_host | default(inventory_hostname) }}:{{ grafana_port }} (admin/{{ grafana_admin_password }})"
`,
},
// ===== 系统管理(新增) =====
{
ID: "host-connectivity", Name: "主机连通性测试", Category: "system",
Description: "批量检测主机SSH连通性、DNS解析、端口可达性",
Icon: "🔗", Tags: []string{"ping", "connectivity", "network", "check"},
Content: `---
- name: 主机连通性测试
hosts: "{{ target_hosts | default('all') }}"
gather_facts: no
tasks:
- name: Ansible Ping 测试
ping:
register: ping_result
ignore_errors: yes
- name: 检测SSH端口
wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: "{{ ansible_port | default(22) }}"
timeout: 5
delegate_to: localhost
register: ssh_port
ignore_errors: yes
- name: 检测DNS解析
shell: nslookup {{ inventory_hostname }} 2>/dev/null || host {{ inventory_hostname }} 2>/dev/null || echo "DNS解析失败"
delegate_to: localhost
register: dns_result
changed_when: false
ignore_errors: yes
- name: 检测常用端口
wait_for:
host: "{{ ansible_host | default(inventory_hostname) }}"
port: "{{ item }}"
timeout: 3
delegate_to: localhost
loop: [80, 443]
register: port_scan
ignore_errors: yes
- name: 输出连通性报告
debug:
msg: |
═══════ {{ inventory_hostname }} 连通性报告 ═══════
SSH Ping: {{ ' 成功' if ping_result is succeeded else ' 失败' }}
SSH 端口: {{ ' 开放' if ssh_port is succeeded else ' 不可达' }}
DNS 解析: {{ dns_result.stdout | default('N/A') }}
HTTP(80): {{ ' 开放' if port_scan.results[0] is succeeded else ' 关闭' }}
HTTPS(443): {{ ' 开放' if port_scan.results[1] is succeeded else ' 关闭' }}
═══════════════════════════════════════
`,
},
{
ID: "ubuntu-multi-ip", Name: "Ubuntu 22.04 多IP配置", Category: "system",
Description: "通过Netplan为Ubuntu 22.04配置多IP地址(主IP+辅助IP",
Icon: "🌐", Tags: []string{"ubuntu", "netplan", "network", "multi-ip"},
Content: `---
- name: Ubuntu 22.04 多IP地址配置
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
# 主网卡名称(根据实际修改)
primary_nic: ens192
# 主IP配置
primary_ip: "192.168.1.100/24"
primary_gateway: "192.168.1.1"
primary_dns:
- 223.5.5.5
- 119.29.29.29
# 辅助IP列表(可添加多个)
secondary_ips:
- "192.168.1.101/24"
- "10.0.0.100/16"
tasks:
- name: 确认系统版本
assert:
that:
- ansible_distribution == "Ubuntu"
- ansible_distribution_version is version('22.04', '>=')
fail_msg: "此模板仅适用于 Ubuntu 22.04+"
success_msg: "系统版本: {{ ansible_distribution }} {{ ansible_distribution_version }}"
- name: 备份现有Netplan配置
shell: |
mkdir -p /etc/netplan/backup
cp /etc/netplan/*.yaml /etc/netplan/backup/ 2>/dev/null || true
args:
creates: /etc/netplan/backup
- name: 生成Netplan配置
copy:
content: |
network:
version: 2
ethernets:
{{ primary_nic }}:
addresses:
- {{ primary_ip }}
{% for ip in secondary_ips %}
- {{ ip }}
{% endfor %}
routes:
- to: default
via: {{ primary_gateway }}
nameservers:
addresses:
{% for dns in primary_dns %}
- {{ dns }}
{% endfor %}
dest: /etc/netplan/01-multi-ip.yaml
mode: '0600'
register: netplan_config
- name: 验证Netplan配置语法
shell: netplan try --timeout=10
environment:
NETPLAN_TRY_TIMEOUT: "10"
ignore_errors: yes
register: netplan_try
- name: 应用Netplan配置
shell: netplan apply
when: netplan_config is changed
- name: 等待网络恢复
wait_for:
host: "{{ primary_ip | ansible.utils.ipaddr('address') }}"
port: 22
timeout: 30
delegate_to: localhost
when: netplan_config is changed
ignore_errors: yes
- name: 验证IP配置
shell: ip addr show {{ primary_nic }} | grep "inet "
register: ip_verify
changed_when: false
- name: 显示配置结果
debug:
msg: |
═══════ 多IP配置完成 ═══════
网卡: {{ primary_nic }}
主IP: {{ primary_ip }}
网关: {{ primary_gateway }}
辅助IP:
{% for ip in secondary_ips %}
- {{ ip }}
{% endfor %}
当前IP列表:
{{ ip_verify.stdout }}
═══════════════════════════
`,
},
{
ID: "ipmitool-bmc", Name: "IPMI配置BMC地址", Category: "system",
Description: "使用ipmitool配置服务器BMC/IPMI管理口IP地址",
Icon: "🔧", Tags: []string{"ipmi", "bmc", "ipmitool", "hardware"},
Content: `---
# ============================================================
# IPMI配置BMC地址
# 使用方法:
# 在主机管理中为每台主机设置变量,例如:
# bmc_ip=10.10.10.1
# bmc_netmask=255.255.255.0
# bmc_gateway=10.10.10.1
# bmc_user=admin
# bmc_password=Admin@123
# 变量会写入inventory,本Playbook自动读取每台主机的值
# 未设置的主机将使用下方默认值
# ============================================================
- name: IPMI配置BMC地址
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
# 默认值(主机变量会覆盖这些)
default_bmc_netmask: "255.255.255.0"
default_bmc_gateway: "10.10.10.1"
default_bmc_user: "admin"
default_bmc_password: "Admin@123"
bmc_channel: 1
# 是否启用DHCP(设为true则忽略静态IP配置)
bmc_dhcp: false
tasks:
- name: 检查BMC变量
assert:
that:
- bmc_ip is defined
fail_msg: "❌ 主机 {{ inventory_hostname }} 未配置 bmc_ip 变量,请在主机管理中设置"
success_msg: "✅ {{ inventory_hostname }} → BMC IP: {{ bmc_ip }}"
- name: 安装ipmitool
package:
name: ipmitool
state: present
- name: 加载IPMI内核模块
modprobe:
name: "{{ item }}"
state: present
loop:
- ipmi_devintf
- ipmi_si
ignore_errors: yes
- name: 查看当前BMC配置
shell: ipmitool lan print {{ bmc_channel }}
register: bmc_current
changed_when: false
ignore_errors: yes
- name: 显示当前BMC信息
debug:
msg: "{{ bmc_current.stdout_lines | default(['无法读取BMC信息']) }}"
- name: 设置BMC为静态IP
shell: ipmitool lan set {{ bmc_channel }} ipsrc static
when: not bmc_dhcp
- name: 配置BMC IP地址
shell: ipmitool lan set {{ bmc_channel }} ipaddr {{ bmc_ip }}
when: not bmc_dhcp
- name: 配置BMC子网掩码
shell: ipmitool lan set {{ bmc_channel }} netmask {{ bmc_netmask | default(default_bmc_netmask) }}
when: not bmc_dhcp
- name: 配置BMC默认网关
shell: ipmitool lan set {{ bmc_channel }} defgw ipaddr {{ bmc_gateway | default(default_bmc_gateway) }}
when: not bmc_dhcp
- name: 设置BMC为DHCP模式
shell: ipmitool lan set {{ bmc_channel }} ipsrc dhcp
when: bmc_dhcp
- name: 配置BMC用户密码
shell: |
ipmitool user set name {{ bmc_channel }} 2 {{ bmc_user | default(default_bmc_user) }} 2>/dev/null || true
ipmitool user set password 2 {{ bmc_password | default(default_bmc_password) }}
ipmitool user enable 2
ipmitool channel setaccess {{ bmc_channel }} 2 privilege=4
no_log: true
ignore_errors: yes
- name: 验证BMC配置
shell: ipmitool lan print {{ bmc_channel }}
register: bmc_verify
changed_when: false
- name: 显示最终配置
debug:
msg: |
═══════ {{ inventory_hostname }} BMC配置完成 ═══════
模式: {{ 'DHCP' if bmc_dhcp else '静态IP' }}
IP: {{ bmc_ip }}
掩码: {{ bmc_netmask | default(default_bmc_netmask) }}
网关: {{ bmc_gateway | default(default_bmc_gateway) }}
用户: {{ bmc_user | default(default_bmc_user) }}
═══════════════════════════
{{ bmc_verify.stdout }}
`,
},
{
ID: "apt-install", Name: "APT软件安装", Category: "system",
Description: "通过apt批量安装/卸载软件包,支持指定版本和仓库",
Icon: "📦", Tags: []string{"apt", "install", "package", "debian"},
Content: `---
- name: APT软件安装
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
# 要安装的软件包列表
packages_install:
- vim
- curl
- wget
- htop
- net-tools
- tree
- unzip
- git
# 要卸载的软件包列表
packages_remove: []
# 是否更新缓存
update_cache: yes
# 是否自动清理
autoremove: yes
# 安装推荐包
install_recommends: no
# 指定版本(可选,格式: "package=version"
# 例: packages_install: ["nginx=1.18.0-0ubuntu1"]
tasks:
- name: 确认Debian系统
assert:
that: ansible_os_family == "Debian"
fail_msg: "此模板仅适用于 Debian/Ubuntu 系统"
- name: 更新APT缓存
apt:
update_cache: yes
cache_valid_time: 3600
when: update_cache
- name: 安装软件包
apt:
name: "{{ packages_install }}"
state: present
install_recommends: "{{ install_recommends }}"
register: install_result
when: packages_install | length > 0
- name: 卸载软件包
apt:
name: "{{ packages_remove }}"
state: absent
purge: yes
when: packages_remove | length > 0
- name: 自动清理
apt:
autoremove: yes
autoclean: yes
when: autoremove
- name: 验证安装结果
shell: dpkg -l {{ packages_install | join(' ') }} 2>/dev/null | grep "^ii" | awk '{print $2, $3}'
register: verify_result
changed_when: false
when: packages_install | length > 0
- name: 显示安装报告
debug:
msg: |
═══════ APT安装报告 ═══════
已安装: {{ packages_install | length }} 个包
已卸载: {{ packages_remove | length }} 个包
变更: {{ install_result.changed | default(false) }}
{% if verify_result is defined and verify_result.stdout_lines is defined %}
已确认:
{% for line in verify_result.stdout_lines %}
{{ line }}
{% endfor %}
{% endif %}
═══════════════════════════
`,
},
{
ID: "dpkg-install", Name: "DPKG软件安装", Category: "system",
Description: "通过dpkg安装本地.deb包,支持URL下载和依赖修复",
Icon: "📥", Tags: []string{"dpkg", "deb", "install", "offline"},
Content: `---
- name: DPKG软件安装
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
# 本地deb包路径列表(服务器上的路径)
local_deb_files: []
# 远程URL列表(会自动下载后安装)
remote_deb_urls: []
# 下载临时目录
download_dir: /tmp/dpkg_install
# 安装后是否修复依赖
fix_dependencies: yes
# 是否保留下载的deb文件
keep_deb_files: no
tasks:
- name: 确认Debian系统
assert:
that: ansible_os_family == "Debian"
fail_msg: "此模板仅适用于 Debian/Ubuntu 系统"
- name: 创建下载目录
file:
path: "{{ download_dir }}"
state: directory
mode: '0755'
when: remote_deb_urls | length > 0
- name: 下载远程deb包
get_url:
url: "{{ item }}"
dest: "{{ download_dir }}/{{ item | basename }}"
mode: '0644'
loop: "{{ remote_deb_urls }}"
register: download_result
when: remote_deb_urls | length > 0
- name: 安装本地deb包
apt:
deb: "{{ item }}"
state: present
loop: "{{ local_deb_files }}"
register: local_install
when: local_deb_files | length > 0
- name: 安装下载的deb包
apt:
deb: "{{ download_dir }}/{{ item | basename }}"
state: present
loop: "{{ remote_deb_urls }}"
register: remote_install
when: remote_deb_urls | length > 0
- name: 修复依赖关系
apt:
update_cache: yes
force_apt_get: yes
shell: apt-get install -f -y
when: fix_dependencies
ignore_errors: yes
- name: 清理下载文件
file:
path: "{{ download_dir }}"
state: absent
when: not keep_deb_files and remote_deb_urls | length > 0
- name: 显示安装报告
debug:
msg: |
═══════ DPKG安装报告 ═══════
本地包: {{ local_deb_files | length }}
远程包: {{ remote_deb_urls | length }}
{% if local_deb_files | length > 0 %}
本地安装:
{% for f in local_deb_files %}
{{ f }}
{% endfor %}
{% endif %}
{% if remote_deb_urls | length > 0 %}
远程安装:
{% for u in remote_deb_urls %}
{{ u | basename }}
{% endfor %}
{% endif %}
依赖修复: {{ '是' if fix_dependencies else '否' }}
═══════════════════════════
`,
},
// ===== 集群部署 =====
{
ID: "k8s-cluster", Name: "Kubernetes集群部署", Category: "cluster",
Description: "使用kubeadm部署K8S集群(Master+Worker),含容器运行时、网络插件、Dashboard",
Icon: "☸️", Tags: []string{"kubernetes", "k8s", "kubeadm", "cluster"},
Content: `---
# ============================================================
# Kubernetes 集群部署 (kubeadm)
# 使用方法:
# 1. 在inventory中定义 k8s_master 和 k8s_worker 组
# 2. 修改下方 vars 中的版本和网段
# 3. 执行本Playbook
# ============================================================
- name: K8S集群 - 基础环境准备
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
k8s_version: "1.29"
pod_network_cidr: "10.244.0.0/16"
service_cidr: "10.96.0.0/12"
cni_plugin: calico # calico 或 flannel
tasks:
- name: 关闭swap
shell: |
swapoff -a
sed -i '/swap/s/^/#/' /etc/fstab
- name: 加载内核模块
modprobe:
name: "{{ item }}"
state: present
loop:
- overlay
- br_netfilter
- name: 持久化内核模块
copy:
content: |
overlay
br_netfilter
dest: /etc/modules-load.d/k8s.conf
- name: 设置内核参数
sysctl:
name: "{{ item.key }}"
value: "{{ item.value }}"
sysctl_set: yes
reload: yes
loop:
- { key: "net.bridge.bridge-nf-call-iptables", value: "1" }
- { key: "net.bridge.bridge-nf-call-ip6tables", value: "1" }
- { key: "net.ipv4.ip_forward", value: "1" }
- name: 关闭防火墙
service:
name: firewalld
state: stopped
enabled: no
ignore_errors: yes
- name: 关闭SELinux
shell: setenforce 0 && sed -i 's/^SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config
ignore_errors: yes
- name: 时间同步检查
shell: timedatectl set-ntp true
ignore_errors: yes
- name: K8S集群 - 安装容器运行时和K8S组件
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
k8s_version: "1.29"
tasks:
- name: 安装containerd依赖
apt:
name:
- apt-transport-https
- ca-certificates
- curl
- gnupg
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: 安装containerd
shell: |
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" > /etc/apt/sources.list.d/docker.list
apt-get update && apt-get install -y containerd.io
when: ansible_os_family == "Debian"
ignore_errors: yes
- name: 配置containerd
shell: |
mkdir -p /etc/containerd
containerd config default > /etc/containerd/config.toml
sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
systemctl restart containerd && systemctl enable containerd
ignore_errors: yes
- name: 添加K8S APT源
shell: |
curl -fsSL https://pkgs.k8s.io/core:/stable:/v{{ k8s_version }}/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg
echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v{{ k8s_version }}/deb/ /" > /etc/apt/sources.list.d/kubernetes.list
apt-get update
when: ansible_os_family == "Debian"
- name: 安装kubeadm/kubelet/kubectl
apt:
name:
- kubelet
- kubeadm
- kubectl
state: present
update_cache: yes
when: ansible_os_family == "Debian"
- name: 锁定K8S包版本
shell: apt-mark hold kubelet kubeadm kubectl
when: ansible_os_family == "Debian"
- name: 启动kubelet
service:
name: kubelet
state: started
enabled: yes
- name: K8S集群 - 初始化Master
hosts: k8s_master
become: yes
vars:
pod_network_cidr: "10.244.0.0/16"
service_cidr: "10.96.0.0/12"
cni_plugin: calico
tasks:
- name: 初始化Master节点
shell: |
kubeadm init \
--pod-network-cidr={{ pod_network_cidr }} \
--service-cidr={{ service_cidr }} \
--upload-certs \
--ignore-preflight-errors=NumCPU
register: kubeadm_init
ignore_errors: yes
- name: 配置kubectl
shell: |
mkdir -p $HOME/.kube
cp -f /etc/kubernetes/admin.conf $HOME/.kube/config
chown $(id -u):$(id -g) $HOME/.kube/config
ignore_errors: yes
- name: 部署Calico网络
shell: kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27.0/manifests/calico.yaml
when: cni_plugin == "calico"
ignore_errors: yes
- name: 部署Flannel网络
shell: kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml
when: cni_plugin == "flannel"
ignore_errors: yes
- name: 获取Worker加入命令
shell: kubeadm token create --print-join-command
register: join_command
changed_when: false
- name: 显示加入命令
debug:
msg: |
═══════ K8S Master 初始化完成 ═══════
Worker加入命令:
{{ join_command.stdout }}
═══════════════════════════════════
- name: K8S集群 - Worker加入集群
hosts: k8s_worker
become: yes
vars:
master_ip: "{{ hostvars[groups['k8s_master'][0]]['ansible_host'] | default(groups['k8s_master'][0]) }}"
tasks:
- name: 获取加入命令
shell: ssh -o StrictHostKeyChecking=no {{ master_ip }} "kubeadm token create --print-join-command"
register: join_cmd
delegate_to: "{{ groups['k8s_master'][0] }}"
ignore_errors: yes
- name: 加入集群
shell: "{{ join_cmd.stdout }}"
when: join_cmd is succeeded
ignore_errors: yes
- name: 验证节点状态
shell: kubectl get nodes
delegate_to: "{{ groups['k8s_master'][0] }}"
register: nodes_status
changed_when: false
ignore_errors: yes
- name: 显示集群状态
debug:
msg: "{{ nodes_status.stdout_lines | default(['等待Master就绪']) }}"
`,
},
{
ID: "ceph-cluster", Name: "Ceph存储集群部署", Category: "cluster",
Description: "使用cephadm部署Ceph集群(MON+OSD+MDS+RGW),含Dashboard",
Icon: "🐙", Tags: []string{"ceph", "storage", "cephadm", "cluster"},
Content: `---
# ============================================================
# Ceph 存储集群部署 (cephadm)
# 使用方法:
# 1. 在inventory中定义 ceph_mon, ceph_osd, ceph_mds 组
# 2. 修改下方 vars 中的网络和磁盘配置
# 3. 执行本Playbook
# ============================================================
- name: Ceph集群 - 基础环境准备
hosts: "{{ target_hosts | default('all') }}"
become: yes
vars:
ceph_release: reef # quincy 或 reef
cluster_network: "10.0.0.0/24"
public_network: "192.168.1.0/24"
tasks:
- name: 安装基础依赖
package:
name:
- python3
- lvm2
- chrony
- podman
state: present
- name: 配置时间同步
service:
name: chronyd
state: started
enabled: yes
ignore_errors: yes
- name: 关闭防火墙
service:
name: firewalld
state: stopped
enabled: no
ignore_errors: yes
- name: 设置主机名解析
lineinfile:
path: /etc/hosts
line: "{{ hostvars[item]['ansible_host'] | default(item) }} {{ item }}"
create: yes
loop: "{{ groups['all'] }}"
when: hostvars[item]['ansible_host'] is defined
- name: Ceph集群 - 安装cephadm
hosts: ceph_mon
become: yes
vars:
ceph_release: reef
tasks:
- name: 安装cephadm
shell: |
curl --silent --remote-name --location https://github.com/ceph/ceph/raw/{{ ceph_release }}/src/cephadm/cephadm
chmod +x cephadm
./cephadm add-repo --release {{ ceph_release }}
./cephadm install
args:
creates: /usr/sbin/cephadm
ignore_errors: yes
- name: 验证cephadm
shell: cephadm version
register: cephadm_ver
changed_when: false
- name: 显示版本
debug:
msg: "{{ cephadm_ver.stdout }}"
- name: Ceph集群 - 引导集群
hosts: ceph_mon[0]
become: yes
vars:
cluster_network: "10.0.0.0/24"
public_network: "192.168.1.0/24"
mon_ip: "{{ ansible_host | default(ansible_default_ipv4.address) }}"
tasks:
- name: 引导Ceph集群
shell: |
cephadm bootstrap \
--mon-ip {{ mon_ip }} \
--cluster-network {{ cluster_network }} \
--allow-overwrite
args:
creates: /etc/ceph/ceph.conf
register: bootstrap_result
ignore_errors: yes
- name: 配置Dashboard
shell: |
ceph dashboard set-rgw-api-ssl-verify False
ceph mgr services
ignore_errors: yes
- name: 获取Dashboard地址
shell: ceph mgr services | grep dashboard
register: dashboard_url
changed_when: false
ignore_errors: yes
- name: 显示集群信息
debug:
msg: |
═══════ Ceph集群引导完成 ═══════
MON IP: {{ mon_ip }}
集群网络: {{ cluster_network }}
公共网络: {{ public_network }}
Dashboard: {{ dashboard_url.stdout | default('http://' + mon_ip + ':8443') }}
═══════════════════════════════════
- name: Ceph集群 - 添加节点
hosts: ceph_osd
become: yes
vars:
mon_host: "{{ groups['ceph_mon'][0] }}"
tasks:
- name: 获取SSH公钥
shell: cat /etc/ceph/ceph.pub
delegate_to: "{{ mon_host }}"
register: ceph_pub_key
changed_when: false
- name: 分发SSH公钥
authorized_key:
user: root
key: "{{ ceph_pub_key.stdout }}"
- name: 添加节点到集群
shell: ceph orch host add {{ inventory_hostname }} {{ ansible_host | default(inventory_hostname) }}
delegate_to: "{{ mon_host }}"
ignore_errors: yes
- name: Ceph集群 - 部署OSD
hosts: ceph_mon[0]
become: yes
vars:
# OSD磁盘配置(根据实际修改)
osd_devices:
- /dev/sdb
- /dev/sdc
osd_all_available: false # 设为true则自动使用所有可用磁盘
tasks:
- name: 部署指定磁盘OSD
shell: ceph orch daemon add osd {{ item.split('/')[2] }}:{{ item }}
loop: "{{ osd_devices }}"
when: not osd_all_available
ignore_errors: yes
- name: 部署所有可用磁盘OSD
shell: ceph orch apply osd --all-available-devices
when: osd_all_available
ignore_errors: yes
- name: 等待OSD就绪
shell: ceph osd stat
register: osd_stat
changed_when: false
retries: 10
delay: 10
until: osd_stat.rc == 0
ignore_errors: yes
- name: 显示OSD状态
debug:
msg: "{{ osd_stat.stdout | default('等待OSD初始化') }}"
- name: Ceph集群 - 部署MDS和RGW
hosts: ceph_mon[0]
become: yes
vars:
deploy_mds: true
deploy_rgw: true
mds_placement: "ceph_mds"
rgw_placement: "ceph_rgw"
tasks:
- name: 部署MDSCephFS元数据服务)
shell: ceph orch apply mds cephfs --placement="{{ mds_placement }}"
when: deploy_mds
ignore_errors: yes
- name: 部署RGW(对象存储网关)
shell: ceph orch apply rgw ceph-rgw --placement="{{ rgw_placement }}" --port=8080
when: deploy_rgw
ignore_errors: yes
- name: 显示集群状态
shell: ceph -s
register: ceph_status
changed_when: false
- name: 最终报告
debug:
msg: "{{ ceph_status.stdout_lines }}"
`,
},
{
ID: "k8s-rook-ceph", Name: "K8S + Rook-Ceph存储", Category: "cluster",
Description: "在K8S集群上部署Rook-Ceph operator,提供持久化存储",
Icon: "💎", Tags: []string{"kubernetes", "rook", "ceph", "storage", "csi"},
Content: `---
# ============================================================
# Rook-Ceph on Kubernetes
# 前提: K8S集群已部署完成
# ============================================================
- name: Rook-Ceph - 部署Operator
hosts: k8s_master[0]
become: yes
vars:
rook_version: "v1.13.3"
tasks:
- name: 克隆Rook仓库
git:
repo: https://github.com/rook/rook.git
dest: /tmp/rook
version: "{{ rook_version }}"
depth: 1
ignore_errors: yes
- name: 部署CRD和Operator
shell: |
cd /tmp/rook/deploy/examples
kubectl create -f crds.yaml -f common.yaml -f operator.yaml
ignore_errors: yes
- name: 等待Operator就绪
shell: kubectl -n rook-ceph get pod -l app=rook-ceph-operator -o jsonpath='{.items[0].status.phase}'
register: operator_status
retries: 30
delay: 10
until: operator_status.stdout == "Running"
ignore_errors: yes
- name: 显示Operator状态
debug:
msg: "Rook Operator: {{ operator_status.stdout | default('等待中') }}"
- name: Rook-Ceph - 创建CephCluster
hosts: k8s_master[0]
become: yes
vars:
# 存储设备配置
use_all_devices: false
devices:
- name: sdb
- name: sdc
# 或使用目录(测试环境)
use_directories: false
data_dir_host_path: /var/lib/rook
tasks:
- name: 创建CephCluster
shell: |
cat <<'EOF' | kubectl apply -f -
apiVersion: ceph.rook.io/v1
kind: CephCluster
metadata:
name: rook-ceph
namespace: rook-ceph
spec:
cephVersion:
image: quay.io/ceph/ceph:v18.2
dataDirHostPath: {{ data_dir_host_path }}
mon:
count: 3
allowMultiplePerNode: false
mgr:
count: 1
dashboard:
enabled: true
ssl: false
storage:
useAllNodes: true
{% if use_all_devices %}
useAllDevices: true
{% elif devices | length > 0 %}
nodes:
- name: "*"
devices:
{% for d in devices %}
- name: "{{ d.name }}"
{% endfor %}
{% elif use_directories %}
directories:
- path: {{ data_dir_host_path }}
{% endif %}
EOF
ignore_errors: yes
- name: 等待CephCluster就绪
shell: kubectl -n rook-ceph get cephcluster -o jsonpath='{.items[0].status.phase}'
register: cluster_phase
retries: 60
delay: 15
until: cluster_phase.stdout == "Ready"
ignore_errors: yes
- name: 显示集群状态
shell: kubectl -n rook-ceph get cephcluster
register: cluster_status
changed_when: false
- name: 报告
debug:
msg: "{{ cluster_status.stdout_lines }}"
- name: Rook-Ceph - 创建StorageClass
hosts: k8s_master[0]
become: yes
vars:
create_rbd_pool: true
create_cephfs_pool: true
rbd_pool_name: replicapool
rbd_pool_replicas: 3
tasks:
- name: 创建RBD StorageClass
shell: |
cat <<'EOF' | kubectl apply -f -
apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
name: {{ rbd_pool_name }}
namespace: rook-ceph
spec:
failureDomain: host
replicated:
size: {{ rbd_pool_replicas }}
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
clusterID: rook-ceph
pool: {{ rbd_pool_name }}
imageFormat: "2"
imageFeatures: layering
csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
allowVolumeExpansion: true
EOF
when: create_rbd_pool
ignore_errors: yes
- name: 创建CephFS StorageClass
shell: |
cat <<'EOF' | kubectl apply -f -
apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
name: ceph-filesystem
namespace: rook-ceph
spec:
metadataPool:
replicated:
size: 3
dataPools:
- name: data0
replicated:
size: 3
metadataServer:
activeCount: 1
activeStandby: true
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: rook-cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
clusterID: rook-ceph
fsName: ceph-filesystem
pool: ceph-filesystem-data0
csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete
EOF
when: create_cephfs_pool
ignore_errors: yes
- name: 设置默认StorageClass
shell: kubectl patch storageclass rook-ceph-block -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
ignore_errors: yes
- name: 显示StorageClass
shell: kubectl get sc
register: sc_list
changed_when: false
- name: 最终报告
debug:
msg: |
═══════ Rook-Ceph 部署完成 ═══════
{{ sc_list.stdout }}
═══════════════════════════════════
`,
},
{
ID: "etcd-cluster", Name: "ETCD集群部署", Category: "cluster",
Description: "部署3/5节点ETCD集群,支持TLS加密和自动发现",
Icon: "🔐", Tags: []string{"etcd", "cluster", "kv", "consensus"},
Content: `---
# ============================================================
# ETCD 集群部署
# 使用方法: 在inventory中定义 etcd 组(3或5个节点)
# ============================================================
- name: ETCD集群 - 安装部署
hosts: etcd
become: yes
vars:
etcd_version: "3.5.12"
etcd_data_dir: /var/lib/etcd
etcd_initial_cluster_token: "etcd-cluster-prod"
# 节点间通信端口
client_port: 2379
peer_port: 2380
# 是否启用TLS
enable_tls: false
tasks:
- name: 创建etcd用户
user:
name: etcd
system: yes
shell: /usr/sbin/nologin
create_home: no
- name: 下载ETCD
get_url:
url: "https://github.com/etcd-io/etcd/releases/download/v{{ etcd_version }}/etcd-v{{ etcd_version }}-linux-amd64.tar.gz"
dest: /tmp/etcd.tar.gz
- name: 解压安装
unarchive:
src: /tmp/etcd.tar.gz
dest: /tmp
remote_src: yes
- name: 安装二进制
copy:
src: "/tmp/etcd-v{{ etcd_version }}-linux-amd64/{{ item }}"
dest: "/usr/local/bin/{{ item }}"
mode: '0755'
remote_src: yes
loop: [etcd, etcdctl]
- name: 创建数据目录
file:
path: "{{ etcd_data_dir }}"
state: directory
owner: etcd
group: etcd
mode: '0700'
- name: 构建集群节点列表
set_fact:
etcd_initial_cluster: "{{ groups['etcd'] | map('extract', hostvars, 'inventory_hostname') | zip(groups['etcd'] | map('extract', hostvars, ['ansible_host'])) | map('join', '=http://') | map('regex_replace', '^(.*)$', '\\1:' + (peer_port | string)) | join(',') }}"
- name: 创建Systemd服务
copy:
content: |
[Unit]
Description=ETCD Key-Value Store
After=network.target
[Service]
Type=notify
User=etcd
ExecStart=/usr/local/bin/etcd \
--name {{ inventory_hostname }} \
--data-dir {{ etcd_data_dir }} \
--listen-client-urls http://0.0.0.0:{{ client_port }} \
--advertise-client-urls http://{{ ansible_host | default(inventory_hostname) }}:{{ client_port }} \
--listen-peer-urls http://0.0.0.0:{{ peer_port }} \
--initial-advertise-peer-urls http://{{ ansible_host | default(inventory_hostname) }}:{{ peer_port }} \
--initial-cluster {{ etcd_initial_cluster }} \
--initial-cluster-token {{ etcd_initial_cluster_token }} \
--initial-cluster-state new
Restart=always
RestartSec=5
LimitNOFILE=65536
[Install]
WantedBy=multi-user.target
dest: /etc/systemd/system/etcd.service
- name: 启动ETCD
systemd:
name: etcd
state: started
enabled: yes
daemon_reload: yes
- name: ETCD集群 - 验证
hosts: etcd[0]
become: yes
vars:
client_port: 2379
tasks:
- name: 检查集群健康
shell: etcdctl endpoint health --cluster
register: health
changed_when: false
ignore_errors: yes
- name: 检查成员列表
shell: etcdctl member list -w table
register: members
changed_when: false
ignore_errors: yes
- name: 显示集群状态
debug:
msg: |
═══════ ETCD集群状态 ═══════
健康检查: {{ health.stdout | default('检查中...') }}
成员列表:
{{ members.stdout | default('等待集群就绪') }}
═══════════════════════════
`,
},
{
ID: "harbor-cluster", Name: "Harbor镜像仓库部署", Category: "cluster",
Description: "部署Harbor企业级容器镜像仓库,含HTTPS和持久化存储",
Icon: "⚓", Tags: []string{"harbor", "registry", "docker", "images"},
Content: `---
# ============================================================
# Harbor 容器镜像仓库部署
# ============================================================
- name: Harbor - 安装部署
hosts: "{{ target_hosts | default('harbor') }}"
become: yes
vars:
harbor_version: "2.10.0"
harbor_hostname: "harbor.example.com"
harbor_admin_password: "Harbor@12345"
harbor_data_dir: /data/harbor
# 协议: http 或 https
harbor_protocol: https
# HTTPS证书(自签名或已有证书)
ssl_cert: ""
ssl_cert_key: ""
tasks:
- name: 安装Docker(如未安装)
shell: |
if ! command -v docker &>/dev/null; then
curl -fsSL https://get.docker.com | sh
systemctl enable docker && systemctl start docker
fi
ignore_errors: yes
- name: 安装Docker Compose
shell: |
if ! command -v docker-compose &>/dev/null; then
curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose
fi
ignore_errors: yes
- name: 创建数据目录
file:
path: "{{ harbor_data_dir }}"
state: directory
mode: '0755'
- name: 下载Harbor离线安装包
get_url:
url: "https://github.com/goharbor/harbor/releases/download/v{{ harbor_version }}/harbor-offline-installer-v{{ harbor_version }}.tgz"
dest: /tmp/harbor.tgz
timeout: 300
- name: 解压安装包
unarchive:
src: /tmp/harbor.tgz
dest: "{{ harbor_data_dir }}"
remote_src: yes
- name: 生成自签名证书
shell: |
mkdir -p /etc/harbor/ssl
openssl req -newkey rsa:4096 -nodes -sha256 \
-keyout /etc/harbor/ssl/harbor.key \
-x509 -days 3650 \
-subj "/CN={{ harbor_hostname }}" \
-addext "subjectAltName=DNS:{{ harbor_hostname }}" \
-out /etc/harbor/ssl/harbor.crt
when: harbor_protocol == "https" and ssl_cert == ""
args:
creates: /etc/harbor/ssl/harbor.crt
- name: 配置Harbor
shell: |
cd {{ harbor_data_dir }}/harbor
cp harbor.yml.tmpl harbor.yml
sed -i "s/^hostname: .*/hostname: {{ harbor_hostname }}/" harbor.yml
sed -i "s/^harbor_admin_password: .*/harbor_admin_password: {{ harbor_admin_password }}/" harbor.yml
sed -i "s|^data_volume: .*|data_volume: {{ harbor_data_dir }}/data|" harbor.yml
{% if harbor_protocol == "https" %}
sed -i "s|^ certificate: .*| certificate: {{ ssl_cert | default('/etc/harbor/ssl/harbor.crt') }}|" harbor.yml
sed -i "s|^ private_key: .*| private_key: {{ ssl_cert_key | default('/etc/harbor/ssl/harbor.key') }}|" harbor.yml
{% else %}
sed -i '/^https:/,/^[a-z]/{ /^https:/d; /^ port:/d; /^ certificate:/d; /^ private_key:/d }' harbor.yml
{% endif %}
- name: 运行Harbor安装脚本
shell: |
cd {{ harbor_data_dir }}/harbor
./install.sh --with-trivy
register: harbor_install
ignore_errors: yes
- name: 等待Harbor启动
wait_for:
port: "{{ 443 if harbor_protocol == 'https' else 80 }}"
timeout: 120
ignore_errors: yes
- name: 显示部署信息
debug:
msg: |
═══════ Harbor 部署完成 ═══════
地址: {{ harbor_protocol }}://{{ harbor_hostname }}
用户: admin
密码: {{ harbor_admin_password }}
数据目录: {{ harbor_data_dir }}
═══════════════════════════════════
`,
},
{
ID: "nfs-cluster", Name: "NFS共享存储集群", Category: "cluster",
Description: "部署NFS服务端+客户端,配置共享存储和自动挂载",
Icon: "📂", Tags: []string{"nfs", "storage", "share", "mount"},
Content: `---
# ============================================================
# NFS 共享存储部署
# 使用方法:
# 1. 在inventory中定义 nfs_server 和 nfs_client 组
# 2. 修改共享目录和客户端挂载配置
# ============================================================
- name: NFS - 服务端部署
hosts: nfs_server
become: yes
vars:
# 共享目录配置
nfs_exports:
- path: /data/nfs/share
clients: "192.168.1.0/24"
options: "rw,sync,no_root_squash,no_subtree_check"
- path: /data/nfs/backup
clients: "192.168.1.0/24"
options: "rw,sync,no_root_squash"
# NFS版本
nfs_versions: "4.2,4.1,4,3"
tasks:
- name: 安装NFS服务端
package:
name: nfs-kernel-server
state: present
when: ansible_os_family == "Debian"
- name: 安装NFS服务端(RedHat)
package:
name: nfs-utils
state: present
when: ansible_os_family == "RedHat"
- name: 创建共享目录
file:
path: "{{ item.path }}"
state: directory
mode: '0777'
loop: "{{ nfs_exports }}"
- name: 配置exports
lineinfile:
path: /etc/exports
line: "{{ item.path }} {{ item.clients }}({{ item.options }})"
create: yes
loop: "{{ nfs_exports }}"
register: exports_config
- name: 重启NFS服务
service:
name: nfs-kernel-server
state: restarted
when: ansible_os_family == "Debian" and exports_config is changed
- name: 重启NFS服务(RedHat)
service:
name: nfs-server
state: restarted
when: ansible_os_family == "RedHat" and exports_config is changed
- name: 导出共享
shell: exportfs -ra
changed_when: false
- name: 验证导出
shell: exportfs -v
register: export_list
changed_when: false
- name: 显示共享信息
debug:
msg: "{{ export_list.stdout_lines }}"
- name: NFS - 客户端挂载
hosts: nfs_client
become: yes
vars:
nfs_server_ip: "{{ hostvars[groups['nfs_server'][0]]['ansible_host'] | default(groups['nfs_server'][0]) }}"
# 客户端挂载配置
nfs_mounts:
- server_path: "/data/nfs/share"
local_path: "/mnt/nfs/share"
options: "rw,soft,timeo=30"
- server_path: "/data/nfs/backup"
local_path: "/mnt/nfs/backup"
options: "rw,soft,timeo=30"
tasks:
- name: 安装NFS客户端
package:
name: nfs-common
state: present
when: ansible_os_family == "Debian"
- name: 安装NFS客户端(RedHat)
package:
name: nfs-utils
state: present
when: ansible_os_family == "RedHat"
- name: 创建挂载点
file:
path: "{{ item.local_path }}"
state: directory
mode: '0755'
loop: "{{ nfs_mounts }}"
- name: 挂载NFS共享
mount:
path: "{{ item.local_path }}"
src: "{{ nfs_server_ip }}:{{ item.server_path }}"
fstype: nfs
opts: "{{ item.options }}"
state: mounted
loop: "{{ nfs_mounts }}"
- name: 验证挂载
shell: df -h | grep nfs
register: mount_check
changed_when: false
ignore_errors: yes
- name: 显示挂载状态
debug:
msg: "{{ mount_check.stdout_lines | default(['等待挂载']) }}"
`,
},
}
}