Ansible 最佳实践:构建可靠的自动化工作流

Ansible 最佳实践:构建可靠的自动化工作流

项目组织结构

推荐的项目结构

# 推荐的 Ansible 项目结构
project_root/
├── ansible.cfg              # Ansible 配置文件
├── inventory/               # Inventory 目录
│   ├── production/          # 生产环境
│   │   ├── hosts.ini        # 主机清单
│   │   └── group_vars/      # 组变量
│   ├── staging/             # 预发布环境
│   │   ├── hosts.ini
│   │   └── group_vars/
│   └── development/         # 开发环境
│       ├── hosts.ini
│       └── group_vars/
├── playbooks/               # Playbook 目录
│   ├── site.yml             # 主 Playbook
│   ├── webservers.yml       # Web 服务器 Playbook
│   └── databases.yml        # 数据库 Playbook
├── roles/                   # 角色目录
│   ├── common/              # 通用角色
│   ├── nginx/               # Nginx 角色
│   ├── mysql/               # MySQL 角色
│   └── php/                 # PHP 角色
├── library/                 # 自定义模块
├── filter_plugins/          # 自定义过滤器
├── host_vars/               # 主机变量(可选,推荐用 inventory/host_vars)
├── group_vars/              # 组变量(可选,推荐用 inventory/group_vars)
├── scripts/                 # 脚本文件
├── templates/               # 模板文件(如果不在角色中)
├── files/                   # 静态文件(如果不在角色中)
├── requirements.yml          # 角色依赖
├── README.md                # 项目文档
└── .gitignore               # Git 忽略文件

环境分离

# 使用不同的 Inventory 文件管理不同环境
ansible-playbook -i inventory/production/hosts.ini site.yml
ansible-playbook -i inventory/staging/hosts.ini site.yml
ansible-playbook -i inventory/development/hosts.ini site.yml

Inventory 最佳实践

使用目录组织 Inventory

inventory/
├── production/
│   ├── hosts.ini           # 主机清单
│   ├── group_vars/         # 组变量
│   │   ├── all.yml
│   │   ├── webservers.yml
│   │   ├── databases.yml
│   │   └── caches.yml
│   └── host_vars/          # 主机变量
│       ├── web1.yml
│       ├── web2.yml
│       └── db1.yml
└── staging/
    └── ...

主机分组策略

# 按 function(功能)分组
[webservers]
web1.example.com
web2.example.com

[databases]
db1.example.com
db2.example.com

[caches]
cache1.example.com
cache2.example.com

# 按 environment(环境)分组
[production]
web1.example.com
db1.example.com

[staging]
web2.example.com
db2.example.com

# 按 location(位置)分组
[beijing]
web1.example.com
db1.example.com

[shanghai]
web2.example.com
db2.example.com

使用 YAML 格式

# YAML 格式更易读
all:
  children:
    webservers:
      hosts:
        web1.example.com:
          ansible_user: deploy
        web2.example.com:
          ansible_user: deploy
    databases:
      hosts:
        db1.example.com:
          ansible_user: dbadmin
        db2.example.com:
          ansible_user: dbadmin
  vars:
    ansible_ssh_private_key_file: ~/.ssh/id_rsa

Playbook 最佳实践

使用 Roles

# 好的做法:使用 Roles
- name: 部署应用
  hosts: app_servers
  become: yes

  roles:
    - common
    - nginx
    - php
    - mysql

# 不好的做法:在 Playbook 中写大量任务
- name: 部署应用
  hosts: app_servers
  become: yes

  tasks:
    - name: 更新缓存
      apt:
        update_cache: yes
    # ... 几百行任务

命名规范

# Playbook 文件命名:使用下划线,描述性名称
site.yml                    # 主 Playbook
webservers.yml             # Web 服务器 Playbook
databases.yml              # 数据库 Playbook
deploy_application.yml     # 部署应用 Playbook

# 任务命名:动词 + 对象
- name: 安装 Nginx           # 好的做法
  package:
    name: nginx
    state: present

- name: 配置 Nginx
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf

# 变量命名:描述性,使用下划线
nginx_port: 80
nginx_worker_processes: auto
server_name: example.com

幂等性设计

# 好的做法:使用幂等模块
- name: 确保 Nginx 服务运行中
  service:
    name: nginx
    state: started
    enabled: yes

# 不好的做法:使用 shell 命令
- name: 启动 Nginx
  shell: systemctl start nginx

# 使用 changed_when 定义幂等性
- name: 检查服务状态
  command: systemctl is-active nginx
  register: nginx_status
  changed_when: false

- name: 启动服务(如果未运行)
  service:
    name: nginx
    state: started
  when: nginx_status.stdout != "active"

错误处理

# 使用 block-rescue-always
- name: 安全部署
  block:
    - name: 备份当前版本
      shell: cp -r {{ app_dir }} {{ backup_dir }}

    - name: 部署新版本
      git:
        repo: "{{ app_repo }}"
        dest: "{{ app_dir }}"

  rescue:
    - name: 部署失败,回滚
      shell: cp -r {{ backup_dir }} {{ app_dir }}
      ignore_errors: yes

    - name: 发送告警
      debug:
        msg: "部署失败,已回滚"

  always:
    - name: 清理临时文件
      file:
        path: /tmp/deploy_temp
        state: absent

使用 Tags

# 为任务和角色添加标签
- name: 部署 Web 应用
  hosts: webservers
  become: yes

  roles:
    - role: nginx
      tags:
        - web
        - nginx

    - role: php-fpm
      tags:
        - web
        - php

    - role: mysql
      tags:
        - database
        - mysql

# 执行特定标签的任务
ansible-playbook site.yml --tags "nginx"
ansible-playbook site.yml --tags "web"
ansible-playbook site.yml --skip-tags "database"

变量管理

变量优先级策略

# 1. defaults/ - 角色默认变量(优先级最低)
# roles/nginx/defaults/main.yml
nginx_port: 80

# 2. group_vars/all - 全局组变量
# group_vars/all.yml
nginx_port: 8080

# 3. group_vars/webservers - 特定组变量
# group_vars/webservers.yml
nginx_port: 8000

# 4. host_vars/web1 - 主机特定变量
# host_vars/web1.yml
nginx_port: 9000

# 5. play vars - Playbook 变量
# 在 Playbook 中定义
- name: 部署 Nginx
  hosts: webservers
  vars:
    nginx_port: 7000

# 6. extra-vars - 命令行变量(优先级最高)
ansible-playbook site.yml -e "nginx_port=6000"

敏感信息管理

# 使用 Ansible Vault 加密敏感信息
# 加密文件
ansible-vault encrypt secrets.yml

# 创建加密文件
ansible-vault create secrets.yml

# secrets.yml
---
db_password: "secure_password_123"
api_key: "api_key_xyz"
ssh_key: |
  -----BEGIN RSA PRIVATE KEY-----
  ...
  -----END RSA PRIVATE KEY-----

# 在 Playbook 中使用
- name: 配置数据库
  template:
    src: database.conf.j2
    dest: /etc/myapp/database.conf
  vars_files:
    - secrets.yml

# 运行时提供密码
ansible-playbook site.yml --ask-vault-pass

# 使用密码文件
echo "vault_password" > .vault_pass
ansible-playbook site.yml --vault-password-file .vault_pass

安全性

SSH 密钥管理

# 使用 SSH 密钥而非密码
# inventory/group_vars/all.yml
ansible_ssh_private_key_file: ~/.ssh/id_rsa_ansible

# 为不同环境使用不同密钥
# inventory/production/group_vars/all.yml
ansible_ssh_private_key_file: ~/.ssh/id_rsa_production

# inventory/staging/group_vars/all.yml
ansible_ssh_private_key_file: ~/.ssh/id_rsa_staging

限制 become 权限

# 只在需要时使用 become
- name: 配置应用(不需要 root 权限)
  copy:
    src: app.conf
    dest: /opt/myapp/app.conf
  # 没有 become

- name: 安装软件包(需要 root 权限)
  become: yes
  package:
    name: nginx
    state: present

# 使用特定的 become 用户
- name: 配置 PostgreSQL
  become: yes
  become_user: postgres
  postgresql_user:
    name: myapp
    state: present

主机密钥检查

# ansible.cfg
[defaults]
# 生产环境开启检查
host_key_checking = True

# 测试环境可以关闭
# host_key_checking = False

# 或者在命令中覆盖
ansible-playbook site.yml -e "ansible_ssh_common_args='-o StrictHostKeyChecking=no'"

模块选择

优先使用 Ansible 模块

# 好的做法:使用 Ansible 模块
- name: 安装 Nginx
  package:
    name: nginx
    state: present

- name: 创建用户
  user:
    name: deploy
    shell: /bin/bash

- name: 配置文件
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf

# 不好的做法:使用 shell 命令
- name: 安装 Nginx
  shell: apt-get install -y nginx

- name: 创建用户
  shell: useradd -m deploy -s /bin/bash

- name: 配置文件
  shell: echo "server ..." > /etc/nginx/nginx.conf

何时使用 shell/command

# 当没有对应模块时使用 shell
- name: 自定义编译安装
  shell: |
    ./configure
    make
    make install
  args:
    chdir: /tmp/source

# 使用 register 获取输出
- name: 获取系统信息
  command: uname -r
  register: kernel_version
  changed_when: false

# 使用 failed_when 自定义失败条件
- name: 检查磁盘空间
  shell: df -h / | tail -1 | awk '{print $5}' | cut -d'%' -f1
  register: disk_usage
  failed_when: disk_usage.stdout | int > 90

性能优化

禁用 Facts 收集

# 不需要 facts 时禁用
- name: 简单任务
  hosts: all
  gather_facts: no

  tasks:
    - name: ping
      ping:

# 只收集特定 facts
- name: 收集特定 facts
  hosts: all
  gather_facts: yes
  gather_subset:
    - network
    - virtual

SSH 管道复用

# ansible.cfg
[ssh_connection]
# 启用 SSH 管道复用
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True

# 提高并发数
[defaults]
forks = 20

使用 async

# 异步执行长时间任务
- name: 下载大文件
  get_url:
    url: http://example.com/large_file.iso
    dest: /tmp/large_file.iso
  async: 3600  # 最长等待 1 小时
  poll: 30      # 每 30 秒检查一次

- name: 完全异步(不等待)
  command: /usr/bin/long_running_task.sh
  async: 3600
  poll: 0  # 不等待,继续执行

调试和故障排查

使用 debug 模块

# 输出变量值
- name: 显示配置
  debug:
    msg: "配置: {{ app_config }}"

# 输出详细变量
- name: 显示所有 facts
  debug:
    var: ansible_facts

# 使用 verbosity 控制输出
- name: 调试信息
  debug:
    msg: "详细调试信息"
  when: ansible_verbosity > 0

使用 check mode

# 只检查不执行(dry run)
ansible-playbook site.yml --check

# 配合 diff 使用
ansible-playbook site.yml --check --diff

启用详细输出

# -v: 详细输出
ansible-playbook site.yml -v

# -vv: 更详细
ansible-playbook site.yml -vv

# -vvv: 最详细(包括 SSH 连接)
ansible-playbook site.yml -vvv

版本控制

Git 配置

# .gitignore
.vault_pass
secrets.yml
*.retry
inventory/**/host_vars/*
inventory/**/group_vars/*_vault.yml
roles/**/tests/*
*.pyc
__pycache__/

# 加密敏感文件后再提交
ansible-vault encrypt group_vars/all/vault.yml
git add group_vars/all/vault.yml

提交规范

# 清晰的提交信息
git commit -m "feat: 添加 MySQL 角色"
git commit -m "fix: 修复 Nginx 配置模板错误"
git commit -m "docs: 更新部署文档"
git commit -m "refactor: 重构变量组织结构"
git commit -m "chore: 升级 Ansible 到 2.15"

文档

README.md

# 项目 README

## 项目描述
这是一个使用 Ansible 自动化部署 Web 应用的项目。

## 环境要求
- Ansible 2.15+
- Python 3.8+
- 目标主机:Ubuntu 20.04+

## 安装
```bash
pip install ansible-core
ansible-galaxy install -r requirements.yml
```

## 使用
```bash
# 更新 Inventory
vim inventory/production/hosts.ini

# 加密密码文件
ansible-vault create secrets.yml

# 执行 Playbook
ansible-playbook -i inventory/production/hosts.ini site.yml --ask-vault-pass
```

## 项目结构
说明各目录和文件的用途。

Role 文档

# roles/nginx/README.md

# Nginx Role

## 描述
安装和配置 Nginx Web 服务器。

## 变量
| 变量 | 默认值 | 描述 |
|------|--------|------|
| nginx_port | 80 | 监听端口 |
| nginx_worker_processes | auto | 工作进程数 |
| server_name | localhost | 服务器名称 |

## 依赖
- common

## 示例
```yaml
- name: 使用 Nginx 角色
  hosts: webservers
  roles:
    - role: nginx
      vars:
        nginx_port: 8080
```

最佳实践检查清单

  1. 项目结构: 使用推荐的目录结构
  2. 环境分离: 使用不同的 Inventory 文件管理环境
  3. 使用 Roles: 将代码组织为可复用的角色
  4. 幂等性: 确保所有任务都是幂等的
  5. 命名规范: 使用一致的命名规范
  6. 变量管理: 合理管理变量优先级
  7. 敏感信息: 使用 Ansible Vault 加密
  8. 错误处理: 添加适当的错误处理
  9. 文档完善: 编写清晰的文档
  10. 版本控制: 使用 Git 管理代码
  11. 测试验证: 充分测试自动化流程
  12. 性能优化: 优化 Ansible 执行性能

总结

通过本教程,你已经了解了:

  • 推荐的项目组织结构
  • Inventory 和 Playbook 最佳实践
  • 变量管理和安全性
  • 性能优化技巧
  • 调试和故障排查方法
  • 版本控制和文档规范

发表回复

后才能评论