ithuang
ithuang
发布于 2026-03-22 / 7 阅读
0

企业级自动化配置工具之 Ansible 进阶

企业级自动化配置工具之 Ansible 进阶

  1. Ansible 模块(重点)

  2. copy 模块(重点)

作用:copy 模块用于对文件的远程拷贝操作(如把本地的文件拷贝到远程的机器上)

本机:nginx.conf,copy 模块:分发到所有 web 节点

https://docs.ansible.com/ansible/latest/modules/copy_module.html#copy-module

在master上准备一个文件,拷贝此文件到web_servers的所有机器上

[root@ansible ~]# echo master > /tmp/file1
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file1 dest=/tmp/file2'
[root@ansible ~]# ansible web_servers -m shell -a 'rm -rf /tmp/*'
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload__92t3xsq
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_lzf3kgbv
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file1 dest=/tmp/file2'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "30f79bec32243c31dd91a05c0ad7b80f1e301aea",
    "dest": "/tmp/file2",
    "gid": 0,
    "group": "root",
    "md5sum": "c963080767f45828c31f83ca5cd25d36",
    "mode": "0644",
    "owner": "root",
    "size": 7,
    "src": "/root/.ansible/tmp/ansible-tmp-1765778516.7883563-2181-99767450792824/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "30f79bec32243c31dd91a05c0ad7b80f1e301aea",
    "dest": "/tmp/file2",
    "gid": 0,
    "group": "root",
    "md5sum": "c963080767f45828c31f83ca5cd25d36",
    "mode": "0644",
    "owner": "root",
    "size": 7,
    "src": "/root/.ansible/tmp/ansible-tmp-1765778516.7813063-2180-260613010036873/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'                     192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_z38yt0xq
file2
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_cowvnsvh
file2
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file2'
192.168.88.102 | CHANGED | rc=0 >>
master
192.168.88.101 | CHANGED | rc=0 >>
master
[root@ansible ~]#

[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file1 dest=/tmp/file1'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "30f79bec32243c31dd91a05c0ad7b80f1e301aea",
    "dest": "/tmp/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "c963080767f45828c31f83ca5cd25d36",
    "mode": "0644",
    "owner": "root",
    "size": 7,
    "src": "/root/.ansible/tmp/ansible-tmp-1765936865.6896715-2121-244780768227156/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "30f79bec32243c31dd91a05c0ad7b80f1e301aea",
    "dest": "/tmp/file1",
    "gid": 0,
    "group": "root",
    "md5sum": "c963080767f45828c31f83ca5cd25d36",
    "mode": "0644",
    "owner": "root",
    "size": 7,
    "src": "/root/.ansible/tmp/ansible-tmp-1765936865.6735682-2120-240347660106036/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_u_2hxqfb
file1
file2
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_ti8byy2w
file1
file2
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file1'              192.168.88.102 | CHANGED | rc=0 >>
master
192.168.88.101 | CHANGED | rc=0 >>
master
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file2'
192.168.88.102 | CHANGED | rc=0 >>
master
192.168.88.101 | CHANGED | rc=0 >>
master
[root@ansible ~]#

使用content参数直接往远程文件里写内容(会覆盖原内容)

[root@ansible ~]# ansible web_servers -m copy -a 'content="ansible\n" dest=/tmp/file3'
[root@ansible ~]# ansible web_servers -m copy -a 'content="ansible\n" dest=/tmp/file3'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "e63dddd358e9693e876f838bf17984a6680a5a0f",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "d32760b1ceec24f0b67e97665d36ac22",
    "mode": "0644",
    "owner": "root",
    "size": 8,
    "src": "/root/.ansible/tmp/ansible-tmp-1765936996.6910822-2243-158968649374909/source",
    "state": "file",
    "uid": 0
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "e63dddd358e9693e876f838bf17984a6680a5a0f",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "d32760b1ceec24f0b67e97665d36ac22",
    "mode": "0644",
    "owner": "root",
    "size": 8,
    "src": "/root/.ansible/tmp/ansible-tmp-1765936996.704181-2244-190013357554854/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'                     
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_fhps0atn
file1
file2
file3
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_7t7geit1
file1
file2
file3
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'              
192.168.88.102 | CHANGED | rc=0 >>
ansible
192.168.88.101 | CHANGED | rc=0 >>
ansible
[root@ansible ~]# ansible web_servers -m copy -a 'content="ansible666\n" dest=/tmp/file3'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "8593085d803a6b0ac9a35e359b8aa58722ed2ed5",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "25aee9bcad947a24be87e0d58c44be1b",
    "mode": "0644",
    "owner": "root",
    "size": 11,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937060.5718365-2345-8938944157835/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "8593085d803a6b0ac9a35e359b8aa58722ed2ed5",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "25aee9bcad947a24be87e0d58c44be1b",
    "mode": "0644",
    "owner": "root",
    "size": 11,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937060.547449-2344-235935175908043/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'              
192.168.88.101 | CHANGED | rc=0 >>
ansible666
192.168.88.102 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]#

注意:ansible中-a后面的参数里也有引号时,记得要单引双引号交叉使用,如果都为单引或者都为双引号会出现问题(某些命令解释异常,比如\n)

[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"                         
192.168.88.101 | CHANGED | rc=0 >>
ansible666
192.168.88.102 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]# ansible web_servers -m copy -a "content="ansible666\n" dest=/tmp/file3"
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "2aad15d0f58a88d52cd85968c8533cb2814da54d",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "bc7902c2bf250febe98bb3ae53d28a8b",
    "mode": "0644",
    "owner": "root",
    "size": 11,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937372.875417-2582-1024982031259                                                   8/source",
    "state": "file",
    "uid": 0
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "2aad15d0f58a88d52cd85968c8533cb2814da54d",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "bc7902c2bf250febe98bb3ae53d28a8b",
    "mode": "0644",
    "owner": "root",
    "size": 11,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937372.8892102-2583-721623581201                                                   08/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"                      
192.168.88.102 | CHANGED | rc=0 >>
ansible666n
192.168.88.101 | CHANGED | rc=0 >>
ansible666n
[root@ansible ~]# ansible web_servers -m copy -a "content="ansible666\\n" dest=/tmp/file3"
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "6b81ae9daf4d2d217283ed288743089634df98e5",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "3045cdec6845de60da11ac96528aa843",
    "mode": "0644",
    "owner": "root",
    "size": 10,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937399.8731997-2651-828095268810                                                   55/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "6b81ae9daf4d2d217283ed288743089634df98e5",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "3045cdec6845de60da11ac96528aa843",
    "mode": "0644",
    "owner": "root",
    "size": 10,
    "src": "/root/.ansible/tmp/ansible-tmp-1765937399.8653953-2650-154228977848                                                   469/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"                          
192.168.88.102 | CHANGED | rc=0 >>
ansible666
192.168.88.101 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]#

[root@ansible ~]# ansible web_servers -m copy -a 'content="ansible2026\n666" dest=/tmp/file3'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "0494e420c02410b0f5f2cc8c4284efc0ab142030",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "6c17c8d2b36487be4651092f5816c1bf",
    "mode": "0644",
    "owner": "root",
    "size": 15,
    "src": "/root/.ansible/tmp/ansible-tmp-1774664815.5691023-41606-102711114999891/source",
    "state": "file",
    "uid": 0
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "0494e420c02410b0f5f2cc8c4284efc0ab142030",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "6c17c8d2b36487be4651092f5816c1bf",
    "mode": "0644",
    "owner": "root",
    "size": 15,
    "src": "/root/.ansible/tmp/ansible-tmp-1774664815.570645-41607-63306198829675/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"             192.168.88.101 | CHANGED | rc=0 >>
ansible2026
666
192.168.88.102 | CHANGED | rc=0 >>
ansible2026
666
[root@ansible ~]#

使用force参数控制是否强制覆盖

force=no 不强制覆盖

[root@ansible ~]# touch /tmp/file2
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=no'
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.102 | CHANGED | rc=0 >>
ansible666
192.168.88.101 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]# touch /tmp/file2
[root@ansible ~]# cat /tmp/file2
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=no'
192.168.88.101 | SUCCESS => {
    "changed": false,
    "dest": "/tmp/file3",
    "src": "/tmp/file2"
}
192.168.88.102 | SUCCESS => {
    "changed": false,
    "dest": "/tmp/file3",
    "src": "/tmp/file2"
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.101 | CHANGED | rc=0 >>
ansible666
192.168.88.102 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]#

force=yes 强制覆盖

[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=yes'
[root@ansible ~]# cat /tmp/file2
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.101 | CHANGED | rc=0 >>
ansible666
192.168.88.102 | CHANGED | rc=0 >>
ansible666
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=yes'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0644",
    "owner": "root",
    "size": 0,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939144.7616148-2878-254315028743422/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0644",
    "owner": "root",
    "size": 0,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939144.744204-2877-225213197264625/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"              192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]# echo 'ansible test 2025/12/17' > /tmp/file2
[root@ansible ~]# cat /tmp/file2
ansible test 2025/12/17
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=yes'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "74c84f0a2652aa399a45279be420de0ca504705c",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "10982e15d6aa7881025fa06ffb610395",
    "mode": "0644",
    "owner": "root",
    "size": 24,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939227.20817-2950-226759330357460/source",
    "state": "file",
    "uid": 0
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "74c84f0a2652aa399a45279be420de0ca504705c",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "10982e15d6aa7881025fa06ffb610395",
    "mode": "0644",
    "owner": "root",
    "size": 24,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939227.2543223-2951-208237732301617/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.102 | CHANGED | rc=0 >>
ansible test 2025/12/17
192.168.88.101 | CHANGED | rc=0 >>
ansible test 2025/12/17
[root@ansible ~]#

[root@ansible ~]# echo 'ansible test 2026/3/28' > /tmp/file2
[root@ansible ~]# cat /tmp/file2
ansible test 2026/3/28
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=yes'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "78b742de3fcf7de6dac7de219ef57ee5725cad23",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "f5e7eb6f16d924369dd3524c0d9af515",
    "mode": "0644",
    "owner": "root",
    "size": 23,
    "src": "/root/.ansible/tmp/ansible-tmp-1774664525.3986037-39667-194113540286984/source",
    "state": "file",
    "uid": 0
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "78b742de3fcf7de6dac7de219ef57ee5725cad23",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "f5e7eb6f16d924369dd3524c0d9af515",
    "mode": "0644",
    "owner": "root",
    "size": 23,
    "src": "/root/.ansible/tmp/ansible-tmp-1774664525.4116302-39668-44581116485775/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.102 | CHANGED | rc=0 >>
ansible test 2026/3/28
192.168.88.101 | CHANGED | rc=0 >>
ansible test 2026/3/28
[root@ansible ~]# echo 'ansible test 2026/5/27' > /tmp/file2
[root@ansible ~]# cat /tmp/file2
ansible test 2026/5/27
[root@ansible ~]# ansible web_servers -m copy -a 'src=/tmp/file2 dest=/tmp/file3 force=yes'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "9f690186b7b21cd1f9a6d8eac8700aac0f7c1c77",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "254c41689bb2575c2ee69e69fddcd7da",
    "mode": "0644",
    "owner": "root",
    "size": 23,
    "src": "/root/.ansible/tmp/ansible-tmp-1779871598.600954-182741-131720317429735/source",
    "state": "file",
    "uid": 0
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "9f690186b7b21cd1f9a6d8eac8700aac0f7c1c77",
    "dest": "/tmp/file3",
    "gid": 0,
    "group": "root",
    "md5sum": "254c41689bb2575c2ee69e69fddcd7da",
    "mode": "0644",
    "owner": "root",
    "size": 23,
    "src": "/root/.ansible/tmp/ansible-tmp-1779871598.6025429-182736-79401831525352/source",
    "state": "file",
    "uid": 0
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'
192.168.88.102 | CHANGED | rc=0 >>
ansible test 2026/5/27
192.168.88.101 | CHANGED | rc=0 >>
ansible test 2026/5/27
[root@ansible ~]#

使用backup参数控制是否备份文件

backup=yes表示如果拷贝的文件内容与原内容不一样,则会备份远程主机的那一份

web_servers的机器上会将/tmp/file3备份一份(备份文件命名加上时间),再远程拷贝新的文件为/tmp/file3

[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/fstab dest=/tmp/file3 backup=yes owner=daemon group=daemon mode=0777'

简单理解:就是对目标文件进行备份操作

[root@ansible ~]# ansible web_servers -m shell -a "ls /tmp/"
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_2o_3z_5b
file1
file2
file3
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_m8_j9o1_
file1
file2
file3
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"
192.168.88.102 | CHANGED | rc=0 >>
ansible test 2025/12/17
192.168.88.101 | CHANGED | rc=0 >>
ansible test 2025/12/17
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/fstab dest=/tmp/file3 backup=yes owner=daemon group=daemon mode=0777'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup_file": "/tmp/file3.8672.2025-12-17@10:43:26~",
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939404.569215-3087-216707197790173/source",
    "state": "file",
    "uid": 2
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup_file": "/tmp/file3.8808.2025-12-17@10:43:26~",
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765939404.5592773-3086-137925502942822/source",
    "state": "file",
    "uid": 2
}
[root@ansible ~]# ansible web_servers -m shell -a "ls /tmp/"                    
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_5lt03vnp
file1
file2
file3
file3.8808.2025-12-17@10:43:26~
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_b8ft31h7
file1
file2
file3
file3.8672.2025-12-17@10:43:26~
[root@ansible ~]# ansible web_servers -m shell -a "cat /tmp/file3"              
192.168.88.101 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
192.168.88.102 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
[root@ansible ~]#

[root@node1 ~]# cd /tmp
[root@node1 tmp]# ls
file1  file2  file3  file3.8808.2025-12-17@10:43:26~
[root@node1 tmp]# cat file3.8808.2025-12-17@10:43:26~
ansible test 2025/12/17
[root@node1 tmp]#

[root@node2 ~]# cd /tmp
[root@node2 tmp]# ls
file1  file2  file3  file3.8672.2025-12-17@10:43:26~
[root@node2 tmp]# cat file3.8672.2025-12-17@10:43:26~
ansible test 2025/12/17
[root@node2 tmp]#

批量模糊查询(拓展)

ansible web_servers -m shell -a 'cat /tmp/file3.*~'
ansible web_servers -m shell -a 'cat /tmp/*~'

img

img

img

img

[root@ansible ~]# ansible web_servers -m shell -a 'rm -rf /tmp/*'               
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/fstab dest=/tmp/file3 backup=yes owner=daemon group=daemon mode=0777'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765940358.5519505-4380-235295417125320/source",
    "state": "file",
    "uid": 2
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765940358.5345984-4379-88961430705659/source",
    "state": "file",
    "uid": 2
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_p_hy3bss
file3
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_jdgmq_c0
file3
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'              
192.168.88.101 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
192.168.88.102 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/fstab dest=/tmp/file3 backup=yes owner=daemon group=daemon mode=0777'
192.168.88.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "mode": "0777",
    "owner": "daemon",
    "path": "/tmp/file3",
    "size": 579,
    "state": "file",
    "uid": 2
}
192.168.88.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "mode": "0777",
    "owner": "daemon",
    "path": "/tmp/file3",
    "size": 579,
    "state": "file",
    "uid": 2
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'                    
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_1a3ht2_z
file3
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_r5bdsa5z
file3
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'              
192.168.88.102 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
192.168.88.101 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
[root@ansible ~]#

[root@ansible ~]# ansible web_servers -m shell -a 'echo 666 > /tmp/file3'
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'
192.168.88.101 | CHANGED | rc=0 >>
666
192.168.88.102 | CHANGED | rc=0 >>
666
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/fstab dest=/tmp/file3 backup=yes owner=daemon group=daemon mode=0777'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup_file": "/tmp/file3.17190.2025-12-17@11:00:31~",
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765940429.7163641-4618-244688894682545/source",
    "state": "file",
    "uid": 2
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "backup_file": "/tmp/file3.17053.2025-12-17@11:00:31~",
    "changed": true,
    "checksum": "bfe63da727a85b28ef2c5f93bd175427c787b2aa",
    "dest": "/tmp/file3",
    "gid": 2,
    "group": "daemon",
    "md5sum": "79ae00f1355484d220d16bba1abba2a2",
    "mode": "0777",
    "owner": "daemon",
    "size": 579,
    "src": "/root/.ansible/tmp/ansible-tmp-1765940429.7300248-4619-251367261972175/source",
    "state": "file",
    "uid": 2
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'                    
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_29tivkxa
file3
file3.17190.2025-12-17@11:00:31~
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_qus_p57r
file3
file3.17053.2025-12-17@11:00:31~
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3'              
192.168.88.102 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
192.168.88.101 | CHANGED | rc=0 >>

#
# /etc/fstab
# Created by anaconda on Fri Jul 18 07:48:14 2025
#
# Accessible filesystems, by reference, are maintained under '/dev/disk/'.
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info.
#
# After editing this file, run 'systemctl daemon-reload' to update systemd
# units generated from this file.
#
/dev/mapper/cs-root     /                       xfs     defaults        0 0
UUID=ba9da24b-c8b7-4a6a-b779-b4d63373d3d2 /boot                   xfs     defaults        0 0
/dev/mapper/cs-swap     none                    swap    defaults        0 0
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.17190.2025-12-17@11:00:31~'
192.168.88.102 | FAILED | rc=1 >>
cat: '/tmp/file3.17190.2025-12-17@11:00:31~': No such file or directorynon-zero return code
192.168.88.101 | CHANGED | rc=0 >>
666
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.17053.2025-12-17@11:00:31~'
192.168.88.101 | FAILED | rc=1 >>
cat: '/tmp/file3.17053.2025-12-17@11:00:31~': No such file or directorynon-zero return code
192.168.88.102 | CHANGED | rc=0 >>
666
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.*~'           
192.168.88.101 | CHANGED | rc=0 >>
666
192.168.88.102 | CHANGED | rc=0 >>
666
[root@ansible ~]#

[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.*~'
192.168.88.102 | CHANGED | rc=0 >>
ansible2026
666
192.168.88.101 | CHANGED | rc=0 >>
ansible2026
666
[root@ansible ~]# ansible web_servers -m shell -a 'ls -l /tmp'                 192.168.88.101 | CHANGED | rc=0 >>
total 16
drwx------ 2 root   root    56 Mar 28 10:57 ansible_ansible.legacy.command_payload_h9o_thzb
-rw-r--r-- 1 root   root     7 Mar 28 10:11 file1
-rw-r--r-- 1 root   root     7 Mar 28 10:10 file2
-rwxrwxrwx 1 daemon daemon 579 Mar 28 10:53 file3
-rw-r--r-- 1 root   root    15 Mar 28 10:50 file3.41567.2026-03-28@10:53:41~
192.168.88.102 | CHANGED | rc=0 >>
total 16
drwx------ 2 root   root    56 Mar 28 10:57 ansible_ansible.legacy.command_payload_4wyh7mx3
-rw-r--r-- 1 root   root     7 Mar 28 10:11 file1
-rw-r--r-- 1 root   root     7 Mar 28 10:10 file2
-rwxrwxrwx 1 daemon daemon 579 Mar 28 10:53 file3
-rw-r--r-- 1 root   root    15 Mar 28 10:50 file3.41471.2026-03-28@10:53:41~
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.*~'
192.168.88.102 | CHANGED | rc=0 >>
ansible2026
666
192.168.88.101 | CHANGED | rc=0 >>
ansible2026
666
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/*~'
192.168.88.101 | CHANGED | rc=0 >>
ansible2026
666
192.168.88.102 | CHANGED | rc=0 >>
ansible2026
666
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.41567.2026-03-28@10:53:41~'
192.168.88.102 | FAILED | rc=1 >>
cat: '/tmp/file3.41567.2026-03-28@10:53:41~': No such file or directorynon-zero return code
192.168.88.101 | CHANGED | rc=0 >>
ansible2026
666
[root@ansible ~]# ansible web_servers -m shell -a 'cat /tmp/file3.41471.2026-03-28@10:53:41~'
192.168.88.102 | CHANGED | rc=0 >>
ansible2026
666
192.168.88.101 | FAILED | rc=1 >>
cat: '/tmp/file3.41471.2026-03-28@10:53:41~': No such file or directorynon-zero return code
[root@ansible ~]#

copy模块拷贝时要注意拷贝目录后面是否带"/"符号

/etc/yum.repos.d后面不带/符号,则表示把/etc/yum.repos.d整个目录及里面的文件拷贝到目标目录,比如/tmp/目录下

[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/yum.repos.d dest=/tmp'
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_exjvtqp3
file1
file2
file3
file3.8672.2025-12-17@10:43:26~
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_gffwddo2
file1
file2
file3
file3.8808.2025-12-17@10:43:26~
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/yum.repos.d dest=/tmp'
192.168.88.101 | CHANGED => {
    "changed": true,
    "dest": "/tmp/",
    "src": "/etc/yum.repos.d"
}
192.168.88.102 | CHANGED => {
    "changed": true,
    "dest": "/tmp/",
    "src": "/etc/yum.repos.d"
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'                     
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_15ziyjgm
file1
file2
file3
file3.8672.2025-12-17@10:43:26~
yum.repos.d
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_cnrk1xzm
file1
file2
file3
file3.8808.2025-12-17@10:43:26~
yum.repos.d
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/yum.repos.d'
192.168.88.102 | CHANGED | rc=0 >>
aliyun.repo
centos-addons.repo
centos.repo
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
192.168.88.101 | CHANGED | rc=0 >>
aliyun.repo
centos-addons.repo
centos.repo
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
[root@ansible ~]# ls /etc/yum.repos.d/
aliyun.repo         epel-cisco-openh264.repo  epel.repo
centos-addons.repo  epel-next.repo            epel-testing.repo
centos.repo         epel-next-testing.repo
[root@ansible ~]#

/etc/yum.repos.d/后面带/符号,则表示把/etc/yum.repos.d/目录里所有文件拷贝到目标目录,比如/tmp/目录下

[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/yum.repos.d/ dest=/tmp/'
[root@ansible ~]# ansible web_servers -m shell -a 'rm -rf /tmp/*'
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_gt8uxqli
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_0yno4mwv
[root@ansible ~]# ansible web_servers -m copy -a 'src=/etc/yum.repos.d/ dest=/tmp/'
192.168.88.102 | CHANGED => {
    "changed": true,
    "dest": "/tmp/",
    "src": "/etc/yum.repos.d/"
}
192.168.88.101 | CHANGED => {
    "changed": true,
    "dest": "/tmp/",
    "src": "/etc/yum.repos.d/"
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'                    
192.168.88.102 | CHANGED | rc=0 >>
aliyun.repo
ansible_ansible.legacy.command_payload_cnug_79_
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
192.168.88.101 | CHANGED | rc=0 >>
aliyun.repo
ansible_ansible.legacy.command_payload_k4fehcj6
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
[root@ansible ~]#

小结:

Ansible有一个特殊模块 copy,用于把本地文件拷贝到远程服务器

copy模块有两个常用的参数:(src)、(dest)

  1. yum/dnf 模块(重点)

作用:yum/dnf模块用于使用yum/dnf命令来实现软件包的安装与卸载

https://docs.ansible.com/ansible/latest/modules/yum_module.html#yum-module

使用yum/dnf安装一个vsftpd软件(前提:web_servers的机器上的yum源配置都已正常)

[root@ansible ~]# ansible web_servers -m yum -a 'name=vsftpd state=present'
或者
[root@ansible ~]# ansible web_servers -m dnf -a 'name=vsftpd state=present'
[root@ansible ~]# ansible web_servers -m yum -a 'name=vsftpd state=present'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: vsftpd-3.0.5-6.el9.x86_64"
    ]
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: vsftpd-3.0.5-6.el9.x86_64"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa vsftpd'
192.168.88.102 | CHANGED | rc=0 >>
vsftpd-3.0.5-6.el9.x86_64
192.168.88.101 | CHANGED | rc=0 >>
vsftpd-3.0.5-6.el9.x86_64
[root@ansible ~]# ansible web_servers -m dnf -a 'name=vsftpd state=present'
192.168.88.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": []
}
192.168.88.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "msg": "Nothing to do",
    "rc": 0,
    "results": []
}
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa vsftpd'
192.168.88.102 | CHANGED | rc=0 >>
vsftpd-3.0.5-6.el9.x86_64
192.168.88.101 | CHANGED | rc=0 >>
vsftpd-3.0.5-6.el9.x86_64
[root@ansible ~]#

使用yum安装httpd,httpd-devel软件,state=latest表示安装最新版本

[root@ansible ~]# ansible web_servers -m yum -a 'name=httpd,httpd-devel state=latest' 
或者
[root@ansible ~]# ansible web_servers -m dnf -a 'name=httpd,httpd-devel state=latest' 

使用yum/dnf卸载httpd,httpd-devel软件

[root@ansible ~]# ansible web_servers -m yum -a 'name=httpd,httpd-devel state=absent' 
或
[root@ansible ~]# ansible web_servers -m dnf -a 'name=httpd,httpd-devel state=absent' 
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa httpd'               
192.168.88.101 | CHANGED | rc=0 >>
httpd-2.4.62-10.el9.x86_64
192.168.88.102 | CHANGED | rc=0 >>
httpd-2.4.62-10.el9.x86_64
[root@ansible ~]# ansible web_servers -m yum -a 'name=httpd,httpd-devel state=absent'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Removed: httpd-2.4.62-10.el9.x86_64",
        "Removed: httpd-devel-2.4.62-10.el9.x86_64",
        "Removed: mod_http2-2.0.26-5.el9.x86_64"
    ]
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Removed: httpd-2.4.62-10.el9.x86_64",
        "Removed: httpd-devel-2.4.62-10.el9.x86_64",
        "Removed: mod_http2-2.0.26-5.el9.x86_64"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa httpd'               
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]#
  1. service 模块(重点)

作用:service 模块用于控制服务的启动,关闭,开机自启动等

https://docs.ansible.com/ansible/latest/modules/service_module.html#service-module

启动httpd服务,如何没有安装,首先进行软件的安装,通过yum模块去进行软件的安装,并设为开机自动启动

[root@ansible ~]# ansible web_servers -m yum -a 'name=httpd,httpd-devel state=present'
[root@ansible ~]# ansible web_servers -m service -a 'name=httpd state=started enabled=true'
[root@ansible ~]# ansible web_servers -m yum -a 'name=httpd,httpd-devel state=present'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: httpd-devel-2.4.62-10.el9.x86_64",
        "Installed: mod_http2-2.0.26-5.el9.x86_64",
        "Installed: httpd-2.4.62-10.el9.x86_64"
    ]
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "msg": "",
    "rc": 0,
    "results": [
        "Installed: httpd-2.4.62-10.el9.x86_64",
        "Installed: httpd-devel-2.4.62-10.el9.x86_64",
        "Installed: mod_http2-2.0.26-5.el9.x86_64"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl status httpd'      192.168.88.101 | FAILED | rc=3 >>
× httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
    Drop-In: /etc/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: failed (Result: exit-code) since Mon 2025-12-15 13:27:17 CST; 2h 42min ago
       Docs: man:httpd.service(8)
   Main PID: 936 (code=exited, status=1/FAILURE)
     Status: "Reading configuration..."
        CPU: 122ms

Dec 15 13:27:17 node1 systemd[1]: Starting The Apache HTTP Server...
Dec 15 13:27:17 node1 httpd[936]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.101. Set the 'ServerName' directive globally to suppress this message
Dec 15 13:27:17 node1 httpd[936]: (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
Dec 15 13:27:17 node1 httpd[936]: no listening sockets available, shutting down
Dec 15 13:27:17 node1 httpd[936]: AH00015: Unable to open logs
Dec 15 13:27:17 node1 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Dec 15 13:27:17 node1 systemd[1]: httpd.service: Failed with result 'exit-code'.
Dec 15 13:27:17 node1 systemd[1]: Failed to start The Apache HTTP Server.non-zero return code
192.168.88.102 | FAILED | rc=3 >>
○ httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: inactive (dead)
       Docs: man:httpd.service(8)

Dec 15 13:27:19 agent1.cluster.com systemd[1]: Starting The Apache HTTP Server...
Dec 15 13:27:26 agent1.cluster.com httpd[880]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using agent1.cluster.com. Set the 'ServerName' directive globally to suppress this message
Dec 15 13:27:30 agent1.cluster.com httpd[880]: Server configured, listening on: port 80
Dec 15 13:27:30 agent1.cluster.com systemd[1]: Started The Apache HTTP Server.
Dec 15 16:05:01 agent2 systemd[1]: Stopping The Apache HTTP Server...
Dec 15 16:05:02 agent2 systemd[1]: httpd.service: Deactivated successfully.
Dec 15 16:05:02 agent2 systemd[1]: Stopped The Apache HTTP Server.
Dec 15 16:05:02 agent2 systemd[1]: httpd.service: Consumed 7.248s CPU time.non-zero return code
[root@ansible ~]# ansible web_servers -m service -a 'name=httpd state=started enabled=true'
192.168.88.101 | FAILED! => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "msg": "Unable to start service httpd: Job for httpd.service failed because the control process exited with error code.\nSee \"systemctl status httpd.service\" and \"journalctl -xeu httpd.service\" for details.\n"
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "enabled": true,
    "name": "httpd",
    "state": "started",
    "status": {
        "ActiveEnterTimestampMonotonic": "0",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "inactive",
        "After": "tmp.mount sysinit.target remote-fs.target systemd-tmpfiles-setup.service system.slice httpd-init.service systemd-journald.socket nss-lookup.target network.target basic.target -.mount",
        "AllowIsolate": "no",
        "AssertResult": "no",
        "AssertTimestampMonotonic": "0",
        "Before": "shutdown.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "[not set]",
        "CPUAccounting": "yes",
        "CPUAffinityFromNUMA": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUQuotaPeriodUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "[not set]",
        "CPUUsageNSec": "[not set]",
        "CPUWeight": "[not set]",
        "CacheDirectoryMode": "0755",
        "CanFreeze": "yes",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore",
        "CleanResult": "success",
        "CollectMode": "inactive",
        "ConditionResult": "no",
        "ConditionTimestampMonotonic": "0",
        "ConfigurationDirectoryMode": "0755",
        "Conflicts": "shutdown.target",
        "ControlGroupId": "0",
        "ControlPID": "0",
        "CoredumpFilter": "0x33",
        "DefaultDependencies": "yes",
        "DefaultMemoryLow": "0",
        "DefaultMemoryMin": "0",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "Documentation": "\"man:httpd.service(8)\"",
        "DynamicUser": "no",
        "Environment": "LANG=C",
        "ExecMainCode": "0",
        "ExecMainExitTimestampMonotonic": "0",
        "ExecMainPID": "0",
        "ExecMainStartTimestampMonotonic": "0",
        "ExecMainStatus": "0",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecReloadEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStartEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExitType": "main",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FinalKillSignal": "9",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "FreezerState": "running",
        "GID": "[not set]",
        "GuessMainPID": "yes",
        "IOAccounting": "no",
        "IOReadBytes": "18446744073709551615",
        "IOReadOperations": "18446744073709551615",
        "IOSchedulingClass": "2",
        "IOSchedulingPriority": "4",
        "IOWeight": "[not set]",
        "IOWriteBytes": "18446744073709551615",
        "IOWriteOperations": "18446744073709551615",
        "IPAccounting": "no",
        "IPEgressBytes": "[no data]",
        "IPEgressPackets": "[no data]",
        "IPIngressBytes": "[no data]",
        "IPIngressPackets": "[no data]",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestampMonotonic": "0",
        "InactiveExitTimestampMonotonic": "0",
        "JobRunningTimeoutUSec": "infinity",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "infinity",
        "KeyringMode": "private",
        "KillMode": "mixed",
        "KillSignal": "28",
        "LimitAS": "infinity",
        "LimitASSoft": "infinity",
        "LimitCORE": "infinity",
        "LimitCORESoft": "infinity",
        "LimitCPU": "infinity",
        "LimitCPUSoft": "infinity",
        "LimitDATA": "infinity",
        "LimitDATASoft": "infinity",
        "LimitFSIZE": "infinity",
        "LimitFSIZESoft": "infinity",
        "LimitLOCKS": "infinity",
        "LimitLOCKSSoft": "infinity",
        "LimitMEMLOCK": "8388608",
        "LimitMEMLOCKSoft": "8388608",
        "LimitMSGQUEUE": "819200",
        "LimitMSGQUEUESoft": "819200",
        "LimitNICE": "0",
        "LimitNICESoft": "0",
        "LimitNOFILE": "524288",
        "LimitNOFILESoft": "1024",
        "LimitNPROC": "14329",
        "LimitNPROCSoft": "14329",
        "LimitRSS": "infinity",
        "LimitRSSSoft": "infinity",
        "LimitRTPRIO": "0",
        "LimitRTPRIOSoft": "0",
        "LimitRTTIME": "infinity",
        "LimitRTTIMESoft": "infinity",
        "LimitSIGPENDING": "14329",
        "LimitSIGPENDINGSoft": "14329",
        "LimitSTACK": "infinity",
        "LimitSTACKSoft": "8388608",
        "LoadState": "loaded",
        "LockPersonality": "no",
        "LogLevelMax": "-1",
        "LogRateLimitBurst": "0",
        "LogRateLimitIntervalUSec": "0",
        "LogsDirectoryMode": "0755",
        "MainPID": "0",
        "ManagedOOMMemoryPressure": "auto",
        "ManagedOOMMemoryPressureLimit": "0",
        "ManagedOOMPreference": "none",
        "ManagedOOMSwap": "auto",
        "MemoryAccounting": "yes",
        "MemoryAvailable": "infinity",
        "MemoryCurrent": "[not set]",
        "MemoryDenyWriteExecute": "no",
        "MemoryHigh": "infinity",
        "MemoryLimit": "infinity",
        "MemoryLow": "0",
        "MemoryMax": "infinity",
        "MemoryMin": "0",
        "MemorySwapMax": "infinity",
        "MountAPIVFS": "no",
        "NFileDescriptorStore": "0",
        "NRestarts": "0",
        "NUMAPolicy": "n/a",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMPolicy": "continue",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "OnSuccessJobMode": "fail",
        "Perpetual": "no",
        "PrivateDevices": "no",
        "PrivateIPC": "no",
        "PrivateMounts": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "PrivateUsers": "no",
        "ProcSubset": "all",
        "ProtectClock": "no",
        "ProtectControlGroups": "no",
        "ProtectHome": "no",
        "ProtectHostname": "no",
        "ProtectKernelLogs": "no",
        "ProtectKernelModules": "no",
        "ProtectKernelTunables": "no",
        "ProtectProc": "default",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "ReloadResult": "success",
        "ReloadSignal": "1",
        "RemainAfterExit": "no",
        "RemoveIPC": "no",
        "Requires": "-.mount sysinit.target system.slice",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartKillSignal": "28",
        "RestartUSec": "100ms",
        "RestrictNamespaces": "no",
        "RestrictRealtime": "no",
        "RestrictSUIDSGID": "no",
        "Result": "success",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "RuntimeDirectoryPreserve": "no",
        "RuntimeMaxUSec": "infinity",
        "RuntimeRandomizedExtraUSec": "0",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitIntervalUSec": "10s",
        "StartupBlockIOWeight": "[not set]",
        "StartupCPUShares": "[not set]",
        "StartupCPUWeight": "[not set]",
        "StartupIOWeight": "[not set]",
        "StateChangeTimestampMonotonic": "0",
        "StateDirectoryMode": "0755",
        "StatusErrno": "0",
        "StopWhenUnneeded": "no",
        "SubState": "dead",
        "SuccessAction": "none",
        "SyslogFacility": "3",
        "SyslogLevel": "6",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "2147483646",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "yes",
        "TasksCurrent": "[not set]",
        "TasksMax": "22927",
        "TimeoutAbortUSec": "1min 30s",
        "TimeoutCleanUSec": "infinity",
        "TimeoutStartFailureMode": "terminate",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopFailureMode": "terminate",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UID": "[not set]",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "disabled",
        "UtmpMode": "init",
        "Wants": "httpd-init.service",
        "WatchdogSignal": "6",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "infinity"
    }
}
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl status httpd'      192.168.88.102 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
     Active: active (running) since Mon 2025-12-15 16:09:59 CST; 3s ago
       Docs: man:httpd.service(8)
   Main PID: 16953 (httpd)
     Status: "Started, listening on: port 80"
      Tasks: 177 (limit: 22927)
     Memory: 21.9M
        CPU: 72ms
     CGroup: /system.slice/httpd.service
             ├─16953 /usr/sbin/httpd -DFOREGROUND
             ├─16955 /usr/sbin/httpd -DFOREGROUND
             ├─16956 /usr/sbin/httpd -DFOREGROUND
             ├─16957 /usr/sbin/httpd -DFOREGROUND
             └─16958 /usr/sbin/httpd -DFOREGROUND

Dec 15 16:09:59 agent2 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:09:59 agent2 httpd[16953]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.102. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:09:59 agent2 httpd[16953]: Server configured, listening on: port 80
Dec 15 16:09:59 agent2 systemd[1]: Started The Apache HTTP Server.
192.168.88.101 | FAILED | rc=3 >>
× httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: failed (Result: exit-code) since Mon 2025-12-15 16:09:59 CST; 3s ago
       Docs: man:httpd.service(8)
    Process: 19596 ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND (code=exited, status=1/FAILURE)
   Main PID: 19596 (code=exited, status=1/FAILURE)
     Status: "Reading configuration..."
        CPU: 38ms

Dec 15 16:09:59 agent1 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:09:59 agent1 httpd[19596]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.101. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:09:59 agent1 httpd[19596]: (98)Address already in use: AH00072: make_sock: could not bind to address 0.0.0.0:80
Dec 15 16:09:59 agent1 httpd[19596]: no listening sockets available, shutting down
Dec 15 16:09:59 agent1 httpd[19596]: AH00015: Unable to open logs
Dec 15 16:09:59 agent1 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Dec 15 16:09:59 agent1 systemd[1]: httpd.service: Failed with result 'exit-code'.
Dec 15 16:09:59 agent1 systemd[1]: Failed to start The Apache HTTP Server.non-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'netstat -pantul|grep 80'
192.168.88.102 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      16953/httpd
192.168.88.101 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      973/nginx: master p
[root@ansible ~]# ansible web_servers -m shell -a 'pkill nginx'
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'netstat -pantul|grep 80'
192.168.88.102 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      16953/httpd
192.168.88.101 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]# ansible web_servers -m service -a 'name=httpd state=started enabled=true'
192.168.88.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "enabled": true,
    "name": "httpd",
    "state": "started",
    "status": {
        "ActiveEnterTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ActiveEnterTimestampMonotonic": "9769845929",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "active",
        "After": "systemd-tmpfiles-setup.service network.target basic.target systemd-journald.socket system.slice httpd-init.service sysinit.target -.mount tmp.mount nss-lookup.target remote-fs.target",
        "AllowIsolate": "no",
        "AssertResult": "yes",
        "AssertTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "AssertTimestampMonotonic": "9769773604",
        "Before": "multi-user.target shutdown.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "[not set]",
        "CPUAccounting": "yes",
        "CPUAffinityFromNUMA": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUQuotaPeriodUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "[not set]",
        "CPUUsageNSec": "153307000",
        "CPUWeight": "[not set]",
        "CacheDirectoryMode": "0755",
        "CanFreeze": "yes",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore",
        "CleanResult": "success",
        "CollectMode": "inactive",
        "ConditionResult": "yes",
        "ConditionTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ConditionTimestampMonotonic": "9769773601",
        "ConfigurationDirectoryMode": "0755",
        "Conflicts": "shutdown.target",
        "ControlGroup": "/system.slice/httpd.service",
        "ControlGroupId": "6111",
        "ControlPID": "0",
        "CoredumpFilter": "0x33",
        "DefaultDependencies": "yes",
        "DefaultMemoryLow": "0",
        "DefaultMemoryMin": "0",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "Documentation": "\"man:httpd.service(8)\"",
        "DynamicUser": "no",
        "Environment": "LANG=C",
        "ExecMainCode": "0",
        "ExecMainExitTimestampMonotonic": "0",
        "ExecMainPID": "16953",
        "ExecMainStartTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ExecMainStartTimestampMonotonic": "9769792448",
        "ExecMainStatus": "0",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecReloadEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[n/a] ; pid=16953 ; code=(null) ; status=0/0 }",
        "ExecStartEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; flags= ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[n/a] ; pid=16953 ; code=(null) ; status=0/0 }",
        "ExitType": "main",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FinalKillSignal": "9",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "FreezerState": "running",
        "GID": "[not set]",
        "GuessMainPID": "yes",
        "IOAccounting": "no",
        "IOReadBytes": "18446744073709551615",
        "IOReadOperations": "18446744073709551615",
        "IOSchedulingClass": "2",
        "IOSchedulingPriority": "4",
        "IOWeight": "[not set]",
        "IOWriteBytes": "18446744073709551615",
        "IOWriteOperations": "18446744073709551615",
        "IPAccounting": "no",
        "IPEgressBytes": "[no data]",
        "IPEgressPackets": "[no data]",
        "IPIngressBytes": "[no data]",
        "IPIngressPackets": "[no data]",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestampMonotonic": "0",
        "InactiveExitTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "InactiveExitTimestampMonotonic": "9769793629",
        "InvocationID": "d7f400ed01524d2586dda7948cbf9a18",
        "JobRunningTimeoutUSec": "infinity",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "infinity",
        "KeyringMode": "private",
        "KillMode": "mixed",
        "KillSignal": "28",
        "LimitAS": "infinity",
        "LimitASSoft": "infinity",
        "LimitCORE": "infinity",
        "LimitCORESoft": "infinity",
        "LimitCPU": "infinity",
        "LimitCPUSoft": "infinity",
        "LimitDATA": "infinity",
        "LimitDATASoft": "infinity",
        "LimitFSIZE": "infinity",
        "LimitFSIZESoft": "infinity",
        "LimitLOCKS": "infinity",
        "LimitLOCKSSoft": "infinity",
        "LimitMEMLOCK": "8388608",
        "LimitMEMLOCKSoft": "8388608",
        "LimitMSGQUEUE": "819200",
        "LimitMSGQUEUESoft": "819200",
        "LimitNICE": "0",
        "LimitNICESoft": "0",
        "LimitNOFILE": "524288",
        "LimitNOFILESoft": "1024",
        "LimitNPROC": "14329",
        "LimitNPROCSoft": "14329",
        "LimitRSS": "infinity",
        "LimitRSSSoft": "infinity",
        "LimitRTPRIO": "0",
        "LimitRTPRIOSoft": "0",
        "LimitRTTIME": "infinity",
        "LimitRTTIMESoft": "infinity",
        "LimitSIGPENDING": "14329",
        "LimitSIGPENDINGSoft": "14329",
        "LimitSTACK": "infinity",
        "LimitSTACKSoft": "8388608",
        "LoadState": "loaded",
        "LockPersonality": "no",
        "LogLevelMax": "-1",
        "LogRateLimitBurst": "0",
        "LogRateLimitIntervalUSec": "0",
        "LogsDirectoryMode": "0755",
        "MainPID": "16953",
        "ManagedOOMMemoryPressure": "auto",
        "ManagedOOMMemoryPressureLimit": "0",
        "ManagedOOMPreference": "none",
        "ManagedOOMSwap": "auto",
        "MemoryAccounting": "yes",
        "MemoryAvailable": "infinity",
        "MemoryCurrent": "23015424",
        "MemoryDenyWriteExecute": "no",
        "MemoryHigh": "infinity",
        "MemoryLimit": "infinity",
        "MemoryLow": "0",
        "MemoryMax": "infinity",
        "MemoryMin": "0",
        "MemorySwapMax": "infinity",
        "MountAPIVFS": "no",
        "NFileDescriptorStore": "0",
        "NRestarts": "0",
        "NUMAPolicy": "n/a",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMPolicy": "continue",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "OnSuccessJobMode": "fail",
        "Perpetual": "no",
        "PrivateDevices": "no",
        "PrivateIPC": "no",
        "PrivateMounts": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "PrivateUsers": "no",
        "ProcSubset": "all",
        "ProtectClock": "no",
        "ProtectControlGroups": "no",
        "ProtectHome": "no",
        "ProtectHostname": "no",
        "ProtectKernelLogs": "no",
        "ProtectKernelModules": "no",
        "ProtectKernelTunables": "no",
        "ProtectProc": "default",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "ReloadResult": "success",
        "ReloadSignal": "1",
        "RemainAfterExit": "no",
        "RemoveIPC": "no",
        "Requires": "sysinit.target -.mount system.slice",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartKillSignal": "28",
        "RestartUSec": "100ms",
        "RestrictNamespaces": "no",
        "RestrictRealtime": "no",
        "RestrictSUIDSGID": "no",
        "Result": "success",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "RuntimeDirectoryPreserve": "no",
        "RuntimeMaxUSec": "infinity",
        "RuntimeRandomizedExtraUSec": "0",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitIntervalUSec": "10s",
        "StartupBlockIOWeight": "[not set]",
        "StartupCPUShares": "[not set]",
        "StartupCPUWeight": "[not set]",
        "StartupIOWeight": "[not set]",
        "StateChangeTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "StateChangeTimestampMonotonic": "9769845929",
        "StateDirectoryMode": "0755",
        "StatusErrno": "0",
        "StatusText": "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec",
        "StopWhenUnneeded": "no",
        "SubState": "running",
        "SuccessAction": "none",
        "SyslogFacility": "3",
        "SyslogLevel": "6",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "2147483646",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "yes",
        "TasksCurrent": "177",
        "TasksMax": "22927",
        "TimeoutAbortUSec": "1min 30s",
        "TimeoutCleanUSec": "infinity",
        "TimeoutStartFailureMode": "terminate",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopFailureMode": "terminate",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UID": "[not set]",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "enabled",
        "UtmpMode": "init",
        "WantedBy": "multi-user.target",
        "Wants": "httpd-init.service",
        "WatchdogSignal": "6",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "0"
    }
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "enabled": true,
    "name": "httpd",
    "state": "started",
    "status": {
        "ActiveEnterTimestampMonotonic": "0",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "failed",
        "After": "system.slice -.mount httpd-init.service network.target remote-fs.target sysinit.target systemd-journald.socket systemd-tmpfiles-setup.service nss-lookup.target tmp.mount basic.target",
        "AllowIsolate": "no",
        "AssertResult": "yes",
        "AssertTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "AssertTimestampMonotonic": "9771650121",
        "Before": "shutdown.target multi-user.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "[not set]",
        "CPUAccounting": "yes",
        "CPUAffinityFromNUMA": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUQuotaPeriodUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "[not set]",
        "CPUUsageNSec": "38082000",
        "CPUWeight": "[not set]",
        "CacheDirectoryMode": "0755",
        "CanFreeze": "yes",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore",
        "CleanResult": "success",
        "CollectMode": "inactive",
        "ConditionResult": "yes",
        "ConditionTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ConditionTimestampMonotonic": "9771650118",
        "ConfigurationDirectoryMode": "0755",
        "Conflicts": "shutdown.target",
        "ControlGroupId": "6383",
        "ControlPID": "0",
        "CoredumpFilter": "0x33",
        "DefaultDependencies": "yes",
        "DefaultMemoryLow": "0",
        "DefaultMemoryMin": "0",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "Documentation": "\"man:httpd.service(8)\"",
        "DropInPaths": "/etc/systemd/system/httpd.service.d/php-fpm.conf",
        "DynamicUser": "no",
        "Environment": "LANG=C",
        "ExecMainCode": "1",
        "ExecMainExitTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ExecMainExitTimestampMonotonic": "9771713440",
        "ExecMainPID": "19596",
        "ExecMainStartTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ExecMainStartTimestampMonotonic": "9771660575",
        "ExecMainStatus": "1",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecReloadEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[Mon 2025-12-15 16:09:59 CST] ; pid=19596 ; code=exited ; status=1 }",
        "ExecStartEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; flags= ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[Mon 2025-12-15 16:09:59 CST] ; pid=19596 ; code=exited ; status=1 }",
        "ExitType": "main",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FinalKillSignal": "9",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "FreezerState": "running",
        "GID": "[not set]",
        "GuessMainPID": "yes",
        "IOAccounting": "no",
        "IOReadBytes": "18446744073709551615",
        "IOReadOperations": "18446744073709551615",
        "IOSchedulingClass": "2",
        "IOSchedulingPriority": "4",
        "IOWeight": "[not set]",
        "IOWriteBytes": "18446744073709551615",
        "IOWriteOperations": "18446744073709551615",
        "IPAccounting": "no",
        "IPEgressBytes": "[no data]",
        "IPEgressPackets": "[no data]",
        "IPIngressBytes": "[no data]",
        "IPIngressPackets": "[no data]",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "InactiveEnterTimestampMonotonic": "9771713654",
        "InactiveExitTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "InactiveExitTimestampMonotonic": "9771661135",
        "InvocationID": "90ad428767664037a64529d6dc5df3fa",
        "JobRunningTimeoutUSec": "infinity",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "infinity",
        "KeyringMode": "private",
        "KillMode": "mixed",
        "KillSignal": "28",
        "LimitAS": "infinity",
        "LimitASSoft": "infinity",
        "LimitCORE": "infinity",
        "LimitCORESoft": "infinity",
        "LimitCPU": "infinity",
        "LimitCPUSoft": "infinity",
        "LimitDATA": "infinity",
        "LimitDATASoft": "infinity",
        "LimitFSIZE": "infinity",
        "LimitFSIZESoft": "infinity",
        "LimitLOCKS": "infinity",
        "LimitLOCKSSoft": "infinity",
        "LimitMEMLOCK": "8388608",
        "LimitMEMLOCKSoft": "8388608",
        "LimitMSGQUEUE": "819200",
        "LimitMSGQUEUESoft": "819200",
        "LimitNICE": "0",
        "LimitNICESoft": "0",
        "LimitNOFILE": "524288",
        "LimitNOFILESoft": "1024",
        "LimitNPROC": "30433",
        "LimitNPROCSoft": "30433",
        "LimitRSS": "infinity",
        "LimitRSSSoft": "infinity",
        "LimitRTPRIO": "0",
        "LimitRTPRIOSoft": "0",
        "LimitRTTIME": "infinity",
        "LimitRTTIMESoft": "infinity",
        "LimitSIGPENDING": "30433",
        "LimitSIGPENDINGSoft": "30433",
        "LimitSTACK": "infinity",
        "LimitSTACKSoft": "8388608",
        "LoadState": "loaded",
        "LockPersonality": "no",
        "LogLevelMax": "-1",
        "LogRateLimitBurst": "0",
        "LogRateLimitIntervalUSec": "0",
        "LogsDirectoryMode": "0755",
        "MainPID": "0",
        "ManagedOOMMemoryPressure": "auto",
        "ManagedOOMMemoryPressureLimit": "0",
        "ManagedOOMPreference": "none",
        "ManagedOOMSwap": "auto",
        "MemoryAccounting": "yes",
        "MemoryAvailable": "infinity",
        "MemoryCurrent": "[not set]",
        "MemoryDenyWriteExecute": "no",
        "MemoryHigh": "infinity",
        "MemoryLimit": "infinity",
        "MemoryLow": "0",
        "MemoryMax": "infinity",
        "MemoryMin": "0",
        "MemorySwapMax": "infinity",
        "MountAPIVFS": "no",
        "NFileDescriptorStore": "0",
        "NRestarts": "0",
        "NUMAPolicy": "n/a",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMPolicy": "continue",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "OnSuccessJobMode": "fail",
        "Perpetual": "no",
        "PrivateDevices": "no",
        "PrivateIPC": "no",
        "PrivateMounts": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "PrivateUsers": "no",
        "ProcSubset": "all",
        "ProtectClock": "no",
        "ProtectControlGroups": "no",
        "ProtectHome": "no",
        "ProtectHostname": "no",
        "ProtectKernelLogs": "no",
        "ProtectKernelModules": "no",
        "ProtectKernelTunables": "no",
        "ProtectProc": "default",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "ReloadResult": "success",
        "ReloadSignal": "1",
        "RemainAfterExit": "no",
        "RemoveIPC": "no",
        "Requires": "system.slice sysinit.target -.mount",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartKillSignal": "28",
        "RestartUSec": "100ms",
        "RestrictNamespaces": "no",
        "RestrictRealtime": "no",
        "RestrictSUIDSGID": "no",
        "Result": "exit-code",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "RuntimeDirectoryPreserve": "no",
        "RuntimeMaxUSec": "infinity",
        "RuntimeRandomizedExtraUSec": "0",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitIntervalUSec": "10s",
        "StartupBlockIOWeight": "[not set]",
        "StartupCPUShares": "[not set]",
        "StartupCPUWeight": "[not set]",
        "StartupIOWeight": "[not set]",
        "StateChangeTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "StateChangeTimestampMonotonic": "9771713654",
        "StateDirectoryMode": "0755",
        "StatusErrno": "0",
        "StatusText": "Reading configuration...",
        "StopWhenUnneeded": "no",
        "SubState": "failed",
        "SuccessAction": "none",
        "SyslogFacility": "3",
        "SyslogLevel": "6",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "2147483646",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "yes",
        "TasksCurrent": "[not set]",
        "TasksMax": "48693",
        "TimeoutAbortUSec": "1min 30s",
        "TimeoutCleanUSec": "infinity",
        "TimeoutStartFailureMode": "terminate",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopFailureMode": "terminate",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UID": "[not set]",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "enabled",
        "UtmpMode": "init",
        "WantedBy": "multi-user.target",
        "Wants": "httpd-init.service php-fpm.service",
        "WatchdogSignal": "6",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "0"
    }
}
[root@ansible ~]# ansible web_servers -m shell -a 'netstat -pantul|grep 80'     192.168.88.102 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      16953/httpd
192.168.88.101 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN      20388/httpd
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl status httpd'      192.168.88.102 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
     Active: active (running) since Mon 2025-12-15 16:09:59 CST; 1min 58s ago
       Docs: man:httpd.service(8)
   Main PID: 16953 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 22927)
     Memory: 21.9M
        CPU: 159ms
     CGroup: /system.slice/httpd.service
             ├─16953 /usr/sbin/httpd -DFOREGROUND
             ├─16955 /usr/sbin/httpd -DFOREGROUND
             ├─16956 /usr/sbin/httpd -DFOREGROUND
             ├─16957 /usr/sbin/httpd -DFOREGROUND
             └─16958 /usr/sbin/httpd -DFOREGROUND

Dec 15 16:09:59 agent2 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:09:59 agent2 httpd[16953]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.102. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:09:59 agent2 httpd[16953]: Server configured, listening on: port 80
Dec 15 16:09:59 agent2 systemd[1]: Started The Apache HTTP Server.
192.168.88.101 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: active (running) since Mon 2025-12-15 16:11:48 CST; 9s ago
       Docs: man:httpd.service(8)
   Main PID: 20388 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 48693)
     Memory: 26.6M
        CPU: 69ms
     CGroup: /system.slice/httpd.service
             ├─20388 /usr/sbin/httpd -DFOREGROUND
             ├─20389 /usr/sbin/httpd -DFOREGROUND
             ├─20390 /usr/sbin/httpd -DFOREGROUND
             ├─20391 /usr/sbin/httpd -DFOREGROUND
             └─20392 /usr/sbin/httpd -DFOREGROUND

Dec 15 16:11:48 agent1 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:11:48 agent1 httpd[20388]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.101. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:11:48 agent1 httpd[20388]: Server configured, listening on: port 80
Dec 15 16:11:48 agent1 systemd[1]: Started The Apache HTTP Server.
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl enable httpd'
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'systemctl disable httpd'
192.168.88.101 | CHANGED | rc=0 >>
Removed "/etc/systemd/system/multi-user.target.wants/httpd.service".
192.168.88.102 | CHANGED | rc=0 >>
Removed "/etc/systemd/system/multi-user.target.wants/httpd.service".
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl enable httpd'
192.168.88.101 | CHANGED | rc=0 >>
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
192.168.88.102 | CHANGED | rc=0 >>
Created symlink /etc/systemd/system/multi-user.target.wants/httpd.service → /usr/lib/systemd/system/httpd.service.
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl enable httpd'
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]#

state服务管理可以选参数:"reloaded"、"restarted"、"started"、"stopped"

关闭httpd服务,并设为开机不自动启动

[root@ansible ~]# ansible web_servers -m service -a 'name=httpd state=stopped enabled=false'
[root@ansible ~]# ansible web_servers -m service -a 'name=httpd state=stopped enabled=false'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "enabled": false,
    "name": "httpd",
    "state": "stopped",
    "status": {
        "ActiveEnterTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "ActiveEnterTimestampMonotonic": "9880515953",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "active",
        "After": "system.slice -.mount httpd-init.service network.target remote-fs.target sysinit.target systemd-journald.socket systemd-tmpfiles-setup.service nss-lookup.target tmp.mount basic.target",
        "AllowIsolate": "no",
        "AssertResult": "yes",
        "AssertTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "AssertTimestampMonotonic": "9880450791",
        "Before": "shutdown.target multi-user.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "[not set]",
        "CPUAccounting": "yes",
        "CPUAffinityFromNUMA": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUQuotaPeriodUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "[not set]",
        "CPUUsageNSec": "115003000",
        "CPUWeight": "[not set]",
        "CacheDirectoryMode": "0755",
        "CanFreeze": "yes",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore",
        "CleanResult": "success",
        "CollectMode": "inactive",
        "ConditionResult": "yes",
        "ConditionTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "ConditionTimestampMonotonic": "9880450788",
        "ConfigurationDirectoryMode": "0755",
        "Conflicts": "shutdown.target",
        "ControlGroup": "/system.slice/httpd.service",
        "ControlGroupId": "6420",
        "ControlPID": "0",
        "CoredumpFilter": "0x33",
        "DefaultDependencies": "yes",
        "DefaultMemoryLow": "0",
        "DefaultMemoryMin": "0",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "Documentation": "\"man:httpd.service(8)\"",
        "DropInPaths": "/etc/systemd/system/httpd.service.d/php-fpm.conf",
        "DynamicUser": "no",
        "Environment": "LANG=C",
        "ExecMainCode": "0",
        "ExecMainExitTimestampMonotonic": "0",
        "ExecMainPID": "20388",
        "ExecMainStartTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "ExecMainStartTimestampMonotonic": "9880464962",
        "ExecMainStatus": "0",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecReloadEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2025-12-15 16:11:48 CST] ; stop_time=[n/a] ; pid=20388 ; code=(null) ; status=0/0 }",
        "ExecStartEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; flags= ; start_time=[Mon 2025-12-15 16:11:48 CST] ; stop_time=[n/a] ; pid=20388 ; code=(null) ; status=0/0 }",
        "ExitType": "main",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FinalKillSignal": "9",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "FreezerState": "running",
        "GID": "[not set]",
        "GuessMainPID": "yes",
        "IOAccounting": "no",
        "IOReadBytes": "18446744073709551615",
        "IOReadOperations": "18446744073709551615",
        "IOSchedulingClass": "2",
        "IOSchedulingPriority": "4",
        "IOWeight": "[not set]",
        "IOWriteBytes": "18446744073709551615",
        "IOWriteOperations": "18446744073709551615",
        "IPAccounting": "no",
        "IPEgressBytes": "[no data]",
        "IPEgressPackets": "[no data]",
        "IPIngressBytes": "[no data]",
        "IPIngressPackets": "[no data]",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "InactiveEnterTimestampMonotonic": "9771713654",
        "InactiveExitTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "InactiveExitTimestampMonotonic": "9880465414",
        "InvocationID": "52dc5165445942688767c2570eea305c",
        "JobRunningTimeoutUSec": "infinity",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "infinity",
        "KeyringMode": "private",
        "KillMode": "mixed",
        "KillSignal": "28",
        "LimitAS": "infinity",
        "LimitASSoft": "infinity",
        "LimitCORE": "infinity",
        "LimitCORESoft": "infinity",
        "LimitCPU": "infinity",
        "LimitCPUSoft": "infinity",
        "LimitDATA": "infinity",
        "LimitDATASoft": "infinity",
        "LimitFSIZE": "infinity",
        "LimitFSIZESoft": "infinity",
        "LimitLOCKS": "infinity",
        "LimitLOCKSSoft": "infinity",
        "LimitMEMLOCK": "8388608",
        "LimitMEMLOCKSoft": "8388608",
        "LimitMSGQUEUE": "819200",
        "LimitMSGQUEUESoft": "819200",
        "LimitNICE": "0",
        "LimitNICESoft": "0",
        "LimitNOFILE": "524288",
        "LimitNOFILESoft": "1024",
        "LimitNPROC": "30433",
        "LimitNPROCSoft": "30433",
        "LimitRSS": "infinity",
        "LimitRSSSoft": "infinity",
        "LimitRTPRIO": "0",
        "LimitRTPRIOSoft": "0",
        "LimitRTTIME": "infinity",
        "LimitRTTIMESoft": "infinity",
        "LimitSIGPENDING": "30433",
        "LimitSIGPENDINGSoft": "30433",
        "LimitSTACK": "infinity",
        "LimitSTACKSoft": "8388608",
        "LoadState": "loaded",
        "LockPersonality": "no",
        "LogLevelMax": "-1",
        "LogRateLimitBurst": "0",
        "LogRateLimitIntervalUSec": "0",
        "LogsDirectoryMode": "0755",
        "MainPID": "20388",
        "ManagedOOMMemoryPressure": "auto",
        "ManagedOOMMemoryPressureLimit": "0",
        "ManagedOOMPreference": "none",
        "ManagedOOMSwap": "auto",
        "MemoryAccounting": "yes",
        "MemoryAvailable": "infinity",
        "MemoryCurrent": "27893760",
        "MemoryDenyWriteExecute": "no",
        "MemoryHigh": "infinity",
        "MemoryLimit": "infinity",
        "MemoryLow": "0",
        "MemoryMax": "infinity",
        "MemoryMin": "0",
        "MemorySwapMax": "infinity",
        "MountAPIVFS": "no",
        "NFileDescriptorStore": "0",
        "NRestarts": "0",
        "NUMAPolicy": "n/a",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMPolicy": "continue",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "OnSuccessJobMode": "fail",
        "Perpetual": "no",
        "PrivateDevices": "no",
        "PrivateIPC": "no",
        "PrivateMounts": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "PrivateUsers": "no",
        "ProcSubset": "all",
        "ProtectClock": "no",
        "ProtectControlGroups": "no",
        "ProtectHome": "no",
        "ProtectHostname": "no",
        "ProtectKernelLogs": "no",
        "ProtectKernelModules": "no",
        "ProtectKernelTunables": "no",
        "ProtectProc": "default",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "ReloadResult": "success",
        "ReloadSignal": "1",
        "RemainAfterExit": "no",
        "RemoveIPC": "no",
        "Requires": "system.slice sysinit.target -.mount",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartKillSignal": "28",
        "RestartUSec": "100ms",
        "RestrictNamespaces": "no",
        "RestrictRealtime": "no",
        "RestrictSUIDSGID": "no",
        "Result": "success",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "RuntimeDirectoryPreserve": "no",
        "RuntimeMaxUSec": "infinity",
        "RuntimeRandomizedExtraUSec": "0",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitIntervalUSec": "10s",
        "StartupBlockIOWeight": "[not set]",
        "StartupCPUShares": "[not set]",
        "StartupCPUWeight": "[not set]",
        "StartupIOWeight": "[not set]",
        "StateChangeTimestamp": "Mon 2025-12-15 16:11:48 CST",
        "StateChangeTimestampMonotonic": "9880515953",
        "StateDirectoryMode": "0755",
        "StatusErrno": "0",
        "StatusText": "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec",
        "StopWhenUnneeded": "no",
        "SubState": "running",
        "SuccessAction": "none",
        "SyslogFacility": "3",
        "SyslogLevel": "6",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "2147483646",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "yes",
        "TasksCurrent": "177",
        "TasksMax": "48693",
        "TimeoutAbortUSec": "1min 30s",
        "TimeoutCleanUSec": "infinity",
        "TimeoutStartFailureMode": "terminate",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopFailureMode": "terminate",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UID": "[not set]",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "enabled",
        "UtmpMode": "init",
        "WantedBy": "multi-user.target",
        "Wants": "httpd-init.service php-fpm.service",
        "WatchdogSignal": "6",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "0"
    }
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "enabled": false,
    "name": "httpd",
    "state": "stopped",
    "status": {
        "ActiveEnterTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ActiveEnterTimestampMonotonic": "9769845929",
        "ActiveExitTimestampMonotonic": "0",
        "ActiveState": "active",
        "After": "systemd-tmpfiles-setup.service network.target basic.target systemd-journald.socket system.slice httpd-init.service sysinit.target -.mount tmp.mount nss-lookup.target remote-fs.target",
        "AllowIsolate": "no",
        "AssertResult": "yes",
        "AssertTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "AssertTimestampMonotonic": "9769773604",
        "Before": "multi-user.target shutdown.target",
        "BlockIOAccounting": "no",
        "BlockIOWeight": "[not set]",
        "CPUAccounting": "yes",
        "CPUAffinityFromNUMA": "no",
        "CPUQuotaPerSecUSec": "infinity",
        "CPUQuotaPeriodUSec": "infinity",
        "CPUSchedulingPolicy": "0",
        "CPUSchedulingPriority": "0",
        "CPUSchedulingResetOnFork": "no",
        "CPUShares": "[not set]",
        "CPUUsageNSec": "205723000",
        "CPUWeight": "[not set]",
        "CacheDirectoryMode": "0755",
        "CanFreeze": "yes",
        "CanIsolate": "no",
        "CanReload": "yes",
        "CanStart": "yes",
        "CanStop": "yes",
        "CapabilityBoundingSet": "cap_chown cap_dac_override cap_dac_read_search cap_fowner cap_fsetid cap_kill cap_setgid cap_setuid cap_setpcap cap_linux_immutable cap_net_bind_service cap_net_broadcast cap_net_admin cap_net_raw cap_ipc_lock cap_ipc_owner cap_sys_module cap_sys_rawio cap_sys_chroot cap_sys_ptrace cap_sys_pacct cap_sys_admin cap_sys_boot cap_sys_nice cap_sys_resource cap_sys_time cap_sys_tty_config cap_mknod cap_lease cap_audit_write cap_audit_control cap_setfcap cap_mac_override cap_mac_admin cap_syslog cap_wake_alarm cap_block_suspend cap_audit_read cap_perfmon cap_bpf cap_checkpoint_restore",
        "CleanResult": "success",
        "CollectMode": "inactive",
        "ConditionResult": "yes",
        "ConditionTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ConditionTimestampMonotonic": "9769773601",
        "ConfigurationDirectoryMode": "0755",
        "Conflicts": "shutdown.target",
        "ControlGroup": "/system.slice/httpd.service",
        "ControlGroupId": "6111",
        "ControlPID": "0",
        "CoredumpFilter": "0x33",
        "DefaultDependencies": "yes",
        "DefaultMemoryLow": "0",
        "DefaultMemoryMin": "0",
        "Delegate": "no",
        "Description": "The Apache HTTP Server",
        "DevicePolicy": "auto",
        "Documentation": "\"man:httpd.service(8)\"",
        "DynamicUser": "no",
        "Environment": "LANG=C",
        "ExecMainCode": "0",
        "ExecMainExitTimestampMonotonic": "0",
        "ExecMainPID": "16953",
        "ExecMainStartTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "ExecMainStartTimestampMonotonic": "9769792448",
        "ExecMainStatus": "0",
        "ExecReload": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecReloadEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -k graceful ; flags= ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }",
        "ExecStart": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; ignore_errors=no ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[n/a] ; pid=16953 ; code=(null) ; status=0/0 }",
        "ExecStartEx": "{ path=/usr/sbin/httpd ; argv[]=/usr/sbin/httpd $OPTIONS -DFOREGROUND ; flags= ; start_time=[Mon 2025-12-15 16:09:59 CST] ; stop_time=[n/a] ; pid=16953 ; code=(null) ; status=0/0 }",
        "ExitType": "main",
        "FailureAction": "none",
        "FileDescriptorStoreMax": "0",
        "FinalKillSignal": "9",
        "FragmentPath": "/usr/lib/systemd/system/httpd.service",
        "FreezerState": "running",
        "GID": "[not set]",
        "GuessMainPID": "yes",
        "IOAccounting": "no",
        "IOReadBytes": "18446744073709551615",
        "IOReadOperations": "18446744073709551615",
        "IOSchedulingClass": "2",
        "IOSchedulingPriority": "4",
        "IOWeight": "[not set]",
        "IOWriteBytes": "18446744073709551615",
        "IOWriteOperations": "18446744073709551615",
        "IPAccounting": "no",
        "IPEgressBytes": "[no data]",
        "IPEgressPackets": "[no data]",
        "IPIngressBytes": "[no data]",
        "IPIngressPackets": "[no data]",
        "Id": "httpd.service",
        "IgnoreOnIsolate": "no",
        "IgnoreSIGPIPE": "yes",
        "InactiveEnterTimestampMonotonic": "0",
        "InactiveExitTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "InactiveExitTimestampMonotonic": "9769793629",
        "InvocationID": "d7f400ed01524d2586dda7948cbf9a18",
        "JobRunningTimeoutUSec": "infinity",
        "JobTimeoutAction": "none",
        "JobTimeoutUSec": "infinity",
        "KeyringMode": "private",
        "KillMode": "mixed",
        "KillSignal": "28",
        "LimitAS": "infinity",
        "LimitASSoft": "infinity",
        "LimitCORE": "infinity",
        "LimitCORESoft": "infinity",
        "LimitCPU": "infinity",
        "LimitCPUSoft": "infinity",
        "LimitDATA": "infinity",
        "LimitDATASoft": "infinity",
        "LimitFSIZE": "infinity",
        "LimitFSIZESoft": "infinity",
        "LimitLOCKS": "infinity",
        "LimitLOCKSSoft": "infinity",
        "LimitMEMLOCK": "8388608",
        "LimitMEMLOCKSoft": "8388608",
        "LimitMSGQUEUE": "819200",
        "LimitMSGQUEUESoft": "819200",
        "LimitNICE": "0",
        "LimitNICESoft": "0",
        "LimitNOFILE": "524288",
        "LimitNOFILESoft": "1024",
        "LimitNPROC": "14329",
        "LimitNPROCSoft": "14329",
        "LimitRSS": "infinity",
        "LimitRSSSoft": "infinity",
        "LimitRTPRIO": "0",
        "LimitRTPRIOSoft": "0",
        "LimitRTTIME": "infinity",
        "LimitRTTIMESoft": "infinity",
        "LimitSIGPENDING": "14329",
        "LimitSIGPENDINGSoft": "14329",
        "LimitSTACK": "infinity",
        "LimitSTACKSoft": "8388608",
        "LoadState": "loaded",
        "LockPersonality": "no",
        "LogLevelMax": "-1",
        "LogRateLimitBurst": "0",
        "LogRateLimitIntervalUSec": "0",
        "LogsDirectoryMode": "0755",
        "MainPID": "16953",
        "ManagedOOMMemoryPressure": "auto",
        "ManagedOOMMemoryPressureLimit": "0",
        "ManagedOOMPreference": "none",
        "ManagedOOMSwap": "auto",
        "MemoryAccounting": "yes",
        "MemoryAvailable": "infinity",
        "MemoryCurrent": "23015424",
        "MemoryDenyWriteExecute": "no",
        "MemoryHigh": "infinity",
        "MemoryLimit": "infinity",
        "MemoryLow": "0",
        "MemoryMax": "infinity",
        "MemoryMin": "0",
        "MemorySwapMax": "infinity",
        "MountAPIVFS": "no",
        "NFileDescriptorStore": "0",
        "NRestarts": "0",
        "NUMAPolicy": "n/a",
        "Names": "httpd.service",
        "NeedDaemonReload": "no",
        "Nice": "0",
        "NoNewPrivileges": "no",
        "NonBlocking": "no",
        "NotifyAccess": "main",
        "OOMPolicy": "continue",
        "OOMScoreAdjust": "0",
        "OnFailureJobMode": "replace",
        "OnSuccessJobMode": "fail",
        "Perpetual": "no",
        "PrivateDevices": "no",
        "PrivateIPC": "no",
        "PrivateMounts": "no",
        "PrivateNetwork": "no",
        "PrivateTmp": "yes",
        "PrivateUsers": "no",
        "ProcSubset": "all",
        "ProtectClock": "no",
        "ProtectControlGroups": "no",
        "ProtectHome": "no",
        "ProtectHostname": "no",
        "ProtectKernelLogs": "no",
        "ProtectKernelModules": "no",
        "ProtectKernelTunables": "no",
        "ProtectProc": "default",
        "ProtectSystem": "no",
        "RefuseManualStart": "no",
        "RefuseManualStop": "no",
        "ReloadResult": "success",
        "ReloadSignal": "1",
        "RemainAfterExit": "no",
        "RemoveIPC": "no",
        "Requires": "sysinit.target -.mount system.slice",
        "RequiresMountsFor": "/var/tmp",
        "Restart": "no",
        "RestartKillSignal": "28",
        "RestartUSec": "100ms",
        "RestrictNamespaces": "no",
        "RestrictRealtime": "no",
        "RestrictSUIDSGID": "no",
        "Result": "success",
        "RootDirectoryStartOnly": "no",
        "RuntimeDirectoryMode": "0755",
        "RuntimeDirectoryPreserve": "no",
        "RuntimeMaxUSec": "infinity",
        "RuntimeRandomizedExtraUSec": "0",
        "SameProcessGroup": "no",
        "SecureBits": "0",
        "SendSIGHUP": "no",
        "SendSIGKILL": "yes",
        "Slice": "system.slice",
        "StandardError": "inherit",
        "StandardInput": "null",
        "StandardOutput": "journal",
        "StartLimitAction": "none",
        "StartLimitBurst": "5",
        "StartLimitIntervalUSec": "10s",
        "StartupBlockIOWeight": "[not set]",
        "StartupCPUShares": "[not set]",
        "StartupCPUWeight": "[not set]",
        "StartupIOWeight": "[not set]",
        "StateChangeTimestamp": "Mon 2025-12-15 16:09:59 CST",
        "StateChangeTimestampMonotonic": "9769845929",
        "StateDirectoryMode": "0755",
        "StatusErrno": "0",
        "StatusText": "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec",
        "StopWhenUnneeded": "no",
        "SubState": "running",
        "SuccessAction": "none",
        "SyslogFacility": "3",
        "SyslogLevel": "6",
        "SyslogLevelPrefix": "yes",
        "SyslogPriority": "30",
        "SystemCallErrorNumber": "2147483646",
        "TTYReset": "no",
        "TTYVHangup": "no",
        "TTYVTDisallocate": "no",
        "TasksAccounting": "yes",
        "TasksCurrent": "177",
        "TasksMax": "22927",
        "TimeoutAbortUSec": "1min 30s",
        "TimeoutCleanUSec": "infinity",
        "TimeoutStartFailureMode": "terminate",
        "TimeoutStartUSec": "1min 30s",
        "TimeoutStopFailureMode": "terminate",
        "TimeoutStopUSec": "1min 30s",
        "TimerSlackNSec": "50000",
        "Transient": "no",
        "Type": "notify",
        "UID": "[not set]",
        "UMask": "0022",
        "UnitFilePreset": "disabled",
        "UnitFileState": "enabled",
        "UtmpMode": "init",
        "WantedBy": "multi-user.target",
        "Wants": "httpd-init.service",
        "WatchdogSignal": "6",
        "WatchdogTimestampMonotonic": "0",
        "WatchdogUSec": "0"
    }
}
[root@ansible ~]# ansible web_servers -m shell -a 'systemctl status httpd'      192.168.88.101 | FAILED | rc=3 >>
○ httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
    Drop-In: /etc/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: inactive (dead)
       Docs: man:httpd.service(8)

Dec 15 16:09:59 agent1 systemd[1]: httpd.service: Main process exited, code=exited, status=1/FAILURE
Dec 15 16:09:59 agent1 systemd[1]: httpd.service: Failed with result 'exit-code'.
Dec 15 16:09:59 agent1 systemd[1]: Failed to start The Apache HTTP Server.
Dec 15 16:11:48 agent1 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:11:48 agent1 httpd[20388]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.101. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:11:48 agent1 httpd[20388]: Server configured, listening on: port 80
Dec 15 16:11:48 agent1 systemd[1]: Started The Apache HTTP Server.
Dec 15 16:12:59 agent1 systemd[1]: Stopping The Apache HTTP Server...
Dec 15 16:13:00 agent1 systemd[1]: httpd.service: Deactivated successfully.
Dec 15 16:13:00 agent1 systemd[1]: Stopped The Apache HTTP Server.non-zero return code
192.168.88.102 | FAILED | rc=3 >>
○ httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; disabled; preset: disabled)
     Active: inactive (dead)
       Docs: man:httpd.service(8)

Dec 15 16:05:02 agent2 systemd[1]: httpd.service: Deactivated successfully.
Dec 15 16:05:02 agent2 systemd[1]: Stopped The Apache HTTP Server.
Dec 15 16:05:02 agent2 systemd[1]: httpd.service: Consumed 7.248s CPU time.
Dec 15 16:09:59 agent2 systemd[1]: Starting The Apache HTTP Server...
Dec 15 16:09:59 agent2 httpd[16953]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.102. Set the 'ServerName' directive globally to suppress this message
Dec 15 16:09:59 agent2 httpd[16953]: Server configured, listening on: port 80
Dec 15 16:09:59 agent2 systemd[1]: Started The Apache HTTP Server.
Dec 15 16:12:59 agent2 systemd[1]: Stopping The Apache HTTP Server...
Dec 15 16:13:00 agent2 systemd[1]: httpd.service: Deactivated successfully.
Dec 15 16:13:00 agent2 systemd[1]: Stopped The Apache HTTP Server.non-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'netstat -pantul|grep 80'     192.168.88.102 | FAILED | rc=1 >>
non-zero return code
192.168.88.101 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]#

小结:

yum/dnf模块:主要负责(安装或卸载软件)

service模块:主要负责(服务的管理、启动、停止、重启、开机自启、开机不自启)

  1. command 与 shell 模块(重点)

两个模块都是用于执行 Linux 命令的,这对于熟悉命令的工程师来说,用起来非常 high

shell 模块与 command 模块差不多(command 模块不能执行一些类似 $HOME,>,<,| 等符号,但 shell 模块可以)

shell 模块(重点)

https://docs.ansible.com/ansible/latest/modules/command_module.html

https://docs.ansible.com/ansible/latest/modules/shell_module.html

[root@ansible ~]# ansible -m command web_servers -a "useradd user666"

[root@ansible ~]# ansible -m command web_servers -a "id user666"

[root@ansible ~]# ansible -m shell web_servers -a "cat /etc/passwd"     

[root@ansible ~]# ansible -m shell web_servers -a "cd $HOME;pwd"

[root@ansible ~]# ansible -m shell web_servers -a "cd /home/user666;pwd"
[root@ansible ~]# ansible web_servers -m shell -a 'id user666'
192.168.88.102 | FAILED | rc=1 >>
id: ‘user666’: no such usernon-zero return code
192.168.88.101 | FAILED | rc=1 >>
id: ‘user666’: no such usernon-zero return code
[root@ansible ~]# ansible -m command web_servers -a "useradd user666"
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]# ansible -m command web_servers -a "id user666"
192.168.88.102 | CHANGED | rc=0 >>
uid=2002(user666) gid=2002(user666) groups=2002(user666)
192.168.88.101 | CHANGED | rc=0 >>
uid=2002(user666) gid=2002(user666) groups=2002(user666)
[root@ansible ~]# ansible web_servers -m shell -a 'id user666'
192.168.88.102 | CHANGED | rc=0 >>
uid=2002(user666) gid=2002(user666) groups=2002(user666)
192.168.88.101 | CHANGED | rc=0 >>
uid=2002(user666) gid=2002(user666) groups=2002(user666)
[root@ansible ~]# ansible -m shell web_servers -a "cat /etc/passwd|tail -n 1"
192.168.88.102 | CHANGED | rc=0 >>
user666:x:2002:2002::/home/user666:/bin/bash
192.168.88.101 | CHANGED | rc=0 >>
user666:x:2002:2002::/home/user666:/bin/bash
[root@ansible ~]# ansible -m shell web_servers -a "cd $HOME;pwd"
192.168.88.102 | CHANGED | rc=0 >>
/root
192.168.88.101 | CHANGED | rc=0 >>
/root
[root@ansible ~]# ansible -m shell web_servers -a "cd /home/user666;pwd"
192.168.88.102 | CHANGED | rc=0 >>
/home/user666
192.168.88.101 | CHANGED | rc=0 >>
/home/user666
[root@ansible ~]#

回顾:wc命令

wc => wordcount统计

wc -c 字节数(统计大小)

wc -m 字符数(统计字符数量)

wc -l 统计总行数 => l == line(行)

wc -w 统计单词数

小结:shell与command

都能完成shell命令,shell模块会更强大一些,支持管道以及特殊符号!

注意:shell模块并不是百分之百任何命令都可以,比如vim或ll别名就不可以。不建议大家去记忆哪些命令不可以,大家只要养成任何在生产环境里的命令都要先在测试环境里测试一下的习惯就好。

  1. fetch 模块

作用:fetch 模块与 copy 模块类似,但作用相反;用于把远程机器的文件拷贝到本地,类似于收作业!

https://docs.ansible.com/ansible/latest/modules/fetch_module.html#fetch-module

注意: fetch模块不能从远程拷贝目录到本地!

img

img

第1步: 在两台被管理机上分别创建一个同名文件(但内容不同)

[root@node1 ~]# echo agent1 > /tmp/1.txt
[root@node2 ~]# echo agent2 > /tmp/1.txt

第2步: 从master上fetch文件

[root@ansible ~]# rm -rf /tmp/*
[root@ansible ~]# ls /tmp
[root@ansible ~]# ansible web_servers -m fetch -a 'src=/tmp/1.txt dest=/tmp/'
192.168.88.102 | CHANGED => {
    "changed": true,
    "checksum": "b27fb3c4285612643593d53045035bd8d972c995",
    "dest": "/tmp/192.168.88.102/tmp/1.txt",
    "md5sum": "cd0bd22f33d6324908dbadf6bc128f52",
    "remote_checksum": "b27fb3c4285612643593d53045035bd8d972c995",
    "remote_md5sum": null
}
192.168.88.101 | CHANGED => {
    "changed": true,
    "checksum": "d2911a028d3fcdf775a4e26c0b9c9d981551ae41",
    "dest": "/tmp/192.168.88.101/tmp/1.txt",
    "md5sum": "0d59da0b2723eb03ecfbb0d779e6eca5",
    "remote_checksum": "d2911a028d3fcdf775a4e26c0b9c9d981551ae41",
    "remote_md5sum": null
}
[root@ansible ~]# ls /tmp
192.168.88.101  192.168.88.102
[root@ansible ~]# ls /tmp/192.168.88.101
tmp
[root@ansible ~]# ls /tmp/192.168.88.102
tmp
[root@ansible ~]# ls /tmp/192.168.88.101/tmp
1.txt
[root@ansible ~]# ls /tmp/192.168.88.102/tmp
1.txt
[root@ansible ~]# cat /tmp/192.168.88.101/tmp/1.txt
agent1
[root@ansible ~]# cat /tmp/192.168.88.102/tmp/1.txt
agent2
[root@ansible ~]#

安装tree:

yum install tree -y

以大树结构显示文件信息(包含多层)
tree /tmp

img

img

小结:

fetch功能与copy功能正好相反,负责从远程服务器收集文件到本地

fetch里面一共有两个参数,src与dest

  1. user 模块

作用:user 模块用于管理用户账号和用户属性

https://docs.ansible.com/ansible/latest/modules/user_module.html#user-module

state 参数有两个值:① present创建 ② absent删除

案例1:创建aaa用户,默认为普通用户,创建家目录

[root@ansible ~]# ansible web_servers -m user -a 'name=aaa state=present'
[root@ansible ~]# ansible web_servers -m shell -a 'id aaa'
192.168.88.101 | FAILED | rc=1 >>
id: ‘aaa’: no such usernon-zero return code
192.168.88.102 | FAILED | rc=1 >>
id: ‘aaa’: no such usernon-zero return code
[root@ansible ~]# ansible web_servers -m user -a 'name=aaa state=present'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1001,
    "home": "/home/aaa",
    "name": "aaa",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1001
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 1001,
    "home": "/home/aaa",
    "name": "aaa",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 1001
}
[root@ansible ~]# ansible web_servers -m shell -a 'id aaa'
192.168.88.101 | CHANGED | rc=0 >>
uid=1001(aaa) gid=1001(aaa) groups=1001(aaa)
192.168.88.102 | CHANGED | rc=0 >>
uid=1001(aaa) gid=1001(aaa) groups=1001(aaa)
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home'
192.168.88.101 | CHANGED | rc=0 >>
aaa
jaking
192.168.88.102 | CHANGED | rc=0 >>
aaa
jaking
[root@ansible ~]#

案例2:创建bbb系统用户,并且登录shell环境为/sbin/nologin

[root@ansible ~]# ansible web_servers -m user -a 'name=bbb state=present system=yes  shell="/sbin/nologin"'
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/passwd|grep aaa'
192.168.88.102 | CHANGED | rc=0 >>
aaa:x:1001:1001::/home/aaa:/bin/bash
192.168.88.101 | CHANGED | rc=0 >>
aaa:x:1001:1001::/home/aaa:/bin/bash
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/passwd|grep bbb'
192.168.88.102 | FAILED | rc=1 >>
non-zero return code
192.168.88.101 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]# ansible web_servers -m user -a 'name=bbb state=present system=yes  shell="/sbin/nologin"'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 987,
    "home": "/home/bbb",
    "name": "bbb",
    "shell": "/sbin/nologin",
    "state": "present",
    "system": true,
    "uid": 987
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 987,
    "home": "/home/bbb",
    "name": "bbb",
    "shell": "/sbin/nologin",
    "state": "present",
    "system": true,
    "uid": 987
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/passwd|grep bbb'    192.168.88.102 | CHANGED | rc=0 >>
bbb:x:987:987::/home/bbb:/sbin/nologin
192.168.88.101 | CHANGED | rc=0 >>
bbb:x:987:987::/home/bbb:/sbin/nologin
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home'
192.168.88.101 | CHANGED | rc=0 >>
aaa
bbb
jaking
192.168.88.102 | CHANGED | rc=0 >>
aaa
bbb
jaking
[root@ansible ~]#

案例3:创建ccc用户, 使用uid参数指定uid, 使用password参数传密码

[root@ansible ~]# echo 123456 | openssl passwd -1 -stdin
$1$DpcyhW2G$Kb/y1f.lyLI4MpRlHU9oq0

passwd可以使用-l选项,代表lock,锁定账号,不允许登录系统
openssl用于生成散列密码,openssl passwd生成密码并交给-stdin标准输入,openssl passwd -1相当于md5算法

下一句命令注意一下格式,密码要用双引号引起来,单引号的话验证时会密码不正确

[root@ansible ~]# ansible web_servers -m user -a 'name=ccc uid=2000 state=present password="$1$DpcyhW2G$Kb/y1f.lyLI4MpRlHU9oq0"'
[root@ansible ~]# echo 123456 | openssl passwd -1 -stdin
$1$vPN25k0n$/W3JBBvD42iqqjn0FGX8g/
[root@ansible ~]# ansible web_servers -m user -a 'name=ccc uid=2000 state=present password="$1$vPN25k0n$/W3JBBvD42iqqjn0FGX8g/"'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 2000,
    "home": "/home/ccc",
    "name": "ccc",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 2000
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 2000,
    "home": "/home/ccc",
    "name": "ccc",
    "password": "NOT_LOGGING_PASSWORD",
    "shell": "/bin/bash",
    "state": "present",
    "system": false,
    "uid": 2000
}
[root@ansible ~]# ansible web_servers -m shell -a 'id ccc'
192.168.88.101 | CHANGED | rc=0 >>
uid=2000(ccc) gid=2000(ccc) groups=2000(ccc)
192.168.88.102 | CHANGED | rc=0 >>
uid=2000(ccc) gid=2000(ccc) groups=2000(ccc)
[root@ansible ~]# ansible web_servers -m shell -a 'grep ccc /etc/shadow'
192.168.88.101 | CHANGED | rc=0 >>
ccc:$1$vPN25k0n$/W3JBBvD42iqqjn0FGX8g/:20439:0:99999:7:::
192.168.88.102 | CHANGED | rc=0 >>
ccc:$1$vPN25k0n$/W3JBBvD42iqqjn0FGX8g/:20439:0:99999:7:::
[root@ansible ~]#

[root@node1 ~]# cat /etc/shadow
root:$6$rounds=100000$1U3lxaOMV42kg600$cWAWsMdMVxwl6YjCBixlBMslL.fn8cjrus4ewVk4tuYy.c3xs9RrO41OzJ9s8MZtobDMqRy.Klvxklo5Sa/S7/:20439:0:99999:7:::
bin:*:19760:0:99999:7:::
daemon:*:19760:0:99999:7:::
adm:*:19760:0:99999:7:::
lp:*:19760:0:99999:7:::
sync:*:19760:0:99999:7:::
shutdown:*:19760:0:99999:7:::
halt:*:19760:0:99999:7:::
mail:*:19760:0:99999:7:::
operator:*:19760:0:99999:7:::
games:*:19760:0:99999:7:::
ftp:*:19760:0:99999:7:::
nobody:*:19760:0:99999:7:::
systemd-coredump:!!:20287::::::
dbus:!!:20287::::::
polkitd:!!:20287::::::
libstoragemgmt:!*:20287::::::
tss:!!:20287::::::
cockpit-wsinstance:!!:20287::::::
sssd:!!:20287::::::
clevis:!!:20287::::::
setroubleshoot:!!:20287::::::
sshd:!!:20287::::::
chrony:!!:20287::::::
tcpdump:!!:20287::::::
jaking:$6$.HAiZN9JrMMVRO3T$ndgJ1K7qQy8CwnMOsmW7eTS0EA47MD4orw.XNO.t5oreA.5nQg6HeW4c.cjpfHPW1.E8hvvYuTf3LF6Jahk52.::0:99999:7:::
apache:!!:20438::::::
aaa:!!:20439:0:99999:7:::
bbb:!!:20439::::::
ccc:$1$vPN25k0n$/W3JBBvD42iqqjn0FGX8g/:20439:0:99999:7:::
[root@node1 ~]# ll /etc/shadow
---------- 1 root root 1013 Dec 17 11:48 /etc/shadow
[root@node1 ~]# su - aaa
[aaa@node1 ~]$ exit
logout
[root@node1 ~]# su - bbb
This account is currently not available.
[root@node1 ~]# su - aaa
[aaa@node1 ~]$ su - ccc
Password: 故意输入错误密码
su: Authentication failure
[aaa@node1 ~]$ su - ccc
Password: 输入正确密码123456
[ccc@node1 ~]$

案例4:创建一个普通用户叫hadoop,并产生空密码密钥对

[root@ansible ~]# ansible web_servers -m user -a 'name=hadoop generate_ssh_key=yes'
[root@ansible ~]# ansible web_servers -m shell -a 'id hadoop'
192.168.88.102 | FAILED | rc=1 >>
id: ‘hadoop’: no such usernon-zero return code
192.168.88.101 | FAILED | rc=1 >>
id: ‘hadoop’: no such usernon-zero return code
[root@ansible ~]# ansible web_servers -m user -a 'name=hadoop generate_ssh_key=yes'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 2001,
    "home": "/home/hadoop",
    "name": "hadoop",
    "shell": "/bin/bash",
    "ssh_fingerprint": "3072 SHA256:gZuW6RsRZYZjxiuOmeqUFf1rjdc9AK0JkzGHqi7T95A ansible-generated on node2 (RSA)",
    "ssh_key_file": "/home/hadoop/.ssh/id_rsa",
    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCyzAx8gVvWI6TrroHHF3YRpakoboD3bu3POXBs+dNrd4jczAL5lGNoDFO1rcgHZxMp0Ljh25cGBYd93yuRVOgoGerwyLMs9DTHazFvoPPHdunyy+Nh1GodTXLNVnXFPBJ3PSI/CIatw9ZdjEHvTmwt8uYIWCJ0fdzQ0bSHMBjclIoqZC8x6Q+OEQXNFgN6fdgJwRVdyHqQnM63Ih54bFaElNauIUDuXE3r7M/CW66q2oW1br+F/TveNWx+zDcbX0U4i2dkN/z/W4NiKcwue8UlMCmgo8B+ELIiElN6YIdb4HT2q/B5KtRfbra3HtZ443ukJsWgFg3zvy9UQ4qkIRe0Iw4sE+ER5lerC6Tz2AlEtVnw6UAhLKdjbKulKNfHBXbfAQ4jq6Ec+Q+kgaRHfl8nJEUGtep0GNoXPn/WaMXguEVw3syFYyGD+VCQYkvAg1Lr3h+7IExEcbcMogH77Y0yqe+bI1o+OE8AFpENVBC2abRQhnMISoxO8LnF26f6WE8= ansible-generated on node2",
    "state": "present",
    "system": false,
    "uid": 2001
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "comment": "",
    "create_home": true,
    "group": 2001,
    "home": "/home/hadoop",
    "name": "hadoop",
    "shell": "/bin/bash",
    "ssh_fingerprint": "3072 SHA256:upHw2exMZf6OXcF4YcgwwSJa4azagAjQW26u4wPgxQ4 ansible-generated on node1 (RSA)",
    "ssh_key_file": "/home/hadoop/.ssh/id_rsa",
    "ssh_public_key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCzDBBA4E44CdSgndpuk8WryLNBai+2QUF4U3N7yybkXWqRQsI4pvQtPeIbKoh4xPFeZ8ITb40tEDaNfh1CtdbOE3ZbRogvhTGUHLzYxiceCc8EjG1QPGU777oObJfuVSZb6HXFWmnLN4zuDcYaM05dDCEdI/Ao3iqmOrNJocQYMXqKogGp6DDrn5k05Hg5/3wUS929eBdj0eZnPpprBACr5jfca8nIu0aGGXdUa+UOtlRFf/RIjvKYicp1Q8MefTs15EXy+hzxqd/63tHVjkg6mvGQ8LRg+OZxJg/NnYBaFZDCipIwBkXPdaK3LDDc5i6jwEVZcvI/RAao0TEQT4DDZSPmdzgf6YF2CmY+k3eZ979G4zctocnz9ZHNwRMI2mtbI0bRujssHopqWAQ9qVlL01pZxFTj86GtNs2s6Tqcco/6X+5+OSCpUHfkUMqJjoKwcEcBblr1RiuLqn0gmZXkCK/mQK4cRWCQxDuA7vdugszycqsDwNuWQG4bUzXCG8c= ansible-generated on node1",
    "state": "present",
    "system": false,
    "uid": 2001
}
[root@ansible ~]# ansible web_servers -m shell -a 'id hadoop'                   
192.168.88.102 | CHANGED | rc=0 >>
uid=2001(hadoop) gid=2001(hadoop) groups=2001(hadoop)
192.168.88.101 | CHANGED | rc=0 >>
uid=2001(hadoop) gid=2001(hadoop) groups=2001(hadoop)
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home/'
192.168.88.101 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
192.168.88.102 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
[root@ansible ~]# ansible web_servers -m shell -a 'ls -a /home/hadoop'
192.168.88.101 | CHANGED | rc=0 >>
.
..
.bash_logout
.bash_profile
.bashrc
.ssh
192.168.88.102 | CHANGED | rc=0 >>
.
..
.bash_logout
.bash_profile
.bashrc
.ssh
[root@ansible ~]# ansible web_servers -m shell -a 'ls -a /home/hadoop/.ssh'
192.168.88.101 | CHANGED | rc=0 >>
.
..
id_rsa
id_rsa.pub
192.168.88.102 | CHANGED | rc=0 >>
.
..
id_rsa
id_rsa.pub
[root@ansible ~]# ansible web_servers -m shell -a 'cat /home/hadoop/.ssh/id_rsa.pub'
192.168.88.102 | CHANGED | rc=0 >>
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCd+etZgBZc+nl3gpz3e3Du/ZIWOdnGDAMAenWzPJBSF22CacAC8kN1LdCph1GSP6gtGM7JduBzGEPTelXovj4lQlYT45J3ScWTEwkPJnJLRBdlzfPnc3l9DOA0WXXcZpiYm1/wTJolk+JKJBghHiqwOlvVrxXIIilkVkKg/NtecTXyK23UOIVS80wOTpr5LqNljXL5S9DYapDO6rlrAJO9hnXOhD8GAwYR21dW2sHjpDCPp2wOZdVjqSMj81gOd8f9UeFCIoPcgPb/85puIA7iAPOD8rKMY9dT/qhEL9EDAfzibt3T116YVcZ5epym4UCKTQ0ocLRqPahIzRZ0IepF5l6KtBtbOMhB3plXiXWEBj5/PCx6PAvSzXuzcR7ugQie6Lk1YfcK+xYvVY6PHC0GxXFDAEy4LEfsLViEr52JzvXwmIpkMpLal5x2ljggFo0lJ878QXoBG/Rti2dWSqGvx5ucecVMKjb3pXigzwY+duirzTq8dHBe140qQfOjCLM= ansible-generated on node2
192.168.88.101 | CHANGED | rc=0 >>
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCrbe6M+BZgXnI5IqYmV9uRCGSTT0fo22AlmyyJ06r4S6sZFp4U7Owv00yxZ96Z5ba/9nzGOxjwbLzrEHyl8a7IPp1gx/3JR8WEKafwAsBsFz5xiThh+IDQgc7QGdFh9LhIBbRKJMxaGfDuj0kV/kvrOZx8XUmbC0L712EGuCB2BRVfmRMHFMPD7DKsnnqKU6ek7aunvGpysyavwghmB8DPJl/HjTwo5E9sot2+aBLXLgvyV4bNtfqZ+rZWv1D7IRxoxdhWIOL9yWWHMbATJZeP9+iE1GRZvh4dTfMFt2riKDb8/2dOO83HmFtKYgRovZeyJ3jNzS4tIRG1fC+Raqis3Ykt8lz1kEy5rmI9amabny2CNayZu6ZfQXXgRD/CDdgmM7wq4z9kQgzedcEF8uR61uAVQLGTQcIVD0BFOrC01z9w7A+GVfS2e/DRHgGI8RjVktAJKom8vi8H+gWB0IJh8zvHoNELCjOvkrMMcq8aSt9CG86jvDSg/ZamILt7sRs= ansible-generated on node1
[root@ansible ~]#

案例5:删除aaa用户,但家目录默认不删除

[root@ansible ~]# ansible web_servers -m user -a 'name=aaa state=absent'
[root@ansible ~]# ansible web_servers -m shell -a 'id aaa'
192.168.88.102 | CHANGED | rc=0 >>
uid=1001(aaa) gid=1001(aaa) groups=1001(aaa)
192.168.88.101 | CHANGED | rc=0 >>
uid=1001(aaa) gid=1001(aaa) groups=1001(aaa)
[root@ansible ~]# ansible web_servers -m user -a 'name=aaa state=absent'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "force": false,
    "name": "aaa",
    "remove": false,
    "state": "absent"
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "force": false,
    "name": "aaa",
    "remove": false,
    "state": "absent"
}
[root@ansible ~]# ansible web_servers -m shell -a 'id aaa'
192.168.88.101 | FAILED | rc=1 >>
id: ‘aaa’: no such usernon-zero return code
192.168.88.102 | FAILED | rc=1 >>
id: ‘aaa’: no such usernon-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home'
192.168.88.101 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
192.168.88.102 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
[root@ansible ~]#

案例6:删除bbb用户,使用remove=yes参数让其删除用户的同时也删除家目录

[root@ansible ~]# ansible web_servers -m user -a 'name=bbb state=absent remove=yes'
[root@ansible ~]# ansible web_servers -m shell -a 'id bbb'
192.168.88.102 | CHANGED | rc=0 >>
uid=987(bbb) gid=987(bbb) groups=987(bbb)
192.168.88.101 | CHANGED | rc=0 >>
uid=987(bbb) gid=987(bbb) groups=987(bbb)
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home'
192.168.88.102 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
192.168.88.101 | CHANGED | rc=0 >>
aaa
bbb
ccc
hadoop
jaking
[root@ansible ~]# ansible web_servers -m user -a 'name=bbb state=absent remove=yes'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "force": false,
    "name": "bbb",
    "remove": true,
    "state": "absent",
    "stderr": "userdel: bbb mail spool (/var/spool/mail/bbb) not found\n",
    "stderr_lines": [
        "userdel: bbb mail spool (/var/spool/mail/bbb) not found"
    ]
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "force": false,
    "name": "bbb",
    "remove": true,
    "state": "absent",
    "stderr": "userdel: bbb mail spool (/var/spool/mail/bbb) not found\n",
    "stderr_lines": [
        "userdel: bbb mail spool (/var/spool/mail/bbb) not found"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'id bbb'                      192.168.88.101 | FAILED | rc=1 >>
id: ‘bbb’: no such usernon-zero return code
192.168.88.102 | FAILED | rc=1 >>
id: ‘bbb’: no such usernon-zero return code
[root@ansible ~]# ansible web_servers -m shell -a 'ls /home'                    192.168.88.101 | CHANGED | rc=0 >>
aaa
ccc
hadoop
jaking
192.168.88.102 | CHANGED | rc=0 >>
aaa
ccc
hadoop
jaking
[root@ansible ~]#

小结:

user模块作用:创建用户 和 删除用户

创建用户(state=present)和删除用户(state=absent)

  1. group 模块

作用:group 模块用于管理用户组和用户组属性

https://docs.ansible.com/ansible/latest/modules/group_module.html#group-module

创建组

[root@ansible ~]# ansible web_servers -m group -a 'name=yunwei gid=3000 state=present'
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/group|grep yunwei'
192.168.88.101 | FAILED | rc=1 >>
non-zero return code
192.168.88.102 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]# ansible web_servers -m group -a 'name=yunwei gid=3000 state=present'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "gid": 3000,
    "name": "yunwei",
    "state": "present",
    "system": false
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "gid": 3000,
    "name": "yunwei",
    "state": "present",
    "system": false
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/group|grep yunwei'  192.168.88.102 | CHANGED | rc=0 >>
yunwei:x:3000:
192.168.88.101 | CHANGED | rc=0 >>
yunwei:x:3000:
[root@ansible ~]#

删除组(如果有用户的gid为此组,则删除不了)

[root@ansible ~]# ansible web_servers -m group -a 'name=yunwei state=absent'
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/group|grep yunwei'
192.168.88.101 | CHANGED | rc=0 >>
yunwei:x:3000:
192.168.88.102 | CHANGED | rc=0 >>
yunwei:x:3000:
[root@ansible ~]# ansible web_servers -m group -a 'name=yunwei state=absent'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "name": "yunwei",
    "state": "absent"
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "name": "yunwei",
    "state": "absent"
}
[root@ansible ~]# ansible web_servers -m shell -a 'cat /etc/group|grep yunwei'
192.168.88.102 | FAILED | rc=1 >>
non-zero return code
192.168.88.101 | FAILED | rc=1 >>
non-zero return code
[root@ansible ~]#

小结:

group 模块比较简单,主要用于(创建用户组)和(删除用户组)

  1. cron 模块

crontab 计划任务(定时器)->(重点)

crontab -l  查看定时器
crontab -e  编辑定时器
分 时 日 月 周 要执行命令的绝对路径
* * * * * /usr/sbin/ntpdate -u ntp4.aliyun.com

如果不知道某个命令的绝对路径 => which 命令
# 每天凌晨4点执行某个命令 => 0 4 * * * 命令
# 每10分钟执行某个命令 => */10 * * * * 命令
# 每周3的凌晨2点执行某个命令 => 0 2 * * 3 命令

计划任务常见格式:

星号(*):代表所有可能的值,例如month字段如果是星号,则表示在满足其它字段的制约条件后每月都执行该命令操作。
逗号(,):可以用逗号隔开的值指定一个列表范围,例如,"1,2,5,7,8,9"
中杠(-):可以用整数之间的中杠表示一个整数范围,例如"2-6"表示"2,3,4,5,6"
正斜线(/):可以用正斜线指定时间的间隔频率,例如"0-23/2"表示每两小时执行一次。同时正斜线可以和星号一起使用,例如*/10,如果用在minute字段,表示每十分钟执行一次。

时间案例:

03 * * * *  每小时03分执行
01 02 * * * 每天两点01分执行
01 02 3 * * 每个月3号2点01分执行
01 02 3 1 * 每年1月3号2点01分执行
01 02 * * 0 每周日的2点01分执行
01,02,03 * * * * 每小时 01 02 03分执行
01,02,03 1,2,3 * * * 每天1 2 3点的  01 02 03分执行
*/10 * * * *   每隔10分执行
00 */2 * * *   每隔两小时执行
00 * */2 * *   每隔两天执行
2 8-20/3 * * *  从8点开始到20点结束,每隔3个小时的02分执行一次

案例1:每分钟创建一个类似/root/file_202512151518.txt这种格式的文件

crontab -e
* * * * * touch /root/file_$(date +\%Y\%m\%d\%H\%M).txt

问题:在crontab中如果出现了%百分号,默认无法执行
解决:添加反斜杠转义\%
[root@ansible ~]# crontab -e
no crontab for root - using an empty one
crontab: installing new crontab
[root@ansible ~]# crontab -l
* * * * * touch /root/file_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ls
[root@ansible ~]# date
Wed Dec 17 02:47:40 PM CST 2025
[root@ansible ~]# ls
[root@ansible ~]# watch -n 1 date
[root@ansible ~]# ls
file_202512171448.txt
[root@ansible ~]# date
Wed Dec 17 02:48:16 PM CST 2025
[root@ansible ~]# watch -n 1 date
[root@ansible ~]# ls
file_202512171448.txt  file_202512171449.txt
[root@ansible ~]# crontab -e
crontab: installing new crontab
[root@ansible ~]# crontab -l
[root@ansible ~]#

案例2:针对/tmp目录及里面的文件定时压缩 => file_202512151518.tar.gz

crontab -e
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp

问题:在crontab中如果出现了%百分号,默认无法执行
解决:添加反斜杠转义\%

注意:如果crontab没有执行成功,可以通过cat /var/log/cron日志文件
[root@ansible ~]# ls /tmp
192.168.88.101  192.168.88.102
[root@ansible ~]# crontab -e
crontab: installing new crontab
[root@ansible ~]# crontab -l
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# ls
file_202512171448.txt  file_202512171449.txt
[root@ansible ~]# date
Wed Dec 17 02:50:32 PM CST 2025
[root@ansible ~]# watch -n 1 date
[root@ansible ~]# ls
file_202512171448.txt  file_202512171449.txt  file_202512171451.tar.gz
[root@ansible ~]# tar xvf file_202512171451.tar.gz
tmp/
tmp/.X11-unix/
tmp/.ICE-unix/
tmp/.XIM-unix/
tmp/.font-unix/
tmp/192.168.88.102/
tmp/192.168.88.102/tmp/
tmp/192.168.88.102/tmp/1.txt
tmp/192.168.88.101/
tmp/192.168.88.101/tmp/
tmp/192.168.88.101/tmp/1.txt
[root@ansible ~]# ls
file_202512171448.txt  file_202512171449.txt  file_202512171451.tar.gz  tmp
[root@ansible ~]# ls tmp/
192.168.88.101  192.168.88.102
[root@ansible ~]# ls
file_202512171448.txt  file_202512171449.txt  file_202512171451.tar.gz  tmp
[root@ansible ~]# date
Wed Dec 17 02:51:32 PM CST 2025
[root@ansible ~]# watch -n 1 date
[root@ansible ~]# ls
file_202512171448.txt  file_202512171451.tar.gz  tmp
file_202512171449.txt  file_202512171452.tar.gz
[root@ansible ~]# date
Wed Dec 17 02:52:07 PM CST 2025
[root@ansible ~]#

cron模块用于管理周期性时间任务

https://docs.ansible.com/ansible/latest/modules/cron_module.html#cron-module

创建一个cron任务,不指定user的话,默认就是root

如果minute,hour,day,month,week不指定的话,默认都为*

创建cron任务

[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron1" user=root job="touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt" minute=*/2' 
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron2" user=root job="/usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt"' 
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'                  
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'rm -rf /tmp/*'               
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'                    192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_2d97h7_s
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_diho066q
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron1" user=root job="touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt" minute=*/2'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "test cron1"
    ]
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "test cron1"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'date'                        192.168.88.101 | CHANGED | rc=0 >>
Wed Dec 17 03:01:43 PM CST 2025
192.168.88.102 | CHANGED | rc=0 >>
Wed Dec 17 03:01:43 PM CST 2025
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'                    192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_r2p8eubg
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_cv840kxo
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_gbfop7jf
test_202512171502.txt
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_0xrf_xep
test_202512171502.txt
[root@ansible ~]# ansible web_servers -m shell -a 'date'
192.168.88.102 | CHANGED | rc=0 >>
Wed Dec 17 03:02:30 PM CST 2025
192.168.88.101 | CHANGED | rc=0 >>
Wed Dec 17 03:02:30 PM CST 2025
[root@ansible ~]# ansible web_servers -m shell -a 'date -R'
192.168.88.102 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:02:41 +0800
192.168.88.101 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:02:41 +0800
[root@ansible ~]# ansible web_servers -m shell -a 'date -R'
192.168.88.101 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:03:33 +0800
192.168.88.102 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:03:33 +0800
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp/'
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_7maedr2f
test_202512171502.txt
test_202512171504.txt
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_fs712da1
test_202512171502.txt
test_202512171504.txt
[root@ansible ~]# ansible web_servers -m shell -a 'date -R'
192.168.88.101 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:04:19 +0800
192.168.88.102 | CHANGED | rc=0 >>
Wed, 17 Dec 2025 15:04:19 +0800
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'
192.168.88.102 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
192.168.88.101 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'                 192.168.88.102 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
192.168.88.101 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'                    192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_e109b4qn
test_202605291158.txt
testfile_202605291159.txt
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_le_jvmx0
test_202605291158.txt
testfile_202605291159.txt
[root@ansible ~]# date
Fri May 29 11:59:56 AM CST 2026
[root@ansible ~]# date
Fri May 29 12:00:04 PM CST 2026
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_y7r6kmtz
test_202605291158.txt
test_202605291200.txt
testfile_202605291159.txt
testfile_202605291200.txt
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_1ek9l7i6
test_202605291158.txt
test_202605291200.txt
testfile_202605291159.txt
testfile_202605291200.txt
[root@ansible ~]#

删除cron任务

[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron1" state=absent'
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron2" state=absent'
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'
192.168.88.101 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
192.168.88.102 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron1" state=absent'
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": []
}
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": []
}
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'                  
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]#

[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'                 192.168.88.102 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
192.168.88.101 | CHANGED | rc=0 >>
#Ansible: test cron1
*/2 * * * * touch /tmp/test_$(date +\%Y\%m\%d\%H\%M).txt
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'                    192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_e109b4qn
test_202605291158.txt
testfile_202605291159.txt
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_le_jvmx0
test_202605291158.txt
testfile_202605291159.txt
[root@ansible ~]# date
Fri May 29 11:59:56 AM CST 2026
[root@ansible ~]# date
Fri May 29 12:00:04 PM CST 2026
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_y7r6kmtz
test_202605291158.txt
test_202605291200.txt
testfile_202605291159.txt
testfile_202605291200.txt
192.168.88.101 | CHANGED | rc=0 >>
ansible_ansible.legacy.command_payload_1ek9l7i6
test_202605291158.txt
test_202605291200.txt
testfile_202605291159.txt
testfile_202605291200.txt
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron1" state=absent'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "test cron2"
    ]
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": [
        "test cron2"
    ]
}
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'
192.168.88.102 | CHANGED | rc=0 >>
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
192.168.88.101 | CHANGED | rc=0 >>
#Ansible: test cron2
* * * * * /usr/bin/touch /tmp/testfile_$(date +\%Y\%m\%d\%H\%M).txt
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron2" state=absent'
192.168.88.102 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": []
}
192.168.88.101 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": true,
    "envs": [],
    "jobs": []
}
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron3" state=absent'
192.168.88.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "envs": [],
    "jobs": []
}
192.168.88.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "envs": [],
    "jobs": []
}
[root@ansible ~]# ansible web_servers -m cron -a 'name="test cron2" state=absent'
192.168.88.102 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "envs": [],
    "jobs": []
}
192.168.88.101 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python3"
    },
    "changed": false,
    "envs": [],
    "jobs": []
}
[root@ansible ~]# ansible web_servers -m shell -a 'crontab -l'                 192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]#

面试题:crond 如何备份以及如何查看 crond 执行日志?

[root@ansible ~]# crontab -l
* * * * * /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# date
Sat Mar 28 03:12:40 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:12:44 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:13:11 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:28:35 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# crontab -l
* * * * * /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# date
Sat Mar 28 03:33:13 PM CST 2026
[root@ansible ~]# crontab -l
* * * * * /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# ls /tmp
[root@ansible ~]# /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# ls /tmp
file1.txt  file2.txt  file3.txt  file4.txt  file5.txt  file6.txt  file7.txt  file8.txt  file9.txt
[root@ansible ~]# rm -rf /tmp/*
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:33:32 PM CST 2026
[root@ansible ~]# date
Sat Mar 28 03:33:34 PM CST 2026
[root@ansible ~]# crontab -l
* * * * * /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# date
Sat Mar 28 03:33:55 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:34:09 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# systemctl status crond
○ crond.service - Command Scheduler
     Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; preset: enabled)
     Active: inactive (dead) since Sat 2026-03-28 15:11:50 CST; 22min ago
   Duration: 6h 33min 35.905s
    Process: 900 ExecStart=/usr/sbin/crond -n $CRONDARGS (code=exited, status=0/SUCCESS)
   Main PID: 900 (code=exited, status=0/SUCCESS)
        CPU: 384ms

Mar 28 13:01:01 ansible run-parts[98636]: (/etc/cron.hourly) finished 0anacron
Mar 28 14:01:01 ansible CROND[120051]: (root) CMD (run-parts /etc/cron.hourly)
Mar 28 14:01:01 ansible run-parts[120060]: (/etc/cron.hourly) finished 0anacron
Mar 28 15:01:01 ansible CROND[141447]: (root) CMD (run-parts /etc/cron.hourly)
Mar 28 15:01:01 ansible run-parts[141450]: (/etc/cron.hourly) starting 0anacron
Mar 28 15:01:01 ansible run-parts[141456]: (/etc/cron.hourly) finished 0anacron
Mar 28 15:11:50 ansible systemd[1]: Stopping Command Scheduler...
Mar 28 15:11:50 ansible crond[900]: (CRON) INFO (Shutting down)
Mar 28 15:11:50 ansible systemd[1]: crond.service: Deactivated successfully.
Mar 28 15:11:50 ansible systemd[1]: Stopped Command Scheduler.
[root@ansible ~]# systemctl start crond
[root@ansible ~]# systemctl status crond
● crond.service - Command Scheduler
     Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled; preset: enabled)
     Active: active (running) since Sat 2026-03-28 15:35:26 CST; 3s ago
   Main PID: 153609 (crond)
      Tasks: 1 (limit: 22927)
     Memory: 968.0K
        CPU: 4ms
     CGroup: /system.slice/crond.service
             └─153609 /usr/sbin/crond -n

Mar 28 15:35:26 ansible systemd[1]: Started Command Scheduler.
Mar 28 15:35:26 ansible crond[153609]: (CRON) STARTUP (1.5.7)
Mar 28 15:35:26 ansible crond[153609]: (CRON) INFO (Syslog will be used instead of sendmail.)
Mar 28 15:35:26 ansible crond[153609]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 37% if used.)
Mar 28 15:35:26 ansible crond[153609]: (CRON) INFO (running with inotify support)
Mar 28 15:35:26 ansible crond[153609]: (CRON) INFO (@reboot jobs will be run at computer's startup.)
[root@ansible ~]# date
Sat Mar 28 03:35:32 PM CST 2026
[root@ansible ~]# ls /tmp
[root@ansible ~]# crontab -l
* * * * * /usr/bin/touch /tmp/file{1..9}.txt
[root@ansible ~]# ls /tmp
[root@ansible ~]# date
Sat Mar 28 03:35:45 PM CST 2026
[root@ansible ~]# date
Sat Mar 28 03:36:12 PM CST 2026
[root@ansible ~]# ls /tmp
file1.txt  file2.txt  file3.txt  file4.txt  file5.txt  file6.txt  file7.txt  file8.txt  file9.txt
[root@ansible ~]#
  1. 备份对象

定时任务

crontab -e
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp

在 RHEL/CentOS 系列系统上,所有会被 crond 真正加载的定时任务只来源于以下 3 个固定位置,不存在“可能”在其他目录的情况:

① /var/spool/cron # 用户 crontab

② /etc/crontab # 系统级 crontab

③ /etc/cron.d/ # 系统级片段

img

/var/spool/cron/用户名
  1. 每个用户(含系统用户)通过 crontab -e 写入的内容,最终都会以纯文本文件的形式落在这里,文件名就是账号名。

  2. 例:root 的任务一定在 /var/spool/cron/root

/etc/crontab
  1. 系统级“主表”,固定由 crond 在启动时一次性读取;字段格式比用户表多一个“用户名”列。
/etc/cron.d/*
  1. 系统级“片段”,只要文件名符合 shell 通配规则就会被 crond 一并读入,格式与 /etc/crontab 相同。

凡是“给系统本身、给别的用户、给软件包”用的定时任务,就放到 /etc/crontab 或 /etc/cron.d/xxx****;只给自己账号用的任务才用 crontab -e 写进 /var/spool/cron/用户名****。

因此,完整备份 cron 需打包以上 3 处:

tar zcf cron-backup.tgz \
  /var/spool/cron \
  /etc/crontab \
  /etc/cron.d

img

  1. 一条命令完成在线热备
tar -zcpf /opt/cron_backup_$(date +%F).tar.gz /var/spool/cron /etc/crontab /etc/cron.d/

-zcpf 参数保证:

• c 创建归档

• p 保留权限(600 属性不能丢)

• f 指定文件名

• z 压缩节省空间

可加 –exclude='*.rpmnew' 跳过升级残留文件

  1. 备份验证
tar -tf /opt/cron_backup_*.tar.gz | less
tar -tf /opt/cron_backup_2025-12-17.tar.gz
[root@ansible ~]# tar -tf /opt/cron_backup_2025-12-17.tar.gz
var/spool/cron/
var/spool/cron/root
etc/crontab
etc/cron.d/
etc/cron.d/0hourly

确认各用户文件、/etc/crontab、/etc/cron.d 下的脚本都在列表里即可

  1. 恢复(面试常追问)
tar -zxf /opt/cron_backup_2025-12-15.tar.gz -C /

恢复后执行

crontab -u <user> /var/spool/cron/<user>

保证重新加载进内存;root 可省略 -u

  1. 日志位置

• RHEL/CentOS 6 及以前:/var/log/cron

• RHEL/CentOS 7/8、Rocky、Alma:/var/log/cron

• Ubuntu/Debian:/var/log/cron.log (若 rsyslog 没开则合并到 /var/log/syslog)

日志由 rsyslog 规则决定,可在 /etc/rsyslog.d/50-default.conf 里看 cron.* /var/log/cron 是否被注释。

  1. 现场查日志三板斧

① 看今天 root 有没有跑

grep "CMD" /var/log/cron | grep "$(date +%b' '%e)" | grep root

② 看某个脚本到底有没有执行

grep -E "(/path/to/script|CMD.*script.sh)" /var/log/cron

③ 把日志按任务名聚合

awk '/CMD.*tar/{print $1,$2,$3}' /var/log/cron | sort | uniq -c
awk '/CMD.*tar/{print $1,$2,$3}' /var/log/cron | sort | uniq -c | wc -l

可快速知道 backup.sh 或者定时任务今天跑了几次。

一句话总结(背下来当收尾):

“备份就是 tar 打包 /var/spool/cron、/etc/crontab、/etc/cron.d/ 三处,恢复后用 crontab –u 用户名 文件路径 重新加载;日志集中在 /var/log/cron,用 grep 过滤 CMD 字段就能确认任务是否真正执行。”

案例:

tar 解包后执行 crontab -u root /var/spool/cron/root 即可把 root 的任务重新载入 crond

拓展:

[root@ansible ~]# crontab -l
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# ls /var/spool/cron/
root
[root@ansible ~]# ls /var/spool/cron/root
/var/spool/cron/root
[root@ansible ~]# cat  /var/spool/cron/root
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# cp /var/spool/cron/root /tmp
[root@ansible ~]# crontab -e
crontab: installing new crontab
[root@ansible ~]# cat /var/spool/cron/root
[root@ansible ~]# cp /tmp/root /var/spool/cron/root
cp: overwrite '/var/spool/cron/root'? y
[root@ansible ~]# cat /var/spool/cron/root
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# crontab -l
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# crontab -u root /var/spool/cron/root
[root@ansible ~]# crontab -l
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# crontab -e
crontab: installing new crontab
[root@ansible ~]# crontab -l
[root@ansible ~]# crontab -u root /var/spool/cron/root
[root@ansible ~]# crontab -l
[root@ansible ~]# crontab -u root /tmp/root
[root@ansible ~]# crontab -l
* * * * * /usr/bin/tar -czf file_$(date +\%Y\%m\%d\%H\%M).tar.gz /tmp
[root@ansible ~]# ls
file_202512151520.tar.gz  file_202512151529.tar.gz  file_202512151538.tar.gz
file_202512151521.tar.gz  file_202512151530.tar.gz  file_202512151539.tar.gz
file_202512151522.tar.gz  file_202512151531.tar.gz  file_202512151540.tar.gz
file_202512151523.tar.gz  file_202512151532.tar.gz  file_202512151541.tar.gz
file_202512151524.tar.gz  file_202512151533.tar.gz  file_202512151542.tar.gz
file_202512151525.tar.gz  file_202512151534.tar.gz  file_202512151543.tar.gz
file_202512151526.tar.gz  file_202512151535.tar.gz  file_202512151544.tar.gz
file_202512151527.tar.gz  file_202512151536.tar.gz  file_202512151545.tar.gz
file_202512151528.tar.gz  file_202512151537.tar.gz

小结:

cron本身比较重要,叫做计划任务(定时器)=> ① 创建计划任务 ② 如何查看 ③ 如何查日志 ④ 如何备份

Ansible可以通过cron模块创建或者移除计划任务

  1. script 模块

作用:script 模块用于在远程机器上执行 master 本地脚本

https://docs.ansible.com/ansible/latest/modules/script_module.html#script-module

在master上准备一个test.sh脚本

[root@ansible ~]# cat >/tmp/test.sh<<EOF
#!/bin/bash
mkdir /export/data -p
touch /export/data/file{1..9}
EOF

在web_servers的远程机器里都执行master上的/tmp/test.sh脚本(此脚本不用给执行权限)

[root@ansible ~]# ansible web_servers -m script -a '/tmp/test.sh'
[root@ansible ~]# cat >/tmp/test.sh<<EOF
#!/bin/bash
mkdir /export/data -p
touch /export/data/file{1..9}
EOF
[root@ansible ~]# cat /tmp/test.sh
#!/bin/bash
mkdir /export/data -p
touch /export/data/file{1..9}
[root@ansible ~]# ansible web_servers -m script -a '/tmp/test.sh'
192.168.88.101 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.88.101 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.88.101 closed."
    ],
    "stdout": "",
    "stdout_lines": []
}
192.168.88.102 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.88.102 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.88.102 closed."
    ],
    "stdout": "",
    "stdout_lines": []
}
[root@ansible ~]# ansible web_servers -m shell -a 'ls /tmp'
192.168.88.102 | CHANGED | rc=0 >>
1.txt
aliyun.repo
ansible_ansible.legacy.command_payload_9bq_aq_q
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
192.168.88.101 | CHANGED | rc=0 >>
1.txt
aliyun.repo
ansible_ansible.legacy.command_payload_en7d76e0
epel-cisco-openh264.repo
epel-next.repo
epel-next-testing.repo
epel.repo
epel-testing.repo
[root@ansible ~]# ansible web_servers -m shell -a 'ls /export/data'
192.168.88.102 | CHANGED | rc=0 >>
file1
file2
file3
file4
file5
file6
file7
file8
file9
192.168.88.101 | CHANGED | rc=0 >>
file1
file2
file3
file4
file5
file6
file7
file8
file9
[root@ansible ~]#

适用场景:对Shell脚本比较熟悉,然后希望通过Shell脚本完成所有机器的环境部署!

  1. Ansible Playbook(重点)

作用:Playbook(剧本),主要用于实现一些较为复杂的自动化部署操作。

软件安装配置,如Keepalived、MySQL集群、Redis集群、大数据集群、K8s集群。

  1. Playbook 概述

Playbook(剧本): 是 Ansible 用于配置,部署和管理被控节点的剧本,用于 Ansible 操作的编排。

参考: https://docs.ansible.com/ansible/latest/user*guide/playbooks*intro.html

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

  1. yaml 格式

  • 以.yaml或.yml结尾
  • 文件的第一行以 "---"开始,表明 yaml 文件的开始(可选)
  • 以#号开头为注释
  • 列表中的所有成员都开始于相同的缩进级别, 并使用一个"- "作为开头(一个横杠和一个空格)
  • 一个字典是由一个简单的 键(key): 值(value)的形式组成(这个冒号后面必须有一个空格)

注意: 写这种文件不要使用Tab键,都使用空格

参考: https://docs.ansible.com/ansible/latest/reference_appendices/YAMLSyntax.html#yaml-syntax

看一个官方案例:

---
# 一位职工记录 name: Example Developer
job: Developer
skill: Elite
employed: True
foods:
  - Apple
  - Orange
  - Strawberry
  - Mango
languages:
  ruby: Elite
  python: Elite
  dotnet: Lame
  1. Playbook 常见语法

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

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

- hosts: web_servers
  remote_user: root

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

如果一个host执行task失败, 修正playbook 中的错误, 然后重新执行即可

tasks:
  - name: ensure apache is at the latest version        
    yum: name=httpd,httpd-devel state=latest
    
  - name: write the apache config file                
    copy: src=/etc/httpd/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf

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

注意:不管有多少个通知者进行了notify,等到play中的所有task执行完成之后,handlers也只会被执行一次

handlers最佳的应用场景是用来重启服务或者触发系统重启操作,除此以外很少用到了

notify:                                  
    - restart apache
    
    - name: ensure apache is running (and enable it at boot)
      service: name=httpd state=started enabled=yes
    
  handlers:
    - name: restart apache
      service: name=httpd state=restarted

英文单词 => variables: 变量 => playbook中简化了

① 定义变量:

vars:

- 变量名称: 变量的值

② 调用变量:

{{变量名称}}

定义变量可以被多次方便调用

Playbook 测试案例

编写测试 Playbook test.yaml

[root@ansible ~]# mkdir -p /etc/ansible/playbook/
[root@ansible ~]# vim /etc/ansible/playbook/test.yaml
---
 - hosts: web_servers
   remote_user: root
   vars:
   - user: test
   tasks:
   - name: create user
     user: name={{user}} state=present

执行测试 Playbook test.yaml

语法检查,没有真正执行
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/test.yaml --syntax-check

执行Playbook脚本
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/test.yaml

或者

[root@ansible ~]# ansible-playbook -i /etc/ansible/hosts /etc/ansible/playbook/test.yaml
[root@ansible ~]# ansible web_servers -m shell -a 'id test'
192.168.88.102 | FAILED | rc=1 >>
id: ‘test’: no such usernon-zero return code
192.168.88.101 | FAILED | rc=1 >>
id: ‘test’: no such usernon-zero return code
[root@ansible ~]# vim /etc/ansible/playbook/test.yaml
[root@ansible ~]# cat /etc/ansible/playbook/test.yaml
---
 - hosts: web_servers
   remote_user: root
   vars:
   - user: test
   tasks:
   - name: create user
     user: name={{user}} state=present
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/test.yaml --syntax-check
playbook: /etc/ansible/playbook/test.yaml
[root@ansible ~]#  ansible-playbook /etc/ansible/playbook/test.yaml

PLAY [web_servers] **************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [create user] *************************************************************
changed: [192.168.88.102]
changed: [192.168.88.101]

PLAY RECAP *********************************************************************
192.168.88.101             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.102             : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@ansible ~]# ansible -m shell web_servers -a "id test"                     
192.168.88.101 | CHANGED | rc=0 >>
uid=2003(test) gid=2003(test) groups=2003(test)
192.168.88.102 | CHANGED | rc=0 >>
uid=2003(test) gid=2003(test) groups=2003(test)
[root@ansible ~]# vim /etc/ansible/playbook/test.yaml
[root@ansible ~]# cat /etc/ansible/playbook/test.yaml
---
# - hosts: web_servers
 - hosts: all
   remote_user: root
   vars:
   - user: test
   tasks:
   - name: create user
     user: name={{user}} state=present
[root@ansible ~]# ansible-playbook -i /etc/ansible/hosts /etc/ansible/playbook/test.yaml

PLAY [all] *********************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.88.102]
ok: [192.168.88.103]
ok: [192.168.88.101]

TASK [create user] *************************************************************
ok: [192.168.88.103]
ok: [192.168.88.102]
ok: [192.168.88.101]

PLAY RECAP *********************************************************************
192.168.88.101             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.102             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.103             : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@ansible ~]#

img

img

  1. Playbook 入门案例

https://docs.ansible.com/ansible/latest/playbook*guide/playbooks*intro.html#playbook-syntax

环境优化(可选)

[root@ansible ~]# ansible web_servers -m shell -a 'hostname'
192.168.88.101 | CHANGED | rc=0 >>
node1
192.168.88.102 | CHANGED | rc=0 >>
node2
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa httpd'
192.168.88.101 | CHANGED | rc=0 >>
httpd-2.4.62-13.el9.x86_64
192.168.88.102 | CHANGED | rc=0 >>
httpd-2.4.62-13.el9.x86_64
[root@ansible ~]# ansible web_servers -m shell -a 'yum remove -y httpd'
192.168.88.101 | CHANGED | rc=0 >>
Dependencies resolved.
================================================================================
 Package                 Arch        Version              Repository       Size
================================================================================
Removing:
 httpd                   x86_64      2.4.62-13.el9        @appstream       59 k
Removing unused dependencies:
 centos-logos-httpd      noarch      90.9-1.el9           @appstream      2.6 M
 mod_http2               x86_64      2.0.26-5.el9         @appstream      442 k
 mod_lua                 x86_64      2.4.62-13.el9        @appstream      142 k

Transaction Summary
================================================================================
Remove  4 Packages

Freed space: 3.2 M
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1
  Running scriptlet: httpd-2.4.62-13.el9.x86_64                             1/4
Removed "/etc/systemd/system/multi-user.target.wants/httpd.service".

  Erasing          : httpd-2.4.62-13.el9.x86_64                             1/4
  Running scriptlet: httpd-2.4.62-13.el9.x86_64                             1/4
  Erasing          : centos-logos-httpd-90.9-1.el9.noarch                   2/4
  Erasing          : mod_http2-2.0.26-5.el9.x86_64                          3/4
  Erasing          : mod_lua-2.4.62-13.el9.x86_64                           4/4
  Running scriptlet: mod_lua-2.4.62-13.el9.x86_64                           4/4
  Verifying        : centos-logos-httpd-90.9-1.el9.noarch                   1/4
  Verifying        : httpd-2.4.62-13.el9.x86_64                             2/4
  Verifying        : mod_http2-2.0.26-5.el9.x86_64                          3/4
  Verifying        : mod_lua-2.4.62-13.el9.x86_64                           4/4

Removed:
  centos-logos-httpd-90.9-1.el9.noarch       httpd-2.4.62-13.el9.x86_64
  mod_http2-2.0.26-5.el9.x86_64              mod_lua-2.4.62-13.el9.x86_64

Complete!Repository baseos is listed more than once in the configuration
Repository baseos-source is listed more than once in the configuration
Repository appstream is listed more than once in the configuration
Repository appstream-source is listed more than once in the configuration
Repository crb is listed more than once in the configuration
Repository crb-source is listed more than once in the configuration
Repository highavailability is listed more than once in the configuration
Repository highavailability-source is listed more than once in the configuration
Repository nfv is listed more than once in the configuration
Repository nfv-source is listed more than once in the configuration
Repository rt is listed more than once in the configuration
Repository rt-source is listed more than once in the configuration
Repository resilientstorage is listed more than once in the configuration
Repository resilientstorage-source is listed more than once in the configuration
Repository extras-common is listed more than once in the configuration
Repository extras-common-source is listed more than once in the configuration
192.168.88.102 | CHANGED | rc=0 >>
Dependencies resolved.
================================================================================
 Package          Architecture  Version                 Repository         Size
================================================================================
Removing:
 httpd            x86_64        2.4.62-13.el9           @appstream         59 k
Removing unused dependencies:
 mod_http2        x86_64        2.0.26-5.el9            @appstream        442 k
 mod_lua          x86_64        2.4.62-13.el9           @appstream        142 k

Transaction Summary
================================================================================
Remove  3 Packages

Freed space: 643 k
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1
  Running scriptlet: httpd-2.4.62-13.el9.x86_64                             1/3
Removed "/etc/systemd/system/multi-user.target.wants/httpd.service".

  Erasing          : httpd-2.4.62-13.el9.x86_64                             1/3
  Running scriptlet: httpd-2.4.62-13.el9.x86_64                             1/3
  Erasing          : mod_http2-2.0.26-5.el9.x86_64                          2/3
  Erasing          : mod_lua-2.4.62-13.el9.x86_64                           3/3
  Running scriptlet: mod_lua-2.4.62-13.el9.x86_64                           3/3
  Verifying        : httpd-2.4.62-13.el9.x86_64                             1/3
  Verifying        : mod_http2-2.0.26-5.el9.x86_64                          2/3
  Verifying        : mod_lua-2.4.62-13.el9.x86_64                           3/3

Removed:
  httpd-2.4.62-13.el9.x86_64             mod_http2-2.0.26-5.el9.x86_64
  mod_lua-2.4.62-13.el9.x86_64

Complete!Repository baseos is listed more than once in the configuration
Repository baseos-source is listed more than once in the configuration
Repository appstream is listed more than once in the configuration
Repository appstream-source is listed more than once in the configuration
Repository crb is listed more than once in the configuration
Repository crb-source is listed more than once in the configuration
Repository highavailability is listed more than once in the configuration
Repository highavailability-source is listed more than once in the configuration
Repository nfv is listed more than once in the configuration
Repository nfv-source is listed more than once in the configuration
Repository rt is listed more than once in the configuration
Repository rt-source is listed more than once in the configuration
Repository resilientstorage is listed more than once in the configuration
Repository resilientstorage-source is listed more than once in the configuration
Repository extras-common is listed more than once in the configuration
Repository extras-common-source is listed more than once in the configuration
[root@ansible ~]# ansible web_servers -m shell -a 'rpm -qa httpd'
192.168.88.102 | CHANGED | rc=0 >>

192.168.88.101 | CHANGED | rc=0 >>

[root@ansible ~]#

阿里源

cat >/etc/yum.repos.d/aliyun.repo<<EOF
[baseos]
name=CentOS Stream \$releasever - BaseOS
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1

[baseos-debug]
name=CentOS Stream \$releasever - BaseOS - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[baseos-source]
name=CentOS Stream \$releasever - BaseOS - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/BaseOS/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[appstream]
name=CentOS Stream \$releasever - AppStream
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1

[appstream-debug]
name=CentOS Stream \$releasever - AppStream - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[appstream-source]
name=CentOS Stream \$releasever - AppStream - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/AppStream/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[crb]
name=CentOS Stream \$releasever - CRB
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0

[crb-debug]
name=CentOS Stream \$releasever - CRB - Debug
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[crb-source]
name=CentOS Stream \$releasever - CRB - Source
baseurl=https://mirrors.aliyun.com/centos-stream/\$stream/CRB/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[highavailability]
name=CentOS Stream \$releasever - HighAvailability
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0

[highavailability-debug]
name=CentOS Stream \$releasever - HighAvailability - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[highavailability-source]
name=CentOS Stream \$releasever - HighAvailability - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/HighAvailability/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[nfv]
name=CentOS Stream \$releasever - NFV
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0

[nfv-debug]
name=CentOS Stream \$releasever - NFV - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[nfv-source]
name=CentOS Stream \$releasever - NFV - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/NFV/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[rt]
name=CentOS Stream \$releasever - RT
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0

[rt-debug]
name=CentOS Stream \$releasever - RT - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[rt-source]
name=CentOS Stream \$releasever - RT - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/RT/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[resilientstorage]
name=CentOS Stream \$releasever - ResilientStorage
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/\$basearch/os/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=0

[resilientstorage-debug]
name=CentOS Stream \$releasever - ResilientStorage - Debug
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/\$basearch/debug/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[resilientstorage-source]
name=CentOS Stream \$releasever - ResilientStorage - Source
baseurl=http://mirrors.aliyun.com/centos-stream/\$stream/ResilientStorage/source/tree/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0

[extras-common]
name=CentOS Stream \$releasever - Extras packages
baseurl=http://mirrors.aliyun.com/centos-stream/SIGs/\$stream/extras/\$basearch/extras-common/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
countme=1
enabled=1

[extras-common-source]
name=CentOS Stream \$releasever - Extras packages - Source
baseurl=http://mirrors.aliyun.com/centos-stream/SIGs/\$stream/extras/source/extras-common/
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-SIG-Extras-SHA512
gpgcheck=1
repo_gpgcheck=0
metadata_expire=6h
enabled=0
EOF

来看一个实例,在Linux系统中安装Apache软件:① yum安装软件 ② 编辑httpd.conf配置文件

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

[root@ansible ~]# mkdir /etc/ansible/playbook -p # 如果已执行过,这一步可以跳过

第2步:准备httpd配置文件,并修改配置

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

按需要修改配置(测试用,只要改正确就行)

[root@ansible ~]# vim /etc/httpd/conf/httpd.conf
Listen 2025

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

[root@ansible ~]# vim /etc/ansible/playbook/httpd.yaml
---
- hosts: web_servers
  remote_user: root
  tasks:
    - name: ensure apache is at the latest version
      yum:
        name: httpd,httpd-devel
        state: latest
    
    - name: write the apache config file
      copy:
        src: /etc/httpd/conf/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
      notify:
        - restart apache

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

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

注意:task任务格式

- name: 任务执行时,打印的文本信息,可以是英文也可以是中文(类似注释,但是会打印输出)
   ansible模块,如file、copy、yum
     参数
   notify:与ansible模块是同级关系
     - restart apache,这个地方的名称与 handlers:中name标签同名,代表调用handlers中定义好的任务操作

第4步: 执行写好的palybook

会显示出执行的过程,并且执行的每一步都有ok,changed,failed等标识

执行如果有错误,解决问题后,直接再执行这条命令即可,并会把failed改为changed(幂等性)

[root@ansible ~]# ansible-playbook /etc/ansible/playbook/httpd.yaml --syntax-check

playbook: /etc/ansible/playbook/httpd.yaml
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/httpd.yaml
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/httpd.yaml

PLAY [web_servers] *************************************************************                                                   *

TASK [Gathering Facts] ********************************************************                                                   *
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [ensure apache is at the latest version] *********************************                                                   *
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [write the apache config file] *******************************************                                                   *
changed: [192.168.88.101]
changed: [192.168.88.102]

TASK [ensure apache is running (and enable it at boot)] ***********************                                                   *
changed: [192.168.88.101]
changed: [192.168.88.102]

RUNNING HANDLER [restart apache] **********************************************                                                   *
changed: [192.168.88.101]
changed: [192.168.88.102]

PLAY RECAP ********************************************************************                                                   *
192.168.88.101             : ok=5    changed=3    unreachable=0    failed=0                                                       skipped=0    rescued=0    ignored=0
192.168.88.102             : ok=5    changed=3    unreachable=0    failed=0                                                       skipped=0    rescued=0    ignored=0

[root@ansible ~]# ansible -m shell web_servers -a "systemctl status httpd"
192.168.88.101 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
    Drop-In: /etc/systemd/system/httpd.service.d
             └─php-fpm.conf
     Active: active (running) since Mon 2025-12-15 17:19:18 CST; 1min 4s ago
       Docs: man:httpd.service(8)
   Main PID: 23911 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 48693)
     Memory: 34.4M
        CPU: 121ms
     CGroup: /system.slice/httpd.service
             ├─23911 /usr/sbin/httpd -DFOREGROUND
             ├─23915 /usr/sbin/httpd -DFOREGROUND
             ├─23916 /usr/sbin/httpd -DFOREGROUND
             ├─23917 /usr/sbin/httpd -DFOREGROUND
             └─23918 /usr/sbin/httpd -DFOREGROUND

Dec 15 17:19:18 agent1 systemd[1]: Starting The Apache HTTP Server...
Dec 15 17:19:18 agent1 httpd[23911]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.101. Set the 'ServerName' directive globally to suppress this message
Dec 15 17:19:18 agent1 httpd[23911]: Server configured, listening on: port 2025
Dec 15 17:19:18 agent1 systemd[1]: Started The Apache HTTP Server.
192.168.88.102 | CHANGED | rc=0 >>
● httpd.service - The Apache HTTP Server
     Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; preset: disabled)
     Active: active (running) since Mon 2025-12-15 17:19:18 CST; 1min 5s ago
       Docs: man:httpd.service(8)
   Main PID: 21254 (httpd)
     Status: "Total requests: 0; Idle/Busy workers 100/0;Requests/sec: 0; Bytes served/sec:   0 B/sec"
      Tasks: 177 (limit: 22927)
     Memory: 23.9M
        CPU: 112ms
     CGroup: /system.slice/httpd.service
             ├─21254 /usr/sbin/httpd -DFOREGROUND
             ├─21256 /usr/sbin/httpd -DFOREGROUND
             ├─21257 /usr/sbin/httpd -DFOREGROUND
             ├─21258 /usr/sbin/httpd -DFOREGROUND
             └─21259 /usr/sbin/httpd -DFOREGROUND

Dec 15 17:19:18 agent2 systemd[1]: Starting The Apache HTTP Server...
Dec 15 17:19:18 agent2 httpd[21254]: AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 192.168.88.102. Set the 'ServerName' directive globally to suppress this message
Dec 15 17:19:18 agent2 systemd[1]: Started The Apache HTTP Server.
Dec 15 17:19:18 agent2 httpd[21254]: Server configured, listening on: port 2025
[root@ansible ~]# ansible -m shell web_servers -a "netstat -pantul|grep httpd"
192.168.88.102 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:2025            0.0.0.0:*               LISTEN      21254/httpd
192.168.88.101 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:2025            0.0.0.0:*               LISTEN      23911/httpd
[root@ansible ~]#

img

img

[root@ansible ~]# vim /etc/ansible/playbook/httpd.yaml
[root@ansible ~]# cat /etc/ansible/playbook/httpd.yaml
---
- hosts: web_servers
  remote_user: root
  tasks:
    - name: ensure apache is at the latest version
      yum:
        name: httpd,httpd-devel
        state: latest

    - name: write the apache config file
      copy:
        src: /etc/httpd/conf/httpd.conf
        dest: /etc/httpd/conf/httpd.conf
      notify:
        - restart apache

    - name: 确保 Apache Web 服务是正常运行的
      service:
        name: httpd
        state: started
        enabled: yes

  handlers:
    - name: restart apache
      service:
        name: httpd
        state: restarted
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/httpd.yaml

PLAY [web_servers] *************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [ensure apache is at the latest version] **********************************
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [write the apache config file] ********************************************
ok: [192.168.88.101]
ok: [192.168.88.102]

TASK [确保 Apache Web 服务是正常运行的] ****************************************
ok: [192.168.88.102]
ok: [192.168.88.101]

PLAY RECAP *********************************************************************
192.168.88.101             : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.102             : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

[root@ansible ~]# ansible -m shell web_servers -a "netstat -pantul|grep 2026"
192.168.88.101 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:2026            0.0.0.0:*               LISTEN      34633/httpd
192.168.88.102 | CHANGED | rc=0 >>
tcp        0      0 0.0.0.0:2026            0.0.0.0:*               LISTEN      33885/httpd
[root@ansible ~]# date -R
Fri, 29 May 2026 16:54:41 +0800
[root@ansible ~]#

img

小结:

Playbook剧本就是按照配置的Task任务流程,按顺序执行。

在工作中,主要编写Task任务,一定要完全按照Task任务格式,具体每个模块如何使用,可以参考官网文档。

  1. Playbook 进阶案例

写一个 playbook 实现

理解以下思路:

a. 配置 yum

b. 安装 vsftpd 包 -> FTP服务 -> 21号端口 -> 专门用于上传和下载

c. 修改配置文件(要求拒绝匿名用户登录)

d. 启动服务并实现 vsftpd 服务开机自动启动

---
- hosts: web_servers                 
  remote_user: root                     
  tasks:                                    
  - name: ensure vsftpd is at the latest version        
    yum: name=vsftpd state=latest
    
  - name: write the vsftpd config file          
    copy: src=/etc/vsftpd/vsftpd.conf dest=/etc/vsftpd/vsftpd.conf 
    notify:                             
    - restart vsftpd
    
  - name: ensure vsftpd is running (and enable it at boot)
    service: name=vsftpd state=started enabled=yes
    
  handlers:                     
    - name: restart vsftpd              
      service: name=vsftpd state=restarted

扩展:循环结构

- name: write the apache config file
  copy:
    src: "{{ item.src }}"
    dest: "{{ item.dest }}"
  loop:
    - { src: "/etc/vsftpd/vsftpd.conf", dest: "/etc/vsftpd/vsftpd.conf" }
    - { src: "/etc/vsftpd/ftpusers", dest: "/etc/vsftpd/ftpusers" }
    - { src: "/etc/vsftpd/user_list", dest: "/etc/vsftpd/user_list" }

扩展:FTP软件的使用 => FTP服务(了解)

FTP服务:文件传输协议,在实际工作中主要上传下载服务,默认端口21

怎么样允许,用户连接呢?

如何管理ansible服务器端没有安装vsftpd软件包,先安装软件包并通过使用user模块创建用户和设置密码

生成登录用户密码

#每个人生成的密码不一样,不要复制课件的密码
[root@ansible playbook]# echo 123456 | openssl passwd -1 -stdin 
$1$DnODAOhn$6n22yMs2Yx.eLuc3H0Tyq1
---
- hosts: webserver
  remote_user: root
  tasks:
    - name: vsftp软件的安装
      dnf:
        name: vsftpd
        state: latest
    - name: write the apache config file
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
      loop:
        - { src: "/etc/vsftpd/vsftpd.conf", dest: "/etc/vsftpd/vsftpd.conf" }
        - { src: "/etc/vsftpd/ftpusers", dest: "/etc/vsftpd/ftpusers" }
        - { src: "/etc/vsftpd/user_list", dest: "/etc/vsftpd/user_list" }
      notify:
        - restarted vsftpd
    - name: 创建登录用户
      user:
        name: itheima
        password: "$1$DnODAOhn$6n22yMs2Yx.eLuc3H0Tyq1"
    - name: 服务的启动
      service:
        name: vsftpd 
        state: started
        enabled: yes
  handlers:
    - name: restarted vsftpd
      service:
        name: vsftpd
        state: restarted

经典案例: 编排 NFS 搭建与客户端挂载(重点)

回顾:NFS(Network File System)网络文件系统,在实际工作中,主要用于实现海量数据存储。

在CRMEB电子商城项目中,我们可以基于NFS/OSS实现静态文件存储 => 商品图片(海报、广告图)、文件、音频、视频。

CRMEB商城源码(服务器1) => 挂载NFS => (服务器2)文件服务器

工作职责:

① 负责整个项目环境搭建

② 基于NFS实现海量数据存储

③ 基于Redis实现数据缓存

④ 基于GTID全局事务实现MySQL主从架构设计

⑤ 基于Zabbix实现监控报警

  1. 在Ansible控制节点上修改NFS配置文件及配置共享目录权限
cat >/etc/exports<<EOF
/share  *(rw,no_root_squash)
EOF

说明:
/etc/exports:NFS默认配置文件
共享目录   *(rw)
*代表任意主机均可挂载访问/share目录
(ro)/(rw) :read only只读/read write可读可写

创建/share目录
mkdir /share

更改共享目录权限
chmod 755 /share
chown nobody:nobody /share

安装 nfs-utils
yum install -y nfs-utils

如果遇到权限问题就执行以下操作
exportfs -r          # 重读 /etc/exports
exportfs -v          # 确认能看到 /share  *(rw,no_root_squash)

img

img

  1. 配置/etc/ansible/hosts分组信息
cat >>/etc/ansible/hosts<<EOF

[nfs_server]
192.168.88.100

[nfs_clients]
192.168.88.101
192.168.88.102
EOF
  1. 编写playbook nfs.yaml编排文件
cat >/etc/ansible/playbook/nfs.yaml<<EOF
---
# 在 nfs_server 组 192.168.88.100 上部署 NFS 服务端
- hosts: nfs_server
  remote_user: root
  tasks:
    - name: 安装 NFS 服务端相关软件包
      yum:
        name:
          - nfs-utils
          - rpcbind
          - setup
        state: latest

    - name: 创建共享目录
      file:
        path: /share
        state: directory
        mode: '0755'

#    - name: 下发 /etc/exports 配置文件
#      copy:
#        src: /etc/exports          # 控制节点上的源文件
#        dest: /etc/exports
#        backup: yes
#      notify: restart nfs

    - name: 启动并开机自启 rpcbind
      service:
        name: rpcbind
        state: started
        enabled: yes

    - name: 启动并开机自启 nfs-server
      service:
        name: nfs-server
        state: started
        enabled: yes

 # handlers:
 #   - name: restart nfs
 #     service:
 #       name: nfs-server
 #       state: restarted


# 在 nfs_clients 组 192.168.88.101、192.168.88.102 上部署 NFS 客户端
- hosts: nfs_clients
  remote_user: root
  tasks:
    - name: 安装 NFS 客户端软件包
      yum:
        name: nfs-utils
        state: latest

    - name: 创建本地挂载点
      file:
        path: /mnt
        state: directory
        mode: '0755'

    - name: 挂载 NFS 共享(临时挂载)
      mount:
        path: /mnt
        src: 192.168.88.100:/share
        fstype: nfs
        opts: defaults
        state: mounted
EOF

rpc一种网络协议,数据通信都需要依靠rpc;nfs本质就是一个rpc服务,所以安装使用nfs之前都需要安装rpcbind

  1. 执行playbook

确保ansible控制节点能免密ssh连自己和目标机器

[root@ansible ~]# ssh-copy-id root@192.168.88.100
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/root/.ssh/id_rsa.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
root@192.168.88.100's password:

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh 'root@192.168.88.100'"
and check to make sure that only the key(s) you wanted were added.

[root@ansible ~]# ssh 192.168.88.100
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Mon Dec 15 13:32:11 2025 from 192.168.88.1
[root@ansible ~]# exit
logout
Connection to 192.168.88.100 closed.
[root@ansible ~]# ssh 192.168.88.101
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Mon Dec 15 17:52:21 2025 from 192.168.88.100
[root@agent1 ~]# exit
logout
Connection to 192.168.88.101 closed.
[root@ansible ~]# ssh 192.168.88.102
Activate the web console with: systemctl enable --now cockpit.socket

Last login: Mon Dec 15 17:52:21 2025 from 192.168.88.100
[root@agent2 ~]# exit
logout
Connection to 192.168.88.102 closed.
[root@ansible ~]#
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/nfs.yaml
[root@ansible ~]# ansible-playbook /etc/ansible/playbook/nfs.yaml              
PLAY [nfs_server] **************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.88.100]

TASK [安装 NFS 服务端相关软件包] ***********************************************
ok: [192.168.88.100]

TASK [创建共享目录] ************************************************************
ok: [192.168.88.100]

TASK [下发 /etc/exports 配置文件] **********************************************
ok: [192.168.88.100]

TASK [启动并开机自启 rpcbind] **************************************************
ok: [192.168.88.100]

TASK [启动并开机自启 nfs-server] ***********************************************
ok: [192.168.88.100]

PLAY [nfs_clients] *************************************************************

TASK [Gathering Facts] *********************************************************
ok: [192.168.88.101]
ok: [192.168.88.102]

TASK [安装 NFS 客户端软件包] ***************************************************
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [创建本地挂载点] **********************************************************
ok: [192.168.88.102]
ok: [192.168.88.101]

TASK [挂载 NFS 共享(临时挂载)] ***********************************************
changed: [192.168.88.102]
changed: [192.168.88.101]

PLAY RECAP *********************************************************************
192.168.88.100             : ok=6    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.101             : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
192.168.88.102             : ok=4    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
[root@ansible ~]# ansible -m shell web_servers -a "df -h"
192.168.88.101 | CHANGED | rc=0 >>
Filesystem             Size  Used Avail Use% Mounted on
devtmpfs               4.0M     0  4.0M   0% /dev
tmpfs                  3.8G     0  3.8G   0% /dev/shm
tmpfs                  1.5G  9.2M  1.5G   1% /run
/dev/mapper/cs-root     17G  5.2G   12G  31% /
/dev/nvme0n1p1         960M  308M  653M  33% /boot
tmpfs                  766M     0  766M   0% /run/user/0
192.168.88.100:/share   17G  2.5G   15G  15% /mnt
192.168.88.102 | CHANGED | rc=0 >>
Filesystem             Size  Used Avail Use% Mounted on
devtmpfs               4.0M     0  4.0M   0% /dev
tmpfs                  1.8G     0  1.8G   0% /dev/shm
tmpfs                  726M  9.1M  717M   2% /run
/dev/mapper/cs-root     17G  2.1G   15G  12% /
/dev/nvme0n1p1         960M  308M  653M  33% /boot
tmpfs                  363M     0  363M   0% /run/user/0
192.168.88.100:/share   17G  2.5G   15G  15% /mnt
[root@ansible ~]# ansible -m shell nfs_clients -a "df -h"
192.168.88.101 | CHANGED | rc=0 >>
Filesystem             Size  Used Avail Use% Mounted on
devtmpfs               4.0M     0  4.0M   0% /dev
tmpfs                  3.8G     0  3.8G   0% /dev/shm
tmpfs                  1.5G  9.2M  1.5G   1% /run
/dev/mapper/cs-root     17G  5.2G   12G  31% /
/dev/nvme0n1p1         960M  308M  653M  33% /boot
tmpfs                  766M     0  766M   0% /run/user/0
192.168.88.100:/share   17G  2.5G   15G  15% /mnt
192.168.88.102 | CHANGED | rc=0 >>
Filesystem             Size  Used Avail Use% Mounted on
devtmpfs               4.0M     0  4.0M   0% /dev
tmpfs                  1.8G     0  1.8G   0% /dev/shm
tmpfs                  726M  9.1M  717M   2% /run
/dev/mapper/cs-root     17G  2.1G   15G  12% /
/dev/nvme0n1p1         960M  308M  653M  33% /boot
tmpfs                  363M     0  363M   0% /run/user/0
192.168.88.100:/share   17G  2.5G   15G  15% /mnt
[root@ansible ~]# ls /share/
[root@ansible ~]# ansible -m shell nfs_clients -a "ls /mnt"
192.168.88.101 | CHANGED | rc=0 >>

192.168.88.102 | CHANGED | rc=0 >>

[root@ansible ~]# echo nfs_test_666 > /share/test.txt
[root@ansible ~]# ls /share/
test.txt
[root@ansible ~]# cat /share/test.txt
nfs_test_666
[root@ansible ~]# ansible -m shell nfs_clients -a "ls /mnt"
192.168.88.102 | CHANGED | rc=0 >>
test.txt
192.168.88.101 | CHANGED | rc=0 >>
test.txt
[root@ansible ~]# ansible -m shell nfs_clients -a "cat /mnt/test.txt"
192.168.88.101 | CHANGED | rc=0 >>
nfs_test_666
192.168.88.102 | CHANGED | rc=0 >>
nfs_test_666
[root@ansible ~]#

img

img

img

img

img

img

img

img

img

img

img

img

img

小结:

NFS全称:Network File System,网络文件系统,文件共享

服务器端必装软件:(nfs-utils) 和 (rpcbind)

注意:NFS服务全名(nfs-server)

  1. Ansible Roles(仅供了解)

作用:把变量、任务、handlers 分别作为角色,按文件夹来实现分离编写过程!

  1. roles 介绍

roles(角色): 就是通过分别将variables, tasks及handlers等放置于单独的目录中,并可以便捷地调用它们的一种机制。

假设我们要写一个playbook来安装管理lamp环境,那么这个playbook就会写很长。所以我们希望把这个很大的文件分成多个功能拆分, 分成apache管理,php管理,mysql管理,然后在需要使用的时候直接调用就可以了,以免重复写。就类似编程里的模块化的概念,以达到代码复用的效果。

  1. 创建 roles 的目录结构

files:用来存放由copy模块或script模块调用的文件。
tasks:至少有一个main.yml文件,定义各tasks。
handlers:有一个main.yml文件,定义各handlers。
templates:用来存放jinjia2模板。
vars:有一个main.yml文件,定义变量。
meta:有一个main.yml文件,定义此角色的特殊设定及其依赖关系,类似说明书。

注意: 在每个角色的目录中分别创建files, tasks,handlers,templates,vars和meta目录,用不到的目录可以创建为空目录.

  1. 通过 roles 实现 lamp

需定制三个角色: httpd,mysql,php

第1步: 创建roles目录及文件,并确认目录结构

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir -p {httpd,mysql,php}/{files,tasks,handlers,templates,vars,meta}
[root@ansible ~]# touch {httpd,mysql,php}/{tasks,handlers,vars,meta}/main.yml

[root@ansible ~]# yum install tree -y
[root@ansible ~]# tree /etc/ansible/roles/
/etc/ansible/roles/
├── httpd
│   ├── files
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   │   └── main.yml
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   └── vars
│       └── main.yml
├── mysql
│   ├── files
│   ├── handlers
│   │   └── main.yml
│   ├── meta
│   │   └── main.yml
│   ├── tasks
│   │   └── main.yml
│   ├── templates
│   └── vars
│       └── main.yml
└── php
    ├── files
    ├── handlers
    │   └── main.yml
    ├── meta
    │   └── main.yml
    ├── tasks
    │   └── main.yml
    ├── templates
    └── vars
        └── main.yml

第2步: 准备httpd服务器的主页文件,php测试页和配置文件等

[root@ansible ~]# echo "test main page" > /etc/ansible/roles/httpd/files/index.html

[root@ansible ~]# echo -e "<?php\n\tphpinfo();\n?>" > /etc/ansible/roles/httpd/files/test.php 

[root@ansible ~]# yum install httpd -y
按需求修改配置文件后,拷贝到httpd角色目录里的files子目录
[root@ansible ~]# vim /etc/httpd/conf/httpd.conf
[root@ansible ~]# cp /etc/httpd/conf/httpd.conf /etc/ansible/roles/httpd/files/

第3步: 编写httpd角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/httpd/tasks/main.yml
---
- name: 安装httpd
  yum: name=httpd,httpd-devel state=present

- name: 同步httpd配置文件
  copy: src=/etc/ansible/roles/httpd/files/httpd.conf dest=/etc/httpd/conf/httpd.conf
  notify: restart httpd

- name: 同步主页文件
  copy: src=/etc/ansible/roles/httpd/files/index.html dest=/var/www/html/index.html

- name: 同步php测试页
  copy: src=/etc/ansible/roles/httpd/files/test.php dest=/var/www/html/test.php

- name: 启动httpd并开机自启动
  service: name=httpd state=started enabled=yes

第4步: 编写httpd角色里的handler

[root@ansible ~]# vim /etc/ansible/roles/httpd/handlers/main.yml
---
- name: restart httpd
  service: name=httpd state=restarted

第5步: 编写mysql角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/mysql/tasks/main.yml
---
- name: 安装mysql
  yum: name=mariadb,mariadb-server state=present

- name: 启动mysql并开机自启动
  service: name=mariadb state=started enabled=yes

第6步: 编写php角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/php/tasks/main.yml
---
- name: 安装php及依赖包
  yum: name=php,php-gd,php-ldap,php-odbc,php-pear,php-xml,php-mbstring,php-snmp,php-soap,curl,curl-devel,php-bcmath,php-mysqlnd state=present

  notify: restart httpd

第7步:编写lamp的playbook文件调用前面定义好的三个角色

[root@ansible ~]# vim /etc/ansible/playbook/lamp.yaml
---
- hosts: web_servers
  remote_user: root
  roles:
    - httpd
    - mysql
    - php

第8步: 执行lamp的playbook文件

[root@ansible ~]# ansible-playbook /etc/ansible/playbook/lamp.yaml

小结:

Ansible Roles:把大的yaml文件拆解为若干个小的main.yml文件,好处:方便调试,每个文件都比较小

缺点:文件夹太多,过于冗余

常见问题:

问题1:YAML语法有错误,千万不要按Tab键,如果出错了,可以借助于YAML校检工具

https://www.bejson.com/validators/yaml_editor/index.html

问题2:Roles脚本是一个整体,不要单独执行某个yaml文件应该统一执行最终的yaml,如lamp.yaml

单独执行某个main.yml,报错信息如下:

以上其实不是错误,而是也能为main.yml文件不完整导致的!

  1. 拓展案例: 通过 roles 实现 lamp 并部署 discuz

作用:基于Ansible Roles搭建Web项目(BBS论坛为例)=> 国内做的最好的BBS论坛 => Discuz

第1步: 创建roles目录及文件,并确认目录结构

[root@ansible ~]# cd /etc/ansible/roles/
[root@ansible ~]# mkdir -p {httpd,mysql,php}/{files,tasks,handlers,templates,vars,meta}
[root@ansible ~]# touch {httpd,mysql,php}/{tasks,handlers,vars,meta}/main.yml

第2步: 准备httpd相关文件

[root@ansible ~]# cp /etc/httpd/conf/httpd.conf /etc/ansible/roles/httpd/files/
[root@ansible ~]# ls /etc/ansible/roles/httpd/files/
Discuz_X3.5_SC_UTF8.zip                                          Discuz相关软件包
httpd.conf                                                                                               配置好的httpd.conf配置文件

第3步: 编写httpd角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/httpd/tasks/main.yml
- name: 安装httpd相关软件包
  yum: name=httpd,httpd-devel state=latest

- name: 同步配置文件
  copy: src=/etc/ansible/roles/httpd/files/httpd.conf dest=/etc/httpd/conf/httpd.conf

  notify: restart httpd

- name: 拷贝discuz压缩包
  copy: src=/etc/ansible/roles/httpd/files/Discuz_X3.5_SC_UTF8.zip dest=/tmp/

- name: 解压并mv网站文件到httpd家目录
  shell: rm -rf /var/www/html/*  && rm -rf /test/ && mkdir -p /test/ &&  unzip /tmp/Discuz_X3.5_SC_UTF8.zip -d /test/ &> /dev/null  && mv /test/upload/* /var/www/html/ && chown -R apache.apache /var/www/html/
# 上面的命令有点多,可以写成脚本,然后使用script模块来调用执行

- name: 启动httpd并开机自启动
  service: name=httpd state=started enabled=on

第4步: 编写httpd角色里的handler

[root@ansible ~]# vim /etc/ansible/roles/httpd/handlers/main.yml
---
- name: restart httpd
  service: name=httpd state=restarted

第5步: 编写mysql角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/mysql/tasks/main.yml
---
- name: 安装mariadb相关软件包
  yum: name=mariadb-server,mariadb state=latest

- name: 启动mariadb服务并设置开机自启动
  service: name=mariadb state=started enabled=on

- name: 执行建库脚本
  script: /etc/ansible/roles/mysql/files/create.sh

第6步: 编写mysql的建库脚本

[root@ansible ~]# vim /etc/ansible/roles/mysql/files/create.sh

#!/bin/bash
mysql << EOF
create database if not exists discuz default charset=utf8;
grant all on discuz.* to 'discuz'@'localhost' identified by '123';
flush privileges;
EOF

第7步: 编写php角色的main.yml文件

[root@ansible ~]# vim /etc/ansible/roles/php/tasks/main.yml
---
- name: 安装php及依赖包
  yum: name=php,php-gd,php-ldap,php-odbc,php-pear,php-xml,php-mbstring,php-snmp,php-soap,curl,curl-devel,php-bcmath,php-mysqlnd state=present

  notify: restart httpd

第8步:编写lamp的playbook文件调用前面定义好的三个角色

[root@ansible ~]# vim /etc/ansible/playbook/discuz.yaml
---
- hosts: web_servers
  remote_user: root
  roles:
    - httpd
    - mysql
    - php

第9步: 执行lamp的playbook文件

[root@ansible ~]# ansible-playbook /etc/ansible/playbook/discuz.yaml