Ansible 自动化运维入门-批量部署 Web 服务器实战

                             引言:为什么每个运维都应该掌握 Ansible 还记得那个凌晨3点被电话叫醒的夜晚吗?生产环境的20台服务器需要紧急更新配置,你不得不一台一台手动SSH登录,重复执...

                             引言:为什么每个运维都应该掌握 Ansible

还记得那个凌晨3点被电话叫醒的夜晚吗?生产环境的20台服务器需要紧急更新配置,你不得不一台一台手动SSH登录,重复执行相同的命令。两个小时后,当你拖着疲惫的身躯完成任务时,心里暗暗发誓:"一定要找个自动化工具!"


如果你有过类似的经历,那么恭喜你,今天这篇文章将彻底改变你的运维生涯。我将带你从零开始掌握Ansible,通过一个实际的Web服务器批量部署项目,让你体验自动化运维的魅力。读完这篇文章,你将能够:


10分钟内完成50台服务器的Nginx部署

一键实现应用的滚动更新和回滚

构建可复用的自动化部署流程

将重复性工作时间缩短90%以上


一、Ansible 是什么?它能解决什么问题?

1.1 传统运维的痛点

在深入Ansible之前,让我们先看看传统运维面临的挑战:


场景一:配置漂移问题你管理着100台服务器,理论上它们的配置应该完全一致。但随着时间推移,因为各种临时修改、紧急补丁,服务器配置开始出现差异。某天一个看似简单的更新,却因为配置不一致导致部分服务器故障。


场景二:规模化挑战公司业务快速增长,服务器数量从10台增长到100台。原本30分钟能完成的部署任务,现在需要5个小时。而且随着操作复杂度增加,人为错误的概率也在上升。


场景三:知识传承困难资深运维离职了,留下的只有一堆零散的Shell脚本和简单的文档。新人接手后发现,每个脚本的执行顺序、参数含义都需要猜测和试错。


1.2 Ansible 的优势

Ansible 是一个开源的IT自动化工具,它通过简单的YAML语法描述系统配置,实现:


无代理架构(Agentless):不需要在被管理节点安装任何客户端,通过SSH即可管理

声明式配置:描述"想要达到的状态",而不是"如何达到"

幂等性保证:多次执行产生相同结果,避免重复操作带来的问题

易学易用:YAML语法简单直观,降低学习门槛

强大的模块库:3000+内置模块,覆盖各种运维场景

二、快速上手:15分钟搭建 Ansible 环境

2.1 环境准备

我们将搭建一个实验环境,包含1台控制节点和3台被管理节点:


# 控制节点(安装Ansible的机器)

control-node: 192.168.1.10


# 被管理节点(目标服务器)

web-01: 192.168.1.11

web-02: 192.168.1.12

web-03: 192.168.1.13

2.2 安装 Ansible

在控制节点上执行:



# CentOS/RHEL 系统

sudo yum install -y epel-release

sudo yum install -y ansible


# Ubuntu/Debian 系统

sudo apt update

sudo apt install -y ansible


# 使用 pip 安装(推荐,获取最新版本)

sudo pip3 install ansible


# 验证安装

ansible --version

2.3 配置 SSH 免密登录

自动化的前提是控制节点能够无密码访问被管理节点:


# 生成SSH密钥对(如果还没有)

ssh-keygen -t rsa -b 2048


# 将公钥复制到所有被管理节点

for ip in 192.168.1.11 192.168.1.12 192.168.1.13; do

    ssh-copy-id -i ~/.ssh/id_rsa.pub root@$ip

done


# 测试连接

ssh root@192.168.1.11 'hostname'

2.4 创建 Inventory 文件

Inventory文件定义了Ansible要管理的主机清单:


# 创建 inventory.ini 文件

[webservers]

web-01 ansible_host=192.168.1.11

web-02 ansible_host=192.168.1.12

web-03 ansible_host=192.168.1.13


[webservers:vars]

ansible_user=root

ansible_python_interpreter=/usr/bin/python3


[all:vars]

ansible_connection=ssh

测试连接所有主机:


ounter(line

ansible -i inventory.ini all -m ping

如果看到所有主机返回 "pong",恭喜你,环境搭建成功!


三、实战项目:批量部署 Nginx Web 服务器

现在让我们通过一个实际项目,深入理解Ansible的强大功能。我们将实现:


批量安装Nginx

部署自定义配置

部署静态网站

实现滚动更新

3.1 项目结构设计

nginx-deployment/

├── inventory.ini           # 主机清单

├── ansible.cfg            # Ansible配置文件

├── site.yml              # 主Playbook

├── roles/                # 角色目录

│   └── nginx/

│       ├── tasks/       # 任务定义

│       │   └── main.yml

│       ├── templates/    # 模板文件

│       │   ├── nginx.conf.j2

│       │   └── index.html.j2

│       ├── handlers/     # 触发器

│       │   └── main.yml

│       └── vars/         # 变量定义

│           └── main.yml

└── group_vars/           # 组变量

    └── webservers.yml

3.2 编写 Playbook

创建主Playbook site.yml:


---

- name: Deploy Nginx Web Servers

  hosts: webservers

  become: yes

  gather_facts: yes


  vars:

    nginx_port: 80

    nginx_worker_processes: "{{ ansible_processor_vcpus }}"

    nginx_worker_connections: 1024

    website_title: "Ansible自动化部署演示"


  tasks:

    - name: 更新系统包缓存

      apt:

        update_cache: yes

      when: ansible_os_family == "Debian"


    - name: 安装Nginx

      package:

        name: nginx

        state: present


    - name: 创建网站目录

      file:

        path: /var/www/html

        state: directory

        mode: '0755'


    - name: 部署Nginx配置文件

      template:

        src: nginx.conf.j2

        dest: /etc/nginx/nginx.conf

        backup: yes

      notify: restart nginx


    - name: 部署网站首页

      template:

        src: index.html.j2

        dest: /var/www/html/index.html

        mode: '0644'


    - name: 确保Nginx服务运行

      service:

        name: nginx

        state: started

        enabled: yes


    - name: 等待端口就绪

      wait_for:

        port: "{{ nginx_port }}"

        host: "{{ ansible_default_ipv4.address }}"

        delay: 5

        timeout: 30


  handlers:

    - name: restart nginx

      service:

        name: nginx

        state: restarted

3.3 创建配置模板

创建 templates/nginx.conf.j2:


worker_processes {{ nginx_worker_processes }};

pid /run/nginx.pid;


events {

    worker_connections {{ nginx_worker_connections }};

    multi_accept on;

    use epoll;

}


http {

    # 基础配置

    sendfile on;

    tcp_nopush on;

    tcp_nodelay on;

    keepalive_timeout 65;

    types_hash_max_size 2048;


    # 日志配置

    access_log /var/log/nginx/access.log;

    error_log /var/log/nginx/error.log;


    # Gzip压缩

    gzip on;

    gzip_vary on;

    gzip_proxied any;

    gzip_comp_level 6;

    gzip_types text/plain text/css text/xml application/json application/javascript;


    # 虚拟主机配置

    server {

        listen {{ nginx_port }} default_server;

        listen [::]:{{ nginx_port }} default_server;


        root /var/www/html;

        index index.html index.htm;


        server_name {{ ansible_hostname }}.example.com;


        location / {

            try_files $uri $uri/ =404;

        }


        # 健康检查端点

        location /health {

            access_log off;

            return 200 "healthy\n";

            add_header Content-Type text/plain;

        }

    }

}

创建 templates/index.html.j2:


<!DOCTYPE html>

<html lang="zh-CN">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>{{ website_title }}</title>

    <style>

        body {

            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;

            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);

            color: white;

            display: flex;

            justify-content: center;

            align-items: center;

            height: 100vh;

            margin: 0;

        }

        .container {

            text-align: center;

            padding: 2rem;

            background: rgba(255, 255, 255, 0.1);

            border-radius: 15px;

            backdrop-filter: blur(10px);

        }

        h1 { font-size: 3rem; margin-bottom: 1rem; }

        .info {

            background: rgba(255, 255, 255, 0.2);

            padding: 1rem;

            border-radius: 10px;

            margin-top: 2rem;

        }

        .info p { margin: 0.5rem 0; }

    </style>

</head>

<body>

    <div class="container">

        <h1> {{ website_title }}</h1>

        <p>恭喜!您已成功使用 Ansible 部署了这个页面</p>

        <div class="info">

            <p><strong>服务器名称:</strong> {{ ansible_hostname }}</p>

            <p><strong>IP地址:</strong> {{ ansible_default_ipv4.address }}</p>

            <p><strong>操作系统:</strong> {{ ansible_distribution }} {{ ansible_distribution_version }}</p>

            <p><strong>部署时间:</strong> {{ ansible_date_time.iso8601 }}</p>

        </div>

    </div>

</body>

</html>

3.4 执行部署

# 语法检查

ansible-playbook -i inventory.ini site.yml --syntax-check


# 模拟执行(Dry Run)

ansible-playbook -i inventory.ini site.yml --check


# 正式部署

ansible-playbook -i inventory.ini site.yml


# 查看详细输出

ansible-playbook -i inventory.ini site.yml -vvv

四、进阶技巧:让你的自动化更强大

4.1 滚动更新策略

在生产环境中,我们需要确保服务的持续可用性。Ansible支持滚动更新:


---

- name: 滚动更新Web服务器

  hosts: webservers

  become: yes

  serial: 1  # 每次更新1台服务器

  max_fail_percentage: 30  # 允许30%的失败率


  pre_tasks:

    - name: 从负载均衡器移除

      uri:

        url: "http://lb.example.com/api/remove"

        method: POST

        body_format: json

        body:

          server: "{{ ansible_hostname }}"

      delegate_to: localhost


  tasks:

    - name: 更新应用代码

      git:

        repo: https://github.com/yourapp/webapp.git

        dest: /var/www/html

        version: "{{ app_version | default('master') }}"


    - name: 重启服务

      service:

        name: nginx

        state: restarted


  post_tasks:

    - name: 健康检查

      uri:

        url: "http://{{ ansible_default_ipv4.address }}/health"

        status_code: 200

      retries: 5

      delay: 10


    - name: 重新加入负载均衡器

      uri:

        url: "http://lb.example.com/api/add"

        method: POST

        body_format: json

        body:

          server: "{{ ansible_hostname }}"

      delegate_to: localhost

4.2 使用 Ansible Vault 保护敏感信息

生产环境中,密码和密钥需要加密存储:


# 创建加密文件

ansible-vault create secrets.yml


# 编辑加密文件

ansible-vault edit secrets.yml


# 在secrets.yml中添加:

db_password: "SuperSecret123!"

api_key: "sk-1234567890abcdef"


# 使用加密变量运行playbook

ansible-playbook -i inventory.ini site.yml --ask-vault-pass

4.3 动态 Inventory

当服务器数量众多或经常变化时,可以使用动态Inventory:


#!/usr/bin/env python3

# dynamic_inventory.py

import json

import boto3


def get_inventory():

    ec2 = boto3.client('ec2', region_name='us-west-2')


    response = ec2.describe_instances(

        Filters=[

            {'Name': 'tag:Environment', 'Values': ['production']},

            {'Name': 'instance-state-name', 'Values': ['running']}

        ]

    )


    inventory = {

        'webservers': {

            'hosts': [],

            'vars': {

                'ansible_user': 'ubuntu',

                'ansible_ssh_private_key_file': '~/.ssh/aws-key.pem'

            }

        }

    }


    for reservation in response['Reservations']:

        for instance in reservation['Instances']:

            inventory['webservers']['hosts'].append(instance['PublicIpAddress'])


    return inventory


if __name__ == '__main__':

    print(json.dumps(get_inventory()))

4.4 性能优化技巧

当管理大规模基础设施时,性能优化至关重要:


# ansible.cfg

[defaults]

host_key_checking = False

gathering = smart

fact_caching = jsonfile

fact_caching_connection = /tmp/ansible_cache

fact_caching_timeout = 86400

pipelining = True

forks = 50


[ssh_connection]

ssh_args = -o ControlMaster=auto -o ControlPersist=60s

control_path = /tmp/ansible-%%h-%%p-%%r

五、实战案例:构建完整的 CI/CD 流程

让我们通过一个完整的案例,展示如何将Ansible集成到CI/CD流程中:

---

# deploy_pipeline.yml

- name: 完整的部署流程

  hosts: webservers

  become: yes


  vars:

    app_name: mywebapp

    app_version: "{{ lookup('env', 'BUILD_NUMBER') | default('latest') }}"

    deploy_user: webapp

    deploy_dir: /opt/{{ app_name }}

    backup_dir: /opt/backups/{{ app_name }}


  tasks:

    - name: 创建部署用户

      user:

        name: "{{ deploy_user }}"

        shell: /bin/bash

        groups: www-data

        append: yes


    - name: 创建必要的目录

      file:

        path: "{{ item }}"

        state: directory

        owner: "{{ deploy_user }}"

        group: "{{ deploy_user }}"

        mode: '0755'

      loop:

        - "{{ deploy_dir }}"

        - "{{ backup_dir }}"

        - /var/log/{{ app_name }}


    - name: 备份当前版本

      archive:

        path: "{{ deploy_dir }}"

        dest: "{{ backup_dir }}/backup-{{ ansible_date_time.epoch }}.tar.gz"

      when: deploy_dir is directory


    - name: 拉取最新代码

      git:

        repo: "https://github.com/company/{{ app_name }}.git"

        dest: "{{ deploy_dir }}"

        version: "{{ app_version }}"

        force: yes

      become_user: "{{ deploy_user }}"


    - name: 安装应用依赖

      pip:

        requirements: "{{ deploy_dir }}/requirements.txt"

        virtualenv: "{{ deploy_dir }}/venv"

        virtualenv_python: python3

      become_user: "{{ deploy_user }}"


    - name: 运行数据库迁移

      command: |

        {{ deploy_dir }}/venv/bin/python manage.py migrate

      args:

        chdir: "{{ deploy_dir }}"

      become_user: "{{ deploy_user }}"

      run_once: true


    - name: 收集静态文件

      command: |

        {{ deploy_dir }}/venv/bin/python manage.py collectstatic --noinput

      args:

        chdir: "{{ deploy_dir }}"

      become_user: "{{ deploy_user }}"


    - name: 配置Systemd服务

      template:

        src: app.service.j2

        dest: /etc/systemd/system/{{ app_name }}.service

      notify:

        - reload systemd

        - restart app


    - name: 配置Nginx反向代理

      template:

        src: nginx_app.conf.j2

        dest: /etc/nginx/sites-available/{{ app_name }}

      notify: reload nginx


    - name: 启用站点

      file:

        src: /etc/nginx/sites-available/{{ app_name }}

        dest: /etc/nginx/sites-enabled/{{ app_name }}

        state: link

      notify: reload nginx


    - name: 运行冒烟测试

      uri:

        url: "http://localhost/api/health"

        status_code: 200

      retries: 5

      delay: 10


  handlers:

    - name: reload systemd

      systemd:

        daemon_reload: yes


    - name: restart app

      systemd:

        name: "{{ app_name }}"

        state: restarted

        enabled: yes


    - name: reload nginx

      service:

        name: nginx

        state: reloaded

六、监控与日志:确保自动化的可观测性

自动化不是"一劳永逸",我们需要持续监控:

---

# monitoring.yml

- name: 配置监控和日志收集

  hosts: webservers

  become: yes


  tasks:

    - name: 安装监控代理

      package:

        name:

          - prometheus-node-exporter

          - filebeat

        state: present


    - name: 配置Prometheus Node Exporter

      lineinfile:

        path: /etc/default/prometheus-node-exporter

        regexp: '^ARGS='

        line: 'ARGS="--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|run)($|/)"'

      notify: restart node-exporter


    - name: 配置Filebeat

      template:

        src: filebeat.yml.j2

        dest: /etc/filebeat/filebeat.yml

        mode: '0600'

      notify: restart filebeat


    - name: 配置自定义指标收集脚本

      copy:

        content: |

          #!/bin/bash

          # 收集应用自定义指标

          echo "app_requests_total $(curl -s localhost/metrics | grep requests_total | awk '{print $2}')"

          echo "app_errors_total $(grep ERROR /var/log/{{ app_name }}/app.log | wc -l)"

          echo "app_response_time_seconds $(tail -n 100 /var/log/nginx/access.log | awk '{sum+=$10} END {print sum/NR}')"

        dest: /usr/local/bin/collect_metrics.sh

        mode: '0755'


    - name: 添加指标收集定时任务

      cron:

        name: "收集应用指标"

        minute: "*/5"

        job: "/usr/local/bin/collect_metrics.sh > /var/lib/node_exporter/textfile_collector/app_metrics.prom"


  handlers:

    - name: restart node-exporter

      service:

        name: prometheus-node-exporter

        state: restarted


    - name: restart filebeat

      service:

        name: filebeat

        state: restarted

七、故障恢复:当事情出错时

即使是最完善的自动化,也可能出现问题。让我们准备一个快速回滚方案:

---

# rollback.yml

- name: 紧急回滚程序

  hosts: webservers

  become: yes

  serial: 1


  vars_prompt:

    - name: confirm_rollback

      prompt: "确认要回滚到上一个版本吗?(yes/no)"

      private: no


  tasks:

    - name: 验证确认

      fail:

        msg: "回滚操作已取消"

      when: confirm_rollback != "yes"


    - name: 查找最新的备份

      find:

        paths: "{{ backup_dir }}"

        patterns: "backup-*.tar.gz"

      register: backup_files


    - name: 确保有可用备份

      fail:

        msg: "没有找到可用的备份文件"

      when: backup_files.files | length == 0


    - name: 获取最新备份

      set_fact:

        latest_backup: "{{ (backup_files.files | sort(attribute='mtime') | last).path }}"


    - name: 停止应用服务

      systemd:

        name: "{{ app_name }}"

        state: stopped


    - name: 清理当前版本

      file:

        path: "{{ deploy_dir }}"

        state: absent


    - name: 恢复备份

      unarchive:

        src: "{{ latest_backup }}"

        dest: /opt/

        remote_src: yes


    - name: 启动应用服务

      systemd:

        name: "{{ app_name }}"

        state: started


    - name: 验证服务状态

      uri:

        url: "http://localhost/api/health"

        status_code: 200

      retries: 3

      delay: 5


    - name: 发送回滚通知

      mail:

        to: ops-team@example.com

        subject: "紧急回滚完成 - {{ ansible_hostname }}"

        body: "服务器 {{ ansible_hostname }} 已成功回滚到备份版本:{{ latest_backup }}"

      delegate_to: localhost

总结:从手动到自动的蜕变

通过这篇文章,我们一起经历了从传统手动运维到Ansible自动化的完整旅程。让我们回顾一下关键收获:


效率提升:原本需要数小时的部署任务,现在只需要几分钟

一致性保证:通过代码化的配置管理,消除了环境差异

可追溯性:每次变更都有记录,便于审计和问题排查

知识沉淀:运维经验转化为可复用的Playbook

降低风险:自动化减少人为错误,回滚机制保障业务连续性

但这仅仅是开始。Ansible的生态系统远比我们今天探索的要丰富:


Ansible Tower/AWX 提供企业级的管理界面

Ansible Galaxy 社区分享了数千个现成的角色

与Kubernetes、Docker、云平台的深度集成

网络设备、数据库、中间件的自动化配置


下一步行动建议

立即实践:选择一个简单的重复性任务,尝试用Ansible自动化

逐步推广:从开发环境开始,逐步扩展到生产环境

持续学习:关注Ansible官方文档和社区最佳实践

分享交流:将你的自动化经验分享给团队,共同成长

记住,自动化不是目的,而是让我们能够专注于更有价值工作的手段。当你不再被重复性任务束缚,你就有更多时间去思考架构优化、性能调优、安全加固这些真正体现运维价值的工作。


  • 发表于 2025-09-08 10:34
  • 阅读 ( 15 )

你可能感兴趣的文章

相关问题

0 条评论

请先 登录 后评论
shitian
shitian

662 篇文章

作家榜 »

  1. shitian 662 文章
  2. 石天 437 文章
  3. 每天惠23 33 文章
  4. 小A 29 文章