Ansible 命令行模式的使用
Ansible 执行自动化任务,分为以下两种执行模式:
(1)ad-hoc(单个模块),单条命令的批量执行,或者叫命令行模式;
(2)playbook,为面向对象的编程,可以把多个想要执行的任务放到一个 playbook 中,当然多个任务在事物逻辑上最好是有上下关联的,通过多个任务可以完成一个总体的目标。
命令行模式一般用于测试、临时应用等场景,而 playbook 方式,主用用于正式环境,通过编写 playbook 文件,可实现固定的、批量的对系统或服务进行配置以及维护工作。
本课时将从头讲 Ansible,使用时需要注意两个概念:管理机和远程主机。管理机是安装 Ansible 的机器,远程主机是 Ansible 批量操作的对象,可以是一个或一组主机。Ansible 通过管理机发出批量操作远程主机的指令,这些指令在每个远程主机上依次执行
(1)hosts 文件(以下 hosts 文件均指 /etc/ansible/hosts 文件)
该文件用来定义 Ansible 批量操作的主机列表,主机列表有多种书写方式,最简单的格式如下:
中括号中的名字代表组名,可以根据需求将庞大的主机分成具有标识的组。比如上面分了两个组 webservers 和 dbservers 组。
主机(hosts)部分可以使用域名、主机名、IP 地址表示;当然使用前两者时,需要主机能反解析到相应的 IP 地址,一般此类配置中多使用 IP 地址;未分组的机器需保留在 hosts 的顶部。
也可在 hosts 文件中,指定主机的范围,示例如下
[web]
www[01:50].ixdba.net
[db]
db[a:f].ixdba.ent
这个配置中,web 主机组的主机为 www01.ixdba.net、www02.ixdba.net、www03.ixdba.net 等以此类推,一直到 www50.ixdba.net。下面的 db 组中的 a:f 表示从 a 到 f 的字符。
在 hosts 文件中,还可以使用变量,变量分为主机变量和组变量两种类型,常用的变量如下表所示
(2)ansible.cfg 文件
此文件定义了 Ansible 主机的默认配置熟悉,比如默认是否需要输入密码、是否开启 sudo 认证、action_plugins 插件的位置、hosts 主机组的位置、是否开启 log 功能、默认端口、key 文件位置等。一般情况下这个文件无需修改,保存默认即可。
注意:host_key_checking 表示是否关闭第一次使用 Ansible 连接客户端时 yes/no 的连接确认提示,False 表示关闭,我们只需要去掉此选项的注释即可。这个问题其实是 SSH 连接的问题,因为 Linux 下的主机在第一次 SSH 连接到一个新的主机时,一般会需要 yes/no 的连接确认,这在自动化运维中是不需要的,因此需要禁止这种确认。在 Ansible 中通过设置 host_key_checking 为 False 就可以避免这种情况。
2. commands 模块
命令行下执行 ansible,基本格式如下
ansible 主机或组 -m 模块名 -a '模块参数' ansible参数
其中:
主机或组,在 /etc/ansible/hosts 里进行指定;
模块名,可以通过 ansible-doc -l 查看目前安装的模块,默认不指定时,使用的是 command 模块;
模块参数,可以通过 “ansible-doc 模块名”查看具体用法
ansible 常用的参数如下表所示:
下面看几个使用 command 模块的例子
[root@docker ~]# ansible 192.168.0.80 -m command -a 'pwd'
192.168.0.80 | CHANGED | rc=0 >>
/root
[root@docker ~]# ansible 192.168.0.80 -m command -a 'chdir=/tmp/ pwd'
192.168.0.80 | CHANGED | rc=0 >>
/tmp
[root@docker ~]# ansible 192.168.0.80 -m command -a 'creates=/tmp/tmp.txt date'
192.168.0.80 | CHANGED | rc=0 >>
Mon Feb 1 12:44:47 CST 2021
[root@docker ~]# ansible 192.168.0.80 -m command -a 'removes=/tmp/tmp.txt date'
192.168.0.80 | SUCCESS | rc=0 >>
skipped, since /tmp/tmp.txt does not exist
[root@docker ~]# ansible 192.168.0.80 -m command -a 'ps -ef|grep sshd'
192.168.0.80 | FAILED | rc=1 >>
error: unsupported SysV option
Usage:
ps [options]
Try 'ps --help '
or 'ps --help '
for additional help text.
For more details see ps(1).non-zero return code
[root@docker ~]#
上面的例子是对主机 192.168.0.80 进行的操作,在实际应用中需要替换为主机组。另外,还用到了 command 模块的几个选项:
creates,后跟一个文件名,当远程主机上存在这个文件时,该命令不执行,否则执行;
chdir,在执行指令之前,先切换到该指定的目录;
removes,后跟一个文件名,当该文件存在时,该选项执行,否则不执行。
注意:commands 模块的执行,在远程主机上,需有 Python 环境的支持。该模块通过在 -a 参数后面跟上要在远程机器上执行的命令即可完成远程操作,不过命令里如果带有特殊字符(“<”、“>”、“|”、“&”等),则执行不成功,也就是 commands 模块不支持这些特殊字符。上面最后那个例子无法执行成功就是这个原因。
3. shell 模块
shell 模块的功能和用法与 command 模块一样,不过 shell 模块执行命令的时候使用的是 /bin/sh,该模块可以执行任何命令。看下面个例子
[root@docker ~]# ansible 192.168.0.80 -m shell -a 'ps -ef|grep sshd'
192.168.0.80 | CHANGED | rc=0 >>
root 1279 1 0 11:55 ? 00:00:00 /usr/sbin/sshd -D
root 1798 1279 0 11:55 ? 00:00:00 sshd: root@pts/0
root 3884 1908 32 12:47 pts/0 00:00:00 /usr/bin/python2 /usr/bin/ansible 192.168.0.80 -m shell -a ps -ef|grep sshd
root 3892 3884 14 12:47 pts/0 00:00:00 /usr/bin/python2 /usr/bin/ansible 192.168.0.80 -m shell -a ps -ef|grep sshd
root 3895 1279 12 12:47 ? 00:00:00 sshd: root@pts/2
root 3979 3974 0 12:47 pts/2 00:00:00 /bin/sh -c ps -ef|grep sshd
root 3981 3979 0 12:47 pts/2 00:00:00 grep sshd
[root@docker ~]#
官方文档表示 command 用起来更安全,更有可预知性,但从我使用角度来说,并没发现有多大差别。
4.raw 模块和 script 模块
raw 模块功能与 command 和 shell 模块类似,shell 能够完成的操作,raw 也都能完成。不同的是,raw 模块不需要远程主机上的 Python 环境。
Ansible 要执行自动化操作,需在管理机上安装 Ansible,客户机上安装 Python,如果客户机上没有安装,那么 command、shell 模块将无法工作,但 raw 可以正常工作。因此,若有的机器没有装 Python,或者装的版本在 2.4 以下,就可以使用 raw 模块来装 Python、python-simplejson 等。
若有些机器根本安装不了 Python 的话(如交换机、路由器等),那么,直接用 raw 模块是最好的选择。下面看个例子:
[root@docker ~]# ansible 192.168.0.80 -m raw -a "ps -ef|grep sshd|awk '{print \$2}'"
192.168.0.80 | CHANGED | rc=0 >>
1279
1798
4137
4145
4147
4148
4158
4171
Shared connection to 192.168.0.80 closed.
[root@docker ~]#
script 模块是将管理端的 shell 脚本拷贝到被管理的远程主机上执行,其原理是先将 shell 复制到远程主机,再在远程主机上执行。此模块的执行,不需要远程主机上的 Python 环境。看下面这个例子:
[root@docker mnt]# cat westos.sh
#!/bin/bash
echo $HOSTNAME
[root@docker mnt]# ansible 192.168.0.38 -m script -a '/mnt/westos.sh'
192.168.0.38 | CHANGED => {
"changed": true,
"rc": 0,
"stderr": "Shared connection to 192.168.0.38 closed.\r\n",
"stderr_lines": [
"Shared connection to 192.168.0.38 closed."
],
"stdout": "zabbix\r\n",
"stdout_lines": [
"zabbix"
]
}
脚本/mnt/westos.sh 在管理端本机上,script 模块执行的时候将脚本传送到远程的 192.168.0.38 主机中,然后执行这个脚本
file 模块、copy 模块与 synchronize 模块
file 模块功能强大,主要用于远程主机上的文件或目录操作,该模块包含如下选项:
下面来看几个使用示例
(1)创建一个不存在的目录,并进行递归授权:
[root@docker mnt]# ansible 192.168.0.80 -m file -a "path=/mnt/abc123 state=directory"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 0,
"group": "root",
"mode": "0755",
"owner": "root",
"path": "/mnt/abc123",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 6,
"state": "directory",
"uid": 0
}
[root@docker mnt]# ll
total 7
drwxr-xr-x. 2 root root 6 Feb 1 15:34 abc123
drwxr-xr-x. 2 root root 6 Dec 11 16:08 hgfs
-rw-r--r--. 1 root root 11 Feb 1 12:55 install.sh
-rw-r--r--. 1 root root 27 Feb 1 15:27 westos.sh
[root@docker mnt]# ansible 192.168.0.80 -m file -a "path=/mnt/abc123 owner=nobody group=nobody mode=0644 recurse=yes"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 99,
"group": "nobody",
"mode": "0644",
"owner": "nobody",
"path": "/mnt/abc123",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 6,
"state": "directory",
"uid": 99
}
[root@docker mnt]# ll
total 8
drw-r--r--. 2 nobody nobody 6 Feb 1 15:34 abc123
drw-r--r--. 2 sshd sshd 6 Feb 1 15:33 ansibletemp
drwxr-xr-x. 2 root root 6 Dec 11 16:08 hgfs
-rw-r--r--. 1 root root 11 Feb 1 12:55 install.sh
-rw-r--r--. 1 root root 27 Feb 1 15:27 westos.sh
[root@docker mnt]# ansible 192.168.0.80 -m file -a "path=/mnt/ansibletemp owner=sshd group=sshd mode=0644 state=directory "
192.168.0.80 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"gid": 74,
"group": "sshd",
"mode": "0644",
"owner": "sshd",
"path": "/mnt/ansibletemp",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 6,
"state": "directory",
"uid": 74
}
[root@docker mnt]# ll
total 8
drw-r--r--. 2 nobody nobody 6 Feb 1 15:34 abc123
drw-r--r--. 2 sshd sshd 6 Feb 1 15:33 ansibletemp
drwxr-xr-x. 2 root root 6 Dec 11 16:08 hgfs
-rw-r--r--. 1 root root 11 Feb 1 12:55 install.sh
-rw-r--r--. 1 root root 27 Feb 1 15:27 westos.sh
[root@docker mnt]#
(2)创建一个文件(如果不存在),并进行授权:
[root@docker mnt]# ansible 192.168.0.80 -m file -a "path=/mnt/syncfile.txt mode=0444"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"gid": 0,
"group": "root",
"mode": "0444",
"owner": "root",
"path": "/mnt/syncfile.txt",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 7,
"state": "file",
"uid": 0
}
[root@docker mnt]# ll
total 12
drw-r--r--. 2 nobody nobody 6 Feb 1 15:34 abc123
drw-r--r--. 2 sshd sshd 6 Feb 1 15:33 ansibletemp
drwxr-xr-x. 2 root root 6 Dec 11 16:08 hgfs
-rw-r--r--. 1 root root 11 Feb 1 12:55 install.sh
-r--r--r--. 1 root root 7 Feb 1 15:41 syncfile.txt
-rw-r--r--. 1 root root 27 Feb 1 15:27 westos.sh
[root@docker mnt]#
(3)创建一个软连接(将 /etc/ssh/sshd_config 软连接到 /mnt/sshd_config):
[root@docker ~]# ansible 192.168.0.80 -m file -a "src=/etc/ssh/sshd_config dest=/mnt/sshd_config owner=sshd state=link"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"dest": "/mnt/sshd_config",
"gid": 0,
"group": "root",
"mode": "0777",
"owner": "root",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 20,
"src": "/etc/ssh/sshd_config",
"state": "link",
"uid": 0
}
[root@docker ~]# cd /mnt
[root@docker mnt]# ll
total 12
drw-r--r--. 2 nobody nobody 6 Feb 1 15:34 abc123
drw-r--r--. 2 sshd sshd 6 Feb 1 15:33 ansibletemp
drwxr-xr-x. 2 root root 6 Dec 11 16:08 hgfs
-rw-r--r--. 1 root root 11 Feb 1 12:55 install.sh
lrwxrwxrwx. 1 root root 20 Feb 1 15:50 sshd_config -> /etc/ssh/sshd_config
-r--r--r--. 1 root root 7 Feb 1 15:41 syncfile.txt
-rw-r--r--. 1 root root 27 Feb 1 15:27 westos.sh
(4)删除一个压缩文件:
[root@docker tmp]# ls -l backup.tar.gz
-rw-r--r--. 1 root root 10240 Feb 1 16:22 backup.tar.gz
[root@docker tmp]# ansible 192.168.0.80 -m file -a "path=/tmp/backup.tar.gz state=absent"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"path": "/tmp/backup.tar.gz",
"state": "absent"
}
[root@docker tmp]# ls -l backup.tar.gz
ls: cannot access backup.tar.gz: No such file or directory
[root@docker tmp]#
(5)创建一个文件:
[root@docker mnt]# ansible 192.168.0.80 -m file -a "path=/mnt/ansibletemp state=touch"
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"dest": "/mnt/ansibletemp",
"gid": 74,
"group": "sshd",
"mode": "0644",
"owner": "sshd",
"secontext": "unconfined_u:object_r:mnt_t:s0",
"size": 6,
"state": "directory",
"uid": 74
}
[root@docker mnt]#
接着继续来看 copy 模块,此模块用来复制文件到远程主机,copy 模块包含的选项如下表所示
(1)拷贝文件并进行权限设置
[root@docker mnt]# ansible 192.168.0.80 -m copy -a 'src=/etc/sudoers dest=/mnt/sudoers owner=root group=root mode=440 backup=yes'
192.168.0.80 | CHANGED => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": true,
"checksum": "e683ad5e5d8d7112d14924c11c98be7bf2ef4918",
"dest": "/mnt/sudoers",
"gid": 0,
"group": "root",
"md5sum": "1b134d95a4618029ff962a63b021e1ca",
"mode": "0440",
"owner": "root",
"secontext": "system_u:object_r:mnt_t:s0",
"size": 4328,
"src": "/root/.ansible/tmp/ansible-tmp-1612167952.14-9699-10783327897062/source",
"state": "file",
"uid": 0
}
[root@docker mnt]# ls
abc123 ansibletemp hgfs install.sh sshd_config sudoers syncfile.txt westos.sh
[root@docker mnt]#
copy 默认会对存在的备份文件进行覆盖,通过 backup=yes 参数可以在覆盖前,对之前的文件进行自动备份。
(2)拷贝文件之后进行验证
这里用了 validate 参数,表示在复制之前验证要拷贝的文件是否正确。如果验证通过则复制到远程主机上,%s 是一个文件路径的占位符,在文件被复制到远程主机之前,它会被替换为 src 后面的文件。
[root@docker mnt]# ansible 192.168.0.80 -m copy -a "src=/etc/sudoers dest=/mnt/sudoers validate='visudo -cf %s'"
192.168.0.80 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"checksum": "e683ad5e5d8d7112d14924c11c98be7bf2ef4918",
"dest": "/mnt/sudoers",
"gid": 0,
"group": "root",
"mode": "0440",
"owner": "root",
"path": "/mnt/sudoers",
"secontext": "system_u:object_r:mnt_t:s0",
"size": 4328,
"state": "file",
"uid": 0
}
(3)拷贝目录并进行递归设定目录的权限
[root@docker mnt]# ansible 192.168.0.80 -m copy -a 'src=/etc/yum dest=/mnt/ owner=hadoop group=hadoop directory_mode=644'
192.168.0.80 | CHANGED => {
"changed": true,
"dest": "/mnt/",
"src": "/etc/yum"
}
[root@docker mnt]# ansible 192.168.0.80 -m copy -a 'src=/etc/yum/ dest=/mnt/bak owner=hadoop group=hadoop directory_mode=644'
192.168.0.80 | CHANGED => {
"changed": true,
"dest": "/mnt/bak/",
"src": "/etc/yum/"
}
[root@docker mnt]#
上面这两个命令执行是有区别的,第一个是拷贝管理机的 /etc/yum 目录到远程主机的 /mnt 目录下;第二个命令是拷贝管理机 /etc/yum 目录下的所有文件或子目录到远程主机的 /mnt/bak 目录下。
copy 模块拷贝小文件还可以,如果拷贝大文件或者目录的话,速度很慢,不建议使用。此时推荐使用synchronize 模块,此模块通过调用 rsync 进行文件或目录同步,同步速度很快,还指出增量同步,该模块常用的选项如下表所示:
下面看几个例子。
(1)同步本地的 /mnt/install.sh 到远程主机
[root@docker mnt]# ansible 192.168.0.38 -m synchronize -a 'src=/mnt/install.sh dest=/tmp'
192.168.0.38 | CHANGED => {
"changed": true,
"cmd": "/usr/bin/rsync --delay-updates -F --compress --archive --rsh=/usr/bin/ssh -S none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null --out-format=<>%i %n%L /mnt/install.sh 192.168.0.38:/tmp",
"msg": "
(2)将远程主机192.168.0.38 上 /mnt/a 文件拷贝到本地的 /tmp 目录下。
[root@docker mnt]# ansible 192.168.0.38 -m synchronize -a 'mode=pull src=/mnt/a dest=/tmp'
192.168.0.38 | CHANGED => {
"changed": true,
"cmd": "/usr/bin/rsync --delay-updates -F --compress --archive --rsh=/usr/bin/ssh -S none -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null --out-format=<>%i %n%L 192.168.0.38:/mnt/a /tmp",
"msg": ">f+++++++++ a\n",
"rc": 0,
"stdout_lines": [
">f+++++++++ a"
]
}
[root@docker mnt]# ls -l /tmp/a
-rw-r--r--. 1 root root 5 Feb 1 16:51 /tmp/a
[root@docker mnt]#
cron 模块、yum 模块与 service 模块
cron 模块用于管理计划任务,常用选项含义如下表所示
下面是几个示例。
(1)系统重启时执行 /data/bootservice.sh 脚本。
ansible 192.168.0.38 -m cron -a 'name="job for reboot" special_time=reboot job="/data/bootservice.sh" '
此命令执行后,会在 192.168.0.88 的 crontab 中写入“@reboot /data/bootservice.sh”,通过“crontab -l ”可以查看到。
(2)表示在每周六的 1:20 分执行"yum -y update"操作。
ansible 192.168.0.38 -m cron -a 'name="yum autoupdate" weekday="6" minute=20 hour=1 user="root" job="yum -y update"'
(3)表示在每周六的 1:30 分以 root 用户执行 "/home/dokcer/backup.sh" 脚本。
ansible 192.168.0.38 -m cron -a 'backup="True" name="autobackup" weekday="6" minute=30 hour=1 user="root" job="/home/dokcer/backup.sh"'
(4)会在 /etc/cron.d 创建一个 check_http_for_ansible 文件,表示每天的 12:30 分通过 root 用户执行 /home/docker/check_http.sh 脚本。
ansible 192.168.0.38 -m cron -a 'name="checkhttp" minute=30 hour=12 user="root" job="/home/docker/check_http.sh" cron_file="check_http_for_ansible" '
(5)删除一个计划任务。
ansible 192.168.0.38 -m cron -a 'name="yum update" state=absent'
再看看 yum 模块的使用,此模块用来通过 yum 包管理器来管理软件包,常用选项以及含义如下表所示:
下面是几个示例。
(1)通过 yum 安装 Redis。
ansible 192.168.0.80 -m yum -a "name=redis state=installed"
(2)通过 yum 卸载 Redis。
ansible 192.168.0.80 -m yum -a "name=redis state=removed"
(3)通过 yum 安装 Redis 最新版本,并设置 yum 源。
ansible 192.168.0.80 -m yum -a "name=redis state=latest enablerepo=epel"
(4)通过指定地址的方式安装 bash
ansible 192.168.0.80 -m yum -a "name=http://mirrors.aliyun.com/centos/7.4.1708/os/x86_64/Packages/bash-4.2.46-28.el7.x86_64.rpm" state=present'
service 模块,此模块用于管理远程主机上的服务,该模块包含如下选项
下面是几个使用示例。
(1)启动 httpd 服务。
ansible 192.168.0.80 -m service -a "name=httpd state=started"
(2)设置 httpd 服务开机自启。
ansible 192.168.0.80 -m service -a "name=httpd enabled=yes"
setup 模块获取 Ansible facts 信息
Ansible facts 是远程主机上的系统信息,主要包含 IP 地址、操作系统版本、网络设备、Mac 地址、内存、磁盘、硬件等信息,这些信息根据远程主机的信息来作为执行条件操作的场景,非常有用。比如,我们可以根据远程主机的操作系统版本,选择安装不同版本的软件包,或者收集远程主机上每个主机的主机名、IP 地址等信息。
那么如何获取 Ansible facts 信息呢,其实,Ansible 提供了一个 setup 模块来收集远程主机的系统信息,这些 facts 信息可以直接以变量的形式使用
下面是两个使用的例子。
(1)查看主机内存信息。
ansible 192.168.0.80 -m setup -a 'filter=ansible_*_mb'
(2)查看接口为 eth0-2 的网卡信息。
ansible 192.168.0.80 -m setup -a 'filter=ansible_em[1-2]'
在后面 ansible-playbook 内容中会讲到的 playbooks 脚本中,经常会用到一个参数 gather_facts,其与该模块相关。gather_facts 默认值为 yes,也就是说,在使用 Ansible 对远程主机执行任何一个 playbook 之前,总会先通过 setup 模块获取 facts,并将信息暂存在内存中,直到该 playbook 执行结束为止。
user 模块与 group 模块
user 模块请求的是 useradd、userdel、usermod 三个指令;group 模块请求的是 groupadd、groupdel、groupmod 三个指令,常用的选项如下表所示
下面看几个使用例子。
(1)创建一个用户 usertest1。
ansible 192.168.0.80 -m user -a "name=usertest1"
(2)创建用户 usertest2,并设置附加组。
ansible 192.168.0.80 -m user -a "name=usertest2 groups=admins,developers"
(3)删除用户 usertest1 的同时,删除用户根目录。
ansible 192.168.0.80 -m user -a "name=usertest1 state=absent remove=yes"
(4)批量修改用户密码。
echo "linux123www" | openssl passwd -1 -salt $(< /dev/urandom tr -dc '[:alnum:]' | head -c 32) -stdin
$1$YrkIHfuu$wzin4k5BFljuCbus5TlC..
ansible 192.168.0.80 -m user -a 'name=usertest2 password="$1$YrkIHfuu$wzin4k5BFljuCbus5TlC.." '
其中:
-1 表示采用的是 MD5 加密算法;
-salt 指定 salt 值,在使用加密算法进行加密时,即使密码一样,由于 salt 不一样,所以计算出来的 hash 值也不一样,除非密码一样,salt 值也一样,计算出来的 hash 值才一样;
“< /dev/urandom tr -dc '[:alnum:]' | head -c 32”产生一个随机的 salt;
passwd 的值不能是明文,passwd 关键字后面应该是密文,密文会被保存在 /etc/shadow 文件中。