Ansible Playbook:任务编排
Ansible Playbook:任务编排
Playbook基础
基本结构
# deploy.yml
---
- name: 部署应用
hosts: webservers
become: yes
vars:
app_name: myapp
app_version: "1.0.0"
pre_tasks:
- name: 更新apt缓存
ansible.builtin.apt:
update_cache: yes
cache_valid_time: 3600
tasks:
- name: 安装依赖
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- python3
- python3-pip
post_tasks:
- name: 清理临时文件
ansible.builtin.file:
path: /tmp/app-install
state: absent
条件判断
tasks:
- name: 仅在Ubuntu上安装
ansible.builtin.apt:
name: nginx
when: ansible_os_family == "Debian"
- name: 检查文件是否存在
ansible.builtin.stat:
path: /etc/app/config.yml
register: config_file
- name: 创建默认配置
ansible.builtin.template:
src: config.yml.j2
dest: /etc/app/config.yml
when: not config_file.stat.exists
- name: 条件重启服务
ansible.builtin.service:
name: nginx
state: restarted
when: config_changed.changed
循环
tasks:
# 简单循环
- name: 安装多个包
ansible.builtin.apt:
name: "{{ item }}"
state: present
loop:
- nginx
- php-fpm
- php-mysql
# 字典循环
- name: 创建用户
ansible.builtin.user:
name: "{{ item.name }}"
groups: "{{ item.groups }}"
shell: "{{ item.shell }}"
loop:
- { name: 'alice', groups: 'admin', shell: '/bin/bash' }
- { name: 'bob', groups: 'dev', shell: '/bin/zsh' }
# 嵌套循环
- name: 创建目录结构
ansible.builtin.file:
path: "/opt/{{ item.0 }}/{{ item.1 }}"
state: directory
loop: "{{ ['app', 'logs', 'data'] | product(['config', 'data']) | list }}"
# 注册循环结果
- name: 检查端口
ansible.builtin.wait_for:
port: "{{ item }}"
timeout: 5
loop:
- 80
- 443
- 8080
register: port_check
ignore_errors: yes
Handlers
tasks:
- name: 复制nginx配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
notify: restart nginx
- name: 复制应用配置
ansible.builtin.template:
src: app.conf.j2
dest: /etc/app/config.yml
notify: restart app
handlers:
- name: restart nginx
ansible.builtin.service:
name: nginx
state: restarted
- name: restart app
ansible.builtin.service:
name: myapp
state: restarted
模板(Jinja2)
{# templates/nginx.conf.j2 #}
server {
listen {{ http_port | default(80) }};
server_name {{ server_name }};
location / {
proxy_pass http://{{ backend_host }}:{{ backend_port }};
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
{% if ssl_enabled | default(false) %}
listen 443 ssl;
ssl_certificate /etc/nginx/ssl/{{ ssl_cert }};
ssl_certificate_key /etc/nginx/ssl/{{ ssl_key }};
{% endif %}
{% for location in extra_locations | default([]) %}
location {{ location.path }} {
proxy_pass {{ location.upstream }};
}
{% endfor %}
}
错误处理
tasks:
- name: 可能失败的任务
ansible.builtin.command: /bin/false
ignore_errors: yes
- name: 失败时执行
ansible.builtin.fail:
msg: "自定义错误信息"
when: some_condition
- name: 使用block/rescue/always
block:
- name: 尝试部署
ansible.builtin.command: deploy.sh
rescue:
- name: 回滚
ansible.builtin.command: rollback.sh
always:
- name: 清理
ansible.builtin.file:
path: /tmp/deploy
state: absent
- name: 注册并检查结果
ansible.builtin.command: check_health.sh
register: health_check
failed_when: "'ERROR' in health_check.stdout"
changed_when: health_check.rc != 0
实践:Web应用部署Playbook
# deploy-webapp.yml
---
- name: 部署Web应用
hosts: webservers
become: yes
vars:
app_name: webapp
app_user: deploy
app_dir: /opt/{{ app_name }}
app_port: 8080
nginx_port: 80
tasks:
- name: 创建应用用户
ansible.builtin.user:
name: {{ app_user }}
system: yes
shell: /bin/bash
- name: 创建应用目录
ansible.builtin.file:
path: {{ item }}
state: directory
owner: {{ app_user }}
group: {{ app_user }}
loop:
- {{ app_dir }}
- {{ app_dir }}/releases
- {{ app_dir }}/shared
- {{ app_dir }}/shared/logs
- name: 部署应用代码
ansible.builtin.unarchive:
src: "releases/{{ app_name }}-{{ version }}.tar.gz"
dest: {{ app_dir }}/releases/
remote_src: yes
owner: {{ app_user }}
- name: 更新软链接
ansible.builtin.file:
src: {{ app_dir }}/releases/{{ app_name }}-{{ version }}
dest: {{ app_dir }}/current
state: link
owner: {{ app_user }}
force: yes
- name: 复制nginx配置
ansible.builtin.template:
src: nginx.conf.j2
dest: /etc/nginx/conf.d/{{ app_name }}.conf
notify: reload nginx
- name: 重启应用
ansible.builtin.service:
name: {{ app_name }}
state: restarted
handlers:
- name: reload nginx
ansible.builtin.service:
name: nginx
state: reloaded
实践:数据库初始化Playbook
# init-database.yml
---
- name: 初始化MySQL数据库
hosts: dbservers
become: yes
vars:
mysql_root_password: "{{ vault_mysql_root_password }}"
databases:
- name: app_production
encoding: utf8mb4
- name: app_staging
encoding: utf8mb4
tasks:
- name: 安装MySQL
ansible.builtin.apt:
name:
- mysql-server
- python3-mysqldb
state: present
- name: 启动MySQL
ansible.builtin.service:
name: mysql
state: started
enabled: yes
- name: 设置root密码
community.mysql.mysql_user:
name: root
password: {{ mysql_root_password }}
host_all: yes
login_unix_socket: /var/run/mysqld/mysqld.sock
- name: 创建数据库
community.mysql.mysql_db:
name: {{ item.name }}
encoding: {{ item.encoding }}
collation: utf8mb4_unicode_ci
state: present
login_unix_socket: /var/run/mysqld/mysqld.sock
loop: {{ databases }}
- name: 配置备份
ansible.builtin.cron:
name: "MySQL backup"
minute: "0"
hour: "2"
job: "/opt/scripts/mysql-backup.sh"
常用命令
# 执行Playbook
ansible-playbook deploy.yml --ask-vault-pass
# 语法检查
ansible-playbook deploy.yml --syntax-check
# 列出任务
ansible-playbook deploy.yml --list-tasks
# 逐步执行
ansible-playbook deploy.yml --step
# 限制主机
ansible-playbook deploy.yml --limit "web1:web2"
# 跳过标签
ansible-playbook deploy.yml --skip-tags "skip_me"
总结
Playbook是Ansible的核心。掌握条件、循环、模板和错误处理,可以编写出灵活、可靠的自动化脚本。