最近在公司推进自动化运维的时候,发现很多同事对Ansible还是一知半解,要么就是简单用用,要么就是直接放弃。其实Ansible真的没那么复杂,我用了这么多年,今天就把我的实战经验分享给大家。

说实话,刚开始接触Ansible的时候我也是懵的。那时候公司有几十台服务器需要管理,每次部署应用或者更新配置都要一台一台去操作,累死累活不说,还容易出错。后来接触到Ansible,真的是解放了双手。

Ansible到底是个什么东西?

简单来说,Ansible就是一个自动化工具。你可以把它理解成一个"遥控器",能够同时控制多台服务器干活。比如你要在100台服务器上安装nginx,传统方式得一台台登录去装,用Ansible的话,写个脚本,一键搞定。

我记得第一次用Ansible批量部署应用的时候,看着终端里刷刷刷地执行命令,那种感觉就像开了挂一样。原本需要一整天的工作,10分钟就搞定了。

控制节点和受控节点的关系

这个概念其实很好理解。控制节点就是你安装Ansible的那台机器,受控节点就是你要管理的那些服务器。

我一般会把Ansible装在跳板机上,这样管理起来比较方便。控制节点需要能SSH到所有受控节点,这是最基本的要求。

# 在控制节点安装Ansible
yum install ansible -y

# 或者用pip安装
pip install ansible

受控节点其实什么都不用装,只要能SSH连接就行。这也是Ansible的一个优势,不像其他工具还要在目标机器上装agent。

清单文件(Inventory):管理服务器的花名册

Inventory文件就像是你的服务器通讯录,记录着所有需要管理的机器信息。

最简单的inventory文件长这样:

[webservers]
192.168.1.10
192.168.1.11
192.168.1.12

[databases]
192.168.1.20
192.168.1.21

但实际生产环境中,我会写得更详细一些:

[webservers]
web01 ansible_host=192.168.1.10 ansible_user=root ansible_ssh_private_key_file=/root/.ssh/id_rsa
web02 ansible_host=192.168.1.11 ansible_user=root ansible_ssh_private_key_file=/root/.ssh/id_rsa

[databases]
db01 ansible_host=192.168.1.20 ansible_user=mysql ansible_become=yes
db02 ansible_host=192.168.1.21 ansible_user=mysql ansible_become=yes

[production:children]
webservers
databases

这样写的好处是可以给不同的机器设置不同的连接参数。比如有些机器用密钥登录,有些用密码,有些需要sudo权限等等。

模块(Modules):Ansible的工具箱

Ansible有几千个模块,每个模块负责不同的功能。刚开始的时候不用全部掌握,先学会常用的几个就够了。

文件操作模块

copy模块用来复制文件:

- name: 复制配置文件
  copy:
    src: /local/path/nginx.conf
    dest: /etc/nginx/nginx.conf
    owner: root
    group: root
    mode: '0644'

file模块用来管理文件和目录:

- name: 创建目录
  file:
    path: /opt/myapp
    state: directory
    owner: www
    group: www
    mode: '0755'

软件包管理

yum模块在CentOS/RHEL上安装软件:

- name: 安装nginx
  yum:
    name: nginx
    state: present

apt模块在Ubuntu/Debian上用:

- name: 安装nginx
  apt:
    name: nginx
    state: present
    update_cache: yes

服务管理

systemd模块管理系统服务:

- name: 启动nginx服务
  systemd:
    name: nginx
    state: started
    enabled: yes

我在实际使用中发现,掌握这几个模块就能解决80%的日常运维工作了。

任务(Tasks)和剧本(Playbooks):自动化的剧本

Task就是一个具体的操作,比如安装软件、复制文件等。Playbook就是把多个Task组织起来,形成一个完整的自动化流程。

我来分享一个实际的例子,部署一个简单的web应用:

---
- name: 部署web应用
  hosts: webservers
  become: yes
  vars:
    app_name: myapp
    app_version: 1.2.3
  
  tasks:
    - name: 安装必要的软件包
      yum:
        name:
          - nginx
          - python3
          - git
        state: present
      
    - name: 创建应用目录
      file:
        path: "/opt/{{ app_name }}"
        state: directory
        owner: nginx
        group: nginx
        mode: '0755'
      
    - name: 下载应用代码
      git:
        repo: https://github.com/company/myapp.git
        dest: "/opt/{{ app_name }}"
        version: "{{ app_version }}"
      notify: restart nginx
    
    - name: 复制nginx配置
      template:
        src: nginx.conf.j2
        dest: /etc/nginx/conf.d/myapp.conf
      notify: restart nginx
    
    - name: 启动nginx服务
      systemd:
        name: nginx
        state: started
        enabled: yes
      
  handlers:
    - name: restart nginx
      systemd:
        name: nginx
        state: restarted

这个playbook做了几件事:安装软件、创建目录、下载代码、配置nginx、启动服务。如果配置文件有变化,还会自动重启nginx。

变量和模板:让配置更灵活

在实际项目中,不同环境的配置肯定是不一样的。这时候就需要用到变量和模板了。

我通常会创建不同环境的变量文件:

group_vars/production.yml:

db_host: prod-db.company.com
db_port: 3306
app_env: production
log_level: warn

group_vars/staging.yml:

db_host: staging-db.company.com
db_port: 3306
app_env: staging
log_level: debug

然后在模板文件中使用这些变量:

templates/app.conf.j2:

[database]
host = {{ db_host }}
port = {{ db_port }}

[app]
environment = {{ app_env }}
log_level = {{ log_level }}

这样同一个playbook就能适用于不同的环境了。

角色(Roles):模块化管理的艺术

当项目变得复杂的时候,把所有东西都写在一个playbook里就不合适了。这时候就需要用到Role。

Role的目录结构是这样的:

roles/
  nginx/
    tasks/main.yml
    handlers/main.yml
    templates/
    files/
    vars/main.yml
    defaults/main.yml

我来展示一个nginx role的例子:

roles/nginx/tasks/main.yml:

---
- name: 安装nginx
  yum:
    name: nginx
    state: present
  
- name: 复制主配置文件
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx

- name: 复制站点配置
  template:
    src: "{{ item }}"
    dest: "/etc/nginx/conf.d/{{ item | basename | regex_replace('.j2$', '') }}"
  with_fileglob:
    - "../templates/sites/*.j2"
  notify: restart nginx

- name: 启动nginx服务
  systemd:
    name: nginx
    state: started
    enabled: yes

roles/nginx/handlers/main.yml:

---
- name: restart nginx
  systemd:
    name: nginx
    state: restarted

使用role的playbook就很简洁了:

---
- name: 配置web服务器
  hosts: webservers
  roles:
    - nginx
    - php
    - mysql

处理器(Handlers):响应式的自动化

Handler是Ansible里一个很有用的特性。它只有在被notify的时候才会执行,而且每次playbook运行时,同一个handler最多只会执行一次。

比如说,你修改了nginx配置文件,肯定要重启nginx服务。但如果你在playbook里修改了多个配置文件,你不希望每修改一个就重启一次,而是希望所有修改完成后再重启一次。这时候handler就派上用场了。

tasks:
  - name: 修改nginx主配置
    template:
      src: nginx.conf.j2
      dest: /etc/nginx/nginx.conf
    notify: restart nginx
  
  - name: 修改站点配置
    template:
      src: site.conf.j2
      dest: /etc/nginx/conf.d/site.conf
    notify: restart nginx
  
  - name: 修改ssl配置
    template:
      src: ssl.conf.j2
      dest: /etc/nginx/conf.d/ssl.conf
    notify: restart nginx

handlers:
  - name: restart nginx
    systemd:
      name: nginx
      state: restarted

即使三个task都触发了notify,nginx也只会重启一次。

实战技巧和踩坑经验

SSH密钥管理

刚开始用Ansible的时候,我总是为SSH连接的问题头疼。后来发现最好的方式是用密钥认证,而且要做好密钥的分发。

# 生成密钥对
ssh-keygen -t rsa -b 4096 -f ~/.ssh/ansible_rsa

# 分发公钥到所有受控节点
ssh-copy-id -i ~/.ssh/ansible_rsa.pub user@target_host

在inventory文件中指定私钥:

[servers]
server1 ansible_host=192.168.1.10 ansible_ssh_private_key_file=~/.ssh/ansible_rsa

幂等性的重要性

Ansible的一个重要特性就是幂等性,也就是说多次执行同一个操作,结果应该是一样的。

比如这样写就不是幂等的:

- name: 添加用户到sudoers
  shell: echo "myuser ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers

每次执行都会添加一行,这显然不对。应该这样写:

- name: 添加用户到sudoers
  lineinfile:
    path: /etc/sudoers
    line: "myuser ALL=(ALL) NOPASSWD:ALL"
    state: present

错误处理和调试

在实际使用中,playbook不可能一次就写对。我总结了几个调试技巧:

  1. 使用-v参数增加输出详细程度:
ansible-playbook -vvv playbook.yml
  1. 使用debug模块输出变量值:
- name: 显示变量值
  debug:
    var: ansible_facts['os_family']
  1. 使用ignore_errors忽略某些错误:
- name: 可能会失败的任务
  command: some_command_that_might_fail
  ignore_errors: yes
  1. 使用failed_when自定义失败条件:
- name: 检查服务状态
  command: systemctl is-active nginx
  register: nginx_status
  failed_when: nginx_status.rc != 0 and nginx_status.rc != 3

性能优化

当管理的机器多了之后,性能就成了问题。我用过的几个优化技巧:

  1. 调整并发数:
# ansible.cfg
[defaults]
forks = 50
  1. 开启SSH pipelining:
[ssh_connection]
pipelining = True
  1. 使用strategy插件:
- name: 快速执行的playbook
  hosts: all
  strategy: free
  tasks:
    - name: 简单任务
      ping:

配置文件管理

Ansible的配置文件ansible.cfg可以放在几个地方,优先级从高到低是:

  1. ANSIBLE_CONFIG环境变量指定的文件
  2. 当前目录下的ansible.cfg
  3. 家目录下的.ansible.cfg
  4. /etc/ansible/ansible.cfg

我一般会在项目目录下放一个ansible.cfg:

[defaults]
inventory = inventory/hosts
host_key_checking = False
timeout = 30
forks = 20
remote_user = root

[ssh_connection]
ssh_args = -o ControlMaster=auto -o ControlPersist=60s
pipelining = True

实际应用场景

我在工作中用Ansible解决了很多实际问题,分享几个典型场景:

批量系统初始化

新机器上架后需要做一堆初始化工作:创建用户、配置SSH、安装基础软件、设置时区等等。用Ansible写个playbook,新机器几分钟就能初始化完成。

应用部署

以前部署应用要登录每台服务器,停服务、备份、更新代码、重启服务。现在一个playbook搞定,还能做到零停机部署。

配置管理

系统配置变更是运维的家常便饭,用Ansible可以确保所有机器的配置一致性,还能追踪变更历史。

定期维护

比如日志清理、证书更新、安全补丁等定期任务,写成playbook配合cron执行,省心省力。

说到这里,我想起前段时间公司要给所有服务器更新SSL证书。如果手动操作,几百台机器得忙好几天。用Ansible写了个playbook,半小时就全部搞定了,而且零出错。

当然,Ansible也不是万能的。对于一些复杂的编排场景,可能还需要结合其他工具。但对于大部分运维工作来说,Ansible已经足够强大了。

学习Ansible最好的方式就是多动手实践。从简单的任务开始,比如批量执行命令、复制文件等,然后慢慢学习更复杂的特性。记住,任何工具都是为了解决实际问题的,不要为了用而用。

希望这篇文章能帮助大家更好地理解和使用Ansible。自动化运维的路还很长,但有了Ansible这个利器,我们的工作会轻松很多。如果你觉得这篇文章有用,记得点赞转发,让更多的同行受益!

关注@运维躬行录,一起在自动化运维的道路上越走越远!

个人博客:躬行笔记

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐