Ansible的配置

Ansible配置文件官方文档

这里基于Ansible版本2.14

Ansible配置文件的优先级

Ansible的默认配置文件是/etc/ansible/ansible.cfg

其实Ansible会按照下面的顺序查找配置文件,并使用第一个发现的配置文件

  • ANSIBLE_CONFIG(如果设置了环境变量)
  • ansible.cfg(在当前目录中)
  • ~/.ansible.cfg(在主目录中)
  • /etc/ansible/ansible.cfg

配置文件

Ansible中某些设置可以通过配置文件进行调整,对于大多数用户来说,默认配置就足够了,如果有特殊原因要修改

获取最新配置文件

你可以生成一个Ansible配置文件,其中列出了所有默认配置,如下

ansible-config init --disabled > ansible.cfg

包括可用插件以创建更完整的Ansible配置,如下

ansible-config init --disabled -t all > ansible.cfg

常用选项

下面是Ansible常用的配置文件参数及其含义:

  1. inventory:指定主机清单文件的路径
inventory = /etc/ansible/hosts
  1. library:用于指定自定义模块的路径。
library = /usr/share/my_modules/
  1. remote_tmp:指定了远程主机上用于存储临时文件的目录路径。
remote_tmp = ~/.ansible/tmp
  1. local_tmp:指定了本地主机上用于存储临时文件的目录路径。
local_tmp = ~/.ansible/tmp
  1. remote_user:指定 Ansible 在远程主机上执行任务时所使用的用户名。默认情况下,Ansible 会使用当前登录用户的用户名。
remote_user = root
  1. private_key_file:指定 Ansible 在远程主机上执行任务时所使用的私钥文件路径。可以使用 SSH 密钥对进行身份验证。
private_key_file = /path/to/file
  1. host_key_checking:指定是否检查远程主机的 SSH 主机密钥。默认情况下,该参数值为 true,表示检查主机密钥;可以将其修改为 False 来关闭主机密钥检查。
    当将 host_key_checking 参数设置为 False 时,表示禁用主机密钥验证(SSH StrictHostKeyChecking)。这使得 Ansible 可以在连接到新主机时自动接受它们的公钥,而无需人工验证。
host_key_checking = False
  1. forks:指定 Ansible 在同时执行任务时所使用的进程数。默认情况下,该参数值为 5
forks          = 5
  1. becomebecome_method:用于在远程主机上以管理员身份执行任务。become 参数用于指定是否启用管理员身份执行任务,可以将其设置为 truefalsebecome_method 参数用于指定管理员身份的获取方式,常用的取值包括 sudosu 等。
    become_ask_pass=False 是一种指示 Ansible 在不提示用户输入提权密码的情况下执行 become 操作(例如使用 sudo 或 su)的方法。默认情况下,Ansible 在执行become操作时会提示用户输入提权密码(除非使用 SSH 密钥文件进行身份验证)。
become=True
become_ask_pass=False
become_method=sudo
  1. log_path:指定 Ansible 的日志文件路径。可以在该文件中查看 Ansible 的执行日志和错误信息。
log_path = /var/log/ansible.log
  1. roles_path:定义 Ansible 角色的搜索路径。可以指定一个或多个路径,路径之间使用冒号分隔。
roles_path = /path/to/roles:/path/to/other_roles

主机清单

Ansible 主机清单(Inventory)是指 Ansible 用于管理和执行任务的主机列表。主机清单可以是一个文本文件,也可以是一个脚本或程序。在主机清单中,每个主机都有一个唯一的名称和一个或多个变量,用于指定主机的连接参数、主机组、主机状态等信息

Inventory的配置文件,想要修改的话要自动生成ansible.cfg配置文件才能修改,根据ansible.cfg优先级读取配置文件

/etc/ansible/hosts

在配置文件中修改 Inventory 文件的位置,如果你要修改到 /opt/ansible/hosts的话

inventory = /opt/ansible/hosts

利用参数-i传递主机清单配置文件

[root@localhost ~]# ansible-playbook -i /opt/ansible/hosts xxx.yaml

远程主机的分组

给远程主机分组,以便于在Playbook中使用。下面的文件中,展示了主机清单文件中最简单的分组方法,[ ]内是组名。将远程主机分为 test1、test2、test3几个组

vim /etc/ansible/ansible.cfg
# 添加如下
192.168.100.10

[test1]
192.168.200.10
192.168.200.20
three[01:50].ipipip.com
# 包含了从 three01.ipipip.com 到 three50.ipipip.com 的连续主机名。

[test2]
192.168.200.40
192.168.200.50

[test3]
192.168.200.60

分组也可以支持嵌套。示例中,我们定义了三个顶级组:web_serversdb_serversapp_servers。然后,我们通过 prod:childrenweb_serversdb_servers 组合并到 prod 组中。类似地,我们通过 all:childrenprodapp_servers 组合并到 all 组中。

[web_servers]
web1.example.com
web2.example.com

[db_servers]
db1.example.com
db2.example.com

[app_servers]
app1.example.com
app2.example.com

[prod:children]
web_servers
db_servers

[all:children]
prod
app_servers

设置连接参数

ansible可以在Inventory文件中指定主机的连接参数,包括连接方法、用户等。在Inventory中设置连接的参数如下,用空格分隔多个参数

[local]
web01 ansible_host=192.168.1.100 ansible_user=admin ansible_port=22
db01 ansible_host=192.168.1.101 ansible_user=dbadmin

常用的连接参数

连接参数作用
ansible_host指定主机的 IP 地址或主机名
ansible_port指定 SSH 连接的端口号
ansible_user指定 SSH 连接时使用的用户名
ansible_connection指定连接方法,常见的有 ssh
ansible_ssh_pass指定 SSH 连接时使用的密码

变量

主机清单文件中的变量

为单个远程主机指定参数

[test1]
192.168.100.10   http_port=80  
192.168.100.20   http_port=303 

为一个组指定变量

[test1]
192.168.100.10   
192.168.100.10
[test1:vars]
http_port=80

按目录结构存储变量

Ansible 支持按目录结构来存储变量,以便更好地组织和管理相关的变量信息。这种变量的目录结构通常称为 “变量路径”(variable path)或 “变量目录”(variable directory)。

Ansible 的项目中,可以创建一个名为 “group_vars” 的目录和一个名为 “host_vars” 的目录。

这两个目录分别用于存储针对主机组和单个主机的变量。这样可以根据需要将变量分别放置在适当的路径下。下面是一个示例,展示了如何按目录结构存储变量:

├── inventory
│   └── hosts
├── group_vars
│   ├── all.yml            # 存放适用于所有主机组的变量
│   ├── web_servers.yml    # 存放适用于 "web_servers" 主机组的变量
│   └── db_servers.yml     # 存放适用于 "db_servers" 主机组的变量
└── host_vars
    ├── web01.yml          # 存放仅适用于 "web01" 主机的变量
    └── db01.yml           # 存放仅适用于 "db01" 主机的变量

文件内容可为

ntp_server: 192.168.200.30
database_server: 192.168.100.10

Ansible的脚本Playbook

Playbook的文件格式YAML

Ansible Playbook 使用 YAML 格式编写,YAML(YAML Ain’t Markup Language)是一种轻量级的数据序列化格式,易于阅读和编写。下面是一个 YAML 文件的示例:

---

- hosts: webservers
  remote_user: root
  tasks:

  - name: Install Apache2
    apt:
     name: apache2
     state: latest

  - name: Start Apache2
    service:
     name: apache2
     state: started
   
# 该示例中,--- 表示 YAML 文件的开始
# hosts 指定了要管理和配置的主机组
# remote_user 在远程主机上执行任务时所使用的用户名
# tasks 则包含了一组有序的任务列表,其中包括了安装和启动 Apache 两个任务。
# apt 和 service分别表示使用 apt 命令安装 Apache 和启动 Apache 服务

需要注意的是,YAML 文件的缩进与格式非常重要,它们决定了文件的语义和结构。

以下是一些常见的 YAML 语法:

  1. 注释 :YAML 文件支持注释注释使用 # 标记

  2. 键值对:在 YAML 文件中,键值对使用冒号 : 分隔,键和值之间使用空格进行缩进。例如:

key: value
  1. 数组列表
- item1
- item2
- item3

数组中的每个元素都是以-开始的

  1. 需要注意的地方

变量里有冒号(:)时要加引号

foo: "ansibleLinuxdocker:i like"

变量以 {开头时要加引号

foo: "{{chenshiren}}"

ansible-playbook的命令

如何执行Ansible脚本Playbook呢?Ansible提供了一个单独的命令:ansible-playbook

常见的ansible-playbook的使用方法如下:

执行Playbook的基本方法

ansible-playbook playbook.yml
# playbook.yml 是要执行的 Ansible Playbook 文件名

使用--syntax-check参数检测脚本的语法

ansible-playbook  playbook.yml --syntax-check

使用--verbose查看输出的细节

ansible-playbook playbook.yml --verbose

使用 --list-hosts 查看该脚本影响哪些主机(host)

ansible-playbook playbook.yml --list-hosts

并行执行脚本

ansible-playbook playbook.yml -f 10

Playbook的基本语法

整个Playbook脚本可以分为三个部分

(1)在什么机器上以什么身份执行:hosts、…

(2)执行的任务是什么:tasks

(3)善后的任务都有什么:handlers

如最上面展示的yaml脚本为例

---

- hosts: test
  vars:    # 变量
    myname: csq
  remote_user: root
  tasks:
  - name: install httpd    # 安装httpd
    yum: name=httpd  state=present
    notify:
    - restart httpd 

  - name: modify index.html # 修改index.html
    shell: echo "<h1>name:{{myname}}</h1>" > /usr/share/httpd/noindex/index.html

  - name: firewall open service httpd # 开放端口
    firewalld: port=80/tcp  state=enabled permanent=true
  - name: reload firewalld   # 重新加载防火墙
    shell: firewall-cmd --reload

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

主机和用户

key含义
hosts为主机的IP,或者主机组名,或者关键字all
remote_user在远程以哪个用户身份执行
var定义play中的变量
become以管理员的身份执行
become_method使用哪种权限提升方法(例如 sudo 或 su)
become_user使用权限提升后“成为”的用户。远程/登录用户必须具有成为此用户的权限。

脚本里面使用become时,执行playbook必须加参数--ask-become-pass,提示用户输入"sudo"的密码

ansible-playbook ansiblehttp.yaml  --ask-become-pass

任务列表

  • 任务(task)是从上至下顺序执行的,如果中间发生错误,那么整个Playbook会中止

  • 每一个任务都是对模块的一次调用,只是使用不同的参数和变量而已

  • 每个任务最好有name属性,这是供人读的,没有实际的操作。然后会在命令行里面输出,提示用户执行情况

任务的基本语法

 tasks:
  - name: Install Apache2
    apt:
     name: apache2
     state: latest
# 其中name是可选的,也可以简写成下面的样子
 tasks:
  - apt:
     name: apache2
     state: latest
# 写了name的任务在Playbook执行时,会显示对应的名字,信息更友好、丰富,如下
TASK [install http]   *********************************************************************
changed: [192.168.200.20]
changed: [192.168.200.30]
# 没有写name的任务在执行Playbook时,直接显示对应的任务语法
# 如果调用模块很多很容易就不知道运行到哪里了
TASK [yum: name=httpd state=present] *********************************************************************
changed: [192.168.200.20]
changed: [192.168.200.30]

参数的不同写法

法1:key=value

 tasks:
  - name: Install Apache2
    apt: name=apache2 state=latest
# 如果参数过长可以分隔多行
  tasks:
  - name: 设置文件权限  
    copy: src=/home/csq.txt  dest=/tmp/csq.txt
           owner=csq  group=csq  mode=0644

法2:使用YML的字段格式传入参数

 tasks:
  - name: Install Apache2
    apt:
     name: apache2
     state: latest

任务执行的状态

每一个模块,都会检查当前系统状态是否需要执行

  • 如果本次执行了,那么Action会得到返回值changed

  • 如果不需要执行,那么Action会得到返回值ok

# 以这个文件内容为例执行两次看看结果
- hosts: server
  tasks:
  - name: 复制文件
    copy:
     src: /etc/sudoers
     dest: /opt/

执行第一次

image-20230601202326290

执行第二次

image-20230601202355665

由于第一次执行时,已经复制过文件,因此Ansible会根据目标文件的状态避免重复执行复制

响应事件handler

什么是handler

Handler 是一种特殊的任务,用于在 Play 中的任务执行完成后触发特定的操作。Handler 类似于响应事件的触发器,当某个任务需要触发一个特定的操作时,可以使用 Handler 来执行该操作

Handler 可以与任务关联,当相关任务执行完成后,Handler 会被触发。与一般任务不同的是,Handler 只有在被触发时才会执行,而不是在每一次 Play 运行时都执行。(只有任务状态是changed时,才会执行该任务调用的handler,ok不会调用handler)

handler应用场景

  • 配置文件更新
  • 消息通知
  • 文件操作
  • 数据库操作

handler按定义的顺序执行

[root@localhost ~]# vim test.yaml              
- hosts: test2
  remote_user: root
  gather_facts: no
  vars:
     random_number1: "{{ 10000 | random }}"
     random_number2: "{{ 100000000 | random }}"
  tasks:
  - name: copy /etc/hosts to /tmp/hosts.{{ random_number1 }}
    copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number1 }}
    notify:
      - define the 3nd handler
  - name: copy the /etc/hosts to /tmp/hosts.{{ 100000000 | random }}
    copy: src=/etc/hosts dest=/tmp/hosts.{{ random_number2 }}
    notify:
      - define the 2nd handler
      - define the 1nd handler

  handlers:
  - name: define the 1nd handler
    debug: msg="define the 1nd handler"
  - name: define the 2nd handler
    debug: msg="define the 2nd handler"
  - name: define the 3nd handler
    debug: msg="define the 3nd handler"

# 定义的顺序1>2>3,调用的顺序3>2>1,实际执行的顺序1>2>3
[root@localhost ~]# ansible-playbook test.yaml 

PLAY [test2] ****************************************************************

TASK [copy /etc/hosts to /tmp/hosts.6438] *********************************************
changed: [192.168.200.30]

TASK [copy the /etc/hosts to /tmp/hosts.44204051] ******************************************
changed: [192.168.200.30]

RUNNING HANDLER [define the 1nd handler] ****************************************************
ok: [192.168.200.30] => {
    "msg": "define the 1nd handler"
}

RUNNING HANDLER [define the 2nd handler] ****************************************************
ok: [192.168.200.30] => {
    "msg": "define the 2nd handler"
}

RUNNING HANDLER [define the 3nd handler] ***************************************************
ok: [192.168.200.30] => {
    "msg": "define the 3nd handler"
}

PLAY RECAP **********************************************
192.168.200.30             : ok=5    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0 

变量

(1)在Playbook中用户自定义的变量

(2)用户无须自定义,Ansible会在执行Playbook之前去远程主机上搜集关于远程节点的系统信息的变量

(3)在文件模板中,可以直接使用上述两种变量

(4)把任务的运行结果作为一个变量来使用,这个变量叫作注册变量

(5)为了使Playbook更灵活、通用性更强,允许用户在执行Playbook时传入变量的值,这个时候就需要使用额外变量

在Playbook中用户自定义的变量

用户可以在 Playbook中,通过vars关键字自定义变量,使用时用{{ }}引用起来即可

(1)playbook中定义和使用变量的方法

例如

[root@localhost ceshi]# vim ansible.yaml 
- hosts: server
  vars:
   stdin: "hello,word"
  tasks:
  - name: 输出hello,word
    command: echo {{stdin}}

(2)把变量放在单独的文件中

在 Ansible 中,当变量较多或需要在多个 Playbook 中重用时,可以将变量放置在一个单独的文件中,然后在 Playbook 中通过 var_files 关键字来引用这个文件中定义的变量。 例如,我们可以在 vars.yml 文件中定义一些变量

例如,我们可以在 vars.yml 文件中定义一些变量:

[root@localhost ceshi]# mkdir vars
[root@localhost ceshi]# vim vars/vars.yaml 
#定义数据库的连接信息
files: /home/csq/csq.txt

然后,在 Playbook 中使用 var_files 来引用这些变量:

[root@localhost ceshi]# vim ansible.yaml 
---

- hosts: server
  vars_files:
   - vars/vars.yaml
  tasks:
  - name: 创建两个文件 /home/csq/csq.txt /home/zhw/zhw.txt
    file:
     path: "{{files}}"
     state: touch
     mode: 0600

远程主机的系统变量(Facts)

ansible会通过模块setup来搜集主机的系统信息,这些搜集到的系统信息叫作Facts

每个Playbook在执行之前都会默认执行setup模块,所以这些Facts信息可以直接以变量的形式使用

# 先查看系统变量有哪些
[root@localhost ~]# ansible test2 -m setup -u root  > log.txt
[root@localhost ~]# cat log.txt  |grep ansible_os_family
        "ansible_os_family": "RedHat",

(1)playbook中使用系统变量(facts)

- hosts: test2
  remote_user: root
  tasks:                
  - name: echo ansible_os_family        # 打印ansible_os_family变量
    shell: echo "{{ansible_os_family}}"  
  - name: install httpd
    yum: name=httpd state=present       # 如果变量ansible_os_family等于Debian就安装httpd
    when: ansible_os_family == "Debian"
  - name: install mariadb
    yum: name=mariadb state=present    # 如果变量ansible_os_family等于RedHat就安装mariadb
    when: ansible_os_family == "RedHat"

(2)使用复杂Facts变量

      "ansible_date_time": {
            "date": "2024-04-10",
            "day": "10",
            "epoch": "1712741937",
            "epoch_int": "1712741937",
            "hour": "17",
            "iso8601": "2024-04-10T09:38:57Z",
            "iso8601_basic": "20240410T173857154217",
            "iso8601_basic_short": "20240410T173857",
            "iso8601_micro": "2024-04-10T09:38:57.154217Z",
            "minute": "38",
            "month": "04",
            "second": "57",
            "time": "17:38:57",  # 如何调用time这个系统变量
            "tz": "CST",
            "tz_dst": "CST",
            "tz_offset": "+0800",
            "weekday": "星期三",
            "weekday_number": "3",
            "weeknumber": "15",
            "year": "2024"
     "ansible_ens160": {
            "active": true,
            "device": "ens160",
            "features": {
                "esp_hw_offload": "off [fixed]",
                "esp_tx_csum_hw_offload": "off [fixed]",
                "fcoe_mtu": "off [fixed]",
                "generic_receive_offload": "on",
                "generic_segmentation_offload": "on",
                "highdma": "on",
                "hsr_dup_offload": "off [fixed]",
                 "hw_timestamp_filters": [],
            "ipv4": {
                "address": "192.168.200.30",  # 如何调用address这个系统变量
                "broadcast": "192.168.200.255",
                "netmask": "255.255.255.0",
                "network": "192.168.200.0",
                "prefix": "24"
# 调用time系统变量】
法一:
{{ ansible_date_time["time"] }}
法二:
{{ ansible_date_time.time}}
# 调用address这个系统变量
法一:
{{ ansible_ens160["ipv4"]["address"] }}
法二:
{{ ansible_ens160.ipv4.address }}

(3)关闭系统变量(facts)

- hosts: test2
  gather_facts: no   # 当你使用自定义变量的时候,可以选择关闭系统变量

文件模板中使用的变量

(1)template中变量的定义

在playbook中定义的变量,可以直接在template中使用,同时Facts变量也可以直接在template中使用,当然也包含Inventory里面定义的Host和Group变量。所有在Playbook中可以访问的变量,都可以在template文件中使用

如下例子,playbook脚本使用template模块来复制文件index.html.j2,并且替换index.html.j2中的变量为Playbook中定义的变量值

[root@localhost ~]# mkdir template
[root@localhost ~]# vim template/index.html.j2
<h1>现在的时间是:{{ ansible_date_time["time"] }}</h1> 
<h1>本地主机IP为:{{ ansible_ens160.ipv4.address }}</h1>
<h1>我的名字(在playbook中定义的变量):{{ myname }} </h1>
[root@localhost ~]# vim httpindex.yaml
- hosts: test2
  remote_user: root
  vars:
    myname: "csq"
  tasks:
  - name: install httpd
    yum: name=httpd state=present

  - name: template index.html.j2
    template: src=templates/index.html.j2 dest=/usr/share/httpd/noindex/index.html

  - name: open 80/tcp port
    firewalld: port=80/tcp permanent=true state=enabled
  - name: reload firewalld
    shell: firewall-cmd --reload

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

image-20240410183001992

把运行结果当作变量使用——注册变量

把任务的执行结果当作一个变量的值,这个时候需要用到“注册变量”,就是把执行结果注册到一个变量中,待后面的任务使用,把执行结果注册到变量中的关键字是register

- hosts: test2
  remote_user: root
  tasks:
  - shell: ls
    register: result
    ignore_errors: True # 表示无论命令是否出错,都将忽略错误,不让任务失败·
  - shell: echo " {{ result.stdout }}"
    when: result.rc == 5  # 判断命令执行的返回码是否等于 5,0为正确,1和其他数字为出错

  - debug: msg="{{ result.stdout }}"
# 注册变量经常和debug模块一起使用,这样可以得到更多的关于执行错误的信息,以帮助用户调试

用命令行传递参数

Playbook允许用户在执行的时候传入变量的值

[root@localhost ~]# ansible-playbook test2.yaml -e "hostname=csq user=root"

ansible魔法变量

不用加载Facts变量就可以使用

  • hostvars:每个主机的详细信息
  • groups:显示当前主机所属于的组
  • group_names:列出当前主机所在的主机组名称
  • inventory_hostname:显示当前主机的主机名
# 不用被提前定义就可以使用的
ansible 主机组 -m debug -a "msg={{hostvars}}" # 主机组详细信息
ansible 主机组 -m debug -a "msg={{groups}}"   # 显示当前主机属于的组
ansible 主机组 -m debug -a "msg={{group_names}}" # 当前主机组名称
ansible 主机组 -m debug -a "msg={{inventory_hostname}}" # 显示当前主机的主机名

用-e指定变量值

ansible-playbook -e "user=user2" -e "group=group2" example-playbook.yaml

Playbook逻辑控制语句

关于Playbook的关键字官方文档

关于Playbook返回值的官方文档

  • when:条件判断语句,类似于编程语言中的if
  • loop:循环语句,类似于编程语言中的while
  • block:把几个任务组成一个代码块,以便于针对一组操作的异常进行处理等操作

条件判断语句when

when的比较运算符

  • ==:判断两个值是否相等,则为真
  • !=:判断两个值是否不相等,不等则为真
  • >:判断左侧值是否大于右侧值,则为真
  • <:判断左侧值是否小于右侧值,则为真
  • >=:判断左侧值是否大于等于右侧值,则为真
  • <=:判断左侧值是否小于等于右侧值,则为真

when的逻辑运算符

  • or:逻辑或,当左右和右边两个表达式任意一个为真,则返回真
  • and:逻辑与,当左边和右边两个表达式同时为真,则返回真
  • not:逻辑否,对表达式取反
  • ():当一组表达式组合在一起,形成一个更大的表达式,组合内的所有表达式都是逻辑与的关系

条件判断

  • is exists: 用于路径存在时返回真
  • is not exists: 用于路径不存在时返回真
  • 也可以在整个条件表达式的前面使用not来取反

判断变量

  • defined:判断变量是否已定义,已定义则返回真
  • undefined:判断变量是否未定义,未定义则返回真
  • none:判断变量的值是否为空,如果变量已定义且值为空,则返回真

判断执行结果

  • succeeded:任务执行成功则返回true
  • failed:任务执行失败则返回true
  • changed:任务执行状态为changed则返回true
  • skipped:任务跳过则返回true
    判断是否在集合
  • in: 用于检查一个元素是否在指定列表或集合中

when基本语法

远程主机为Debian Linux 立刻关机

tasks:
 - name: 如果是Debian Linux 立刻关机
   shell: shutdown -t now
   when: ansible_os_family == "Debian"

根据动作的执行结果,来决定接下来执行的任务

- hosts: test2
  remote_user: root
  tasks:
  - command: /bin/ls
    register: result
    ignore_errors: True

  - debug: msg="failed"
    when: result is failed   # 判断result是否failed

  - debug: msg="succeeded"
    when: result is succeeded # 判断result是否succeeded

  - debug: msg="skipped"      # 判断result是否skipped
    when: result is skipped

远程中的系统变量Facts作为when的条件

- hosts: test2
  remote_user: root
  tasks:
  - name: 如果是centos 7操作系统就执行关机
    shell: shutdown -t now
    when: ansible_distribution_major_version == "7" and ansible_distribution == "CentOS"

条件表达式

假设Playbook中定义了变量epic,那么如何在when中使用该变量写出各种条件表达式

- hosts: test2
  vars:
    epic: True
    epic2:
  remote_user: root
  tasks:
  - name: 如果epic变量定义了就执行该任务
    debug: msg="epic is defined epic value {{ epic }}"
    when: epic is defined
      
  - name: 如果epic1变量没有被定义就执行该任务
    debug: msg="epic1 is undefined"
    when: epic1 is undefined

  - name: 如果epic2变量为空就执行该任务
    debug: msg="epic2 is none"
    when: epic2 is none

数值表达

- hosts: test2
  remote_user: root
  tasks:
  - name: 给出一串数找出比5大的数
    command: echo {{item}}
    with_items: { 0,2,4,6,8,10 }
    when: item > 5

Loop循环

标准循环

with_items循环是按照列表中元素的顺序执行的

重复的任务可以使用如下方式

- hosts: test2
  remote_user: root
  tasks:
  - name: 创建用户
    user: name={{ item }} state=present groups=wheel
    with_items:
      - testuser1
      - testuser2
      - testuser3

如果在变量文件中或者“vars”区域定义了一组列表变量somelist

- hosts: test2
  vars:
    somelist: ["testuser1","testuser2","testuser3"]
  remote_user: root
  tasks:
  - name: 创建用户
    user: name={{ item }} state=present groups=wheel
    with_items: "{{somelist}}"

"with_items"用于迭代的list类型变量,不仅支持简单的字符串列表,如果你有一个哈希列表,那么可以用以下方式来引用子项

- hosts: test2
  remote_user: root
  tasks:
  - name: 创建用户
    user: name={{ item.name }} state=present groups={{ item.groups }}
    with_items:
    - { name: 'testuser1',groups: 'wheel' }
    - { name: 'testuser2',groups: 'root' }

如果同时使用when和with_items(或其他循环声明),那么when声明会为每个条目单独判断一次

嵌套循环

with_nested用于对多个列表进行嵌套循环

作用是将多个列表中的元素进行组合,然后在任务中使用这些组合

循环会将两个或多个列表中的元素进行组合

循环也可以嵌套,用[]访问内层和外层的循环

- hosts: test
  remote_user: root
  tasks:
  - name: create user
    mysql_user:  login_host=localhost login_password=000000 login_user=root
      name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=true  password=000000
    with_nested:
     - ['csq', 'csr']  
     - ['clientdb','employeedb','providerd'] 
# 通过循环遍历上述两个列表,该任务将创建两个MySQL用户:csq和csr
# 并为每个用户分别授予clientdb、employeedb和providerdb数据库的全部权限
# {{ item.0 }} 也可以换成点(.)访问内层和外层的变量

对哈希表使用循环

with_dict用于哈希表中的键值对。它允许你在任务中使用字典中的键和值进行操作

with_dict循环是按照字典中键的顺序执行的

- hosts: test2
  vars: 
   users:
    csq:
      name: csq
      telephone: 101010
    csr:
      name: csr
      telephone: 111111
  remote_user: root
  tasks:
  - name: print name phone
    debug: msg="User name is {{item.key}} telephone is ({{ item.value.telephone }})"
    with_dict: "{{ users }}"

对文件列表使用循环

with_fileglob可以以非递归的方式来模拟匹配单个目录中的文件

- hosts: test2
  remote_user: root
  tasks:
  - name: create dir
    file: path=/etc/test_dir state=directory

  - name: copy file
    copy: src={{ item }} dest=/etc/test_dir/ owner=root mode=600
    with_fileglob:
    - /etc/*
    
[root@localhost ~]# ls -al /etc/test_dir/
-rw-------.   1 root root     28  411 15:41 vconsole.conf
-rw-------.   1 root root   4017  411 15:41 vimrc
-rw-------.   1 root root   1184  411 15:41 virc
-rw-------.   1 root root   4925  411 15:41 wgetrc
-rw-------.   1 root root    817  411 15:41 xattr.conf
-rw-------.   1 root root    108  411 15:41 yum.conf

Block块

多个action组成块后,可以根据不同条件执行一段语句

block:block的任务列表

- hosts: test2
  remote_user: root
  tasks:
  - block:
     - name: install httpd memcached
       yum: name={{ item }} state=installed
       with_items:
        - httpd
        - memcached
     - template: src=templates/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf
     - service: name=httpd state=started enabled=true
       
       
    when: ansible_distribution == "CentOS"  # 当系统为CentOS才会执行上面的block

处理异常也会很方便

rescue关键字在任务执行过程中捕获错误,并执行指定的任务

always关键字定义的任务始终会被执行,不管前面的任务执行成功还是失败

- hosts: test2
  remote_user: root
  tasks:
  - block:
    - debug: msg="i execute normally"    # 失败不会执行
    - command: /bin/false   
    - debug: msg="i never execute,cause ERROR" 
    
    rescue:
    - debug: msg='I caught an error'  # 捕获错误但是最后还会执行
    - command: /bin/false
    - debug: msg="I also never execute"
    
    always:
    - debug: msg="this always executes" # 不管前面的任务执行成功还是失败都会执行

重用Playbook

Ansible支持的两种重用机制是Roles和Includes

  • Roles:将相关的任务、变量、模板等组织在一起并进行复用

  • Includes:则是一种将一个Playbook分解成多个文件的方法,可以将一些常用的任务和变量放在单独的文件中,然后在需要的Playbook中引用这些文件。

include语句

在Ansible中,可以使用include关键字将一个或多个文件引入到一个Playbook中,从而实现代码重用的目的。

以下是一个使用include语句的简单案例:

假设我们有一个ansible项目,其中使用了两个Playbook:web.yaml和db.yaml,它们分别用于部署Web服务器和数据库服务器。现在我们想要在这两个Playbook中引入一个共同的任务文件:common.yaml,以避免在两个Playbook中重复编写相同的任务代码。

首先,在项目的根目录下创建一个名为tasks的文件夹,用于存放共同的任务文件common.yml。然后在该文件夹下创建一个名为common.yml的文件,内容如下:

  - name: install packages
    remote_user: root
    yum:
     name: git,vim,curl,wget,unzip,zip,net-tools
     state: present

这个任务用于安装一些常用的软件包,这些软件包在Web服务器和数据库服务器中都需要安装。

接下来,我们可以在web.yaml和db.yaml中使用include语句来引入该文件,代码如下:

web.yaml文件:

- name: Deploy web server
  hosts: test1
  tasks:
  - include: tasks/common.yaml
  - name: install and configure Apache
    yum:
     name: httpd
     state: present
  - service:
     name: httpd
     enabled: true
     state: started

db.yaml文件:

- name: Deploy database server
  hosts: test2
  tasks:
  - include: tasks/common.yaml
  - name: install and configure Mysql
    yum:
     name: mariadb-server
     state: present
  - service:
     name: mariadb
     enabled: true
     state: started

在上面的代码中,我们使用了include语句来引入tasks/common.yml文件中的任务,这样就避免了在web.yaml和db.yaml中重复编写安装常用软件包的任务代码。

在include语句中使用参数

如何定义?拿上面的例子来说

  - name: install packages
    remote_user: root
    yum:
     name: {{ pkge }}
     state: present

如何传入参数?

# 法一
- name: Deploy web server
  hosts: test1
  tasks:
  - include: tasks/common.yaml  pkge=git,vim,curl,wget,unzip,zip,net-tools
  - name: install and configure Apache
    yum:
     name: httpd
     state: present
  - service:
     name: httpd
     enabled: true
     state: started
# 法二
- name: Deploy web server
  hosts: test1
  tasks:
  - include: tasks/common.yaml
    vars:
      pkge: git,vim,curl,wget,unzip,zip,net-tools
  - name: install and configure Apache
    yum:
     name: httpd
     state: present
  - service:
     name: httpd
     enabled: true
     state: started
# 法三
- hosts: test2
  vars:
    pkge: git,vim,curl,wget,unzip,zip,net-tools
  remote_user: root
  tasks:
    - include: tasks/common.yaml
      vars:
        pkge: git,vim,curl,wget,unzip,zip,net-tools
    - name: install and configure Apache
       yum:
       name: httpd
       state: present
    - service:
       name: httpd
       enabled: true
       state: started

Role-Playbook

role有比include更强大灵活的代码重用和分享机制。include类似于编程语言中的include,是重用单个文件的,重用的功能有限。

而role类似于编程语言中的 “Package”,可以重用一组文件,形成完整的功能。例如,安装和配置Apache,既需要任务实现安装包和复制模板,也需要httpd.conf 和 index.html 的模板文件,以及handler文件实现的重启功能。这些文件都可以放在一个role里面,以供不同的Playbook文件重用

定义role完整的目录结构

在Ansible中,通过遵循特定的目录结构,就可以实现对role定义。

那么如何创建角色目录呢?

ansible-galaxy init myrole
# 上述命令会在当前目录下创建一个名为myrole的目录,其默认包含了标准的角色目录结构。
[root@localhost ~]# tree  myrole/
myrole/
├── defaults
│   └── main.yml
├── files
├── handlers
│   └── main.yml
├── meta
│   └── main.yml
├── README.md
├── tasks
│   └── main.yml
├── templates
├── tests
│   ├── inventory
│   └── test.yml
└── vars
    └── main.yml

一个标准的角色目录结构如下:

image-20230610155911393

如果ansible.yaml 中要调用 role

---
- hosts: test1
  remote_user: root
  roles:
   - myrole

Ansible 并不要求role包含上述所有的目录及文件,可以根据role的功能,加入对应的目录和文件即可。下面是每个目录和文件的功能:

  • tasks目录:存放角色的任务文件,其中main.yaml是必须的,其他任务文件可按需添加。
  • templates目录:存放角色使用的Jinja2模板文件。
  • files目录:存放角色使用的普通文件。
  • vars目录:存放角色使用的变量文件,其中main.yaml是必须的,其他变量文件可按需添加。
  • defaults目录:存放角色的默认变量,其中main.yaml是必须的,其他默认变量文件可按需添加。
  • meta目录:存放角色的元数据文件,包括角色名称、作者、依赖等信息。
  • handlers目录:存放角色的处理程序,其中main.yaml是必须的,其他处理程序可按需添加。
  • README.md:角色的说明文件,包括角色的用途、使用方法等信息。
  • tests目录,包含了以下文件:
    • inventory:角色的测试用例的主机清单文件,定义了测试用例中需要使用的主机和主机组等信息。
    • test.yaml:角色的测试用例文件,定义了测试用例中需要执行的任务和测试方法等信息。

此外,下面的文件不需要绝对或者是相对路径,和放在同一目录下的文件一样,直接使用即可

copy 或 scipt 使用 roles/x/files/下的文件

template使用roles/x/templates下的文件

include使用roles/x/tasks下的文件

而在写role的时候,一般要包含role入口文件 roles/x/tasks/main.yaml,其他的文件和目录,可以根据自己需求选择是否加入

带参数的role

下面定义了一个带参数的role,名字myrole,其目录结构如下

main.yaml
  roles
    myrole
      tasks
        main.yaml

在roles/myrole/tasks/main.yaml中,使用{{ }}定义的变量就可以了

---

- name: use param
  debug: 
   msg="{{ param }}"

使用带有参数的role

在main.yaml中可以用如下的方法使用myrole了

---

- hosts: test1
  remote_user: root
  roles:
   - role: myrole
     param: 'Test ansible'
   - role: myrole
     param: 'Test ansible2'

role指定默认的参数

指定默认的参数后,如果在调用时传参数,那么就使用传入的参数值,如果在调用的时候没有传参数,那么就使用默认的参数值

指定默认的参数很简单,一上面的参数为例

main.yaml
  roles
    myrole
      tasks
        main.yaml
      defaults
        main.yaml

在roles/myrole/defaults/main.yaml 中

---

- hosts: test1
  remote_user: root
  roles:
   - role: myrole
   - role: myrole
     param: "I am the value from external"

role与条件判断语句when一起

下面例子中,myrole只在Red Hat系列的主机上才能执行

- hosts: test2
  roles:
  - role: my_role
    when: "ansible_os_family == 'RedHat'"

用标签,实现执行Playbook中的部分任务

当一个Playbook文件比较大,并且在执行的时候只是想执行部分功能,那么这个时候就可以用到Playbook提供的标签(tags)

标签的基本用法

例如,文件example.yaml标记了两个标签:packages和configuration

tasks:
 - yum: name={{ item }} state=installed
   with_items:
     - httpd
   tage:
     - packages
 - name: copy httpd.conf
   template: src=templates/httpd.conf.j2  dest=/etc/httpd/conf/httpd.conf
   tage:
     - configuration
 - name: coyp index.html
   template: src=templates/index.html.j2 dest=/usr/share/httpd/noindex/index.html
   tage:
     - configuration
# 执行的时候不带任何tag参数,那么会执行所有标签对应的任务
ansible-playbook test2.yaml
# 如果指定执行部分安装的任务,则可利用关键字tags指定需要执行的任务
ansible-playbook test2.yaml --tags "packages"
# 如果指定不执行tag packages对应的任务,则可利用关键字skip-tags
ansible-playbook test2.yaml --skip-tags "configuration"

特殊的标签

always

标签的名字是用户自定义的,但是如果把标签的名字定义为always,那么always标签所对应的任务就始终会被执行

tasks:
 - debug: msg="start playbook"
   tags:
   - always
   
 - yum: name={{ item }} state=installed
   with_items:
     - httpd
   tage:
     - packages
     
 - name: copy httpd.conf
   template: src=templates/httpd.conf.j2  dest=/etc/httpd/conf/httpd.conf
   tage:
     - configuration
# 只指定允许packages标签时,还是会执行always标签对应的任务
ansible-palybook test2.yaml --tags "packages"

tagged、untagged和all

- hosts: test2
  remote_user: root
  tasks:
  - debug: msg="I am not tagged"
    tags:
      - tag1
        
  - debug: msg="I am not tagged"
# 分别指定--tags为"tagged"、"untagged"和"all"
# 利用"--tags tagged"来执行所有标记了标签的任务
ansible-playbook test2.yaml --tags tagged
# 利用 --tags untagged 来执行所有没有被标记标签的任务
ansible-playbook test2.yaml --tags untagged
# 利用 "--tags all"来执行所有任务
ansible-playbook test2.yaml --tags all

更多的Ansible模块

模块的分类

core模块

不需要额外下载和配置,安装ansible就可以使用

比较常用的模块

Extra模块

需要下载和额外配置才能使用的模块

可能存在bug

Extra模块的使用方法

搜索模块地址(类似dockerhub)

搜索关键字,点进去复制安装命令,执行即可

写Playbook的原则

  • 尽量使用include和role避免重复代码

  • 尽量把大文件分成小的文件

  • 重要的一点:多练习(操千曲而后晓声,观千剑而后识器)

变量的优先级

变量的优先级官方文档

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐