Ansible-playbook剧本


一、playbook介绍

playbook:ansible格式的脚本。将所有需要执行的操作按照ansible的编程语法,放到文件中执行。

我们之前使用ansible命令模式被称为:ad-hoc

playbook(剧本): 是ansible用于配置、部署和管理被控节点执行复杂任务时的一种模块编排封装技术。ad-hoc无法持久使用,playbook可以持久使用。

playbook是由一个或多个任务组成的列表,从根本上来讲,所谓的task(任务)无非是调用ansible的一个模块执行的功能,可以让它们联合起来按事先编排的机制完成某一任务。

参考 :https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html

使用的格式为yaml格式(saltstack,elk,docker等也都会用到yaml格式)

Playbook使用场景

  • Playbook使用场景执行一些简单的任务,使用ad-hoc命令可以方便的解决问题,但是有时一个设施过于复杂,需要大量的操作的时候,执行的ad-hoc命令是不合适的,这时候最好使用playbook
  • 就像执行shell命令与写shell脚本一样,也可以理解为批处理任务,不过playbook有自己的语法格式
  • 使用playbook可以方便的重复使用这些代码,可以移植到不同的机器上面,像函数一样,最大化的利用代码
  • 在你使用Ansible的过程中,你也会发现,你所处理的大部分操作都是编写playbook可以把常见的应用都编写playbook,之后管理服务器会变得很简单

Playbook核心元素

  • Hosts 执行的远程主机列表
  • Tasks 任务集
  • Varniables 内置变量或自定义变量在playbook中调用
  • Templates 模板,即使用模板语法的文件,比如配置文件等
  • Handlers 和notity结合使用,由特定条件触发的操作,满足条件方才执行,否则不执行
  • tags 标签,指定某条任务执行,用于选择运行playbook中的部分代码

playbook与AD-Hoc的关系

  • playbook是对AD-Hoc的一种编排方式
  • playbook可以持久运行(重复),而Ad-Hoc只能临时运行
  • playbook适合复杂的任务,而Ad-Hoc适合做快速简单的任务(检查,查询, 巡检)
  • playbook能控制任务执行的先后顺序
  • ad-hoc 用于检查,测试,临时获取数据
  • playbook剧本适用于,重复性操作(部署环境、服务,初始化操作(优化))

playbook替代方案

1)、完全可以用shell脚本来替代playbook
    将所有的ansible命令放入脚本,shell脚本中写的是ansible指令
#!/bin/bash
for IP in `seq 21 23`
   do
       ansible -m hostname 192.168.8.$IP -a "name=node${IP}"
done

2)、ansible+shell脚本,使用script模块
ansible -m script '/etc/ansible/srcipts/nginx_install.sh' group1

案例1:PlayBook安装LNMP环境

vim lnmp.yaml
---
- name: 部署LNMP环境
  hosts: group1
  become: true
  tasks:
    - name: 安装Nginx
      apt:
        name: nginx
        state: present
    - name: 安装MySQL服务器
      apt:
        name: mysql-server
        state: present
    - name: 安装PHP及相关扩展
      apt:
        name: "{{ packages }}"
        state: present
      vars:
        packages:
          - php-fpm
          - php-mysql
          - php-cli
          - php-gd
          - php-curl
          - php-mbstring
    - name: 启动并启用Nginx服务
      service:
        name: nginx
        state: started
        enabled: true
    - name: 启动并启用MySQL服务
      service:
        name: mysql
        state: started
        enabled: true
    - name: 启动并启用PHP-FPM服务
      service:
        name: php8.1-fpm
        state: started
        enabled: true

执行剧本

# 执行剧本但不真正生效,用于检测脚本是否正确
ansible-playbook -C lnmp.yml

# 正常执行剧本
ansible-playbook lnmp.yml

案例2:二进制部署MySQL

vim mysql_binary_install.yml
---
# 该 Playbook 用于在 Ubuntu 22.04 系统上以二进制方式安装 MySQL 8.0.20
- name: Binary install MySQL 8.0.20 on Ubuntu 22.04
  # 指定要执行该 Playbook 的目标主机,'all' 表示所有在清单文件中定义的主机
  hosts: db1
  # 以超级用户权限执行任务
  become: true
  # 定义 Playbook 中使用的变量
  vars:
    # MySQL 的版本号
    mysql_version: "8.0.20"
    # MySQL 二进制包的下载地址,使用了前面定义的版本号变量
    mysql_download_url: "https://dev.mysql.com/get/Downloads/MySQL-8.0/mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.xz"
    # MySQL 的安装目录
    mysql_install_dir: "/usr/local/mysql"
    # MySQL 数据文件的存储目录
    mysql_data_dir: "/var/lib/mysql"
    # MySQL root 用户的密码,需要替换为实际的密码
    mysql_root_password: "your_root_password"

  # 定义一系列要执行的任务
  tasks:
    # 安装安装 MySQL 所需的必要软件包
    - name: Install necessary packages
      # 使用 apt 模块来管理 Ubuntu 系统上的软件包
      apt:
        # 指定要安装的软件包列表
        name:
          - wget  # 用于下载文件
          - libaio1  # MySQL 依赖的异步 I/O 库
          - libnuma1  # MySQL 依赖的 NUMA 库
        # 确保软件包已安装
        state: present

    # 创建 MySQL 的安装目录
    - name: Create MySQL installation directory
      # 使用 file 模块来管理文件和目录
      file:
        # 指定要创建的目录路径
        path: "{{ mysql_install_dir }}"
        # 确保该路径是一个目录
        state: directory
        # 设置目录的权限为 0755
        mode: '0755'

    # 从指定的 URL 下载 MySQL 二进制包
    - name: Download MySQL binary package
      # 使用 get_url 模块来下载文件
      get_url:
        # 下载的 URL
        url: "{{ mysql_download_url }}"
        # 下载文件的保存路径
        dest: "/tmp/mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.xz"
      # 将任务的执行结果注册到 download_result 变量中
      register: download_result
      # 重复执行该任务直到下载成功
      until: download_result is succeeded
      # 最多重试 3 次
      retries: 3
      # 每次重试间隔 5 秒
      delay: 5

    # 解压下载的 MySQL 二进制包到安装目录
    - name: Extract MySQL binary package
      # 使用 unarchive 模块来解压文件
      unarchive:
        # 要解压的文件路径
        src: "/tmp/mysql-{{ mysql_version }}-linux-glibc2.12-x86_64.tar.xz"
        # 解压的目标目录
        dest: "{{ mysql_install_dir }}"
        # 表示源文件是远程文件
        remote_src: yes
        # 解压时去掉一层目录结构
        extra_opts: [--strip-components=1]

    # 创建 MySQL 数据目录
    - name: Create MySQL data directory
      # 使用 file 模块来管理文件和目录
      file:
        # 指定要创建的目录路径
        path: "{{ mysql_data_dir }}"
        # 确保该路径是一个目录
        state: directory
        # 设置目录的所有者为 mysql 用户
        owner: mysql
        # 设置目录的所属组为 mysql 组
        group: mysql
        # 设置目录的权限为 0750
        mode: '0750'

    # 初始化 MySQL 数据目录
    - name: Initialize MySQL data directory
      # 使用 command 模块执行命令
      command: "{{ mysql_install_dir }}/bin/mysqld --initialize --user=mysql --basedir={{ mysql_install_dir }} --datadir={{ mysql_data_dir }}"
      # 将任务的执行结果注册到 init_result 变量中
      register: init_result
      # 忽略该任务执行过程中可能出现的错误
      ignore_errors: yes

    # 从错误日志中查找 MySQL 初始化时生成的临时密码
    - name: Find temporary password
      # 使用 shell 模块执行 shell 命令
      shell: "grep 'temporary password' {{ mysql_data_dir }}/error.log | awk '{print $NF}'"
      # 将任务的执行结果注册到 temp_password_result 变量中
      register: temp_password_result
      # 只有当 MySQL 数据目录初始化成功时才执行该任务
      when: init_result is succeeded

    # 启动 MySQL 服务
    - name: Start MySQL service
      # 使用 command 模块执行命令
      command: "{{ mysql_install_dir }}/bin/mysqld_safe --user=mysql &"

    # 修改 MySQL root 用户的密码
    - name: Change MySQL root password
      # 使用 command 模块执行命令
      command: "{{ mysql_install_dir }}/bin/mysql -u root -p'{{ temp_password_result.stdout }}' --connect-expired-password -e \"ALTER USER 'root'@'localhost' IDENTIFIED BY '{{ mysql_root_password }}';\""
      # 只有当成功找到临时密码时才执行该任务
      when: temp_password_result is succeeded

    # 创建 MySQL 的 systemd 服务文件
    - name: Create MySQL systemd service file
      # 使用 copy 模块复制文件内容到指定路径
      copy:
        # 要复制的文件内容
        content: |
          [Unit]
          # 服务的描述信息
          Description=MySQL Server
          # 该服务在网络服务启动后启动
          After=network.target

          [Service]
          # 服务运行的用户
          User=mysql
          # 服务运行的用户组
          Group=mysql
          # 服务的启动命令
          ExecStart={{ mysql_install_dir }}/bin/mysqld --defaults-file={{ mysql_install_dir }}/my.cnf
          # 服务允许打开的最大文件描述符数量
          LimitNOFILE = 5000

          [Install]
          # 该服务在多用户模式下启动
          WantedBy=multi-user.target
        # 复制文件的目标路径
        dest: /etc/systemd/system/mysql.service
      # 当该任务执行成功后,触发以下处理程序
      notify:
        - Reload systemd manager configuration
        - Enable and start MySQL service

  # 定义处理程序,用于响应任务中的 notify 指令
  handlers:
    # 重新加载 systemd 管理器的配置
    - name: Reload systemd manager configuration
      # 使用 systemd 模块来管理 systemd 服务
      systemd:
        # 重新加载 systemd 配置
        daemon_reload: yes

    # 启用并启动 MySQL 服务
    - name: Enable and start MySQL service
      # 使用 systemd 模块来管理 systemd 服务
      systemd:
        # 服务的名称
        name: mysql
        # 启用该服务,使其在系统启动时自动启动
        enabled: yes
        # 启动该服务
        state: started
# 检测剧本是否能正常执行
ansible-playbook -C mysql_binary_install.yaml
# 正常执行剧本
ansible-playbook mysql_binary_install.yaml

案例3:部署tomcat

vim tomcat_deployment.yml
---
- name: 部署 Tomcat
  hosts: web1
  become: true
  tasks:
    - name: 安装必要的依赖(Ubuntu)
      apt:
        name:
          - openjdk-11-jdk
          - wget
          - unzip
        state: present
        update_cache: true
      when: ansible_os_family == "Debian"

    - name: 安装必要的依赖(CentOS)
      yum:
        name:
          - java-11-openjdk
          - wget
          - unzip
        state: present
      when: ansible_os_family == "RedHat"

    - name: 创建 Tomcat 目录
      file:
        path: /opt/tomcat
        state: directory
        mode: '0755'

    - name: 下载 Tomcat
      get_url:
        url:  https://archive.apache.org/dist/tomcat/tomcat-9/v9.0.78/bin/apache-tomcat-9.0.78.zip
        dest: /tmp/apache-tomcat-9.0.78.zip

    - name: 解压 Tomcat
      unarchive:
        src: /tmp/apache-tomcat-9.0.78.zip
        dest: /opt/tomcat
        remote_src: true
        extra_opts: [--strip-components=1]

    - name: 配置 Tomcat 权限
      file:
        path: /opt/tomcat
        owner: tomcat
        group: tomcat
        recurse: true
        mode: '0755'

    - name: 创建 Tomcat 服务文件
      copy:
        dest: /etc/systemd/system/tomcat.service
        content: |
          [Unit]
          Description=Apache Tomcat Web Application Container
          After=network.target

          [Service]
          Type=forking

          Environment=JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
          Environment=CATALINA_PID=/opt/tomcat/temp/tomcat.pid
          Environment=CATALINA_HOME=/opt/tomcat
          Environment=CATALINA_BASE=/opt/tomcat
          Environment='CATALINA_OPTS=-Xms512M -Xmx1024M -server -XX:+UseParallelGC'
          Environment='JAVA_OPTS=-Djava.awt.headless=true -Djava.security.egd=file:/dev/./urandom'

          ExecStart=/opt/tomcat/bin/startup.sh
          ExecStop=/opt/tomcat/bin/shutdown.sh

          User=tomcat
          Group=tomcat
          UMask=0007
          RestartSec=10
          Restart=always

          [Install]
          WantedBy=multi-user.target

    - name: 重新加载 systemd 管理器配置
      systemd:
        daemon_reload: true

    - name: 启动并设置 Tomcat 开机自启
      service:
        name: tomcat
        state: started
        enabled: true    
# 检测剧本是否能正常执行
ansible-playbook -C tomcat_deployment.yml
# 执行剧本
ansible-playbook tomcat_deployment.yml

PlayBook基础案例

1、命令相关

  • 执行命令或脚本
- name: Add route
  command: /sbin/ip route add 192.168.8.2/24 dev ens33

2、文件文本相关

- name: Copy sources.list
  copy:
    src: sources.list.stretch
    dest: /etc/apt/sources.list
  • 修改文字
- name: Update monit service
  lineinfile:
    path: /etc/init.d/monit
    regexp: '^CONFIG='
    line: 'CONFIG=/root/monitrc'
  • 删除文件
- name: Remove tunnel service
  file:
    path: /etc/systemd/system/node-exporter-tunnel.service
    state: absent

3、安装卸载相关

  • 安装
- name: Install tcpdump
  apt:
    name: tcpdump
    update_cache: yes
  • 卸载
- name: Uninstall ntpd
  apt:
    name: ntp
    state: absent

4、服务相关

  • 设置开机自启动
- name: Enable systemd-timesyncd service
  systemd:
    name: systemd-timesyncd
    state: restarted
    enabled: yes
    daemon_reload: yes
  • 关闭开机自启动
- name: Disable autossh-tunnel service
  systemd:
    name: autossh-tunnel
    state: stopped
    enabled: no
    daemon_reload: yes
  ignore_errors: yes
  • 重启服务
- name: Restart journald service
  systemd:
    name: systemd-journald
    state: restarted

5、 其他

  • 设置时区
- name: Set timezone to Asia/Shanghai
  timezone:
    name: Asia/Shanghai
  • 添加定时任务
- name: Add cronjob
  cron:
    name: "cell_mgmt.sh"
    user: root
    job: "/root/cell_mgmt.sh | sponge /root/cell_mgmt.prom"
    cron_file: /etc/crontab

6.CentOS7中apache安装及业务初始化

第1步: 创建一个存放playbook的目录(路径自定义)

[root@manage01 ~]# mkdir -p /etc/ansible/playbook/web

第2步: 准备httpd配置文件,并修改成你想要的配置

[root@manage01 ~]#  yum install httpd -y

按需要修改你想要的配置(为了测试可以随意改动标记一下)
[root@manage01 ~]#  vim /etc/httpd/conf/httpd.conf

第3步: 写一个playbook文件(后缀为.yml或.yaml)

#tasks
#1、创建apache管理用户
#2、安装httpd
#3、服务启动管理
#4、拷贝配置文件,业务初始化
#5、触发重启服务httpd
[root@manage01 web]# cat apache.yaml 
---
- hosts: group1
  remote_user: root
  vars:
    - user: tom
  tasks:
    - name: create user use variable
      user: user={{user}} state=present

    - name: install httpd server
      yum: name={{item}} state=latest
      with_items:
        - httpd
        - httpd-devel

    - name: start httpd service
      service: name=httpd state=started enabled=yes

    - name: copy httpd.conf to group1:/etc/httpd/conf/
      copy: src=/etc/ansible/playbook/web/httpd.conf dest=/etc/httpd/conf
      notify:
        - restart httpd service

  handlers:
       - name: restart httpd service
         service: name=httpd state=restarted

第4步: 执行写好的palybook

  • 会显示出执行的过程,并且执行的每一步都有ok,changed,failed等标识
  • 执行如果有错误(failed)会回滚,解决问题后,直接再执行这条命令即可,并会把failed改为changed(幂等性)
[root@manage01 web]# ansible-playbook /etc/ansible/playbook/web/apache.yaml

1.2、Playbook常见语法

hosts: 用于指定要执行任务的主机,其可以是一个或多个由冒号分隔主机组.

remote_user: 用于指定远程主机上的执行任务的用户.

become: true 它允许你在需要更高权限的操作时自动提升权限(root),从而避免手动干预,确保任务能够顺利执行。

- hosts: group1
  remote_user: root

tasks: 任务列表, 按顺序执行任务.

  • 如果一个host执行task失败, 整个tasks都会回滚, 修正playbook 中的错误, 然后重新执行即可.
tasks:
  - name: create user use variable
    user: name={{user}} state=present

  - name: install httpd server
    yum: name=httpd state=latest name=httpd-devel state=latest

  - name: start httpd service
    service: name=httpd state=started enabled=yes

  - name: copy httpd.conf to group1:/etc/httpd/conf/
    copy: src=/opt/httpd.conf dest=/etc/httpd/conf/

handlers: 类似task,但需要使用notify通知调用,实现按需调用。

  • 不管有多少个通知者进行了notify,等到play中的所有task执行完成之后,handlers也只会被执行一次.
  • handlers最佳的应用场景是用来重启服务,或者触发系统重启操作.除此以外很少用到了.
    notify:
    - restart httpd service

  handlers:
    - name: restart httpd service
      service: name=httpd state=restarted

  #注意: handlers 需要notify调用,他和tasks不同的是  tasks每次都会调用,heandlers触发才调用,比如配置文件修改了,在执行playbook的时候,就会将管理机上的新改的copy到被管理机,那么就会触发headlers重启服务,否则不会执行heanlers

练习: 修改httpd的端口为8080,再执行playbook测试

variables: 变量

  • 定义变量可以被多次方便调用
vars:
    - user: zhangsan

with_items: 迭代列表

  • 其使用格式为将需要迭代的内容定义为item变量引用,并通过with_items语句指明迭代的元素列表即可。

例如安装多个软件包

yum: name={{item}} state=latest
with_items:
  - httpd
  - httpd-devel

执行后有如下警告

警告.png

解决方法:

在/etc/ansible/ansible.cfg配置文件里的[default]配置段下面加上deprecation_warnings=False参数即可

二、练习案例

写一个playbook实现

  1. 配置yum
  2. 安装vsftpd包
  3. 修改配置文件(要求拒绝匿名用户登录)
  4. 启动服务并实现vsftpd服务开机自动启动
---
- hosts: group1
  remote_user: root
  tasks:
  - name: rm yum repository
    file: path=/etc/yum.repos.d/ state=absent

  - name: 同步master上的yum源到group1
    copy: src=/etc/yum.repos.d dest=/etc/

  - name: ensure vsftpd is at the latest version
    yum: name=vsftpd state=latest

  - name: write the apache config file
    copy: src=/etc/vsftpd/vsftpd.conf dest=/etc/vsftpd/vsftpd.conf

    notify:
    - restart vsftpd

  - name: ensure vsftpd is running (and enable it at boot)
    service: name=vsftpd state=started enabled=yes

  handlers:
    - name: restart vsftpd
      service: name=vsftpd state=restarted