ansible大全详解【帮助你从playbook小白一跃成为playbook专家】

使用ansible运行任务

目录

两种方式:

  • ad-hoc:类似于直接在shell终端敲打命令,执行简单的任务
  • playbook:剧本,类似于shell脚本,执行复杂的任务

【注:导航器也可以执行任务,但只可以执行playbook,不可以执行ad-hoc】

ansible-1:192.168.96.202

ansible-2:192.168.96.203

ansible-3:192.168.96.204

ad-hoc

ad-hoc执行任务格式

ansible 主机/主机组 -m 模块 -a ‘模块的参数’  ansbile的参数 
eg: ansbile all -m shell -a 'useradd devops' -u root -k
  -u 指定用户
  -k 使用密码认证    

在配置完免密后 -u 和-k 可以不加

测试管控

[root@localhost .ssh]# ansible all -m ping
ansible-2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
ansible-3 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}

查询模块

不同安装方式的模块数量不同,ansible-core最少

第三方安装:https://galaxy.ansible.com/

查看当前系统所有的模块数量

[root@localhost .ssh]# ansible-doc -l | wc -l
[WARNING]: template parsing did not produce documentation.
[WARNING]: win_template parsing did not produce documentation.
3681

在这其中,可以看到包含cloud 、huawei、vmware、win等的模块

查看模块详细参数

[root@localhost .ssh]#  ansible-doc -s user 
- name: Manage user accounts
  user:
      append:                # If `yes', add the user to the groups specified in `groups'. If `no', user will only be added to the groups specified in `groups', removing them from
                               all other groups. Mutually exclusive with `local'
      authorization:         # Sets the authorization of the user. Does nothing when used with other platforms. Can set multiple authorizations using comma separation. To delete all
                               authorizations, use `authorization='''. Currently supported on Illumos/Solaris.
...【以下省略】

查看模块详细参数及其案例

[root@localhost .ssh]# ansible-doc  user

常用模块

命令执行模块

一共有四种模块:

  1. command
  2. shell
  3. raw
  4. script

command

通过执行ansible-doc command 可以看到,conmmand模块的使用跟正常shell命令差别不大。

img

[root@localhost ~]# ansible all -m command -a 'touch /opt/file1'
[WARNING]:  Consider using the file module  with state=touch rather than running 'touch'.
If you need to use command because file is insufficient you can add 'warn: false' to this
command task or set ' command_warnings=False ' in ansible.cfg to get rid of this message.
【此处warning警告是说,创建文件这个命令推荐使用file模块,而不是command模块,但这并不影响正常使用,如果想关闭这个警告,在【default】下添加 command_warnings=False 即可】
ansible-2 | CHANGED | rc=0 >>


ansible-1 | CHANGED | rc=0 >>


ansible-3 | CHANGED | rc=0 >> command

command模块有几样是不可用的,<>【重定向】 | 【管道符】 & 【and符号】

[root@localhost ~]# ansible all -m command -a 'echo hellp > /opt/file4'
ansible-1 | CHANGED | rc=0 >>
hellp > /opt/file4

ansible-2 | CHANGED | rc=0 >>
hellp > /opt/file4

ansible-3 | CHANGED | rc=0 >>
hellp > /opt/file4
[root@localhost ~]# ansible all -m command -a 'echo hello | echo hell0'
ansible-2 | CHANGED | rc=0 >>
hello | echo hell0

ansible-3 | CHANGED | rc=0 >>
hello | echo hell0

ansible-1 | CHANGED | rc=0 >>
hello | echo hell0
[root@localhost ~]# ansible all -m command -a 'echo hello & echo hell0'
ansible-3 | CHANGED | rc=0 >>
hello & echo hell0

ansible-1 | CHANGED | rc=0 >>
hello & echo hell0

ansible-2 | CHANGED | rc=0 >>
hello & echo hell0

如上所示,如果使用>&|,那么command会默认将其认为是要输出的字符。

拓展-ansbile默认模块

[root@localhost ~]# ansible all -a "echo a"
ansible-2 | CHANGED | rc=0 >>
a

ansible-3 | CHANGED | rc=0 >>
a

ansible-1 | CHANGED | rc=0 >>
a

这里可以看到,没有指定模块,但他仍然有结果返回。

可以在ansible.cfg中看到

# default module name for /usr/bin/ansible
#module_name = command
这里设定了在不指定模块的情况下,默认使用的是什么模块


shell

跟正常使用shell命令没区别,并且还拥有一些高级特性,chdir、creates

[root@localhost ~]# ansible all -m shell -a 'echo hello & echo hell0'
ansible-3 | CHANGED | rc=0 >>
hell0
hello

ansible-2 | CHANGED | rc=0 >>
hell0
hello

ansible-1 | CHANGED | rc=0 >>
hell0
hello
 正常使用

高级特性

chdir

img

在执行后续命令前,会更改工作目录。【因为主控执行的命令,默认是在被控的家目录下执行的,使用chdir,可以修改执行的工作目录】
[root@localhost ~]# ansible all -m shell -a 'chdir=/opt touch example-1'
ansible-1 | CHANGED | rc=0 >>


ansible-3 | CHANGED | rc=0 >>


ansible-2 | CHANGED | rc=0 >>


[root@localhost ~]# ansible all -m shell -a 'chdir=/opt  ls'
ansible-2 | CHANGED | rc=0 >>
example-1

ansible-1 | CHANGED | rc=0 >>
example-1

ansible-3 | CHANGED | rc=0 >>
example-1
 chdir

img

creates
当文件存在,命令不执行
[root@localhost ~]# ansible all -m shell -a "creates=/etc/shadow ls /opt/"
ansible-3 | SUCCESS | rc=0 >>
skipped, since /etc/shadow exists

ansible-2 | SUCCESS | rc=0 >>
skipped, since /etc/shadow exists

ansible-1 | SUCCESS | rc=0 >>
skipped, since /etc/shadow exists

可以看到因为/etc/shadow存在,所以后面的步骤被跳过了

[root@localhost ~]# ansible all -m shell -a "creates=/etc/shadows ls /opt/"
ansible-1 | CHANGED | rc=0 >>
example-1

ansible-3 | CHANGED | rc=0 >>
example-1

ansible-2 | CHANGED | rc=0 >>
example-1

如果不存在,则正常执行后面的命令

 creates
removes

img

当文件存在,命令执行
[root@localhost ~]# ansible all -m shell -a "removes=/etc/shadow ls /opt/"
ansible-1 | CHANGED | rc=0 >>
example-1

ansible-2 | CHANGED | rc=0 >>
example-1

ansible-3 | CHANGED | rc=0 >>
example-1



当文件不存在时,命令不执行【跳过】
[root@localhost ~]# ansible all -m shell -a "removes=/etc/shadows ls /opt/"
ansible-2 | SUCCESS | rc=0 >>
skipped, since /etc/shadows does not exist

ansible-3 | SUCCESS | rc=0 >>
skipped, since /etc/shadows does not exist

ansible-1 | SUCCESS | rc=0 >>
skipped, since /etc/shadows does not exist

 removes

raw

用法与shell一模一样,只是不支持chdir、creates、removes。但支持<>|&

[root@localhost ~]# ansible all -m raw -a "chdir=/opt ls  "
ansible-2 | CHANGED | rc=0 >>
Shared connection to ansible-2 closed.


ansible-3 | CHANGED | rc=0 >>
ansible-example
Shared connection to ansible-3 closed.


ansible-1 | CHANGED | rc=0 >>
Shared connection to ansible-1 closed.

可以看到他并没有在/opt下执行,而是出现在了用户的家目录下。


[root@localhost ~]# ansible all -m raw -a "echo hello > /opt/file1 |cat /opt/file1  "
ansible-1 | CHANGED | rc=0 >>
hello
Shared connection to ansible-1 closed.


ansible-3 | CHANGED | rc=0 >>
hello
Shared connection to ansible-3 closed.


ansible-2 | CHANGED | rc=0 >>
hello
Shared connection to ansible-2 closed.

raw可以支持<>|&


script

将管理端的shell脚本中的指令放到被控节点执行。

【但这并不是执行脚本,而是将脚本中的命令单独拎出来执行,只是形式上像是执行了shell脚本,所以脚本也就不需要可执行权限】

#!/bin/bash
useradd anssh
rm -rf /opt/*
echo hello> /opt/hello.txt
 脚本
[root@localhost ~]# ansible all -m script -a "ans.sh"
ansible-1 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to ansible-1 closed.\r\n",
    "stderr_lines": [
        "Shared connection to ansible-1 closed."
    ],
    "stdout": "\r\n",
    "stdout_lines": [
        ""
    ]
}
ansible-3 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to ansible-3 closed.\r\n",
    "stderr_lines": [
        "Shared connection to ansible-3 closed."
    ],
    "stdout": "",
    "stdout_lines": []
}
ansible-2 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to ansible-2 closed.\r\n",
    "stderr_lines": [
        "Shared connection to ansible-2 closed."
    ],
    "stdout": "",
    "stdout_lines": []
}
 script
[root@localhost ~]# ansible all -m shell -a "id anssh ; ls /opt/ ; cat /opt/hello.txt "
ansible-3 | CHANGED | rc=0 >>
uid=1001(anssh) gid=1001(anssh) groups=1001(anssh)
hello.txt
hello

ansible-2 | CHANGED | rc=0 >>
uid=1001(anssh) gid=1001(anssh) groups=1001(anssh)
hello.txt
hello

ansible-1 | CHANGED | rc=0 >>
uid=1001(anssh) gid=1001(anssh) groups=1001(anssh)
hello.txt
hello
 验证

文件相关模块

file

file模块中有两个选项尤为重要

1、path

2、state

path 指定文件和目录的详细路径
state 指定动作
   file【默认动作】查看文件或者目录的属性信息
   touch 创建文件和更新时间戳
   directory 创建目录
   absent 删除目录,删除文件,取消链接文件
   hard 硬链接
   link 软链接
     force 强制创建
src 创建链接文件时指定源文件路径
dest 创建链接文件时指定链接文件路径 
mode 指定权限
owner 指定拥有人
group 指定拥有组 file模块的选项

path

img

img

[root@localhost ~]# ansible all -m file -a "path=/etc/passwd  "
ansible-3 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/etc/passwd",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "state": "file",
    "uid": 0
}
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/etc/passwd",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "state": "file",
    "uid": 0
}
ansible-2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "path": "/etc/passwd",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "state": "file",
    "uid": 0
}

可以看到默认指定的就是state=file ,查看文件的详细信息

state

[root@localhost ~]# ansible all -m file -a 'path=/opt/file.txt state=touch mode=777 owner=devops group=devops '
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/file.txt",
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 0,
    "state": "file",
    "uid": 1000
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/file.txt",
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 0,
    "state": "file",
    "uid": 1000
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/file.txt",
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 0,
    "state": "file",
    "uid": 1000
}
 创建文件

在这里,我们不止使用了path和state,还使用了mode、owner、group,分别对应着权限、拥有人、拥有组。

mode-owner-group

😕如果,我们不知道该使用什么选项怎么办?

查帮助 ansbile-doc 模块名

img

directory

[root@localhost ~]# ansible all -m file -a 'path=/opt/dir.txt  state=directory mode=777 owner=devops group=devops '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "path": "/opt/dir.txt",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 6,
    "state": "directory",
    "uid": 1000
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "path": "/opt/dir.txt",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 6,
    "state": "directory",
    "uid": 1000
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 1000,
    "group": "devops",
    "mode": "0777",
    "owner": "devops",
    "path": "/opt/dir.txt",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 6,
    "state": "directory",
    "uid": 1000
}
 创建目录

在将state的选项改为directory后,我们便可以创建一个目录

软链接-硬链接

[root@localhost ~]# ansible all -m file -a 'src=/etc/shadow   dest=/opt/shadow-l state=link'
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/shadow-l",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 11,
    "src": "/etc/shadow",
    "state": "link",
    "uid": 0
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/shadow-l",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 11,
    "src": "/etc/shadow",
    "state": "link",
    "uid": 0
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/shadow-l",
    "gid": 0,
    "group": "root",
    "mode": "0777",
    "owner": "root",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 11,
    "src": "/etc/shadow",
    "state": "link",
    "uid": 0
}
 创建软链接
[root@localhost ~]# ansible all -m shell -a 'ls -l /opt/shadow-l'
ansible-1 | CHANGED | rc=0 >>
lrwxrwxrwx. 1 root root 11 Jul 28 15:31 /opt/shadow-l -> /etc/shadow

ansible-2 | CHANGED | rc=0 >>
lrwxrwxrwx. 1 root root 11 Jul 28 15:31 /opt/shadow-l -> /etc/shadow

ansible-3 | CHANGED | rc=0 >>
lrwxrwxrwx. 1 root root 11 Jul 28 15:31 /opt/shadow-l -> /etc/shadow
 验证软链接
force

😕如果在创建软链接的时候,源文件不存在怎么办?

[root@localhost ~]# ansible all -m file -a 'src=/tmp/abc dest=/opt/abc-h state=link '
ansible-3 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
     "msg": "src file does not exist, use \"force=yes\" if you really want to create the link: /tmp/abc", 
    "path": "/opt/abc-h",
    "src": "/tmp/abc"
}
ansible-2 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
     "msg": "src file does not exist, use \"force=yes\" if you really want to create the link: /tmp/abc", 
    "path": "/opt/abc-h",
    "src": "/tmp/abc"
}
ansible-1 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
     "msg": "src file does not exist, use \"force=yes\" if you really want to create the link: /tmp/abc", 
    "path": "/opt/abc-h",
    "src": "/tmp/abc"
} 源文件不存在

可以看到,他给了一个force的选项,就是在指定的源文件不存在的时候,可以使用force=yes强制创建。

[root@localhost ~]# ansible all -m file -a 'src=/tmp/abc dest=/opt/abc-h state=link force=yes '
[WARNING]: Cannot set fs attributes on a non-existent symlink target. follow should be set to False to avoid this.

ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/abc-h",
    "src": "/tmp/abc"
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/abc-h",
    "src": "/tmp/abc"
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/abc-h",
    "src": "/tmp/abc"
}

但是,这种方法不会创建源文件,还是需要自己手动创建源文件

hard
[root@localhost ~]# ansible all -m file -a 'src=/etc/passwd dest=/opt/passwd-h state=hard '
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/passwd-h",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "src": "/etc/passwd",
    "state": "hard",
    "uid": 0
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/passwd-h",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "src": "/etc/passwd",
    "state": "hard",
    "uid": 0
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "dest": "/opt/passwd-h",
    "gid": 0,
    "group": "root",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:passwd_file_t:s0",
    "size": 1035,
    "src": "/etc/passwd",
    "state": "hard",
    "uid": 0
}
 创建硬链接

在硬链接中,如果遇到源文件不存在的情况,是不可以使用force的。

[root@localhost ~]# ansible all -m file -a 'src=/tmp/aaa dest=/opt/aaa-h state=hard '
ansible-1 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "dest": "/opt/aaa-h",
     "msg": "src does not exist", 
    "src": "/tmp/aaa"
}
ansible-2 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "dest": "/opt/aaa-h",
     "msg": "src does not exist", 
    "src": "/tmp/aaa"
}
ansible-3 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "dest": "/opt/aaa-h",
     "msg": "src does not exist", 
    "src": "/tmp/aaa"
}

可以看到,并没有给可以使用force的提示。

copy

src:主控节点的路径
    如果是src=/opt/,说明拷贝的是目录下的文件,如果是src=/opt,则拷贝的是目录
dest:被控节点的路径
backup: 在覆盖原文件的时候,将原文件先备份,后覆盖
remote_src :在被控节点上操作拷贝,不涉及其他机器,只涉及指定的机器
content :直接在文件中写入内容。
    force:默认为yes,表示覆盖文件内容,如果改为no,则文件存在则不会覆盖,文件不存在则创建。


将主控节点的文件传输到被控节点上

img

src-dest
src: 主控节点的路径
      【如果是src= /opt/ ,则是拷贝的/opt目录下的内容,如果是src= /opt ,则拷贝的是/opt这个目录。】
dest:被控节点的路径
backup :如果发现文件被覆盖,则会在覆盖之前将文件备份【默认是False,需要手动开启---yes】
remote_src :将被控节点文件拷贝到被控节点的目录上
    在这个选项下,src和dest都指定的是被控的目录。
content:在文件中,添加内容。
    force:是否覆盖文件。默认是yes,若不想覆盖,则手动改为no即可。
   copy的选项
[root@localhost ~]# ansible ansible-1 -m copy  -a 'src=/opt/file1 dest=/opt/ '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "3f786850e387550fdab836ed7e6dc881de23001b",
    "dest": "/opt/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "60b725f10c9c85c70d97880dfe8191b3",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:usr_t:s0",
    "size": 2,
    "src": "/home/devops/.ansible/tmp/ansible-tmp-1722157209.5900252-67050581745827/source",
    "state": "file",
    "uid": 0
}
[root@localhost ~]# ansible ansible-1 -m shell -a 'cat /opt/file1'
ansible-1 | CHANGED | rc=0 >>
a
 拷贝文件
backup
[root@localhost ~]# ansible ansible-1 -m copy  -a 'src=/opt/file1 dest=/opt/  backup=yes  '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "backup_file": "/opt/file1.5437.2024-07-28@17:02:58~",
    "changed": true,
    "checksum": "89e6c98d92887913cadf06b2adb97f26cde4849b",
    "dest": "/opt/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "3b5d5c3712955042212316173ccf37be",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:usr_t:s0",
    "size": 2,
    "src": "/home/devops/.ansible/tmp/ansible-tmp-1722157374.3473442-152415013307471/source",
    "state": "file",
    "uid": 0
}
[root@localhost ~]# ansible ansible-1 -m shell -a 'ls /opt/'
ansible-1 | CHANGED | rc=0 >>
file1
 file1.5437.2024-07-28@17:02:58~ 备份

这里使用了backup ,在覆盖原文件的时候,也将做出了备份,可以看到备份的格式使用的是文件名+时间。

remote_src
[root@localhost ~]# ansible ansible-1 -m copy  -a 'src=/opt/file1 dest=/tmp/ remote_src=yes '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "89e6c98d92887913cadf06b2adb97f26cde4849b",
    "dest": "/tmp/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "3b5d5c3712955042212316173ccf37be",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:usr_t:s0",
    "size": 2,
    "src": "/opt/file1",
    "state": "file",
    "uid": 0
}
[root@localhost ~]# ansible ansible-1 -m shell -a 'ls /tmp/file1 '
ansible-1 | CHANGED | rc=0 >>
/tmp/file1
 被控内移动

remote_src可以理解为在被控节点上使用cp命令

content
[root@localhost ~]# ansible ansible-1 -m shell -a 'cat /opt/file1'
ansible-1 | CHANGED | rc=0 >>
abcd

[root@localhost ~]# ansible ansible-1 -m copy -a 'content="hello redhat" dest=/opt/file1    '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "6e675264326181bb5bf13870ba93961932318ddf",
    "dest": "/opt/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "170585e2951b22198fe6d54373c2763c",
    "mode": "0644",
    "owner": "root",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 12,
    "src": "/home/devops/.ansible/tmp/ansible-tmp-1722188413.5113533-195614691982237/source",
    "state": "file",
    "uid": 0
}

[root@localhost ~]# ansible ansible-1 -m shell -a 'cat /opt/file1'
ansible-1 | CHANGED | rc=0 >>
hello redhat

 添加文本-覆盖

这里我们没有指定force,所以出现了覆盖的情况。

【注:这里我们使用ansbile主控查看的内容,但是在被控,他会出现不换行的情况】

[devops@ansible-1 opt]$ cat file1
hello redhat[devops@ansible-1 opt]$

所以,我们可以加上 \n 来手动换行

[root@localhost ~]# ansible ansible-1 -m copy -a 'content="hello redhat\n" dest=/opt/file1    '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "f31aadcb365412bdf9cbe138f3b02e93099e2822",
    "dest": "/opt/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "f8ed0becc9468514b70a74fa06cf8c50",
    "mode": "0644",
    "owner": "root",
    "secontext": "unconfined_u:object_r:usr_t:s0",
    "size": 13,
    "src": "/home/devops/.ansible/tmp/ansible-tmp-1722188625.357919-29281748514975/source",
    "state": "file",
    "uid": 0
}

[devops@ansible-1 opt]$ cat file1
hello redhat


force
[root@localhost ~]# ansible ansible-1 -m copy -a 'content="hello redhat" dest=/opt/file1  force=no   '
ansible-1 | SUCCESS => {
    "changed": false,
    "dest": "/opt/file1",
    "src": "/root/.ansible/tmp/ansible-local-18496t814muv/tmppmlxjlja"
}
 手动指定no-文件存在
[root@localhost ~]# ansible ansible-1 -m copy -a 'content="hello rehl" dest=/opt/file2  force=no   '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "e2fb26851b44498dca03f4df9dc14b27cb5a926e",
    "dest": "/opt/file2",
    "gid": 0,
    "group": "root",
    "md5sum": "270e1a38e03b9db98003f8995e70cd0b",
    "mode": "0644",
    "owner": "root",
    "secontext": "system_u:object_r:usr_t:s0",
    "size": 10,
    "src": "/home/devops/.ansible/tmp/ansible-tmp-1722189230.7588613-45338143629576/source",
    "state": "file",
    "uid": 0
}
 文件不存在

fetch

将被控文件传到主控。【注意,fetch不可以拉取目录,只可以拉取文件】

src:被控节点的文件路径
dest:主控节点的文件路径
flat:默认是no,默认情况下拉取文件到主控,是以被控节点主机名命名的目录,在这之中保存着拉取的文件。
    更改为yes后,拉取的文件不再以目录的形式呈现,而是直接以拉取的文件本身呈现。
    

src-dest

[root@localhost ~]# ansible ansible-1 -m fetch -a 'src=/opt/file1 dest=/opt'
ansible-1 | CHANGED => {
    "changed": true,
    "checksum": "6e675264326181bb5bf13870ba93961932318ddf",
    "dest": "/opt/ansible-1/opt/file1",
    "md5sum": "170585e2951b22198fe6d54373c2763c",
    "remote_checksum": "6e675264326181bb5bf13870ba93961932318ddf",
    "remote_md5sum": null
}
[root@localhost ~]# ls  /opt/
ansible-1  
[root@localhost ~]# ls /opt/ansible-1/
opt
[root@localhost ~]# ls /opt/ansible-1/opt/
file1

flat

😕易错点

如果出现以下情况,说明dest路径有问题

[root@localhost ~]# ansible ansible-1 -m fetch -a 'src=/opt/file1 dest=/opt flat=yes '
ansible-1 | FAILED! => {
    "changed": false,
    "file": "/opt",
    "msg": "dest is an existing directory, use a trailing slash if you want to fetch src into that directory"
}

在修改flat后,dest的路径便不能只是/opt,而是要详细到/opt/,指定在目录下

[root@localhost ~]# ansible ansible-1 -m fetch -a 'src=/opt/file1 dest=/opt/ flat=yes '
ansible-1 | CHANGED => {
    "changed": true,
    "checksum": "6e675264326181bb5bf13870ba93961932318ddf",
    "dest": "/opt/file1",
    "md5sum": "170585e2951b22198fe6d54373c2763c",
    "remote_checksum": "6e675264326181bb5bf13870ba93961932318ddf",
    "remote_md5sum": null
}
[root@localhost ~]# ls /opt/
file1

软件相关模块

yum_repository

配置仓库的模块,可以使用当前模块配置仓库

file:文件名【后面不用接.repo,因为当前选项会自动补全.repo】
name: 仓库名字
description:描述信息
baseurl :仓库路径
enabled:仓库是否开启
gpgcheck :是否验证密钥
gpgkey :密钥的路径
state :动作
    absent:删除
    present:默认是这个,表示添加

案例:搭建仓库,搭建BaseOS和AppStream仓库

【在ad-hoc中,一次只能搭建一个仓库】

[root@localhost ~]# ansible all -m yum_repository -a 'file=dvd name=BaseOS description=BaseOS enabled=1 gpgcheck=0 baseurl=file:///mnt/cdrom/BaseOS'
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "BaseOS",
    "state": "present"
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "BaseOS",
    "state": "present"
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "BaseOS",
    "state": "present"
}
 BaseOS
[root@localhost ~]# ansible all -m yum_repository -a 'file=dvd name=AppStream description=AppStream enabled=1 gpgcheck=0 baseurl=file:///mnt/cdrom/AppStream'
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "AppStream",
    "state": "present"
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "AppStream",
    "state": "present"
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "AppStream",
    "state": "present"
}
 AppStream

😕在ansible的yum_repository模块中,name和description的位置需要注意

[BaseOS]    ---name 
baseurl = file:///mnt/cdrom/BaseOS
enabled = 1
gpgcheck = 0
name = BaseOS    ---description

absent

[root@localhost ~]# ansible ansible-1  -m yum_repository -a 'file=dvd name=AppStream  state=absent '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "AppStream",
    "state": "absent"
}
[root@localhost ~]# ansible ansible-1 -m shell -a 'cat /etc/yum.repos.d/dvd.repo'
ansible-1 | CHANGED | rc=0 >>
[BaseOS]
baseurl = file:///mnt/cdrom/BaseOS
enabled = 1
gpgcheck = 0
name = BaseOS
 删除

在删除的时候,只需指定file和name即可。

yum

删除,安装,卸载软件包/软件包组

name :软件包名,软件包组名
state 
    present 安装软件包
    absent 卸载软件包
    latest 更新软件包

😕在ansbile中,也是需要注意镜像文件是否连接和挂载的。

[root@localhost ~]# ansible all -m shell -a 'mkdir /mnt/cdrom ; echo "/dev/cdrom /mnt/cdrom iso9660 defaults 0 0 " >/etc/fstab && mount -a '
ansible-1 | CHANGED | rc=0 >>
mount: /mnt/cdrom: WARNING: source write-protected, mounted read-only.

ansible-3 | CHANGED | rc=0 >>
mount: /mnt/cdrom: WARNING: source write-protected, mounted read-only.

ansible-2 | CHANGED | rc=0 >>
mount: /mnt/cdrom: WARNING: source write-protected, mounted read-only. 挂载

present

[root@localhost ~]# ansible all -m yum -a 'name=httpd state=present '
[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/core.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/mathstuff.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/core.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/core.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/mathstuff.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/mathstuff.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)


ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: httpd",
        "Installed: apr-1.7.0-11.el9.x86_64",
        "Installed: mailcap-2.1.49-5.el9.noarch",
        "Installed: apr-util-1.6.1-20.el9.x86_64",
        "Installed: redhat-logos-httpd-90.4-1.el9.noarch",
        "Installed: apr-util-bdb-1.6.1-20.el9.x86_64",
        "Installed: mod_http2-1.15.19-2.el9.x86_64",
        "Installed: apr-util-openssl-1.6.1-20.el9.x86_64",
        "Installed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-filesystem-2.4.51-7.el9_0.noarch",
        "Installed: httpd-tools-2.4.51-7.el9_0.x86_64"
    ]
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: httpd",
        "Installed: apr-1.7.0-11.el9.x86_64",
        "Installed: mailcap-2.1.49-5.el9.noarch",
        "Installed: apr-util-1.6.1-20.el9.x86_64",
        "Installed: redhat-logos-httpd-90.4-1.el9.noarch",
        "Installed: apr-util-bdb-1.6.1-20.el9.x86_64",
        "Installed: mod_http2-1.15.19-2.el9.x86_64",
        "Installed: apr-util-openssl-1.6.1-20.el9.x86_64",
        "Installed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-filesystem-2.4.51-7.el9_0.noarch",
        "Installed: httpd-tools-2.4.51-7.el9_0.x86_64"
    ]
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: httpd",
        "Installed: apr-1.7.0-11.el9.x86_64",
        "Installed: mailcap-2.1.49-5.el9.noarch",
        "Installed: apr-util-1.6.1-20.el9.x86_64",
        "Installed: redhat-logos-httpd-90.4-1.el9.noarch",
        "Installed: apr-util-bdb-1.6.1-20.el9.x86_64",
        "Installed: mod_http2-1.15.19-2.el9.x86_64",
        "Installed: apr-util-openssl-1.6.1-20.el9.x86_64",
        "Installed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-2.4.51-7.el9_0.x86_64",
        "Installed: httpd-filesystem-2.4.51-7.el9_0.noarch",
        "Installed: httpd-tools-2.4.51-7.el9_0.x86_64"
    ]
}
 安装httpd

在这里,可以看到有warning警告,但这并不是说安装失败,而是指的是在rehl9中jinjia2版本过高,与python版本不匹配,所以出现warning。

解决方法:升级python,或者降级jinjia2

absent

[root@localhost ~]# ansible all -m yum -a 'name=httpd state=absent '
[WARNING]: Skipping plugin (/usr/local/lib/python3.9/site-packages/ansible-2.9.0-py3.9.egg/ansible/plugins/filter/core.py) as it seems to be invalid: cannot import name
'environmentfilter' from 'jinja2.filters' (/usr/local/lib/python3.9/site-packages/jinja2-3.1.4-py3.9.egg/jinja2/filters.py)

ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Removed: mod_http2-1.15.19-2.el9.x86_64",
        "Removed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Removed: httpd-2.4.51-7.el9_0.x86_64"
    ]
}
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Removed: mod_http2-1.15.19-2.el9.x86_64",
        "Removed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Removed: httpd-2.4.51-7.el9_0.x86_64"
    ]
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Removed: mod_http2-1.15.19-2.el9.x86_64",
        "Removed: mod_lua-2.4.51-7.el9_0.x86_64",
        "Removed: httpd-2.4.51-7.el9_0.x86_64"
    ]
 删除absent

安装包组

包组前一定要加@符号

[root@localhost ~]# ansible all -m yum -a 'name="@Development tools" state=present'

ansible-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": [
        "Group development installed."
    ]
}
ansible-2 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": [
        "Group development installed."
    ]
}
ansible-3 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": [
        "Group development installed."
    ]
}
 Development tools

服务管理模块

service

管理服务启动、停止、重启、是否开机自启

name :服务名称
state :操作
    started 开启
    stopped 停止
    restarted 重启
    reloaded 重新加载配置
enabled :是否开机自启



[root@localhost ~]# ansible all -m service -a 'name=httpd state=started enabled=yes'

systemd

name :服务名称
state :操作
    started 开启
    stopped 停止
    restarted 重启
    
enabled :是否开机自启
    daemon_reload : 是否加载配置
[root@localhost ~]# ansible all -m systemd -a 'name=httpd state=stopped '
 关闭服务

firewalld模块

service :服务名
port :端口  (80/tcp)
state :enabled(accept接受) disabled(reject拒绝)
permanent:是否永久生效
immediate:是否立即生效(与reload类似)
还可以添加富规则和端口转发,查询ansbile-doc

[root@localhost ~]# ansible all -m firewalld -a 'service=http state=enabled permanent=yes immediate=yes '
ansible-1 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "Permanent and Non-Permanent(immediate) operation, Changed service http to enabled"
}
ansible-3 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "Permanent and Non-Permanent(immediate) operation, Changed service http to enabled"
}
ansible-2 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "msg": "Permanent and Non-Permanent(immediate) operation, Changed service http to enabled"
}
 放行http

周期性任务模块

cron模块

name : 任务名字,便于ansible删除任务
minute:分钟
hour  小时
day :日
month :月
weekday :周
job :任务执行内容
cron_file: 指定任务保存位置(默认是在/var/spool/cron目录下)
state  present 创建(默认) 删除使用absent


[root@localhost ~]# ansible all -m cron -a 'name="this is rm" minute="50" hour="21" user=root job="rm -rf /opt/*"  '
 创建计划任务

这里默认是在/var/spool/cron目录下创建的,如果想要修改,则使用cron_file来指定其他位置

[root@localhost ~]# ansible all -m cron -a 'name="this is rm" state=absent'


 删除
[root@localhost ~]# ansible all -m cron -a 'name="this is rm" minute="50" hour="21" user=root job="rm -rf /opt/*"  cron_file=/etc/crontab ' cron_file

如果要删除,则也需指定详细位置

[root@localhost ~]# ansible all -m cron -a 'name="this is rm" state=absent cron_file=/etc/crontab'

用户管理模块

user

user : 创建修改删除用户

name: 用户名
state  present/absent 创建/删除
home:家目录
create_home:是否创建家目录 (默认是yes)
shell :登录shell
remove :只有state=absent 有效,等价于userdel
move_home:移动家目录
uid :指定uid
comment:描述信息
passwd  设置密码(明文密码)

创建了一个叫test的用户,uid是2005, 描述信息是mysql user, shell是/bin/bash 家目录是/tmp/test

[root@localhost ~]# ansible all -m user -a 'name=test uid=2005 comment="mysql user" shell=/bin/bash home=/tmp/test state=present '

删除用户

[root@localhost ~]# ansible all -m user -a 'name=test state=absent remove=yes'

absent和remove都要使用,这样才是userdel -r

设置密码

[root@localhost ~]# ansible all -m user -a 'name=test uid=2005 comment="mysql user" shell=/bin/bash home=/tmp/test state=present password=redhat'
 passwd

但这样设置的密码是明文的,如果想设置密文密码则需自己加密。

[root@localhost ~]# openssl passwd -6 redhat
$6$9pWQQDwKpVpGuDbh$9sc0tdox0o1EFUaDhvO4KAof0nF0iVgllF7JVn3piVEaXcQ2ETagtATs/hIWZ/dpGHJs5RpqCz/kq90AOzvPp.
[root@localhost ~]# ansible all -m user -a 'name=test uid=2005 comment="mysql user" shell=/bin/bash home=/tmp/test state=present password=$6$9pWQQDwKpVpGuDbh$9sc0tdox0o1EFUaDhvO4KAof0nF0iVgllF7JVn3piVEaXcQ2ETagtATs/hIWZ/dpGHJs5RpqCz/kq90AOzvPp.'

group

创建修改删除用户组

name : 组名字
gid :组id
state :present/absent
[root@localhost ~]# ansible all -m group -a 'name=itgroup gid=2055 state=present '
 创建

文件下载模块

get_url模块

下载文件,等价于wget

url :下载的文件地址
dest :下载到哪个目录
url_username:如果需要验证账户密码,这里写账号
url_password:如果需要验证账户密码,这里写密码
mode:权限
owner:拥有人
group:拥有组


[root@localhost ~]# ansible all -m get_url -a 'url="https://rpmfind.net/linux/fedora/linux/updates/40/Everything/x86_64/Packages/h/httpd-2.4.62-2.fc40.x86_64.rpm" dest=/opt/ '

unarchive模块

解压缩模块

src :主控节点包名
dest :解压到被控节点的位置
mode :权限
owner:拥有人
group:拥有组
remote_src: 指定为yes后,src指的是被控节点用户的家目录,在被控节点的家目录下,解压缩到被控节点的dest
creates:指定一个文件,当文件存在,命令不执行
list_files:可以看到指定的文件
s 

解压缩

[root@localhost ~]# ansible all -m unarchive -a 'src=ansible.tar.gz dest=/opt/ '

remote_src

[root@localhost ~]# ansible ansible-1 -m unarchive -a ' src=/home/devops/ansible.tar.gz  dest=/tmp/  remote_src=yes '

使用绝对路径

同步文件模块

synchronize模块

等价于rsync命令 (默认是push,将主控推送到被控)

src:源路径
dest:目标路径
archive:归档,默认开启recursive(递归)、links、perms、times、owner、group、-D默认都为yes
compress:是否压缩
rsync_opts:rsync的选项,选项之间用逗号隔开(-a 归档,-v详细信息,-z压缩)
delete:删除不存在的文件,默认为no
mode :默认是push,主控推送到被控,此时src是主控,dest是被控
       手动修改为pull,被控拉取到主控,此时src是被控,dest是被控
       【注意目录后/,在此处仍然有特殊含义】
dest_port:rsync是通过ssh协议传输,如果默认端口不是22,则通过该选项修改


[root@localhost ~]# ansible ansible-1 -m synchronize -a 'src=/opt dest=/tmp'

[root@localhost ~]# ansible ansible-1 -m synchronize -a 'src=/tmp/opt dest=/tmp mode=pull  '
 mode=pull
[root@localhost ~]# ansible ansible-1 -m synchronize -a 'src=/opt dest=/tmp rsync_opts=-avz '
 rsync_opts

PlayBook

什么是playbook(剧本)?

playbook又叫做剧本,一系列的ansible模块的集合,利用yaml文件进行编写;

playbook里面是通过任务来实现,每一个任务就是调用一个模块;

执行顺序是从上到下执行

同时playbook中,支持更多特性,变量、jinjia2模板、循环、判断;

允许你抓取一个任务的返回状态作为变量给另外一个任务来调用

案例:配置软件仓库,安装软件包,生成网页文件,防火墙放行,服务启动

如果使用ad-hoc,至少要使用五条,但使用playbook,一个剧本即可。

playbook的调用命令是ansible-playbook去调用,同样也支持-k -K -u 等参数

😕在创建playbook时候,结尾用.yml或者.yaml都可以

后缀是给人看的,并无实际意义,但使用vim打开特定后缀文件,可以看到高亮显示

---  #换行
- name: First playbook  #剧本名称
  hosts: all            #作用于哪些主机
  tasks:                #剧本中的任务
    - name: create zhangsan  #任务名
      user:                  #调用模块及其参数
        name: zhangsan
        state: present
 playbook格式

img

yaml语言编写

yaml语言:非标记型语言

标记型语言:html

<html>
abc
</html>

有头有尾有标签

yaml语言有明显约束:

  1. 大小写严格区分 — 大写变量和小写变量不同
  2. 有严格的层级关系,使用缩进来表示
  3. 使用空格缩进,不可以用tab
  4. 支持注释,使用#号注释

yaml的数据类型

- name: First playbook
  hosts: all
  tasks:
    - name: create zhangsan
      user:
        name: zhangsan
        state: present
    - name: create file-example
      shell: touch example

-代表的是一个列表,也叫数组,每一个“-”都是一个数组

- name: First playbook
  hosts: all
  tasks:
  这就是个列表

纯量:不可再被分割的量

name:  First playbook 纯量例子

此处的First playbook就是纯量

对象【字典】:键值对的集合

name: First playbook
name是键
First playbook是值
一整个 “name: First playbook”叫对象

img

基本语法

编写一个yaml文件,配置软件仓库,安装软件包,生成网页文件,防火墙放行,服务启动

- name: web
  hosts: all
  tasks:
   - name: mount /dev/sr0
     shell: mount -a
   - name: yumrepository
     yum_repository:
      file: dvd
      name: BaseOS
      description: BaseOS
      baseurl: file:///mnt/cdrom/BaseOS
      enabled: yes
      gpgcheck: no
   - name: yumrepository
     yum_repository:
      file: dvd
      name: AppStream
      description: AppStream
      baseurl: file:///mnt/cdrom/AppStream
      enabled: yes
      gpgcheck: no
   - name: dnf makecache
     shell: dnf makecache
   - name: dnf intall httpd
     yum:
      name: httpd
      state: present
   - name: firewalld
     firewalld:
      service: http
      state: enabled
      permanent: yes
      immediate: yes
   - name: start httpd
     systemd:
      name: httpd
      state: started
   - name: touch /var/www/html/index.html
     copy:
      content: "hello redhat\n"
      dest: /var/www/html/index.html

ansible-playbook的选项

判断语法错误

- name: First playbook
  hosts: all
  tasks:
    - name: create zhangsanlishiwangwu
      user:
        name: zhangsan
        state: present
 正确版本
- name: First playbook
  hosts: all
  tasks:
    - name: create zhangsanlishiwangwu
      user:
        name: zhangsan
       state: present 错误版本

假设,我们有一段很长的yaml文件,我们并不能如同上述代码般,仅凭肉眼看出来,那么该如何判断?

[root@localhost ~]# ansible-playbook user.yml --help | grep syn
                        [--skip-tags SKIP_TAGS] [-C] [--syntax-check] [-D]
  --syntax-check        perform a syntax check on the playbook, but do not

😉使用--syntax-check

[root@localhost ~]# ansible-playbook user.yml --syntax-check
ERROR! Syntax Error while loading YAML.
  did not find expected key

The error appears to be in '/root/user.yml': line 7, column 8, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

        name: zhangsan
       state: present
       ^ here

模拟运行

只是模拟运行,不会真的产生文件和结果

[root@localhost ~]# ansible-playbook web.yaml -C

查看详细信息

-v 查看调用的是哪个配置文件

-vv 查看到python版本ansible版本

-vvv 查看到更多信息,不仅可以看到vv的,还可以看到ssh连接信息

但也到此为止,最高就三个v,哪怕使用更多的v,但也就查看到三个v

拓展

gather_facts: false 可以跳过当前剧本获取变量的步骤

- name: First playbook            
  hosts: all
  gather_facts: false
  tasks:
    - name: create zhangsan
      user:
        name: zhangsan
        state: present

Multiple plays

在一个剧本中,可以编写多个play,并且可以做到每一个play可以应用到不同的主机上

- name: First playbook            #第一个剧本
  hosts: all
  tasks:
    - name: create zhangsan
      user:
        name: zhangsan
        state: present

- name: Second playbook           #第二个剧本
  hosts: ansible-2
  tasks:
    - name: create lisi
      user:
        name: lisi
        state: present

- name: Third playbook            #第三个剧本
  hosts: ansible-3
  tasks:
    - name: create wangwu
      user:
        name: lisi
        state: present

playbook是由一个play或者多个play组成,每一个play的下都有一个tasks任务列表,任务列表下定义的task任务就是我们要执行的需求,而每一个task任务都是对一个模块的调用,不能调用多个模块。

因为playbook就是根据一定的逻辑顺序来编写一个完整的复杂的任务,这种方式称之为playbook的编排。

playbook的组成结构

主要有四大部分

1、Target section: 用于定义将要执行playbook的远程主机组及远程主机组上的用户,还包括定义通过什 么样的方式连接远程主机(默认ssh)

- name: web
  hosts: all
  remote_user: root
  become: yes
  become_method: sudo
 例子

😉注意,在playbook中编写的配置,会比在ansible.cfg的优先级更高

2、Variable section: 定义playbook运行时需要使用的变量 【剧本中定义的变量优先级更高】

vars:
   OS: Rehl9
   Name: zhangsan
 例子 可接在Target section下,比如上述代码become_method下

3、Task section: 定义将要在远程主机上执行的任务列表

tasks:
   - name: mount /dev/sr0
     shell: mount -a
   - name: yumrepository
     yum_repository:
      file: dvd
      name: BaseOS
      description: BaseOS
      baseurl: file:///mnt/cdrom/BaseOS
      enabled: yes
      gpgcheck: no
 ...以下省略
 例子

4、Handler section: 定义task执行完成以后需要调用的任务【只有触发条件满足,才会执行这个任务,在所有tasks任务完成后,才会执行handlers任务】

tasks任务列表

所有的task任务都是定义在tasks任务列表下:

  1. 任务列表在执行的时候,是根据编写顺序从上往下执行
  2. 如果有一个task任务执行失败,playbook停止执行
    • 如果任务执行失败,只有失败的主机会停止任务

ansible具有幂等性,如果任务列表中的task任务反复执行,一次执行和多次执行,结果都一致,也就是ansible具有期望值,任务执行的结果和期望值保持一致,则任务成功

🧐我们在执行tasks任务列表的时候,入股出现任务失败的情况,task任务会停止执行,那么如何解决?

  1. 如果调用的是命令执行模块,可以直接在后面加上ture命令,或者一个可以执行的命令

    - name: use module
      hosts: all
      tasks:
       - name: use shell
         shell: ls /avcd
       - name: use shell
         shell: ls /opt
    
    
    fatal: [ansible-3]: FAILED! => {"changed": true, "cmd": "ls /avcd", "delta": "0:00:00.002306", "end": "2024-08-10 16:50:04.804529", "msg": "non-zero return code",  "rc": 2 , "start": "2024-08-10 16:50:04.802223", "stderr": "ls: cannot access '/avcd': No such file or directory", "stderr_lines": ["ls: cannot access '/avcd': No such file or directory"], "stdout": "", "stdout_lines": []} 执行失败的代码-rc
    

    此处可以看到rc返回值不为0,所以代表执行失败,那么可以使用true来让其返回值为0

    - name: use shell
      shell: ls /avcd || true
     true
    

    因为调用的是shell模块,那么同样也会有命令的返回值—“rc”,如果返回值rc为0则执行成功,非0则执行失败。

  2. 如果调用的是其他的模块,可以通过关键字ignore_errors:yes来忽略错误

    这样可以正常执行后面的task任务

    - name: use module
      hosts: all
      tasks:
       - name: use file
         file:
           path: /etc/src
           state: present
          ignore_errors: yes 
       - name: use shell
         shell: ls /opt
    

handlers

handler通过notify来监听task任务的执行情况,他只会在tasks任务全部结束后才会执行

- name: use handlers
  hosts: all
  gather_facts: false
  tasks:
   - name: use shell
     shell: echo a
      notify: what_a 
   handlers: 
   -  name: what_a 
     file:
      path: /opt/abcd
      state: touch

这里handlers的位置要和tasks保持一致,因为他们的层级是一样的,并且notify监听的变量名要与handlers的name保持一致。

【只有当监听的模块返回值是changed时,才会执行,其他的返回值都不执行】

那什么情况下返回值是changed呢?而不是ok

当ansible的期望值与实际结果保持一致,返回值就为ok,因为ansible具有幂等性

🧐如果出现task任务执行失败的情况,那么handlers会不会执行?

不会执行

但我们可以通过关键字force_handlers: yes来强制执行 【前提是,handlers监听的任务会触发】

- name: use handlers
  hosts: all
  gather_facts: false
  force_handlers: yes
  tasks:
   - name: use shell
     shell: echo a
     notify: what_a
   - shell: ehco a
  handlers:
   - name: what_a
     file:
      path: /opt/abcd
      state: touch

变量

img

ansible的变量可以在被控节点的端口、主机不一致时,起到奇效。

变量的定义

  1. 变量名应该由字母、数字、下划线组成
  2. 变量名必须以字母开头
  3. ansbile内置的关键字不能作为变量名,最好不要定义ansible开头的变量,因为会和ansible的内置冲突

通过vars定义变量

只能在playbook中使用

引用变量

- name: use variable
  hosts: all
  vars:
   hostname: rehl9.example.com
   os: rehl9
  tasks:
    - copy:
       content:  "{{ hostname }} - {{ os }}"
       dest: /opt/abcd

这是在不使用debug模块的时候,如何引用模块

debug模块

msg:直接输出msg后面的内容[也可以调用vars变量]
var: 调用变量

msg

- name: use variable
  hosts: all
  vars:
   hostname: rehl9.example.com
   os: rehl9
  tasks:
    - debug:
       msg: hello redhat

img

- name: use variable
  hosts: all
  gather_facts: false
  vars:
   hostname: rehl9.example.com
   os: rehl9
  tasks:
    - debug:
       msg: "{{ hostname }}"
 调用变量

img

var

- name: use variable
  hosts: all
  gather_facts: false
  vars:
   hostname: rehl9.example.com
   os: rehl9
  tasks:
    - debug:
       var: '"hostname" "os"'

img

通过vars_files定义变量

vars_files的文件路径最好接上绝对路径,因为如果ansible-playbook命令执行位置里没有vars_files文件,那么将执行失败

引用普通变量

abc: 100 
 os.file 冒号“:”后面要空格,遵顼yaml文件格式
- name: use variable
  hosts: all
  gather_facts: false
  vars:
   hostname: rehl9.example.com
   os: rehl9
  vars_files:
    - /opt/os.file
  tasks:
    - debug:
       msg: "{{ abc }}"
~
 variable.yml

引用模块格式变量

必须是键值对的形式,所以不用加“-”

user:
 name: zhangsan
 uid: 8000
 os.file
- name: use variable
  hosts: all
  gather_facts: false
  vars:
   hostname: rehl9.example.com
   os: rehl9
  vars_files:
    - /opt/os.file
  tasks:
    - debug:
       msg: "{{ user }}"
 variable.yml

img

如果我只想输出name怎么办?

- name: use variable
  hosts: all
  gather_facts: false
  vars:
   hostname: rehl9.example.com
   os: rehl9
  vars_files:
    - /opt/os.file
  tasks:
    - debug:
       msg: "{{ user.name }}"

img

通过主机清单定义变量

当你没有在/etc/hosts配置解析的时候,可以在/etc/ansible/hosts配置变量

ansible-1 ansible_host=192.168.96.202 

[root@localhost ~]# ansible ansible-1 -m ping
The authenticity of host '192.168.96.202 (192.168.96.202)' can't be established.
ED25519 key fingerprint is SHA256:TsJwb/IYanzD8Yba1wcsI2Yk1ZIg/38StQYcRKhYrgo.
This host key is known by the following other names/addresses:
    ~/.ssh/known_hosts:1: ansible-1
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "ping": "pong"
}


同样这也不止ansbile_host一个变量名

# 一般连接
ansible_host #用于指定被管理的主机的真实IP
ansible_port #用于指定连接到被管理主机的ssh端口号,默认是22
ansible_user #ssh连接时默认使用的用户名
# 特权升级
ansible_become #相当于ansible_sudo或者ansible_su,允许强制特权升级
ansible_become_user #通过特权升级到的用户,相当于ansible_sudo_user或者ansible_su_user
ansible_become_pass # 提升特权时,如果需要密码的话,可以通过该变量指定,相当于ansible_sudo_pass
或者ansible_su_pass
ansible_sudo_exec #如果sudo命令不在默认路径,需要指定sudo命令路径
# 特定ssh连接
ansible_connection #SSH连接的类型:local, ssh, paramiko,默认是ssh【可以接管本机,并且不论用户】
ansible_ssh_pass #ssh连接时的密码,明文,【ansible_ssh_pass=redhat】在指定密码后,不管当前用户原本密码是什么,都统一用redhat作为登录密码
ansible_ssh_private_key_file #秘钥文件路径,如果不想使用ssh-agent管理秘钥文件时可以使用此选项
ansible_ssh_executable #如果ssh指令不在默认路径当中,可以使用该变量来定义其路径


这些以ansible开头的变量,就是ansible的内置变量,调用的就是ansbile.cfg配置文件中的参数。

controller ansible_host=192.168.96.200  ansible_connection=local
 ansible_connection接管本机

给主机定义变量

ansible-1 ansible_host=192.168.96.202 os=rehl9

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ os }}"

img

因为只给ansible-1定义了变量,所以会发现,只有ansible-1执行成功,其他全部失败

给主机组定义变量

[webserver]
ansible-1
ansible-2
ansible-3

[webserver:vars]
web=hello

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ web }}"


img

如图所示,在webserver主机组内的用户都拥有了这个变量

🧐当主机和主机组定义了同一个变量名,该怎么办

ansible-1 ansible_host=192.168.96.202  os=rehl9 主机变量
[webserver:vars]
web=hello
 os=redhat 主机组变量
- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ os }}"

img

会发现,定义了主机变量的ansible-1输出rehl9,而没有主机变量的ansible-2输出redhat

  • 结论,当主机变量和主机组变量重名时,主机变量优先级更高

通过host_vars和group_vars定义变量

host_vars

img

[root@localhost host_vars]# pwd
/etc/ansible/host_vars
[root@localhost host_vars]# touch ansible-1
[root@localhost host_vars]# cat ansible-1
server: httpd

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ server }}"

img

group_vars

img

[root@localhost group_vars]# pwd
/etc/ansible/group_vars
[root@localhost group_vars]# touch webserver
[root@localhost group_vars]# cat webserver
nginx: hello-world

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ nginx }}"


img

注册变量

可以将某个任务任务的执行结果保存下来,便于在接下来的任务中调用或者做些判断。

可以通过register关键字来实现对某一个任务结果保存为一个变量。

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - name: touch file
      file:
        path: /opt/file1
        state: touch
      register: get_file
    - debug:
       msg: "{{ get_file }}"

正常情况下,我们只看的到changed和ok,如果使用了注册变量,那么就可以看到更多信息

img

注册变量还用很多拓展玩法

与when结合

当我们使用命令模块的时候,是有rc返回值的

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - name: touch file
      shell: ls /etc/ssh/ssh_host_ecdsa_key.pub
      register: get_file
    - debug:
       msg: "{{ get_file }}"
    - file:
       path: /opt/file2
       state: touch
      when: get_file.rc == 0

img

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - name: touch file
      shell: cat  /etc/ssh/ssh_host_ecdsa_key.pub
      register: get_file
    - debug:
       msg: "{{ get_file }}"
    - copy:
       content: "{{ get_file.stdout }}"
       dest: /opt/pubkey

可以通过变量.stdout的方式来输出结果

同理,也可以使用这种方式收集日志信息

通过命令行定义变量(使用较少)

[root@localhost ~]# ansible all -m debug -a 'var=a' -e "a=1000"

facts变量

facts变量会收集被控节点所有的主机信息,包括但不限于主机名、网卡设备名、IP地址、cpu、内存、操作系统版本、bios版本等等,facts变量专门来收集主机的主机信息,可以通过模块setup开启,默认在执行playbook的时候就收集facts变量,所有变量的值都会保存到ansible_facts变量中。

通过setup收集主机信息

[root@localhost ~]# ansible ansible-1 -m setup 可以通过这条命令可以查看ansible-1的详细信息
为了方便查看,可以通过重定向到文件中
[root@localhost ~]# ansible ansible-1 -m setup > /opt/ansible-1
或者使用
[root@localhost ~]# ansible ansible-1 -m setup --tree /opt/ansible-1
【只不过都是一行,不方便查看】


ansible-1 | SUCCESS => {
     "ansible_facts" : {                  所有变量都位于ansible_facts变量下 
        "ansible_all_ipv4_addresses":  [ 
            "192.168.96.202"            []中括号表示是个列表 
         ] ,
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fe45:d088"
        ],
        "ansible_apparmor":  { 
            "status": "disabled"        {}大括号表示是对象 
         } ,
        "ansible_architecture": "x86_64",
        "ansible_bios_date": "11/12/2020",
        "ansible_bios_version": "6.00",
        "ansible_cmdline": {
            "BOOT_IMAGE": "(hd0,msdos1)/vmlinuz-5.14.0-70.13.1.el9_0.x86_64",
            "quiet": true,
            "resume": "UUID=5252899e-2986-411c-85a8-6d7951f22bc1",
            "rhgb": true,
            "ro": true,
            "root": "UUID=f24d04c5-4f06-4477-9e6f-ec69adbd99ad"
        }, 解析

通过filter过滤主机信息

[root@localhost ~]# ansible ansible-1 -m setup -a 'filter=ansible_hostname'
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "ansible_hostname": "ansible-1",
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}

✨注意,filter过滤时,不能使用ansible_facts,因为他默认过滤的是ansible_facts的下一层级

"ansible_selinux": {
            "config_mode": "enforcing",
            "mode": "enforcing",
            "policyvers": 33,
            "status": "enabled",
            "type": "targeted"
        },

以上是主机信息的一部分,如果我想看到ansible_selinux中的mode信息,可以吗?

[root@localhost ~]# ansible ansible-1 -m setup -a 'filter=ansible_selinux.mode'
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}

虽然有输出结果,但根本就没有值,所以filter只能看到ansible_facts的下一层级

通过通配符收集信息

[root@localhost ~]# ansible ansible-1 -m setup -a 'filter=ansible*'

引用facts变量

- name: use variable
  hosts: all
  tasks:
    - debug:
       msg: "{{ ansible_hostname }}"

如果我想引用的是ansible_ens160的ip地址怎么办?

[root@localhost ~]# ansible ansible-1 -m setup -a 'filter=ansible_ens160'
ansible-1 | SUCCESS => {
    "ansible_facts": {
        "ansible_ens160": {
            "ipv4": {
                "address": "192.168.96.202",
                "broadcast": "192.168.96.255",
                "netmask": "255.255.255.0",
                "network": "192.168.96.0"
            },
            "ipv6": [
                {
                    "address": "fe80::20c:29ff:fe45:d088",
                    "prefix": "64",
                    "scope": "link"
                }
            ],

会发现,如果正常使用filter肯定是不能引用的,所以我们可以使用debug

- name: use variable
  hosts: all
  tasks:
    - debug:
       msg: "{{ ansible_ens160.ipv4.address }}"

禁用facts变量

使用gather_facts

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - debug:
       msg: "{{ ansible_ens160.ipv4.address }}"

img

虽然通过这种方法,我们可以禁用facts变量,但我们也可以通过别的方法将他打开

- name: use variable
  hosts: all
  gather_facts: false
  tasks:
    - setup:
    - debug:
       msg: "{{ ansible_ens160.ipv4.address }}"
 setup模块

使用setup模块仍然可以获取facts变量

自定义facts变量

每一个主机都有自己的facts变量,在 【被控节点的】主机的/etc/ansible/facts.d目录下创建变量文件,这个变量文件的后缀必须是.fact结尾

配置文件格式一般为ini或者是yaml格式

ini就是REHL9配置网卡的格式

[root@localhost ~]# cat /etc/NetworkManager/system-connections/ens160.nmconnection
[connection]
id=ens160
uuid=42e71f79-cede-36dd-940b-eaa023d6ccef
type=ethernet
autoconnect-priority=-999
interface-name=ens160
timestamp=1721978658

[ethernet]

[ipv4]
address1=192.168.96.200/24,192.168.96.2
dns=192.168.96.2;
method=manual

[ipv6]
addr-gen-mode=eui64
method=auto

[proxy]

自定义的facts变量都保存在ansible_loacl变量下

- name: use fact
  hosts: all
  tasks:
    - file:
       path: /etc/ansible/facts.d
       state: directory
    - copy:
       src: /root/facts.fact
       dest: /etc/ansible/facts.d
    - debug:
       msg: "{{ ansible_local.facts  }}"

如果想要输出变量的时候,一定要格式正确,ansible_local.文件名(不加.fact)

自定义文件名.fact

[root@localhost ~]# cat /etc/ansible/facts.d/facts.fact
[name]
name=zhangsan
age=30

不仅可以通过playbook查看,也可以通过ad-hoc查看(前提是文件存在)

[root@localhost ~]# ansible all -m setup -a 'filter=ansible_local'
controller | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "facts": {
                "name": {
                    "age": "30",
                    "name": "zhangsan"
                }
            }
        },
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false
}

由于filter只能过滤到下一层级,所以要查看详细的,只能使用playbook

- name: use fact
  hosts: all
  tasks:
    - debug:
       msg: "{{ ansible_local.facts.name.age }}"
 如果执行失败,排查文件名和变量名

set_fact模块

整合多个变量为一个变量

- name: use fact
  hosts: all
  tasks:
   - set_fact:
      os: "{{ ansible_ens160.ipv4.address  }} - {{ ansible_hostname }}"
   - debug:
      msg: "{{ os }}"

os变量的名字可以自定义,不一定非要是os

使用lookup手动生成变量

在ansible中有诸多变量,比如我们需要从某个文件或者命令的输出结果的时候,我们就可以使用lookup插件直接从这些数据源中读取参数传递给ansible变量。

在一些特殊情况下,需要从外部引用变量,比如读取本地公钥作为变量,然后放到被控节点下;比如创建用户的时候需要指定密码,可以利用命令的执行结果作为密码的值等等。

查看lookup插件支持的数据源

[root@localhost ~]# ansible-doc -t lookup -l
etcd                  get info from an etcd server
file                  read file contents
fileglob              list files matching a pattern
filetree              recursively match all files in a directory tree
first_found           return first file found from list
flattened             return single list completely flattened
gcp_storage_file      Return GC Storage content
grafana_dashboard     list or search grafana dashboards
hashi_vault           retrieve secrets from HashiCorp's vault
hiera                 get info from hiera data
indexed_items         rewrites lists to return 'indexed items'
ini                   read data from a ini file
inventory_hostnames   list of inventory hosts matching a host pattern
items                 list of items
k8s                   Query the K8s API

- name: use fact
  hosts: all
  tasks:
   - set_fact:
      user_passwd: "{{ lookup('file','/etc/passwd') }}"
   - debug:
      msg: "{{ user_passwd }}"

使用lookup插件,然后通过file数据源,读取了/etc/passwd中的数据,作为user_passwd变量的值,并通过debug输出

- name: use fact
  hosts: all
  tasks:
   - set_fact:
      user_passwd: "{{ lookup('file','/etc/passwd') }}"
   - set_fact:
      user_env: "{{ lookup('env','HOME') }}"
   - set_fact:
      user_openssl: "{{ lookup('pipe','openssl passwd -6 redhat') }}"
   - debug:
      msg: "{{ user_passwd }} - {{ user_env }} - {{ user_openssl }}"

env:环境变量数据源

pipe:命令数据源

实际应用

- name: use fact
  hosts: all
  tasks:
   - set_fact:
      user_passwd: "{{ lookup('file','/etc/passwd') }}"
   - set_fact:
      user_env: "{{ lookup('env','HOME') }}"
   - set_fact:
      user_openssl: "{{ lookup('pipe','openssl passwd -6 redhat') }}"
   - debug:
      msg: "{{ user_passwd }} - {{ user_env }} - {{ user_openssl }}"
    - user:
      name: zhaoliu
      password: "{{ lookup('pipe','openssl passwd -6 redhat')  }}"
  - set_fact:
      user_passwd: "{{ lookup('file','/etc/passwd') }}"
      user_env: "{{ lookup('env','HOME') }}"
      user_openssl: "{{ lookup('pipe','openssl passwd -6 redhat') }}"

这也可以省略一部分

ansible魔法变量

ansible提供一些内置变量提供一些特定功能,而这,我们称之为魔法变量

hostvars : 获取指定主机的facts变量信息
inventory_hostname :列出当前正在执行任务的主机
groups:列出主机清单中的所有主机

hostvars

使用时,必须保证facts变量开启

- name: use magic
  hosts: ansible-1,ansible-2,ansible-3
  tasks:
   - debug:
      msg: "{{ hostvars[ 'ansible-1' ].ansible_hostname }}" 注意这里的hostvars['ansible-1']获取的是主机清单中的主机名,如果在主机清单中定义的是主机ip,则此处也要改成ip

不管你是哪台主机,最后的结果都是输出ansible-1的主机名

inventory_hostname

- name: use magic
  hosts: ansible-1,ansible-2,ansible-3
  tasks:
   - debug:
      msg: "{{ inventory_hostname }}"

一般可以结合when使用

- name: use magic
  hosts: ansible-1,ansible-2,ansible-3
  tasks:
   - debug:
      msg: "{{ inventory_hostname }}"
     when: inventory_hostname == 'ansible-1'

img

当变量==ansible-1的时候,才执行,否则跳过

groups

- name: use magic
  hosts: ansible-1,ansible-2,ansible-3
  tasks:
   - debug:
      msg: "{{ groups }}"

img

如果不想列出主机组,则使用groups.all

- name: use magic
  hosts: ansible-1,ansible-2,ansible-3
  tasks:
   - debug:
      msg: "{{ groups.all }}"

这个all可以参考上图

魔法变量无法通过ansible手动查看,只能通过官方文档查看 —> docs.ansible.com

img

ansible的when条件判断

如果要给节点安装软件,如果磁盘空间不够了,还能安装吗?

所以我们要在安装之前,进行一个判断,来衡量剩余空间是否可以安装软件,所以,我们就要使用when判断 (类似于shell中的if)

比较运算符

== : 比较两边是否一致
>= <= :比较两边大小
>  < : 比较两边大小
!= 比较两边对象是否不一致
- name: use when
  hosts: all
  tasks:
    - shell: ls /etc/passwd
      when: ansible_hostname == 'ansible-1'

如果不想使用变量,而是想使用字符串

- name: use when
  hosts: all
  tasks:
    - shell: ls /etc/passwd
      when: "'ansible-1'== 'ansible-1' "

也可以定义vars

- name: use when
  hosts: all
  vars:
    hostname: ansible-1
  tasks:
    - shell: ls /etc/passwd
      when: hostname == "ansible-1"

例子

  1. 如果主机名是ansible-1,则执行任务
  2. 如果主机内存大于300m ,则执行任务
  3. 如果主机名不是ansible-1,则执行任务
- name: use when
  hosts: all
  tasks:
    - shell: ls /etc/passwd
      when: ansible_hostname == 'ansible-1' 1
- name: use when
  hosts: all
  vars:
    hostname: ansible-1
  tasks:
    - shell: ls /etc/passwd
      when: ansible_memfree_mb > 300
 2
- name: use when
  hosts: all
  tasks:
    - shell: ls /etc/passwd
      when: ansible_hostname != 'ansible-1' 3

when判断,默认识别后面的变量,所以不需要加“{{ }}”如果使用字符串,最好使用双引号和单引号都引起来。

逻辑运算符

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

例子

1、当主机名是ansible-1的时候,并且ens160网卡的IP地址是192.168.96.202

- name: use when
  hosts: ansible-1,ansible-2
  tasks:
    - file:
       name: /opt/file123
       state: touch
      when: ansible_hostname == "ansible-1" and ansible_ens160.ipv4.address == "192.168.96.202"
 逻辑与

2、当主机名是ansible-1的时候,并且ens160网卡的IP地址是192.168.96.203

- name: use when
  hosts: ansible-1,ansible-2
  tasks:
    - file:
       name: /opt/file123
       state: touch
      when: ansible_hostname == "ansible-1" or ansible_ens160.ipv4.address == "192.168.96.203"
 逻辑或

3、主机名是ansible-1,并且操作系统是RedHat 或者主机的ens160网卡ip地址是192.168.96.203并且主机的空闲内存大于300M

- name: use when
  hosts: ansible-1,ansible-2
  tasks:
    - file:
       name: /opt/file321
       state: touch
      when:  ( ansible_hostname == "ansible-1" and ansible_distribution == "RedHat" ) or ( ansible_ens160.ipv4.address == "192.168.96.203" and ansible_memfree_mb  组合表达

4、当主机名不是ansible-1的时候,其他主机可以执行

- name: use when
  hosts: ansible-1,ansible-2
  tasks:
    - file:
       name: /opt/file321
       state: touch
      when: not ansible_hostname == "ansible-1"
 取反

借助register注册变量来进行when判断

根据rc返回值来判断task任务是否执行成功

- name: use register and when
  hosts: all
  tasks:
   - shell: ls /etc/passwd
     register: get_status
   - debug:
      msg: everything is ok
     when: get_status.rc == 0

通过判断是否是一个文件、目录、挂载点、链接文件或者是否存在一个文件/目录

在shell中可以通过 "-f" "-d"来判断

但在ansible中有别的判断方式

file : 文件
directory : 目录
mount : 挂载点
link : 链接文件
exists : 是否存在


filepath作为变量名,when可以直接识别

- name: use when
  hosts: all
  vars:
    filepath: /etc/passwd
  tasks:
    - debug:
       msg: file is ok
      when: filepath is file
    - debug:
       msg: directory is ok
      when: filepath is directory
    - debug:
       msg: exists is ok
      when: filepath is exists

当你不用变量,使用字符串的时候,可以使用双引号加单引号的方式写

- debug:
   msg: exists is ok
   when: "'/etc/passwd' is exists "

判断变量

defined: 判断变量是否定义,如果定义了,那么就为真 【有变量有值】
undefined: 判断变量是否未定义,如果没定义,那么就为真 【无变量无值】
none: 判断变量是否为空,如果为空,则为真 【有变量不赋值】
- name: use when
  hosts: all
  gather_facts: false
  vars:
     node1: dns
    node2: 
  tasks:
    - debug:
       msg: node1
      when:  node1 is defined 
    - debug:
       msg: node2
      when:  node2 is none 
    - debug:
       msg: node3
      when:  node3 is undefined 


判断执行变量结果

ok:目标状态与期望值一致,没有发生变更
change或changed:目标发生变更,与期望值一样
sucess或succeeded:目标状态与期望值一致,或者任务执行成功
failure或failed:任务执行失败
skip或skipped:任务被跳过
- name: use when
  hosts: all
  gather_facts: true
  tasks:
   - shell: ls /etc/passwd
     register: get_first
   - debug:
      msg: first ok
     when: get_first is change
   - yum:
      name: httpd
      state: present
     register: get_second
   - debug:
      msg: second ok
     when: get_second is success
   - shell: ls /etc/passwd
     register: get_third
     when: ansible_hostname == "ansible-1"
   - debug:
      msg: third ok
     when: get_third is skip
   - shell: ls /opt/aaaa
     register: get_fouth
     ignore_errors: yes
   - debug:
      msg: fouth ok
     when: get_fouth is failed

判断字符串是大写还是小写

lower:判断字符串中的所有字母是否都是小写,是则为真
upper:判断字符串中的所有字母是否都是大写,是则为真
- name: use when
  hosts: all
  gather_facts: false
  vars:
   os: rehl
  tasks:
   - shell: ls /etc/passwd
     when: os is upper
   - shell: ls /etc/passwd
     when: os is lower

判断整除

even:判断数值是否为偶数,是则为真
odd:判断数值是否为奇数,是则为真
divisibleby(num):判断是否可以整除指定的数值,是则为真
- name: use number
  hosts: all
  gather_facts: false
  vars:
   os: 9
  tasks:
   - shell: ls /etc/passwd
     when: os is even
   - shell: ls /etc/passwd
     when: os is odd
   - shell: ls /etc/passwd
     when: os is divisibleby(2)

其他判断

subset 判断一个list是不是另一个list的子集:when: a is subset(b)
superset 判断一个list是不是另一个list的父集:when: b is superset(a)
in 判断一个字符串是否存在于另一个字符串中,也可用于判断某个特定的值是否存在于列表中
  supported_distros:
  - RedHat
  - CentOS
  when: ansible_distribution in supported_distros
string 判断对象是否为一个字符串,是则为真 when: var1 is string
number 判断对象是否为一个数字,是则为真 when: var3 is number
- name: use list
  hosts: all
  gather_facts: false
  vars:
   user:
    - zhangsan
    - lisi
   user2:
    - zhangsan
  tasks:
   - shell: echo hello
     when: user2 is subset(user)   #user2列表是不是user列表的子集,如果是,则执行shell
   - shell: echo hello
     when: user is superset(user2)  #user列表是不是user2列表的父级,如果是,则执行
 subset和superset

✨in必须记住

- name: use list
  hosts: all
  gather_facts:  true
  tasks:
   - shell: echo a
     when: "'nvme0n1' in ansible_devices "
 in
- name: use list
  hosts: all
  gather_facts:  false
  vars:
   user: zhangsan
  tasks:
    - shell: echo a
      when:  user is string
    - shell: echo a
      when: user is number
 string和number

block块

在某些情况下,我们要对多个task任务进行一个when判断条件

- name: use list
  hosts: all
  gather_facts:  true
  vars:
   user: zhangsan
  tasks:
     - shell: echo a
      when:  ansible_hostname == "ansible-1"
    - shell: echo a
      when: ansible_hostname == "ansible-1" 
     - name: use block
      block:
       - shell: echo a
       - file:
          path: /opt/aaaaa
          state: touch
      when: ansible_hostname == "ansible-1" 
      使用此方法,更为简洁一些

rescue错误处理

- name: use list
  hosts: all
  gather_facts:  true
  vars:
   user: zhangsan
  tasks:
    - name: use block
      block:
       - shell: echo a
       - file:
          path: /opt/aaaaa     
           state: path    此处有错误,所以触发了rescue 
      rescue:
       - debug:
          msg: hello
      when: ansible_hostname == "ansible-1"




TASK [file] *****************************************************************************************************************************************************
skipping: [controller]
skipping: [ansible-2]
skipping: [ansible-3]
fatal: [ansible-1]: FAILED! => {"changed": false, "msg": "value of state must be one of: absent, directory, file, hard, link, touch, got: path"}

TASK [debug] ****************************************************************************************************************************************************
ok: [ansible-1] => {
    "msg": "hello"
}

always

不管block中的task任务是否执行成功,我都要执行always的任务

- name: use list
  hosts: all
  gather_facts:  true
  vars:
   user: zhangsan
  tasks:
    - name: use block
      block:
       - shell: echo a
       - file:
          path: /opt/aaaaa
           state: path 
      rescue:
       - debug:
          msg: hello
      always:
       - debug:
          msg: hello
      when: ansible_hostname == "ansible-1"

虽然state处有错误,但我always仍然执行了

fail模块

- name: use fail
  hosts: all
  gather_facts:  false
  tasks:
    - shell: ls /opt/passwda
      ignore_errors: yes
      register: get_status
    - name: use fail
      fail:
       msg: /opt/passwda is not exists
      when: get_status.rc != 0
    - debug:
       msg: hello

在当前代码中,因为有注册变量和fail模块的原因,所以,可以在输出结果的同时对playbook截停

还可以使用failed_when

- name: use fail
  hosts: all
  gather_facts:  false
  tasks:
    - shell: ls /opt/passwda
      ignore_errors: yes
      register: get_status
    - debug:
       msg: /opt/passwda is not exists
      failed_when: get_status.rc != 0
    - debug:
       msg: hello

循环

with_items循环列表

1、 使用vars定义

- name: use fail
  hosts: all
  gather_facts:  false
  vars:
    pkgs:
     - httpd
     - nginx
     - nfs-utils
  tasks:
   - yum:
      name: "{{ item }}"
      state: present
     with_items: "{{ pkgs }}"
 通过列表批量安装

✨item是固定的

2、直接编写列表

- name: use fail
  hosts: all
  gather_facts:  false
  tasks:
   - yum:
      name: "{{ item }}"
      state: present
     with_items:
      - httpd
      - nginx
      - autofs

3、行内对象

- name: use fail
  hosts: all
  gather_facts:  false
  tasks:
   - yum:
      name: "{{ item }}"
      state: present
     with_items: ['httpd', 'vsftpd','autofs']

with_dict循环字典

- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   user:
    username01: zhangsan
    username02: lisi
  tasks:
   - debug:
      msg:  "{{ item }}"
     with_dict:  "{{ user }}"

也可以只输出key或者value

msg:  "{{ item.key }}"
msg:  "{{ item.value }}"

loop循环

是在ansible2.6版本的时候开始的,所以不需要with_x,loop循环默认支持列表循环,但不支持字典循环,除非使用jinjia2的过滤器,才可以循环字典

- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   pkgs:
    - httpd
    - nginx
    - mariadb
  tasks:
   - debug:
      msg:  "{{ item }}"
     loop: "{{ pkgs }}"
 循环列表1
- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   pkgs:
  tasks:
   - debug:
      msg:  "{{ item }}"
     loop:
      - httpd
      - vsftpd
 循环列表2

如果想要过滤字典,可以使用这种方式,使用dict2item进行转换

- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   user:
    username01: zhangsan
    username02: lisi
  tasks:
   - debug:
      msg:  "{{ item }}"
      loop: "{{ user|dict2items }}"

dict2items是jinjia2的过滤器,常见的过滤器有以下

1、dict2items : 将字典转换成列表

2、 default : 默认值,当变量不存在的时候,给一个默认的值

- name: use fail
  hosts: all
  gather_facts:  false
  tasks:
   - debug:
      msg:  "{{ hello|default('world') }}"

当前没有hello这个变量,也没有他的值,所以使用default给予一个默认的值,但如果变量存在,则没用

3、passwd_hash : 将字符串进行加密【括号内的加密算法要加上sha】

- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   pawd: redhat
  tasks:
   - debug:
       msg:  "{{ pawd|password_hash('sha512') }}"

文件管理模块

  1. lineinfile : 修改文件的单行内容
  2. blockinfile: 修改文件的多行内容

lineinfile

path:要修改的文件
regexp:正则表达式匹配要修改的行
line:修改或者插入行的内容
insertbefore:在匹配的行前面插入
insertafter:在匹配的行后面插入
backup:是否备份文件
backrefs:如果匹配字符不存在的时候,默认添加至文件尾部,默认为false,如果为yes,则不添加
create:文件不存在则创建文件
validate:验证文件修改的有效性,需要文件自带验证机制 参数
- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      regexp: "^Listen 80"
      line: "Listen 82"
 path、regexp、line
- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      insertbefore: "^Listen 82"
      line: "I am a boy"
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      insertafter: "^Listen 82"
      line: "hello world"
 insertbefore、insertafter

注意: 这两个不能写在同一个task任务里面,如果没有匹配到关键字,默认添加到末尾

- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      regexp: "^hello world"
      state: absent
 删除添加的行
- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      backup: yes
      regexp: "^I am a boy"
      state: absent

[root@localhost ~]# ls /etc/httpd/conf/
httpd.conf  httpd.conf.179348.2024-09-01@16:03:09~  magic

 备份
- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      regexp: "^I am a boy"
      line: "aaa"
      backrefs: false         #如果为false,则添加;如果yes,则不添加
 backrefs添加
ansible中是没有校验模块的,他是用的服务本身的模块,比如httpd服务
- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      regexp: "^Listen 82"
      line: "Listen 80"
      validate: httpd -tf %s
 validate校验

当前校验逻辑是,在校验成功后,运行task任务,如果校验失败,则不运行

注意: httpd服务本身自带的就有-t 和-f选项,-t是校验,-f是指定路径 , %s是路径

比如,如果你是httpd服务,使用-t校验

如果你是nginx服务,使用-t校验

追加部分

  1. 如果只写了path和line,那么直接追加内容
  2. 如果匹配到了多行内容,只会修改最后一行匹配到的内容
  3. 如果删除匹配的行,则会删除所有匹配的行

blockinfile

与lineinfile使用方法差距不大,支持的参数几乎都有,区别在于支持block插入的文本内容,marker去标记

- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - blockinfile:
      path: /etc/httpd/conf/httpd.conf
      block: |
        hello
        redhat

如果不做任何操作,默认就是添加,这个添加添加在行尾,并会显示如下标识

# BEGIN ANSIBLE MANAGED BLOCK
hello
redhat
# END ANSIBLE MANAGED BLOCK

使用marker标识

- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - blockinfile:
      path: /etc/httpd/conf/httpd.conf
      marker: "#good  {mark}  hello"
      block: |
        rehl9

此处的{mark}就是下文中的begin和end,可以理解为,他为我们想要追加的内容增添了行首和行尾

#good BEGIN hello
rehl9
#good END hello


在blockinfile中如何删除呢?

- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - blockinfile:
      path: /etc/httpd/conf/httpd.conf
      marker: "#good {mark} hello"
      state: absent

marker会根据自己所定下的标识符,将其中内容删除

如果删除的时候,匹配到了多个mark标识,则都会进行删除。

结合使用

使用lineinfile和blockinfile删除多余的内容

- name: use lineinfile
  hosts: all
  gather_facts: false
  tasks:
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      line: "aaaa"
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      line: "dwad awd"
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      insertbefore: "^aaaa"
      line: "# BEGIN ANSIBLE MANAGED BLOCK"
   - lineinfile:
      path: /etc/httpd/conf/httpd.conf
      insertbefore: "^d*"
      line: "# END ANSIBLE MANAGED BLOCK"
   - blockinfile:
      path: /etc/httpd/conf/httpd.conf
      marker: "# {mark} ANSIBLE MANAGED BLOCK"
      state: absent

jinja2模块管理

修改配置文件的时候,都只是修改较小的地方,大部分配置不会去修改,而且每个主机修改的配置文件都是同一个地方

jinja2版本修改

解决cannot import name 'environmentfilter' from 'jinja2.filters' 问题

pip uninstall jinja2
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple jinja2==2.10.1
pip uninstall markupsafe
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple markupsafe==1.1.1


如果需要ansible-1机器的httpd文件监听到他自身ens160的网卡ip上
需要ansible-2机器的httpd文件监听到他自身ens160的网卡ip上
需要ansible-3机器的httpd文件监听到他自身ens160的网卡ip上

考虑到将模板复制,所以在配置文件使用变量
Listen {{ ansible_ens160.ipv4.address }}:80
那么如何将配置文件发送到被控主机呢?
如果使用copy,那么将不能识别变量,而是直接将变量识别为字符串复制粘贴过去。
所以我们在拷贝模板的时候,会使用一个新的模块

 在使用模板文件的时候,一定不要关闭facts变量!!!!!! 
 因为模板文件依赖于facts变量 

 案例

template

传输模板文件

使用方法与copy类似,copy怎么用,template就怎么用
- name: use jinja
  hosts: all
  tasks:
   - template:
      src: /etc/httpd/conf/httpd.conf
      dest: /etc/httpd/conf/httpd.conf


支持if判断

如果主机名是ansible-1,那么配置文件的内容是ansible-1
如果主机名是ansible-2,那么配置文件的内容是ansible-2
如果主机名是ansible-3,那么配置文件的内容是ansible-3


格式:
{% if 表达式 %}
执行语句
{% elif 表达式%}
执行语句
{% else %}
执行语句
{% endif %}
{% if ansible_hostname == "ansible-1" %}
ansible-1
{% elif ansible_hostname == "ansible-2" %}
ansible-2
{% else %}
ansible-3
{% endif %}

如果判断成功,则会打印执行语句在文件中

for循环

在ansible-1主机上的/home/devops/hosts文件中,保存ansible-1,ansible-2,ansible-3的主机和ip对应关系

格式:
{% for 变量 in 循环的对象 %}
执行语句
{% endfor %}

{% for abc in groups.all  %}
{{ ansible_ens160.ipv4.address }} {{ ansible_hostname }}
{% endfor %}
如果使用当前代码,会出现一个问题,每台主机获取的是自己主机的主机名和ip,并且重复主机台数的次数,所以要引用hostvars魔法变量


jinja过滤器

dict2items:将字典转换成列表
default:默认值,当变量不存在的时候,给一个默认的值
passwd_hash:将字符串进行加密【括号内的加密算法要加上sha】
upper:将所有字符串转换为大写
lower:将所有字符串转换为小写
capitalize:将字符串的首字母大写,其他字母小写
reverse:将字符串倒序排列
first:返回字符串的第一个字符
last:返回字符串的最后一个字符
trim:将字符串开头和结尾的空格去掉
center(30):将字符串放在中间,并且字符串两边用空格补齐30位
length:返回字符串的长度,与count等价
list:将字符串转换为列表
shuffle:list将字符串转换为列表,但是顺序排列,shuffle同样将字符串转换为列表,但是会随机打乱字符
串顺序 字符串相关过滤器
- hosts: test
  gather_facts: no
  vars:
   teststr: "abc123ABV"
   teststr1: " abc "
   teststr2: "123456789"
   teststr3: "sfacb1335@#$%"
  tasks:
  - debug:
     msg: "{{ teststr | upper }}"
  - debug:
     msg: "{{ teststr | lower }}"
  - debug:
     msg: "{{ teststr | capitalize }}"
  - debug:
     msg: "{{ teststr | reverse }}"
  - debug:
     msg: "{{ teststr|first }}"
  - debug:
     msg: "{{ teststr|last }}"
int: 将对应的值转换为整数
float:将对应的值转换为浮点数
abs:获取绝对值
round:小数点四舍五入
random:从一个给定的范围中获取随机值 数字相关过滤器
- hosts: test
  gather_facts: no
  vars:
   testnum: -1
  tasks:
   - debug:
       msg: "{{ 8+('8'|int) }}"
   - debug:
# 默认情况下,如果无法完成数字转换则返回0
# 这里指定如果无法完成数字转换则返回6
       msg: "{{ 'a'|int(default=6) }}"
   - debug:
       msg: "{{ '8'|float }}"
   - debug:
       msg: "{{ 'a'|float(8.88)' }}"
   - debug:
       msg: "{{ testnum|abs }}"
   - debug:
       msg: "{{ 12.5|round }}"
   - debug:
       msg: "{{ 3.1415926|round(5) }}"
   - debug:
# 从0到100中随机返回一个数字
       msg: "{{ 100|random }}"
   - debug:
# 从5到10中随机返回一个数字
       msg: "{{ 10|random(start=5) }}"
   - debug:
# 从4到15中随机返回一个数字,步长为3
# 返回的随机数只可能是:4,7,10,13中的一个
       msg: "{{ 15|random(start=4,step=3) }}"
   - debug:
# 从0到15随机返回一个数字,步长为4
       msg: "{{ 15|random(step=4) }}"
length: 返回列表长度
first:返回列表的第一个值
last:返回列表的最后一个值
min:返回列表中最小的值
max:返回列表中最大的值
sort:重新排列列表,默认为升序排列,sort(reverse=true)为降序
sum:返回纯数字非嵌套列表中所有数字的和
flatten:如果列表中包含列表,则flatten可拉平嵌套的列表,levels参数可用于指定被拉平的层级
join:将列表中的元素合并为一个字符串
random:从列表中随机返回一个元素
union:将两个列表合并,如果元素有重复,则只留下一个
intersect:获取两个列表的交集
difference:获取存在于第一个列表中,但不存在于第二个列表中的元素
symmetric_difference:取出两个列表中各自独立的元素,如果重复则只留一个 列表过滤器
- hosts: test
  gather_facts: false
  vars:
   testlist1: [1,2,4,6,3,5]
   testlist2: [1,[2,3,4,[5,6]]]
   testlist3: [1,2,'a','b']
   testlist4: [1,'A','b',['C','d'],'Efg']
   testlist5: ['abc',1,2,'a',3,2,'1','abc']
   testlist6: ['abc',3,'1','b','a']
  tasks:
   - debug:
       msg: "{{ testlist1 | length }}"
   - debug:
       msg: "{{ testlist1 |first }}"
   - debug:
       msg: "{{ testlist1 | last }}"
   - debug:
       msg: "{{ testlist1 | min }}"
   - debug:
       msg: "{{ testlist1 | max }}"
   - debug:
       msg: "{{ testlist1 | sort }}"
   - debug:
       msg: "{{ testlist1 | sort(reverse=true) }}"
   - debug:
       msg: "{{ testlist2 | flatten | max }}"
   - debug:
       msg: "{{ testlist4 | upper }}"
   - debug:
       msg: "{{ testlist4 | lower }}"
   - debug:
       msg: "{{ testlist5 | union(testlist6) }}"
   - debug:
       msg: "{{ testlist5 | intersect(testlist6) }}"
   - debug:
       msg: "{{ testlist5 | difference(testlist6) }}"

roles角色管理

ansible的roles就是将playbook的内容进行拆分出来,把每一个结构都分为了不同的文件和目录,task任务的文件、变量文件、handlers触发器的文件、template模板文件放到不同的目录下,以前是独权,现在是多权分立

target section:执行的主机,远程操控的用户等等

变量:定义的变量

tasks任务列表

handlers触发器

因为一个playbook如果直接移植复制给其他人使用,那么,如果playbook中有使用vars_files引用外部的变量文件,或者类似于copy template这种模块,要拷贝主控节点的文件到被控,其他主机没有这些文件,执行这个playbook就会失败。

所以这个时候,使用role去管理,所有的task任务、引用的普通文件、变量文件都在同一个目录下。

所以role解决的最大的问题就是playbook的移植性问题,让你的playbook可以在任何的ansible主控节点执行

ansible2.9之前的版本使用的是role角色—>tasks任务列表、变量文件、handlers触发器等等

ansible2.9之后的版本能使用的集合(collections)包含role角色、module模块、plugin插件

ansible-core是不完整的ansible、模块很少;比如没有firewalld模块、lvol模块

必须要通过安装collections也就是集合来得到模块

访问网站:galaxy.ansible.com

role角色结构

roles角色的结构
files:用于存放一些非模板文件的文件,如https证书等。
tempaltes:用于存放角色相关的Jinja2模板文件,当使用角色相关的模板时,
如未明确指定模板路径,则默认使用此目录中的模板
tasks:角色所要执行的所有任务文件都存放于此,包含一个主文件main.yml,
可以在主文件中通过include的方式引入其他任务文件
handlers:用于定义角色中需要调用 的handlers,包含一个主配置文件main.yml,
可通过include引入其他的handlers文件。
vars:用于定义此角色用到的变量,包含一个主文件main.yml
meta:用于存储角色的元数据信息,这些元数据用于描述角色的相关属性,
包括作者,角色的主要作用,角色的依赖关系等。默认这些信息会写入到当前目录下的main.yml文件中
defaults:除了vars目录,defaults目录也用于定义此角色用到的变量,
与vars不同的是,defaults中定义的变量的优先级最低。

默认路径:/etc/ansible/roles 通过修改ansible.cfg配置文件可以指定角色路径【roles_path = /etc/ansible/roles】

角色的名字就是目录的名字,通过ansible-galaxy init role-name 可以初始化角色目录结构,生成目录和文件

[root@rhel9 roles]# ansible-galaxy init apahce
- Role apache was created successfully
[root@rhel9 roles]# ls 
apache  
[root@controller roles]# ls apache/
defaults  handlers  README.md  templates  vars
files     meta      tasks      tests

include_tasks的应用

include_tasks则专门用于包含tasks,基本方法如下:
[root@ansible nginx]# tree tasks/
tasks/
├── main.yml
├── nginx1.yml
└── nginx2.yml
#当任务较多时,可以将任务分开写,通过include_tasks引入
[root@ansible tasks]# cat main.yml
- include_tasks:
   file: nginx1.yml
- include_tasks:
   file: nginx2.yml
   当然也可以使用列表来写
   - include_tasks:
          file: 
             - 1
             - 2 
             - 3
             

在playbook中调用roles

[root@rhel9 ansible]# cat roles.yml
- hosts: all
  roles:
    - apache

pre_tasks和post_tasks

如 果 在 执 行 一 个 role 时 , 需 要 在 其 前 或 其 后 依 然 要 执 行 某 些 任 务 , 我 们 可 以 使 用 pre_tasks 及 post_tasks来声明。pre_tasks是在role之前执行,而post_tasks则在role之后执行:

- name: use pro
  host: all
  roles:
   - apache
  pre_tasks:
    - debug:
       msg: a
  post_tasks:
    - debug:
       msg: a

不论pre_tasks和post_tasks位置在哪都是先执行pre_tasks再执行roles,最后再执行post_tasks

Ansible Galaxy

在系统当中,是有角色的概念的,如果你使用的是iso镜像,那么在系统就有一个包装的是红帽自带的管理的角色

rhel-system-roles.noarch 包名

完成安装后,通过rpm -ql查看位置并且查看

[root@localhost ~]# ls /usr/share/ansible/roles/
linux-system-roles.certificate      linux-system-roles.nbde_server  rhel-system-roles.certificate      rhel-system-roles.nbde_server
linux-system-roles.cockpit          linux-system-roles.network      rhel-system-roles.cockpit          rhel-system-roles.network
linux-system-roles.crypto_policies  linux-system-roles.postfix      rhel-system-roles.crypto_policies  rhel-system-roles.postfix
linux-system-roles.firewall         linux-system-roles.selinux      rhel-system-roles.firewall         rhel-system-roles.selinux
linux-system-roles.ha_cluster       linux-system-roles.ssh          rhel-system-roles.ha_cluster       rhel-system-roles.ssh
linux-system-roles.kdump            linux-system-roles.sshd         rhel-system-roles.kdump            rhel-system-roles.sshd
linux-system-roles.kernel_settings  linux-system-roles.storage      rhel-system-roles.kernel_settings  rhel-system-roles.storage
linux-system-roles.logging          linux-system-roles.timesync     rhel-system-roles.logging          rhel-system-roles.timesync
linux-system-roles.metrics          linux-system-roles.tlog         rhel-system-roles.metrics          rhel-system-roles.tlog
linux-system-roles.nbde_client      linux-system-roles.vpn          rhel-system-roles.nbde_client      rhel-system-roles.vpn

可以查看角色的EXAMPLE

[root@localhost rhel-system-roles.cockpit]# ls
ansible_pytest_extra_requirements.txt  handlers  molecule_extra_requirements.txt  pytest_extra_requirements.txt  tasks      tox.ini
custom_requirements.txt                LICENSE   pylint_extra_requirements.txt    README.html                    templates  vars
defaults                               meta      pylintrc                          README.md                       tests

在这个README.md下

如何使用呢?

[root@localhost roles]# cp -a rhel-system-roles.timesync /etc/ansible/roles/timesync
[root@localhost roles]# cd /etc/ansible/roles/
[root@localhost roles]# ls timesync/
ansible_pytest_extra_requirements.txt  defaults  meta                             pylintrc                       README.md  tests
COPYING                                handlers  molecule_extra_requirements.txt  pytest_extra_requirements.txt  tasks      tox.ini
custom_requirements.txt                library   pylint_extra_requirements.txt    README.html                    templates  vars

在README.md中可以看到如何使用

Example Playbook
----------------

Install and configure ntp to synchronize the system clock with three NTP servers:

 ```yaml 
- hosts: targets
  vars:
    timesync_ntp_servers:
      - hostname: foo.example.com
        iburst: yes
      - hostname: bar.example.com
        iburst: yes
      - hostname: baz.example.com
        iburst: yes
  roles:
    - rhel-system-roles.timesync
 ``` EXAMPLE

可以看到在这个EXAMPLE中有'''yaml作为提示,


- hosts: all
  vars:
    timesync_ntp_servers:
      - hostname: ntp.tuna.tsinghua.edu.cn
        iburst: yes
  roles:
    - timesync
 在roles中根据自身需要修改

大致如此,但如果出现其他的服务或者文件问题,另外查询

命令操作

初始化一名角色

[root@localhost ~]# ansible-galaxy init nginx
- Role nginx was created successfully

他会在当前目录下创建角色名的目录
[root@localhost ~]# ls nginx/
defaults  files  handlers  meta  README.md  tasks  templates  tests  vars


查询当前拥有的角色

前提是角色都要放在/etc/ansible/roles/下
[root@localhost ~]# ansible-galaxy role list
# /etc/ansible/roles
- timesync, (unknown version)
[root@localhost ~]# mv nginx/ /etc/ansible/roles/
[root@localhost ~]# ansible-galaxy role list
# /etc/ansible/roles
- timesync, (unknown version)
- nginx, (unknown version)



从网络上安装角色

img

img

[root@localhost ~]# ansible-galaxy role install vijayrajuyj1.docker

执行即可,如有报错,另行处理

本地包安装

[root@localhost ~]# ansible-galaxy role install -r url -p 安装后的路径
[devops@workstation ansible]$ vim roles/requirements.yml
- name: balancer
 src: http://classroom.example.com/content/haproxy.tar.gz
- name: phpinfo
 src: http://classroom.example.com/content/phpinfo.tar.gz
  
[devops@workstation ansible]$ ansible-galaxy install -r roles/requirements.yml -
p roles/ url

从这里看到url就是提前编写的yml文件,可以通过yml文件中的地址下载包,并决定安装位置

查询角色

[root@localhost ~]# ansible-galaxy role search nginx

卸载角色

[root@localhost ~]# ansible-galaxy role remove nginx
- successfully removed nginx

ansible-vault配置加密

    create              创建新的 Vault 加密文件
    decrypt             解密 Vault 加密文件
    edit                编辑 Vault 加密文件
    view                查看 Vault 加密文件
    encrypt             加密 YAML 文件
    encrypt_string      加密字符串
    rekey               重新生成 Vault 加密文件的密钥

创建加密文件

[root@localhost ~]# ansible-vault create role.yml
New Vault password:
Confirm New Vault password:
 create

在输入密码后,会直接进入编辑

加密文件

[root@localhost ~]# ansible-vault encrypt other.yml
New Vault password:
Confirm New Vault password:
Encryption successful
 encrypt

查看加密文件

[root@localhost ~]# ansible-vault view other.yml
Vault password:
- name: use fail
  hosts: all
  gather_facts:  false
  vars:
   pawd: redhat
  tasks:
   - debug:
      msg:  "{{ pawd|password_hash('sha512') }}"
 view

编辑加密文件

[root@localhost ~]# ansible-vault edit other.yml
Vault password:
 edit

解密文件

[root@localhost ~]# ansible-vault decrypt other.yml
Vault password:
Decryption successful
 decrypt

结合使用

如果我当前有一个roles.yml的文件,他调用了一个os变量文件,但是os变量文件被valut加密了,那么该怎么办?

- name: use
  hosts: all
  gather_facts: false
  vars_files:
    - /opt/aaa
  tasks:
    - debug:
       msg: "{{ os }}"
os: rehl9

但加密后会报错

[root@localhost ~]# ansible-vault encrypt /opt/aaa
New Vault password:
Confirm New Vault password:
Encryption successful
[root@localhost ~]# ansible-playbook roles.yml
ERROR! Attempting to decrypt but no vault secrets found

所以我们要查看帮助

[root@localhost ~]# ansible-playbook --help | grep pas
                        [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES]
   --ask-vault-pass       ask for vault password
   --vault-password-file  VAULT_PASSWORD_FILES
 

解密的使用

[root@localhost ~]# ansible-playbook roles.yml --ask-vault-pass
Vault password:

PLAY [use] *********************************************************************************************************************************

TASK [debug] *******************************************************************************************************************************
ok: [ansible-1] => {
    "msg": "rehl9"
}
ok: [ansible-2] => {
    "msg": "rehl9"
}
ok: [ansible-3] => {
    "msg": "rehl9"
}

PLAY RECAP *********************************************************************************************************************************
ansible-1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-2                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-3                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

 --ask-vault-pass 

也可以编辑一个存储密码的文件

[root@localhost ~]# echo a > a.passwd
[root@localhost ~]# ansible-playbook roles.yml --vault-password-file a.passwd

PLAY [use] *********************************************************************************************************************************

TASK [debug] *******************************************************************************************************************************
ok: [ansible-1] => {
    "msg": "rehl9"
}
ok: [ansible-2] => {
    "msg": "rehl9"
}
ok: [ansible-3] => {
    "msg": "rehl9"
}

PLAY RECAP *********************************************************************************************************************************
ansible-1                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-2                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
ansible-3                  : ok=1    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
 --vault-password-file

ansible的导航器

通过容器来运行,必须要安装导航器来获取容器镜像

挂载iso镜像

img

[root@localhost ~]# lsblk
NAME        MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
sr0          11:0    1    8G  0 rom  /mnt/cdrom
sr1          11:1    1  1.6G  0 rom
nvme0n1     259:0    0  100G  0 disk
├─nvme0n1p1 259:1    0  500M  0 part /boot
├─nvme0n1p2 259:2    0    4G  0 part [SWAP]
└─nvme0n1p3 259:3    0 95.5G  0 part /
[root@localhost ~]# mkdir /mnt/ansible
[root@localhost ~]# mount /dev/sr1 /mnt/ansible/
mount: /mnt/ansible: WARNING: source write-protected, mounted read-only.

编写yum文件

[ansible]
name=ansible
baseurl=file:///mnt/ansible
enabled=1
gpgcheck=0

安装导航器

[root@localhost ~]# dnf install ansible-navigator

使用镜像内已经打包好的容器镜像rhaap22.tar

[root@localhost ~]# podman load < /mnt/ansible/rhaap22.tar

到此已经可以使用

使用导航器查询模块

[root@localhost ~]# ansible-navigator doc -l
amazon.aws.aws_caller_info                                     Get information about the user and account being used to make AWS calls
amazon.aws.aws_s3                                              manage objects in S3

 amazon.aws. aws_s3 前者是模块的集合,后者是模块的名字 


查询模块的用法

[root@localhost ~]# ansible-navigator doc user

查询主机

使用导航器运行剧本

当你每次使用导航器的时候,都会在你运行的目录下创建一个日志文件ansible-navigator.log

运行ansible导航器的时候,有两种方式,一种是可视化模式,一种是字符模式

[root@localhost ~]# ansible-navigator run -m stdout when.yml
这种方式跟ansible-playbook when.yml 的执行过程无区别 字符
[root@localhost ~]# ansible-navigator run  when.yml 可视化

存放集合的位置

源码包2.9版本,看不到collections位置

在ansible-core上可以看到

[root@ansible-1 ~]# ansible --version
ansible [core 2.12.2]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /root/.ansible/collections:/usr/share/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.10 (main, Feb  9 2022, 00:00:00) [GCC 11.2.1 20220127 (Red Hat 11.2.1-9)]
  jinja version = 2.11.3
  libyaml = True
[root@ansible-1 ~]# vim /etc/ansible/ansible.cfg
[root@ansible-1 ~]# mkdir /etc/ansible/collections
[root@ansible-1 ~]# ansible --version
ansible [core 2.12.2]
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/root/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3.9/site-packages/ansible
  ansible collection location = /etc/ansible/collections
  executable location = /usr/bin/ansible
  python version = 3.9.10 (main, Feb  9 2022, 00:00:00) [GCC 11.2.1 20220127 (Red Hat 11.2.1-9)]
  jinja version = 2.11.3
  libyaml = True
[root@ansible-1 ~]#

[defaults]
collections_path = /etc/ansible/collections

安装集合

img

[root@localhost ~]# ansible-galaxy collection install community.general

下载完成后,会在以下目录显示,不同的集合会有所不同

[root@ansible-1 modules]# pwd
/etc/ansible/collections/ansible_collections/linode/cloud/docs/modules

posted @   super派大星  阅读(69)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示