RHCSE--ansible2--剧本

Ansible Playbook

Ansible Playbook(剧本)是 Ansible 核心配置文件,采用 YAML 格式编写,用于定义一系列有序的自动化任务集合,描述 "要在哪些远程主机上执行哪些操作"。

YAML 基本规则

YAML 是一种易读的序列化格式,是 Playbook 的核心语法,核心规则如下:

  1. 缩进规则:仅允许空格缩进(禁止 Tab),通常 2 个半角空格为 1 级,缩进层级代表逻辑层级;

    全角空格是中文输入法下的(肉眼看和半角空格一样,但解析器不认),必须替换为半角空格(英文输入法下的空格)。

  2. 大小写敏感 :变量名、模块名、键名(如 hosts/Hosts)均区分大小写;

  3. 注释规则 :单行注释用 ## 后内容会被忽略),无原生多行注释(可每行加 #);

  4. 字符串规则 :字符串可加引号(单 / 双)或不加,含特殊字符(:、$、空格)时必须加引号(如 name: "web-server:8080");

  5. 核心标识:

    • 元素以 - 开头(短横线 + 空格),如 - nginx

    • 键值对用 key: value(冒号后必须加空格),如 name: nginx

  6. playbook文件命名:文件名后缀无强制要求,只要内容是 YAML 即可执行,.yaml.yml结尾(前者更规范,后者是简写,均常用)

Playbook 常用 YAML 结构

Playbook(YAML文件)

├─ Play 1(一个部署单元,以 - 开头)

│ ├─ 基础配置(hosts、remote_user、gather_facts)

│ ├─ 变量(vars)

│ ├─ 任务(tasks)→ 每个任务由「模块+参数」组成

│ ├─ 模块+参数

│ └─ 模块+参数

├─ Play 2(另一个部署单元)

│ └─ ...(同Play 1的结构)

└─ ...

Playbook 以 "Play" 为基本单元,常见结构分为 2 类:

1. 单 Play 结构(极简版)
复制代码
- hosts: webservers  # 目标主机组
  remote_user: root  # 远程执行用户
  tasks:  # 任务列表
    - name: 安装 Nginx
      yum:
        name: nginx
        state: present
    - name: 启动 Nginx
      service:
        name: nginx
        state: started
2. 多 Play 结构(多主机组执行不同任务)
复制代码
- hosts: webservers
  vars:
    web_name: nginx
  tasks:
    - name: 安装 Nginx
      yum: 
        name: "{{ nginx }}" 
        state: present
- hosts: dbservers
  tasks:
    - name: 安装 MySQL
      yum: 
        name: mysql-server 
        state: present
3. 带变量 / 处理器的嵌套结构

以下以「部署 MySQL 服务」为例,重新编写带变量 / 处理器的嵌套结构 Playbook,保持核心逻辑一致但场景更贴近实际运维,同时补充关键注释便于理解

复制代码
# 针对所有被管理主机部署MySQL服务
- hosts: all
  # 1. 定义Play级变量(可复用,修改时仅需改此处)
  vars:
    mysql_pkg: mariadb-server  # CentOS默认MySQL兼容包
    mysql_port: 3306
    mysql_conf_path: /etc/my.cnf.d/mysql-server.cnf
    mysql_service: mariadb

  # 2. 定义处理器(仅被notify触发,用于配置变更后重启服务)
  handlers:
    - name: 重启MySQL服务并设置开机自启
      ansible.builtin.service:
        name: "{{ mysql_service }}"  # 引用变量
        state: restarted            # 重启服务
        enabled: true               # 开机自启

  # 3. 定义核心任务列表
  tasks:
    # 任务1:安装MySQL软件包(引用变量)
    - name: 安装 {{ mysql_pkg }} 软件包
      ansible.builtin.yum:
        name: "{{ mysql_pkg }}"
        state: present  # 确保安装(幂等:已装则不操作)

    # 任务2:启动MySQL服务(首次部署先启动)
    - name: 启动 {{ mysql_service }} 服务
      ansible.builtin.service:
        name: "{{ mysql_service }}"
        state: started

    # 任务3:复制自定义MySQL配置文件(修改配置后触发处理器)
    - name: 复制自定义MySQL配置文件(指定端口{{ mysql_port }})
      ansible.builtin.copy:
        src: ./mysql-server.cnf  # 本地配置文件路径
        dest: "{{ mysql_conf_path }}"  # 远程目标路径(引用变量)
        mode: '0644'  # 配置文件权限
        owner: root   # 属主
        group: root   # 属组
      # 仅当配置文件发生变更时,触发处理器重启MySQL
      notify: 重启MySQL服务并设置开机自启
ansible-playbook用法
选项 简写 核心说明
--check -C 模拟执行(干跑):仅展示要执行的操作,不实际修改目标主机系统
--inventory <路径> -i 指定自定义 Inventory(主机清单)文件,默认路径 /etc/ansible/hosts
--user <用户名> -u 指定远程执行任务的用户(如 rootansible
--become -b 提权执行(等效 sudo),普通用户执行系统操作(如装软件)时必备
--become-user <用户> 指定提权后的目标用户(默认 root),如 --become-user=nginx
--verbose -v 详细输出执行日志:-v 基础详情,-vvv 最详细(排错专用),-vv 中等
--ask-pass -k 交互式提示输入远程主机 SSH 密码(避免明文写在 Inventory 中)
--ask-become-pass -K 交互式提示输入提权密码(如 sudo 密码)
--limit <主机/组> 限制仅执行指定主机 / 主机组的任务,如 --limit 192.168.1.100

Playbook 执行流程解读

复制代码
任务1: [host1, host2, host3, ... hostN] ← 同时执行
等待任务1所有主机成功,然后开始执行任务任务2
任务2: [host1, host2, host3, ... hostN] ← 同时执行

问题:如果太多主机并发,等待的时间会过长,所以就有了`--limit <主机/组>`
# 仅在host1和host2上执行Playbook(减少并发数,快速验证)
ansible-playbook task.yml -i /hosts --limit "host1,host2"
# 仅在host1和host2上执行Playbook(减少并发数,快速验证)
ansible-playbook task.yml -i /hosts --limit "host1,host2"
# 若Inventory中有子组,可直接限制组(此处仍用web_servers的子集示例)
# 先在Inventory中添加子组:
# [web_servers_subset]
# host1
# host3
# 然后执行:
ansible-playbook task.yml -i /hosts --limit web_servers_subset

#作用:检查 YAML 语法、Playbook 结构(如字段缺失、缩进错误),不执行任务,输出报错位置和原因
ansible-playbook -C -v   playbook.yml

无 Role的playbook

复制代码
# site-simple.yml
---
- hosts: webservers
  vars:
    pkg_nginx: nginx
    nginx_port: 80
  handlers:
    - name: 重启Nginx
      service:
        name: nginx
        state: restarted
  tasks:
    - name: 安装{{ pkg_nginx }}
      yum:
        name: "{{ pkg_nginx }}"
        state: present
    - name: 复制配置文件
      copy:
        src: ./nginx.conf
        dest: /etc/nginx/nginx.conf
      notify: 重启Nginx
    - name: 启动Nginx
      service:
        name: nginx
        state: started

含Role的playbook:

复制代码
/ansible/
├── roles/
│   ├── web/  # web角色目录(变量仅属于web角色)
│   │   ├── vars/
│   │   │   └── main.yml  # web角色的变量文件(Ansible自动识别)
│   │   └── tasks/
│   │       └── main.yml  # web角色的任务文件
│   └── security/  # security角色目录(变量仅属于security角色)
│       ├── vars/
│       │   └── main.yml  # security角色的变量文件(Ansible自动识别)
│       └── tasks/
│           └── main.yml  # security角色的任务文件
└── site.yml  # 主Playbook

mkdir -p /ansible/roles/web/vars
mkdir -p /ansible/roles/web/tasks
mkdir -p /ansible/roles/security/vars
mkdir -p /ansible/roles/security/tasks
mkdir -p /ansible/roles/security/{vars,tasks}
1. 清单文件:
复制代码
# 仅定义webservers主机组(学生替换为自己的受控节点IP)
[newserver]
192.168.223.151
192.168.223.152
2. web 角色 - 变量:roles/web/vars/main.yml
复制代码
# web角色仅保留核心变量(减少记忆成本)
nginx_pkg: nginx    # Nginx软件包名
nginx_service: nginx # Nginx服务名
3.web 角色 - 任务:roles/web/tasks/main.yml
复制代码
# web角色核心任务:安装+启动Nginx(无复杂配置,聚焦角色逻辑)
---
- name: 安装Nginx软件包
  ansible.builtin.yum:
    name: "{{ nginx_pkg }}"
    state: present  # 确保安装(幂等,多次执行不重复安装)

- name: 启动并开机自启Nginx服务
  ansible.builtin.service:
    name: "{{ nginx_service }}"
    state: started
    enabled: true
4. security 角色 - 任务:roles/security/tasks/main.yml
复制代码
# security角色核心任务:防火墙放行80端口 + SELinux宽松模式
- name: 放行防火墙80端口(http服务)
  ansible.builtin.firewalld:
    service: http  # 直接用预设的http服务(对应80端口,比写port更简单)
    permanent: true # 永久生效
    immediate: true # 立即生效(无需重启防火墙)
    state: enabled

- name: 设置SELinux为宽松模式(临时生效,避免重启)
  ansible.builtin.selinux:
    policy: targeted
    state: permissive  # 宽松模式:不阻止操作,仅记录日志
5. 主 Playbook:site.yml
复制代码
# 主Playbook仅做2件事:指定目标主机 + 引入两个角色
---
- hosts: webservers  # 关联清单中的webservers主机组
  remote_user: root  # 远程执行用户
  gather_facts: true # 自动收集Facts(firewalld/selinux模块依赖)

  # 按顺序引入角色:先做安全配置,再部署Web(逻辑合理)
  # 提前写好仓库挂载好,或者增加一个cnagku角色
  roles:
    - security  # 引入security角色(执行防火墙+SELinux配置)
    - web       # 引入web角色(执行Nginx部署)

四、执行与验证

复制代码
ansible-playbook -i /hosts  /ansible/site.yml

扩展:把某个角色需要的变量单独放到文件里,那ansible-playboot如何识别哪个变量文件是哪个角色的?

当在 Playbook 的roles中直接以 "字符串形式" 引用角色(如- web)时,Ansible 会默认将该角色名称作为目录名 ,去roles/目录下查找同名目录(如roles/web/),这是最规范、最常用的方式,无需额外配置。

案例1:远程批量安装 nginx,修改端口号,并使配置生效

注意:

  • 每个 Task 只能调用 1 个模块

  • description参数用于描述 yum 源的用途(比如 "CentOS BaseOS Repository"),是yum_repository模块的强制参数,没有它 Ansible 会判定参数不完整,直接终止任务并抛出Parameter 'description' is required错误。

  • yum_repository指定的file名称(比如x)和/etc/yum.repos.d/目录下已存在的.repo文件(如x.repo)重名时,Ansible 的处理逻辑非常友好 ------不会直接覆盖整个文件,而是 "精准修改 / 追加",完全不用担心原有配置丢失,


    • name: 给web服务器部署nginx # Play的名字(方便看日志)
      hosts: webservers # 这个Play要操作的主机(对应你的Hosts列表)
      tasks: # 这个Play里的Task列表
      • name: 挂载光盘
        mount:
        path: /mnt
        src: /dev/sr0
        state: mounted
        fstype: iso9660 # 光盘专属文件系统类型(必须加)
      • name: 配置仓库base
        yum_repository:
        file: x
        name: base
        description: base
        state: present
        enabled: yes
        gpgcheck: no
        baseurl: /mnt/BaseOS
      • name: 配置仓库app
        yum_repository:
        file: x
        name: app
        description: app
        state: present
        enabled: yes
        gpgcheck: no
        baseurl: /mnt/AppStream
      • name: 安装nginx
        yum:
        name: nginx
        state: present
      • name: 修改nginx配置文件
        lineinfile:
        path: /etc/nginx/nginx.conf # 主流端口配置文件路径
        regexp: "^\s*listen\s+\d+;?$" # 匹配以listen开头、后跟数字的行(如listen 80;)
        line: " listen 8080;" # 替换为监听8080端口(缩进和原文件保持一致)
        backup: yes # 修改前自动备份原文件(建议开启,方便回滚)
        state: present
      • name: 修改Nginx默认欢迎页面为welcome test
        copy:
        content: "welcome test" # 页面要显示的内容
        dest: /usr/share/nginx/html/index.html # Nginx默认首页路径
        backup: yes # 备份原首页文件(生成index.html.bak.xxxxxx)
        mode: '0644' # 设置文件权限,确保Nginx能读取(必需)
      • name: 启动nginx
        service:
        name: nginx
        state: started

实验2:纯剧本方式:部署 LNMP+Discuz(扁平任务结构)

复制代码
[root@rhce ~]# cat discuz.yml
---
- name: 纯剧本部署 LNMP + Discuz 论坛(适配文件名不固定)
  hosts: 192.168.223.151
  gather_facts: no
  become: yes

  vars:
    # 基础配置
    mount_src: /dev/sr0
    mount_path: /mnt
    mount_fstype: iso9660
    web_root: /var/www/html/discuz
    nginx_conf_path: /etc/nginx/conf.d/discuz.conf
    discuz_url: "https://gitee.com/Discuz/DiscuzX/attach_files/2335009/download"
    # 数据库配置
    db_root_pwd: "Redhat123!"  # 学生可自行修改目标密码
    db_user: "root"
    db_host: "localhost"

  tasks:
    ###########################################################################
    # 步骤 0:挂载光盘(原生幂等)
    ###########################################################################
    - name: 挂载光盘到/mnt目录
      mount:
        src: "{{ mount_src }}"
        path: "{{ mount_path }}"
        fstype: "{{ mount_fstype }}"
        state: mounted

    ###########################################################################
    # 步骤 1:安装基础软件(原生幂等)
    ###########################################################################

    - name: 安装LNMP及基础依赖包
      package:
        name:
          - unzip
          - nginx
          - php
          - php-fpm
          - php-mysqlnd
          - mariadb-server
          - python3-PyMySQL
        state: present

    ###########################################################################
    # 步骤 2:启动基础服务(原生幂等)
    ###########################################################################
    - name: 启动并开机自启Nginx服务
      systemd:
        name: nginx
        state: started
        enabled: yes

    - name: 启动并开机自启PHP-FPM服务
      systemd:
        name: php-fpm
        state: started
        enabled: yes

    - name: 启动并开机自启MariaDB服务
      systemd:
        name: mariadb
        state: started
        enabled: yes

    ###########################################################################
    # 步骤 3:创建网站根目录(原生幂等)
    ###########################################################################
    - name: 创建Discuz网站根目录并设置权限
      file:
        path: "{{ web_root }}"
        state: directory
        owner: nginx
        group: nginx
        mode: 0755

    ###########################################################################
    # 步骤 4:下载+解压Discuz(核心适配:文件名不固定,全靠find动态处理)
    ###########################################################################
    # 第一步:检查是否已有任意Discuz开头的压缩包(不限制具体名称)
    - name: 检查{{ web_root }}是否有Discuz开头的压缩包
      find:
        path: "{{ web_root }}"
        patterns: "Discuz*.zip"  # 匹配所有Discuz开头的zip包
        file_type: file
      register: existing_discuz_zip

    # 第二步:仅当无任何Discuz压缩包时,才下载(避免重复下载/401)
    - name: 下载Discuz压缩包(文件名不固定,下载到目录即可)
      get_url:
        url: "{{ discuz_url }}"
        dest: "{{ web_root }}/"  # 指向目录,让文件按URL默认名保存
        validate_certs: no
        timeout: 60
      when: existing_discuz_zip.files | length == 0  # 无任何Discuz包时才下载

    # 第三步:再次find所有Discuz压缩包(适配下载后的随机名称)
    - name: 动态查找{{ web_root }}下所有Discuz开头的压缩包
      find:
        path: "{{ web_root }}"
        patterns: "Discuz*.zip"
        file_type: file
      register: discuz_zip_files

    # 第四步:检查是否已解压(通过Discuz核心目录判断,与文件名无关)
    - name: 检查Discuz是否已解压(通过upload目录判断)
      stat:
        path: "{{ web_root }}/upload"
      register: discuz_unzip_stat

    # 第五步:解压(仅找到压缩包且未解压时执行,适配任意Discuz开头的包)
    - name: 解压动态匹配到的Discuz压缩包
      unarchive:
        src: "{{ discuz_zip_files.files[0].path | default('') }}"  # 取第一个匹配的包,兜底赋空
        dest: "{{ web_root }}"
        remote_src: yes
      when:
        - discuz_zip_files.files | length > 0  # 至少找到1个Discuz包
        - not discuz_unzip_stat.stat.exists    # 未解压过
      ignore_errors: yes  # 兜底:压缩包损坏/格式问题不中断流程
#当 {{ web_root }}/upload 目录不存在时:discuz_unzip_stat.stat.exists 为 False,经 not 取反后为 True,条件满足,解压任务会执行;
#当 {{ web_root }}/upload 目录已存在时:discuz_unzip_stat.stat.exists 为 True,经 not 取反后为 False,条件不满足,解压任务会跳过。

    ###########################################################################
    # 步骤 5:配置Nginx虚拟主机(原生幂等)
    ###########################################################################
    - name: 写入Discuz的Nginx配置文件
      copy:
        content: |
          server {
              listen 80;
              root {{ web_root }}/upload;
              include /etc/nginx/default.d/php.conf;
          }
        dest: "{{ nginx_conf_path }}"
        backup: yes
      register: nginx_conf
      changed_when: nginx_conf.changed

    - name: 重新加载Nginx配置(仅配置变更时执行)
      systemd:
        name: nginx
        state: reloaded
      when: nginx_conf.changed

    ###########################################################################
    # 步骤 6:配置防火墙和SELinux(原生幂等)
    ###########################################################################
    - name: 永久开放80端口
      firewalld:
        port: 80/tcp
        permanent: yes
        state: enabled
        immediate: yes

    - name: 临时关闭SELinux
      command: setenforce 0
      ignore_errors: yes
      changed_when: no

    ###########################################################################
    # 步骤 7:修改Discuz权限(幂等:仅目录存在时执行)
    ###########################################################################
    - name: 检查Discuz upload目录是否存在
      stat:
        path: "{{ web_root }}/upload"
      register: discuz_upload_stat

    - name: 修改Discuz data目录权限为777
      file:
        path: "{{ web_root }}/upload/data"
        mode: 0777
        recurse: yes
        state: directory
      when: discuz_upload_stat.stat.exists

    - name: 修改Discuz config目录权限为777
      file:
        path: "{{ web_root }}/upload/config"
        mode: 0777
        recurse: yes
        state: directory
      when: discuz_upload_stat.stat.exists

    - name: 批量修改uc_client/uc_server目录权限为777
      file:
        path: "{{ web_root }}/upload/{{ item }}"
        mode: 0777
        recurse: yes
        state: directory
      loop:
        - uc_client
        - uc_server
      when: discuz_upload_stat.stat.exists

    ###########################################################################
    # 步骤 8:初始化数据库(原生幂等)

    ###########################################################################
    # 步骤1:判断root是否有密码(核心:用简单命令测试)
    - name: 测试root无密码能否登录
      ansible.builtin.shell: 'mysql -u{{ db_user }} -S /var/lib/mysql/mysql.sock -e "show databases;" 2>/dev/null'
      register: no_pwd_login
      ignore_errors: yes  # 有密码时登录失败,忽略错误不中断
      changed_when: no    # 仅判断状态,不标记"变更"

    # 步骤2:仅无密码时,设置root密码(幂等:设过就不重复设)
    - name: 初始化root密码(仅第一次执行)
      community.mysql.mysql_user:
        name: "{{ db_user }}"
        host: "localhost"  # 简化:只设localhost的密码,去掉冗余host
        password: "{{ db_root_pwd }}"
          #  check_implicit_admin: yes  # 无密码时允许登录的关键参数
        login_unix_socket: /var/lib/mysql/mysql.sock
        login_user: "{{ db_user }}"
        login_host: "{{ db_host }}"
        login_password: ""  # 无密码时填空
        state: present      # 幂等核心:密码已设则跳过
      when: no_pwd_login.rc == 0  # 只有无密码登录成功时才执行

    # 步骤3:创建测试库(幂等:已存在则跳过,适配有/无密码登录)
    - name: 创建luntan数据库(多次执行不报错)
      community.mysql.mysql_db:
        name: luntan
        state: present  # 幂等:确保库存在,不存在才创建
        encoding: utf8mb4
        # 动态适配密码:有密码用db_root_pwd,无密码填空
        login_unix_socket: /var/lib/mysql/mysql.sock
        login_user: "{{ db_user }}"
        login_host: "{{ db_host }}"
        login_password: "{{ db_root_pwd }}"

执行剧本 & 验证

  1. 执行命令:`ansible-playbook discuz-lnmp-playbook.yml`

  2. 验证:浏览器访问 `http://受控节点IP`,进入 Discuz 安装向导,输入剧本中定义的数据库信息(库名 `discuz_db`、用户 `discuz_user`、密码 `Discuz@123`),完成安装。

问题:

解决:

用户现在遇到的 Ansible 执行修改 MariaDB root 密码任务失败的问题,首先要解释错误原因,然后给解决方案。

首先,错误原因:Ansible 的 mysql_user/mysql_db 模块依赖 Python 的 MySQL 数据库驱动包,但受控节点上没有安装对应的包,或者 Python 解释器版本不匹配导致找不到驱动。

然后,分步骤解决:

  1. 先解释错误核心:
  • 错误提示明确说需要 MySQL Python 模块,Python2.7 需要 PyMySQL/MySQL-python,Python3 需要 mysqlclient/PyMySQL;

  • 受控节点要么没装这些包,要么 Ansible 用的 Python 解释器和安装的驱动版本不匹配。

  1. 解决方案步骤:

步骤 1:在受控节点安装 Python MySQL 驱动(区分 CentOS 版本,yum/dnf);

步骤 2:(可选)指定 Ansible 的 Python 解释器(避免版本不匹配);

步骤 3:重新执行任务。

相关推荐
❀͜͡傀儡师7 分钟前
centos 7部署dns服务器
linux·服务器·centos·dns
Dying.Light26 分钟前
Linux部署问题
linux·运维·服务器
S190127 分钟前
Linux的常用指令
linux·运维·服务器
萤丰信息29 分钟前
AI 筑基・生态共荣:智慧园区的价值重构与未来新途
大数据·运维·人工智能·科技·智慧城市·智慧园区
小义_1 小时前
【RH134知识点问答题】第7章 管理基本存储
linux·运维·服务器
运维小欣1 小时前
Agentic AI 与 Agentic Ops 驱动,智能运维迈向新高度
运维·人工智能
梁洪飞2 小时前
内核的schedule和SMP多核处理器启动协议
linux·arm开发·嵌入式硬件·arm
_运维那些事儿2 小时前
VM环境的CI/CD
linux·运维·网络·阿里云·ci/cd·docker·云计算
Y1rong3 小时前
linux之文件IO
linux
Trouvaille ~3 小时前
【Linux】UDP Socket编程实战(一):Echo Server从零到一
linux·运维·服务器·网络·c++·websocket·udp