返回顶部

Ansible之playbook剧本

Ansible之playbook剧本

1. playbook的组成

playbooks 本身由以下各部分组成
(1)Tasks:任务,即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行
(2)Variables:变量
(3)Templates:模板
(4)Handlers:处理器,当changed状态条件满足时,(notify)触发执行的操作
(5)Roles:角色

2. 剧本示例test1

2.1 剧本制作

[root@ansible ansible]# vim test1.yaml

---
##yaml文件以---开头,以表明这是一个yaml文件,可省略
- name: first test
##定义一个play的名称,可省略
  gather_facts: false
##设置不进行facts信息收集,这可以加快执行速度,可省略
  hosts: webservers
##指定要执行任务的被管理主机组,如多个主机组用冒号分隔
  remote_user: root
##指定被管理主机上执行任务的用户
  tasks:
##定义任务列表,任务列表中的各任务按次序逐个在hosts中指定的主机上执行
   - name: test connection
##自定义任务名称
     ping:
##使用 module: [options] 格式来定义一个任务
   - name: disable selinux
     command: '/sbin/setenforce 0'
##command模块和shell模块无需使用key=value格式
     ignore_errors: True
##如执行命令的返回值不为0,就会报错,tasks停止,可使用ignore_errors忽略失败的任务
   - name: disable firewalld
     service: name=firewalld state=stopped
##使用 module: options 格式来定义任务,option使用key=value格式
   - name: install httpd
     yum: name=httpd state=latest
   - name: install configuration file for httpd
     copy: src=/root/ansible/httpd.conf dest=/etc/httpd/conf/httpd.conf
##这里需要一个事先准备好的/opt/httpd.conf文件
     notify: "restart httpd"
##如以上操作后为changed的状态时,会通过notify指定的名称触发对应名称的handlers操作
   - name: start httpd service
     service: enabled=true name=httpd state=started
  handlers:
##handlers中定义的就是任务,此处handlers中的任务使用的是service模块
   - name: restart httpd
##notify和handlers中任务的名称必须一致
     service: name=httpd state=restarted

Ansible在执行完某个任务之后并不会立即去执行对应的handler,而是在当前play中所有普通任务都执行完后再去执行handler,这样的好处是可以多次触发notify,但最后只执行一次对应的handler,从而避免多次重启。

2.2 准备http.conf

[root@ansible ansible]# vim httpd.conf

#42行,指定端口
Listen 8080
#95行,指定域名
ServerName www.test.com:8080

2.3 运行剧本

ansible-playbook test1.yaml

[root@ansible ansible]# ansible-playbook test1.yaml

PLAY [first test] ****************************************************************************************************************

TASK [test connection] ***********************************************************************************************************
ok: [192.168.122.11]

TASK [disable selinux] ***********************************************************************************************************
fatal: [192.168.122.11]: FAILED! => {"changed": true, "cmd": ["/sbin/setenforce", "0"], "delta": "0:00:00.001853", "end": "2021-10-22 16:10:22.925768", "msg": "non-zero return code", "rc": 1, "start": "2021-10-22 16:10:22.923915", "stderr": "/sbin/setenforce: SELinux is disabled", "stderr_lines": ["/sbin/setenforce: SELinux is disabled"], "stdout": "", "stdout_lines": []}
...ignoring

TASK [disable firewalld] *********************************************************************************************************
ok: [192.168.122.11]

TASK [install httpd] *************************************************************************************************************
ok: [192.168.122.11]

TASK [install configuration file for httpd] **************************************************************************************
changed: [192.168.122.11]

TASK [start httpd service] *******************************************************************************************************
changed: [192.168.122.11]

RUNNING HANDLER [restart httpd] **************************************************************************************************
changed: [192.168.122.11]

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=7    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=1   

2.4 查看webserbers服务器

[root@ansible ansible]# ansible webservers -m shell -a 'netstat -natp | grep httpd'
192.168.122.11 | CHANGED | rc=0 >>
tcp6       0      0 :::8080                 :::*                    LISTEN      26582/httpd       

2.5 补充参数

-k(–ask-pass):用来交互输入ssh密码
-K(-ask-become-pass):用来交互输入sudo密码
-u:指定用户
ansible-playbook test1.yaml --syntax-check
检查yaml文件的语法是否正确

[root@ansible ansible]# ansible-playbook test1.yaml --syntax-check

playbook: test1.yaml

ansible-playbook test1.yaml --list-task
检查tasks任务

[root@ansible ansible]# ansible-playbook test1.yaml --list-task

playbook: test1.yaml

  play #1 (webservers): first test	TAGS: []
    tasks:
      test connection	TAGS: []
      disable selinux	TAGS: []
      disable firewalld	TAGS: []
      install httpd	TAGS: []
      install configuration file for httpd	TAGS: []
      start httpd service	TAGS: []

ansible-playbook test1.yaml --list-hosts
检查生效的主机

[root@ansible ansible]# ansible-playbook test1.yaml --list-hosts

playbook: test1.yaml

  play #1 (webservers): first play	TAGS: []
    pattern: [u'webservers']
    hosts (1):
      192.168.122.11

ansible-playbook test1.yaml --start-at-task='install httpd'
指定从某个task开始运行

[root@ansible ansible]# ansible-playbook test1.yaml --start-at-task='install httpd'

PLAY [first test] ****************************************************************************************************************

TASK [install httpd] *************************************************************************************************************
changed: [192.168.122.11]

TASK [install configuration file for httpd] **************************************************************************************
changed: [192.168.122.11]

TASK [start httpd service] *******************************************************************************************************
changed: [192.168.122.11]

RUNNING HANDLER [restart httpd] **************************************************************************************************
changed: [192.168.122.11]

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

3. 剧本示例test2--定义、引用变量

3.1 剧本制作

[root@ansible ansible]# vim test2.yaml

---
- name: second test
  hosts: dbservers
  remote_user: root
  vars:
#定义变量
   - groupname: mysql
#格式为 key: value
   - username: nginx
  tasks:
   - name: create group
     group: name={{groupname}} system=yes gid=306
#使用 {{key}} 引用变量的值
   - name: create user
     user: name={{username}} uid=306 group={{groupname}} 
   - name: copy file
     copy: content="{{ansible_default_ipv4}}" dest=/opt/vars.txt
#在setup模块中可以获取facts变量信息

3.2 运行剧本

[root@ansible ansible]# ansible-playbook test2.yaml

PLAY [second play] ***************************************************************************************************************

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

TASK [create group] **************************************************************************************************************
changed: [192.168.122.12]

TASK [create user] ***************************************************************************************************************
changed: [192.168.122.12]

TASK [copy file] *****************************************************************************************************************
changed: [192.168.122.12]

PLAY RECAP ***********************************************************************************************************************
192.168.122.12             : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

3.3 查看dbservers服务器

[root@ansible ansible]# ansible dbservers -a 'grep "mysql" /etc/group'
192.168.122.12 | CHANGED | rc=0 >>
mysql:x:306:
[root@ansible ansible]# ansible dbservers -a 'grep "nginx" /etc/passwd'
192.168.122.12 | CHANGED | rc=0 >>
nginx:x:306:306::/home/nginx:/sbin/nologin
[root@ansible ansible]# ansible dbservers -a 'cat /opt/vars.txt'
192.168.122.12 | CHANGED | rc=0 >>
{"macaddress": "00:0c:29:55:18:bd", "network": "192.168.122.0", "mtu": 1500, "broadcast": "192.168.122.255", "alias": "ens33", "netmask": "255.255.255.0", "address": "192.168.122.12", "interface": "ens33", "type": "ether", "gateway": "192.168.122.2"}

3.4 修改剧本中的变量设定

删除dbservers中的mysql组和nginx用户以及/opt/var.txt

[root@ansible ansible]# ansible dbservers -a 'userdel -r nginx'
192.168.122.12 | CHANGED | rc=0 >>
userdel:组“nginx”没有移除,因为它不是用户 nginx 的主组
[root@ansible ansible]# ansible dbservers -a 'groupdel mysql'
192.168.122.12 | CHANGED | rc=0 >>

[root@ansible ansible]# ansible dbservers -a 'rm -rf /opt/vars.txt'
[WARNING]: Consider using the file module with state=absent rather than running 'rm'.  If you need to use command because file is
insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this
message.
192.168.122.12 | CHANGED | rc=0 >>

确认用户、组以及文件已删除

[root@ansible ansible]# ansible dbservers -a 'grep "nginx" /etc/passwd'
192.168.122.12 | FAILED | rc=1 >>
non-zero return code
[root@ansible ansible]# ansible dbservers -a 'grep "mysql" /etc/group'
192.168.122.12 | FAILED | rc=1 >>
non-zero return code
[root@ansible ansible]# ansible dbservers -a 'cat /opt/vars.txt'
192.168.122.12 | FAILED | rc=1 >>
cat: /opt/vars.txt: 没有那个文件或目录non-zero return code

删除/注释“- username: nginx”变量

[root@ansible ansible]# vim test2.yaml

---
- name: second test
  hosts: dbservers
  remote_user: root
  vars:
   - groupname: mysql
#   - username: nginx
#删除或注释username变量
  tasks:
   - name: create group
     group: name={{groupname}} system=yes gid=306
   - name: create user
     user: name={{username}} uid=306 group={{groupname}} 
   - name: copy file
     copy: content="{{ansible_default_ipv4}}" dest=/opt/vars.txt

3.5 在命令行定义变量运行剧本

[root@ansible ansible]# ansible-playbook test2.yaml -e "username=nginx"

PLAY [second play] ***************************************************************************************************************

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

TASK [create group] **************************************************************************************************************
changed: [192.168.122.12]

TASK [create user] ***************************************************************************************************************
changed: [192.168.122.12]

TASK [copy file] *****************************************************************************************************************
changed: [192.168.122.12]

PLAY RECAP ***********************************************************************************************************************
192.168.122.12             : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

3.6 查看dbservers服务器

[root@ansible ansible]# ansible dbservers -a 'grep "nginx" /etc/passwd'
192.168.122.12 | CHANGED | rc=0 >>
nginx:x:306:306::/home/nginx:/bin/bash
[root@ansible ansible]# ansible dbservers -a 'grep "mysql" /etc/group'
192.168.122.12 | CHANGED | rc=0 >>
mysql:x:306:
[root@ansible ansible]# ansible dbservers -a 'cat /opt/vars.txt'
192.168.122.12 | CHANGED | rc=0 >>
{"macaddress": "00:0c:29:55:18:bd", "network": "192.168.122.0", "mtu": 1500, "broadcast": "192.168.122.255", "alias": "ens33", "netmask": "255.255.255.0", "address": "192.168.122.12", "interface": "ens33", "type": "ether", "gateway": "192.168.122.2"}

4. 剧本示例test3--指定远程主机sudo切换用户

[root@ansible ansible]# vim test3.yaml

---
- hosts: dbservers
  remote_user: zhangsan            
  become: yes
#2.6版本以后的参数,之前是sudo,意思为切换用户运行
  become_user: root
#指定sudo用户为root

执行playbook时:ansible-playbook test1.yml -K <密码>

5. 剧本示例test4--when条件判断

在Ansible中,提供的唯一一个通用的条件判断是when指令,当when指令的值为true时,则该任务执行,否则不执行该任务。
when一个比较常见的应用场景是实现跳过某个主机不执行任务或者只有满足条件的主机执行任务

[root@ansible ansible]# vim test4.yaml

---
- hosts: all
  remote_user: root
  tasks:
    - name: shutdown host
      command: /sbin/shutdown -r now
      when: ansible_default_ipv4.address == "192.168.122.12"
#when指令中的变量名不需要手动加上{{}}
#或者使用
#      when: inventory_hostname == "<主机名>"

执行

[root@ansible ansible]# ansible-playbook test4.yaml 

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

TASK [Gathering Facts] ***********************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.12]

TASK [shutdown host] *************************************************************************************************************
skipping: [192.168.122.11]
fatal: [192.168.122.12]: FAILED! => {"msg": "Failed to connect to the host via ssh: ssh_exchange_identification: read: Connection reset by peer"}

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=1    changed=0    unreachable=0    failed=0    skipped=1    rescued=0    ignored=0   
192.168.122.12             : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0   

执行后,仅有指定主机重启,执行ping模块查看

[root@ansible ansible]# ansible  all -m ping
192.168.122.11 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}
192.168.122.12 | UNREACHABLE! => {
    "changed": false, 
    "msg": "Failed to connect to the host via ssh: ssh: connect to host 192.168.122.12 port 22: Connection timed out", 
    "unreachable": true
}

6. 剧本示例test5--迭代

Ansible提供了很多种循环结构,一般都命名为with_items,作用等同于 loop 循环。

[root@ansible ansible]# vim test5.yaml

---
- name: test5
  hosts: all
  gather_facts: false
  tasks:
    - name: create directories
      file:
        path: "{{item}}"
        state: directory
      with_items:
#等同于 loop:
        - /test/test1
        - /test/test2
    - name: add users
      user: name={{item.name}} state=present groups={{item.groups}}
      with_items:
        - name: test1
          groups: test
        - name: test2
          groups: root
#或使用以下格式
#      with_items:
#        - {name:'test1', groups:'test'}
#        - {name:'test2', groups:'root'}

执行

[root@ansible ansible]# ansible-playbook test5.yaml 

PLAY [test5] *********************************************************************************************************************

TASK [create directories] ********************************************************************************************************
changed: [192.168.122.12] => (item=/test/test1)
changed: [192.168.122.11] => (item=/test/test1)
changed: [192.168.122.12] => (item=/test/test2)
changed: [192.168.122.11] => (item=/test/test2)

TASK [add users] *****************************************************************************************************************
changed: [192.168.122.12] => (item={u'name': u'test1', u'groups': u'test'})
changed: [192.168.122.11] => (item={u'name': u'test1', u'groups': u'test'})
ok: [192.168.122.12] => (item={u'name': u'test2', u'groups': u'root'})
changed: [192.168.122.11] => (item={u'name': u'test2', u'groups': u'root'})

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.122.12             : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

查看验证

[root@ansible ansible]# ansible all -a "ls -l /test/"
192.168.122.12 | CHANGED | rc=0 >>
总用量 0
drwxr-xr-x 2 root root 6 10月 24 15:54 test1
drwxr-xr-x 2 root root 6 10月 24 15:54 test2
192.168.122.11 | CHANGED | rc=0 >>
总用量 0
drwxr-xr-x 2 root root 6 10月 24 15:54 test1
drwxr-xr-x 2 root root 6 10月 24 15:54 test2
[root@ansible ansible]# ansible all -m shell -a "id test1"
192.168.122.11 | CHANGED | rc=0 >>
uid=1002(test1) gid=1002(test1) 组=1002(test1),1001(test)
192.168.122.12 | CHANGED | rc=0 >>
uid=1001(test1) gid=1002(test1) 组=1002(test1),1004(test)
[root@ansible ansible]# ansible all -m shell -a "id test2"
192.168.122.12 | CHANGED | rc=0 >>
uid=1002(test2) gid=1003(test2) 组=1003(test2),0(root)
192.168.122.11 | CHANGED | rc=0 >>
uid=1003(test2) gid=1003(test2) 组=1003(test2),0(root)

7. Template模块

Jinja是基于Python的模块引擎。Template类是Jinja的一个重要组件,可以看做是一个编译过的模板文件,用来产生目标文本,传递Python的变量给模板去替换模板中的标记。

7.1 准备template模板文件

先准备一个以.j2为后缀的template模板文件,设置引用的变量
模板文件使用test1曾用的httpd.conf配置文件

[root@ansible ansible]# cp httpd.conf httpd.conf.j2
[root@ansible ansible]# vim httpd.conf.j2 

##42行,修改
Listen {{http_port}}
##95行,修改
ServerName {{server_name}}
##119行,修改 
DocumentRoot "{{root_dir}}"
##124行,修改
<Directory "{{root_dir}}">

7.2 修改主机清单文件

修改主机清单文件,使用主机变量定义一个变量名相同,而值不同的变量

[root@ansible ansible]# vim /etc/ansible/hosts 

[webservers]
192.168.122.11 http_port=192.168.122.11:80 server_name=www.test1.com:80 root_dir=/etc/httpd/htdocs

[dbservers]
192.168.122.12 http_port=192.168.122.12:80 server_name=www.test2.com:80 root_dir=/etc/httpd/htdocs

7.3 编写playbook

[root@ansible ansible]# vim test6.yaml

---
- hosts: all
  remote_user: root
  vars:
    - package: httpd
    - service: httpd
  tasks:
    - name: install httpd package
      yum: name={{package}} state=latest
    - name: install configure file
      template: src=/root/ansible/httpd.conf.j2 dest=/etc/httpd/conf/httpd.conf     
#使用template模板
      notify:
        - restart httpd
    - name: create root dir
      file: path=/etc/httpd/htdocs state=directory
    - name: start httpd server
      service: name={{service}} enabled=true state=started
  handlers:
    - name: restart httpd
      service: name={{service}} state=restarted

7.4 执行playbook

[root@ansible ansible]# ansible-playbook test6.yaml 

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

TASK [Gathering Facts] ***********************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.12]

TASK [install httpd package] *****************************************************************************************************
ok: [192.168.122.11]
ok: [192.168.122.12]

TASK [install configure file] ****************************************************************************************************
changed: [192.168.122.12]
changed: [192.168.122.11]

TASK [create root dir] ***********************************************************************************************************
changed: [192.168.122.12]
changed: [192.168.122.11]

TASK [start httpd server] ********************************************************************************************************
ok: [192.168.122.11]
changed: [192.168.122.12]

RUNNING HANDLER [restart httpd] **************************************************************************************************
changed: [192.168.122.12]
changed: [192.168.122.11]

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=6    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
192.168.122.12             : ok=6    changed=4    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

7.5 制作测试网页

[root@ansible ansible]# ansible 192.168.122.11 -m shell -a "echo 'this is test1 template test' > /etc/httpd/htdocs/index.html"
192.168.122.11 | CHANGED | rc=0 >>

[root@ansible ansible]# ansible 192.168.122.12 -m shell -a "echo 'this is test2 template test' > /etc/httpd/htdocs/index.html"
192.168.122.12 | CHANGED | rc=0 >>

7.6 访问测试

[root@ansible ansible]# curl 192.168.122.11
this is test1 template test
[root@ansible ansible]# curl 192.168.122.12
this is test2 template test
[root@ansible ansible]# echo '192.168.122.11 www.test1.com' >> /etc/hosts
[root@ansible ansible]# echo '192.168.122.11 www.test2.com' >> /etc/hosts
[root@ansible ansible]# curl www.test1.com
this is test1 template test
[root@ansible ansible]# curl www.test2.com
this is test2 template test

8. tags模块

可以在一个playbook中为某个或某些任务定义“标签”,在执行此playbook时通过ansible-playbook命令使用--tags选项能实现仅运行指定的tasks。
playbook还提供了一个特殊的tags为always。作用就是当使用always当tags的task时,无论执行哪一个tags时,定义有always的tags都会执行。

8.1 编写脚本

[root@ansible ansible]# vim test7.yaml

---
- hosts: webservers
  remote_user: root
  tasks:
    - name: mkdir directory
      file: path=/opt/test/ state=directory
      tags:
      - always
    - name: touch file
      file: path=/opt/test/testhost state=touch
      tags:
      - test1
      - all
    - name: copy hosts file
      copy: src=/etc/hosts dest=/opt/test/hosts
      tags:
      - test2
      - all

8.2 执行tags="test1"

[root@ansible ansible]# ansible-playbook test7.yaml --tags="test1"

PLAY [webservers] ****************************************************************************************************************

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

TASK [mkdir directory] ***********************************************************************************************************
changed: [192.168.122.11]

TASK [touch file] ****************************************************************************************************************
changed: [192.168.122.11]

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

验证

[root@ansible ansible]# ansible webservers -a "ls -l /opt/test/"
192.168.122.11 | CHANGED | rc=0 >>
总用量 0
-rw-r--r-- 1 root root 0 10月 25 12:14 testhost

8.3 执行tags="test2"

删除文件夹

[root@ansible ansible]# ansible webservers -m file -a "path=/opt/test/ state=absent"
192.168.122.11 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "path": "/opt/test/", 
    "state": "absent"
}
[root@ansible ansible]# ansible webservers -a "ls -l /opt/test/"
192.168.122.11 | FAILED | rc=2 >>
ls: 无法访问/opt/test/: 没有那个文件或目录non-zero return code

执行tags="test2"

[root@ansible ansible]# ansible-playbook test7.yaml --tags="test2"

PLAY [webservers] ****************************************************************************************************************

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

TASK [mkdir directory] ***********************************************************************************************************
changed: [192.168.122.11]

TASK [copy hosts file] ***********************************************************************************************************
changed: [192.168.122.11]

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=3    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

验证

[root@ansible ansible]# ansible webservers -a "ls -l /opt/test/"
192.168.122.11 | CHANGED | rc=0 >>
总用量 4
-rw-r--r-- 1 root root 233 10月 25 12:24 hosts

8.4 执行tags="all"

删除文件夹

[root@ansible ansible]# ansible webservers -m file -a "path=/opt/test/ state=absent"
192.168.122.11 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "path": "/opt/test/", 
    "state": "absent"
}
[root@ansible ansible]# ansible webservers -a "ls -l /opt/test/"
192.168.122.11 | FAILED | rc=2 >>
ls: 无法访问/opt/test/: 没有那个文件或目录non-zero return code

执行tags="all"

[root@ansible ansible]# ansible-playbook test7.yaml --tags="all"

PLAY [webservers] ****************************************************************************************************************

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

TASK [mkdir directory] ***********************************************************************************************************
changed: [192.168.122.11]

TASK [touch file] ****************************************************************************************************************
changed: [192.168.122.11]

TASK [copy hosts file] ***********************************************************************************************************
changed: [192.168.122.11]

PLAY RECAP ***********************************************************************************************************************
192.168.122.11             : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

验证

[root@ansible ansible]# ansible webservers -a "ls -l /opt/test/"
192.168.122.11 | CHANGED | rc=0 >>
总用量 4
-rw-r--r-- 1 root root 233 10月 25 12:27 hosts
-rw-r--r-- 1 root root   0 10月 25 12:27 testhost
posted @ 2021-08-28 23:17  丨君丶陌  阅读(159)  评论(0编辑  收藏  举报