← 返回首页
🔧

Ansible Playbook:任务编排

📂 devops ⏱ 3 min 589 words

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的核心。掌握条件、循环、模板和错误处理,可以编写出灵活、可靠的自动化脚本。