Ansible 常用模块

Ansible模块的使用

文件模块

Files文件模块库包含的模块可以对Linux文件进行管理,如创建、删除、编辑和修改文件的权限与属性等。

模块 说明
blockinfile 插入、更新或删除由可定义标记线包围的多行文本块
lineinfile 确保特定行位于某个文件中,或使用反向引用正则表达式来替换现有行。此模块可以在想要更改某一个行的文本时使用
copy 将文件从本地或远程计算机复制到目标主机的某个位置。类似于file模块,copy模块还可以设置文件属性,包括SELinux上下文
fetch 该模块和copy类似,但以相反的方式工作。fetch用来从目标主机获取文件到本机控制节点上
file 设置权限、所有权、SELinux上下文以及常规文件、符号链接、硬连接、目录时间戳等。此模块还可以创建或删除常规文件、符号链接、硬连接和目录。其他过个与文件相关的模块支持与file模块相同的属性设置选项,包括copy模块
stat 检索文件的状态信息,与Linux stat命令相似
synchronize 对rsync命令的打包

文件模块使用示例

确保目标主机上存在文件

1
2
3
4
5
6
7
- name: Touch a file and set permissions
file:
path: /home/student/touch.me
owner: student
group: root
mode: 0000
state: touch

如果目标主机已存在该文件,则会进行touch操作。上面的task除了确保文件存在以外,还会保证文件的权限为设定值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
  File: touch.me
Size: 14 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 163550 Links: 1
Access: (0000/----------) Uid: ( 1000/ student) Gid: ( 0/ root)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2020-06-02 17:11:40.642364330 -0400
Modify: 2020-06-02 17:11:40.642364330 -0400
Change: 2020-06-02 17:11:40.644530998 -0400
Birth: -
------------------------------
File: touch.me
Size: 14 Blocks: 8 IO Block: 4096 regular file
Device: 801h/2049d Inode: 163550 Links: 1
Access: (0000/----------) Uid: ( 1000/ student) Gid: ( 0/ root)
Context: unconfined_u:object_r:user_home_t:s0
Access: 2020-06-02 17:18:59.746867300 -0400
Modify: 2020-06-02 17:18:59.746867300 -0400
Change: 2020-06-02 17:18:59.746867300 -0400
Birth: -

修改文件属性

使用file模块,确保新的或现有文件具有正确的文件属性和SELinux类型。

1
2
3
4
- name: SELinux type is set to samba_share_t
file:
path: /home/student/touch.me
setype: samba_share_t

修改前touch.me文件的setype属性是user_home_t,使用file模块处理后setype属性已经改变为samba_share_t

1
2
3
4
$ ls -lZ
----------. 1 student root unconfined_u:object_r:user_home_t:s0 14 Jun 2 17:18 touch.me
$ ls -lZ
----------. 1 student root unconfined_u:object_r:samba_share_t:s0 14 Jun 2 17:18 touch.me

永久更改SELinux文件上下文属性

设置上下文属性时,file模块的行为和chcon类似。通过运行restorecon可能会意外的撤销使用该模块对文件上下文所做的更改。当使用file设置上下文后,可以使用System模块集合中的sefcontext来更新SELinux策略,如semanage fcontext

1
2
3
4
5
- name: SELinux type is persistently set to samba_share_t
sefcontext:
target: /home/student/touch.me
setype: samba_share_t
state: present

可以看得到在SELinux上下文策略中目标的默认上下文已经更改为samba_share_t

1
2
# semanage fcontext -l | grep touch.me
/home/student/touch.me all files system_u:object_r:samba_share_t:s0

注意:sefcontext模块只更新SELinux策略中目标的默认上下文,并不更改当前现有文件的上下文。

在目标主机上复制和编辑文件

使用copy模块时,模块假定设置了force: yes。这会强制copy模块覆盖远程文件(如果存在并且包含于与当前要发送的文件内容不同)。如果手动设置force: no,则它仅会在目标主机不存在要复制的这个文件时才会进行复制。

1
2
3
4
5
- name: Copy a file to managed hosts
copy:
src: file
dest: /home/student/touch.me
force: yes

如果要从目标主机上拉取文件到本机,则使用fetch模块。

1
2
3
4
- name: Retrieve SSH key from reference host
fetch:
src: "/home/{{ user }}/.ssh/id_rsa.pub"
dest: "files/keys/{{ user }}.pub"

要确保现有文件中存在某行文本,可以使用lineinfile模块。

1
2
3
4
5
- name: Add a line of text to file
lineinfile:
path: /home/student/touch.me
line: "Can you touch me?"
state: present

如果要将文本块插入到文档中,应使用blockinfile模块。

1
2
3
4
5
6
7
8
- name: Add additional lines to a file
blockinfile:
path: /home/student/touch.me
block: |
This is the block of first line.
And
This is the block of third line.
state: present

使用blockinfile模块时,注释块标记插入到块的开头和结尾,用来让Ansible识别和保持幂等性。

1
2
3
# BEGIN ANSIBLE MANAGED BLOCK
This is the first line.
# END ANSIBLE MANAGED BLOCK

在目标主机上删除文件

在大多数情况下,如果控制目标主机文件的删除使用file模块的state: absent参数来控制。

1
2
3
4
- name: Removed file in the server
file:
dest: /home/student/touch.me
state: absent

检索文件的详细信息

使用stat模块可以查看文件的详细信息,并返回文件的事实。你可以利用这些Facts对文件进行检索和校验。stat模块类似于Linux系统中的stat命令。

1
2
3
4
5
6
7
- name: Check all stat of /etc/passwd
stat:
path: /etc/passwd
register: results

- debug:
vars: results

同步控制节点和受控节点之间的文件

使用synchronize模块来操作同步主机间的文件。synchronize对rsync工具进行打包,它简化了playbook中常见文件管理任务。使用该模块要求双方主机安装rsync工具。

1
2
3
4
- name: synchronize local file to server file
synchronize:
src: /etc
dest: /home/student/

使用Jinja2模版部署自定义文件

可以利用Jinja2模版语法通过和变量与事实相配合,对固定地方的值进行覆盖和编辑。来实现定制化修改配置文件。

1
2
3
使用`{% EXPR %}`用于表达式或逻辑(循环、判断)
使用`{{ EXPR }}`用于输出最终表达式或变量的结果
使用`{# COMMENT #}`注释,注释的内容不会出现在最终的结果里

构建Jinja2模版

Jinja2模版由多个元素组成:数据、变量和表达式。在呈现Jinja2模版时,这些变量和表达式被替换为对应的值。模版中使用的变量可以在playbook的vars部分中指定。可以将目标主机的事实作为模版中使用的变量。

可以使用ansible all -m setup来查看目标主机中全部的Fact事实。模版文件没有固定的文件拓展名,只要是文本文件即可,但是为了方便记忆理解,通常使用.j2来代表文本文件是Jinja2的模版文件。

部署Jinja2模板

我们刚刚创建好了Jinja2模版,现在要利用这些模版。我们需要使用template模块。src参数指的是模版文件在控制节点中的路径,dest的值是在目标主机的指定目录生成文件。

1
2
3
4
5
6
tasks:
- name: template render
template:
src: motd.j2
dest: /etc/motd
backup: true

template和file模块一样支持对文件权限进行设置。

标示配置文件由Ansible管理

我们使用模版生成文件后,为了避免管理员用户手动的修改这些配置文件,我们最好在模版的开头写上声明。虽然template不会自动地帮我们完成,但是我们可以在模版文件的开头手动引入设定好的提醒文本,使用Jinja2语法将变量的内容填写到配置文件中。

可以使用ansible_managed 指令中默认设置的 Ansible managed字符串来执行此操作。这不是一个正常的变量,但是可以在模版中用作一个变量。ansible_managed指令在ansible.cfg文件中的设置:

ansible_managed = Ansible managed: modified on %Y-%m-%d %H:%M:%S

要将在ansible.cfg文件中配置的ansible_managed字符串包含在Jinja2模版内,使用下面的引用变量语法即可。

1
{{ ansible_managed }}

通过模版生成的配置文件开头存在了Ansible managed: modified on 2020-06-10 15:55:29。这样就能对修改此文件的人有一个提示的作用。

控制结构

可以在Jinja2模版中使用控制结构,以减少重复的输入。为Play中每个主机能够动态的生成条目,或者有条件的将文本插入到文件中。

循环

Jinja2使用for语句来提供循环功能。

1
2
3
{% for user in users %}
{{ user }}
{% endfor %}

下面的示例模版使用for逐一遍历users变量中的所有值,将myuser替换为各个值,但值为root时除外。

1
2
3
4
{# for statement #}
{% for myuser in users if not myuser == "root" %}
User number is {{ loop.index }} - {{ myuser }}
{% endfor %}

loop.index变量是循环到当前处的索引号。他在循环第一次执行时的值为1,每一次迭代递增1。

下面的例子是生成hosts文件。

1
2
3
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_facts']['default_ipv4']['address'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
1
2
3
4
5
6
7
8
9
---
- name: generate hosts
hosts: all
tasks:
- name: generate template
template:
src: hosts.j2
dest: /home/student/hosts
...

条件语句

Jinja2使用if语句来提供条件控制。如果满足某些条件,则会按照语句块内的规则继续生成。

1
2
3
{% if finished %}
{{ result }}
{% endif %}

Jinja2仅能用于模版,不能用于Playbook。条件语句和循环语句可以相互嵌套。

变量过滤器

可以使用Jinja2提供的过滤器改变原有变量输出的格式,例如将字符串转换为JSON或YAML。

如果要转换为json格式时,使用to_json过滤器进行输出。

如果要转换为yaml格式时,使用to_yaml过滤器进行输出。

1
2
{{ output | to_json }}
{{ output | to_yaml }}

如果要使结构更适于人类阅读,可以使用下面的过滤器来输出人类可读格式。

1
2
{{ output | to_nice_json }}
{{ output | to_nice_yaml }}

变量测试

在Ansible Playbook中与when子句一同使用的表达式是Jinja2表达式。用于测试返回值的内置Ansible测试failed、changed、succeeded、skipped四种。

1
2
3
4
5
tasks:
.......
- debug:
msg: "The task was aborted"
when: returnvalue is faild

软件包管理

使用dnf的Ansible模块可以在受控主机上控制dnf软件包管理器。dnf是RHEL8使用的默认包管理器用于替代yum。不过也可以使用yum模块来对RHEL8进行操作。

下面示例中使用task任务来代替原有的dnf包管理器指令:

dnf install httpd -y

1
2
3
4
- name: Install the httpd packages
dnf:
name: httpd
state: present

state关键字有如下参数:

  • present 如果软件包不存在,则安装软件包
  • absent 如果已安装,则删除软件包
  • latest 如果软件包不是最新版本,则会对软件包进行更新。要是没有安装则会安装最新版本的软件包

name关键字有以下使用方式:

  • 直接填写某一个或以列表的形式填写多个软件包名称
  • 使用'*'配合latest可以进行更新系统全部软件包
  • 若要管理模块或组需要使用@符号

安装Development Tools软件组的示例:

1
2
3
4
- name: Install the Development Tools group
dnf:
name: ‘@Development Tools’
state: present

安装postgresql数据库模块:

1
2
3
4
- name: Install the postgresql module
dnf:
name: '@postgresql:9.6/client'
state: present

如果包管理器不是yum或者dnf。可以使用package模块替代yum/dnf模块。package模块可以自动检测并使用受控主机的包管理器去安装配置的软件包。

1
2
3
4
- name: Install httpd
package:
name: httpd
state: present

收集已安装的软件包信息

使用package_facts模块就能获取到受控主机中已经安装的软件包。它会将获取到的全部软件包信息存入ansible_facts.packages变量中。

使用package_facts模块并进行输出的示例:

1
2
3
4
5
6
7
8
9
---
- name: Output
hosts: prod
tasks:
- name: Check packages
package_facts:
manager: auto
- debug:
var: ansible_facts.packages.httpd

package_facts模块有两个选项:

  • manager 选择软件包管理器。默认auto自动识别
  • strategy 策略

返回prod主机组中已安装的httpd软件包信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
ok: [serverc] => {
"ansible_facts.packages.httpd": [
{
"arch": "x86_64",
"epoch": null,
"name": "httpd",
"release": "10.module+el8+2764+7127e69e",
"source": "rpm",
"version": "2.4.37"
}
]
}
ok: [serverd] => {
"ansible_facts.packages.httpd": [
{
"arch": "x86_64",
"epoch": null,
"name": "httpd",
"release": "10.module+el8+2764+7127e69e",
"source": "rpm",
"version": "2.4.37"
}
]
}

配置yum仓库

添加yum仓库

使用yum_repository模块来控制第三方yum仓库。在添加仓库时即可配置GPG密钥。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- name: Create yum repo
hosts: prod
tasks:
- name: Create yum repo
yum_repository:
name: rpmforge
description: RPMforge YUM repo
file: test
gpgkey: http://materials.example.com/yum/repository/RPM-GPG-KEY-example
baseurl: http://materials.example.com/yum/repository/
enabled: yes
present: yes
gpgcheck: yes

可以直接使用gpgkey选项而不需要使用其他模块进行配置。

用户管理和身份认证

如何管理用户和用户组,以及配置ssh-key。

用户模块

使用user模块可以管理主机上的用户以及它们的许多参数。也可以删除用户、设置主目录、设置UID、关联的用户组等很多参数。

如果需要创建可以登录的计算机用户,需要使用password参数和password_hash('sha512')搭配生成Hash后的密码才可以登陆系统。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---
- name: Add user
hosts: dev
tasks:
- name: User modul
user:
name: natsumi
shell: /bin/bash
groups: wheel
append: yes
state: present
password: "{{ pwd | password_hash('sha512') }}"
vars:
pwd: test123

组模块

group模块可以管理受控主机中的用户组。

1
2
3
4
5
6
7
8
---
- name: Add group
hosts: dev
tasks:
- name: change group
group:
name: devuser
state: present

group模块参数: gid,local,name,state,system(如果设置为yes,则表示创建的组是系统组)

系统调度

at一次性任务

使用at模块来创建一个一次性任务。可以安排任务在未来的某一个时间点执行一次。

at模块参数:

参数 选项 说明
command - 计划要运行的命令
count - 单位数字。必须和units一同使用
script_file - 计划要执行的现有脚本文件
state absent/present 添加或删除命令或脚本的状态
unique yes/no 如果任务已在运行,则不会再次执行
units minutes/hours/days/weeks 时间单位

at模块使用示例:

1
2
3
4
5
6
7
8
9
10
---
- name: Using at make task
hosts: dev
tasks:
- name: create task
at:
command: "echo 'rick' > lala.txt"
count: 1
units: minutes
unique: yes

目前使用at模块总会在文件名末尾拼接上marcinDELIMITERxxxx字符串,经过Google发现这一串文本是用来在at pool中用来标示任务的。可是在这里为什么将他们输出了出来目前还不得而知。

cron计划任务

要完成重复性的任务,也可以使用cron模块来创建计划任务。

1
2
3
4
5
6
- name: Ensure a job that runs at 2 and 5 exists. Creates an entry like "0 5,2 * * ls -alh > /dev/null"
cron:
name: "check dirs"
minute: "0"
hour: "5,2"
job: "ls -alh > /dev/null"

想要在特殊时间点,比如系统重启后运行一个任务,可以使用special_time参数来配置cron计划任务。

1
2
3
4
5
- name: Ensure a job that runs at system reboot. Creates an entry like "@reboot ls -alh > /dev/null"
cron:
name: "check dirs"
special_time: reboot
job: "ls -alh > /dev/null"

reboot模块

使用reboot模块重新启动比直接用shell模块发起关机更安全,使用shell模块关闭受控主机后,它会等待再次开机恢复运行,才会继续向下执行其他任务和Play。

对受控主机重启后并持续等待180s。如果受控主机恢复运行则继续执行接下来的任务。

1
2
3
- name: Reboot a slow machine that might have lots of updates to apply
reboot:
reboot_timeout: 180

如果超出运行时间,则执行出错。接下来的task和play都不会继续执行。

fatal: [servera]: FAILED! => {"changed": false, "elapsed": 19, "msg": "Timed out waiting for last boot time check (timeout=18)", "rebooted": true}