17.1、前言:
1、说明:
ansible是自动化运维工具,基于Python开发,实现了批量系统配置、批量程序部署、批量运行命令等功能。
ansible是基于模块工作的,本身没有批量部署的能力,真正具有批量部署的是ansible所运行的模块,
ansible只是提供一种框架,主要组成如下:
(1)Ansible:
是Ansible的命令工具,核心执行工具,一次性或临时执行的操作都是通过该命令执行。
(2)Ansible Playbook:
任务剧本(又称任务集),编排定义Ansible任务集的配置文件,由Ansible顺序依次执行,yaml格式。
(3)Inventory:
Ansible管理主机的清单,默认是/etc/ansible/hosts文件。
(4)Modules:
Ansible执行命令的功能模块,Ansible2.3版本为止,共有1039个模块,还可以自定义模块。
(5)Plugins:
插件,模块功能的补充,常有连接类型插件,循环插件,变量插件,过滤插件,插件功能用的较少。
(6)API:
提供给第三方程序调用的应用程序编程接口。
2、总体架构:
3、ansible特性:
(1)no agents:不需要在被管控主机上安装任何客户端;
(2)no server:无服务器端,使用时直接运行命令即可;
(3)modules in any languages:基于模块工作,可使用任意语言开发模块;
(4)yaml,not code:使用yaml语言定制剧本playbook;
(5)ssh by default:基于SSH工作;
(6)strong multi-tier solution:可实现多级指挥。
4、ansible优点:
(1)轻量级,无需在客户端安装agent,更新时,只需在操作机上进行一次更新即可;
(2)批量任务执行可以写成脚本,而且不用分发到远程就可以执行;
(3)使用python编写,维护更简单,ruby语法过于复杂;
(4)支持sudo。
17.2、环境说明:
1、基本环境:
服务器名称 |
主从关系 |
ip地址 |
controller-node1 |
主 |
172.16.1.90 |
slave-node1 |
从 |
172.16.1.91 |
slave-node2 |
从 |
172.16.1.92 |
2、环境准备(主从操作相同):
(1)关闭防火墙:systemctl stop firewalld && systemctl disable firewalld
(2)禁用selinux:
sed -i "s/SELINUX=enforcing/SELINUX=disabled/g" /etc/sysconfig/selinux
setenforce 0
(3)更改yum和epel源:
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo
yum install -y epel-release
wget -O /etc/yum.repos.d/epel.repo http://mirrors.aliyun.com/repo/epel-7.repo
yum clean all
yum makecache
yum repolist enabled
(4)查看系统:
cat /etc/redhat-release
#CentOS Linux release 7.5.1804 (Core)
uname -r
#3.10.0-862.el7.x86_64
17.3、ansible基本配置:
1、安装ansible:
[root@controller-node1 ~]# yum -y install ansible
2、查看ansible的版本:
[root@controller-node1 ~]# ansible --version
3、ansible inventory文件:
Inventory文件通常用于定义要管理的主机的认证信息,例如ssh登录用户名、密码以及key相关信息。可以同时操作一个组的多台主机,
组与主机组之间的关系都是通过inventory文件配置。配置文件路径为:/etc/ansible/hosts。提示,在定义连接从端的配置时最好都采
用一种连接方式,防止在操作时从端主机重复出现。
(1)基于密码连接从端:
[root@controller-node1 ~]# vim /etc/ansible/hosts
# 主机+端口号+密码
[webserver]
172.16.1.91 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456"
172.16.1.92 ansible_ssh_port=22 ansible_ssh_user=root ansible_ssh_pass="123456"
(2)基于秘钥连接从端:
1)生成公钥私钥并把公钥发送到从端:
[root@controller-node1 ~]# ssh-keygen
[root@controller-node1 ~]# for i in {1,2}; do ssh-copy-id -i 172.16.1.9$i ; done
2)配置连接:
[root@controller-node1 ~]# vim /etc/ansible/hosts
# 方法一,主机+端口号+秘钥
[webserver]
172.16.1.91:22
172.16.1.92:22
# 方法二,别名主机+端口+秘钥
[webserver]
slave-node1 ansible_ssh_host=172.16.1.91 ansible_ssh_port=22
slave-node2 ansible_ssh_host=172.16.1.92 ansible_ssh_port=22
(3)主机组的使用(别名主机+端口+秘钥):
[apache]
slave-node1 ansible_ssh_host=172.16.1.91 ansible_ssh_port=22
slave-node2 ansible_ssh_host=172.16.1.92 ansible_ssh_port=22
[nginx]
slave-node2 ansible_ssh_host=172.16.1.92 ansible_ssh_port=22
#定义多个组,把一个组当另外一个组的组员:
[webserver:children]
apache
nginx
(4)临时指定 inventory 文件:
1)先定义一个主机定义清单(别名主机+端口+秘钥):
[root@controller-node1 ~]# vim /etc/webservers
[apache]
slave-node1 ansible_ssh_host=172.16.1.91 ansible_ssh_port=22
slave-node2 ansible_ssh_host=172.16.1.92 ansible_ssh_port=22
2)在执行命令时指定 inventory 文件:
[root@controller-node1 ~]# ansible apache -m ping -i /etc/webservers -o
(5)inventory 内置参数:
4、ansible ad-hoc:
ad-hoc(临时的),在ansible中是指需要快速执行,并且不需要保存的命令。说白了就是执行简单的命令,
对于复杂的命令则为 playbook,类似于 saltstack 的 state sls 状态文件。
(1)ansible命令格式:
1)常用命令参数:
[root@controller-node1 ~]# ansible -h
Usage: ansible <host-pattern> [options]
-m MODULE_NAME
#模块名字
-a MODULE_ARGS
#模块参数
-o
#使用精简的输出
-C --check
#检查语法
-f FORKS
#并发
--list-hosts
#列出主机列表
2)示例:
[root@controller-node1 ~]# ansible webserver -m shell -a 'uptime' -o
3)命令说明:
ansible webserver -m shell -a 'uptime' -o
(2)host-pattern格式:
1)主机匹配:
说明:匹配主机ip方式不适用于"别名主机+端口+秘钥"的认证方式,需要将ip换成主机别名。
A、匹配一台目标主机:
[root@controller-node1 ~]# ansible 172.16.1.91 -m ping
B、匹配多台目标主机:
[root@controller-node1 ~]# ansible 172.16.1.91,172.16.1.92 -m ping
C、匹配所有目标主机:
[root@controller-node1 ~]# ansible all -m ping
2)组的匹配:
A、列出指定组下的所有主机:
[root@controller-node1 ~]# ansible apache --list
[root@controller-node1 ~]# ansible nginx --list
B、匹配一个组下的所有主机:
[root@controller-node1 ~]# ansible apache -m ping -o
C、匹配apache组中有,但是nginx组中没有的所有主机:
[root@controller-node1 ~]# ansible 'apache:!nginx' -m ping
D、匹配apache组和nginx组中都有的机器(交集):
[root@controller-node1 ~]# ansible 'apache:&nginx' -m ping
E、匹配apache组nginx组两个组所有的机器(并集,等于 ansible apache,nginx -m ping):
[root@controller-node1 ~]# ansible 'nginx:apache' -m ping
17.4、ansible-module操作:
ansible-doc常用命令:
[root@controller-node1 ~]# ansible-doc -h
Usage: ansible-doc [-l|-F|-s] [options] [-t <plugin type> ] [plugin
-j:以json格式显示指定的模块信息。
-l:列出所有的模块。
-s:查看模块常用参数,直接跟模块名,显示模块所有信息。
[root@controller-node1 ~]# ansible-doc -l |wc -l
3387
1、命令相关的模块:
(1)command模块:
ansible默认的模块,执行命令。
注意:shell中的"<", ">", "|", ";", "&","$"等特殊字符不能在command模块中使用,如果需要使用,则用shell模块。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s command
2)在192.168.1.31服务器上面执行ls命令,默认是在当前用户的家目录/root/:
[root@controller-node1 ~]# ansible slave-node1 -a 'ls'
3)chdir 先切换工作目录,再执行后面的命令,一般情况下在编译时候使用:
[root@controller-node1 ~]# ansible slave-node1 -a 'chdir=/tmp/ pwd'
4)creates:
A、如果creates的文件存在,则不执行后面的操作:
[root@controller-node1 ~]# ansible slave-node1 -a 'creates=/tmp/ ls /etc/passwd'
B、如果creates的文件不存在,则执行后面的操作:
[root@controller-node1 ~]# ansible slave-node1 -a 'creates=/tmp11/ ls /etc/passwd'
5)removes 和 creates 相反:
A、如果removes的文件存在,则执行后面的操作:
[root@controller-node1 ~]# ansible slave-node1 -a 'removes=/tmp/ ls /etc/passwd'
B、如果removes的文件不存在,则不执行后面的操作:
[root@controller-node1 ~]# ansible slave-node1 -a 'removes=/tmp11/ ls /etc/passwd'
(2)shell模块:
专门用来执行shell命令的模块,和command模块一样,参数基本一致,都有 chdir、creates、removes 模块。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s shell
2)示例:
[root@controller-node1 ~]# ansible slave-node1 -m shell -a 'mkdir /tmp/test/'
# ansible slave-node1 -m command -a 'mkdir /tmp/test/'
[root@controller-node1 ~]# ansible slave-node1 -m shell -a 'ls /tmp/'
# ansible slave-node1 -m command -a 'ls /tmp/'
3)执行下面这条命令,每次执行都会更新文件的时间戳:
[root@controller-node1 ~]# ansible slave-node1 -m shell -a 'cd /tmp/test && touch 1.txt && ls'
4)由于有时候不想更新文件的创建时间戳,如果文件存在就不执行 creates:
[root@controller-node1 ~]# ansible slave-node1 -m shell -a 'creates=/tmp/test/1.txt cd /tmp/test && touch 1.txt && ls'
# ansible slave-node1 -m command -a 'creates=/tmp/test/1.txt cd /tmp/test && touch 1.txt && ls'
(3)script模块:
用于在被管理机器上执行shell脚本的模块,脚本无需在被管理的机器上存放。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s script
2)编写shell脚本:
[root@controller-node1 ~]# cat ansible_test.sh
#!/bin/sh
echo `hostname`
3)在被管理的机器上执行该脚本:
[root@controller-node1 ~]# ansible all -m script -a '/root/ansible_test.sh'
2、文件相关的模块:
(1)file模块:
用于对文件的处理、创建、删除、权限的控制等。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s file
path:要管理的文件路径
recurse:递归
state(指定服务状态):
directory:创建目录,如果目标不存在则创建目录及其子目录
touch:创建文件,如果文件存在,则修改文件属性
absent:删除文件或目录
mode:设置文件或目录权限
owner:设置文件或目录属主信息
group:设置文件或目录属组信息
link:创建软连接,需要和src配合使用
hard:创建硬连接,需要和src配合使用
2)创建目录:
[root@controller-node1 ~]# ansible slave-node2 -m file -a 'path=/tmp/test1/ state=directory'
3)创建文件:
[root@controller-node1 ~]# ansible slave-node2 -m file -a 'path=/tmp/test2 state=touch'
4)建立软链接(src表示源文件,path表示目标文件):
[root@controller-node1 ~]# ansible slave-node2 -m file -a 'src=/tmp/test1/ path=/tmp/test3 state=link'
5)删除文件:
[root@controller-node1 ~]# ansible slave-node2 -m file -a 'path=/tmp/test2 state=absent'
6)创建文件时同时设置权限等信息:
[root@controller-node1 ~]# ansible slave-node2 -m file -a 'path=/tmp/test4 state=directory mode=775 owner=root group=root'
(2)copy模块:
用于管理端复制文件到远程主机(拷贝后的默认权限文件是644),并可以设置权限,属组,属主等。
重复拷贝时一个文件文件名称和文件的内容相同的文件时执行失败,当文件内容更新时可以再次拷
贝。
1)查看模块参数:
[root@ansible ~]# ansible-doc -s copy
src:需要copy的文件的源路径
dest:需要copy的文件的目标路径
backup:对copy的文件进行备份
content:直接在远程主机被管理文件中添加内容,会覆盖原文件内容
mode:对copy到远端的文件设置权限
owner:对copy到远端的文件设置属主
group:对copy到远端文件设置属组
2)复制文件到远程主机并改名:
[root@controller-node1 ~]# ansible slave-node2 -m copy -a 'src=/root/ansible_test.sh dest=/root/a.sh'
3)复制文件到远程主机,并备份远程文件,安装时间信息备份文件(当更新文件内容后,重新copy时用到):
[root@controller-node1 ~]# ansible slave-node2 -m copy -a 'src=/root/ansible_test.sh dest=/root/a.sh backup=yes'
4)直接在远程主机a.sh中添加内容:
[root@controller-node1 ~]# ansible slave-node2 -m copy -a 'dest=/tmp/a.sh content="#!/bin/bash\necho `uptime`"'
5)复制文件到远程主机,并设置权限及属主与属组:
[root@controller-node1 ~]# ansible slave-node2 -m copy -a 'src=/etc/passwd dest=/tmp/passwd mode=700 owner=root group=root'
(3)fetch模块:
用于从被管理机器上拉取文件,拉取下来的内容会保留目录结构,常用于收集被管理机器的日志文件等。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s fetch
src:指定需要从远端机器拉取的文件路径。
dest:指定从远端机器拉取下来的文件存放路径。
2)从被管理机器上拉取cron日志文件,默认会已管理节点地址创建一个目录,并存放在内
[root@controller-node1 ~]# ansible slave-node2 -m fetch -a 'src=/var/log/cron dest=/tmp/'
[root@controller-node1 ~]# tree /tmp/
3、用户相关的模块:
用于对系统用户的管理,用户的创建、删除、家目录、属组等设置。
(1)user:
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s user
name:指定用户的名字
home:指定用户的家目录
uid:指定用户的uid
group:指定用户的用户组
groups:指定用户的附加组
password:指定用户的密码
shell:指定用户的登录shell
create_home:是否创建用户家目录,默认是yes
remove:删除用户时,指定是否删除家目录
state(指定服务状态):
absent:删除用户
2)创建用户名,指定uid,指定家目录及主组:
[root@controller-node1 ~]# ansible slave-node2 -m user -a 'name=chang uid=1002 home=/opt/chang/ group=root'
[root@controller-node1 ~]# ansible slave-node2 -m shell -a 'id chang && ls -l /opt'
3)创建用户,不创建家目录,并且不能登录:
[root@controller-node1 ~]# ansible slave-node2 -m user -a 'name=lc uid=1003 create_home=no shell=/sbin/nologin'
[root@controller-node1 ~]# ansible slave-node2 -m shell -a 'id lc && tail -1 /etc/passwd'
4)删除用户:
[root@controller-node1 ~]# ansible slave-node2 -m user -a 'name=lc state=absent'
5)删除用户并删除家目录:
[root@controller-node1 ~]# ansible slave-node2 -m user -a 'name=chang state=absent remove=yes'
(2)group:
用于创建组,当创建用户时如果需要指定组,组不存在的话就可以通过group先创建组。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s group
name:指定组的名字
gid:指定组的gid
state(指定服务状态):
absent:删除组
present:创建组(默认的状态)
2)创建组:
[root@controller-node1 ~]# ansible slave-node2 -m group -a 'name=lc2'
3)创建组并指定gid:
[root@controller-node1 ~]# ansible slave-node2 -m group -a 'name=lc3 gid=1005'
3)删除组:
[root@controller-node1 ~]# ansible slave-node2 -m group -a 'name=lc3 state=absent'
4、软件包相关的模块:
(1)yum模块:
用于对软件包的管理,下载、安装、卸载、升级等操作。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s yum
name:指定要操作的软件包名字
download_dir:指定下载软件包的存放路径,需要配合download_only一起使用
download_only:只下载软件包,而不进行安装,和yum --downloadonly一样
list:
installed:列出所有已安装的软件包
updates:列出所有可以更新的软件包
repos:列出所有的yum仓库
state(指定服务状态):
installed, present:安装软件包(两者任选其一都可以)
removed, absent:卸载软件包
latest:安装最新软件包
2)列出所有已安装的软件包:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'list=installed'
3)列出所有可更新的软件包:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'list=updates'
4)列出所有的yum仓库:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'list=repos'
5)只下载软件包并到指定目录下:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'name=httpd download_only=yes download_dir=/tmp/'
6)安装软件包:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'name=httpd state=installed'
7)卸载软件包:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'name=httpd state=removed'
8)安装包组,类似yum groupinstall 'Development Tools':
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'name="@Development Tools" state=installed'
(2)pip模块:
用于安装python中的包。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s pip
2)使用pip时,需要保证被管理机器上有python-pip软件包:
[root@controller-node1 ~]# ansible slave-node2 -m yum -a 'name=python-pip'
3)安装pip包:
[root@controller-node1 ~]# ansible slave-node2 -m pip -a 'name=django'
(3)service模块:
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s service
name:指定需要管理的服务名
enabled:指定是否开机自启动
state(指定服务状态):
started:启动服务
stopped:停止服务
restarted:重启服务
reloaded:重载服务
2)启动服务,并设置开机自启动:
[root@controller-node1 ~]# ansible slave-node2 -m service -a 'name=crond state=started enabled=yes'
5、计划任务相关的模块:
(1)cron模块:
用于指定计划任务,和crontab -e一样。
1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s cron
job:指定需要执行的任务
minute:分钟
hour:小时
day:天
month:月
weekday:周
name:对计划任务进行描述
state(指定服务状态):
absetn:删除计划任务
2)创建一个没有带描述的计划任务:
[root@controller-node1 ~]# ansible slave-node2 -m cron -a 'job="/bin/sh /root/test.sh"'
[root@controller-node1 ~]# ansible slave-node2 -a 'crontab -l'
3)创建一个带描述的计划任务:
[root@controller-node1 ~]# ansible slave-node2 -m cron -a 'name="这是一个测试的计划任务" minute=* hour=* day=* month=* weekday=* job="/bin/bash /root/test.sh"'
[root@controller-node1 ~]# ansible slave-node2 -a 'crontab -l'
4)删除计划任务:
[root@controller-node1 ~]# ansible slave-node2 -m cron -a 'name="None" job="/bin/sh /root/test/sh" state=absent'
6、系统信息相关的模块 setup:
用于获取系统信息的一个模块。
(1)查看模块参数:
[root@controller-node1 ~]# ansible-doc -s setup
(2)查看系统所有信息:
[root@controller-node1 ~]# ansible slave-node1 -m setup
(3)filter 对系统信息进行过滤:
[root@controller-node1 ~]# ansible slave-node1 -m setup -a 'filter=ansible_all_ipv4_addresses'
(4)常用的过滤选项:
ansible_all_ipv4_addresses
# 所有的ipv4地址
ansible_all_ipv6_addresses
# 所有的ipv6地址
ansible_architecture
# 系统的架构
ansible_date_time
# 系统时间
ansible_default_ipv4
# 系统的默认ipv4地址
ansible_distribution
# 系统名称
ansible_distribution_file_variety
# 系统的家族
ansible_distribution_major_version
# 系统的版本
ansible_domain
# 系统所在的域
ansible_fqdn
# 系统的主机名
ansible_hostname
# 系统的主机名,简写
ansible_os_family
# 系统的家族
ansible_processor_cores
# cpu的核数
ansible_processor_count
# cpu的颗数
ansible_processor_vcpus
# cpu的个数
17.5、ansible-playbook操作:
1、playbook介绍:
Playbook与ad-hoc相比是一种完全不同的运用ansible的方式,类似与saltstack的state状态文件。ad-hoc无法持久使用,playbook可以持久使用。
playbook是由一个或多个play组成的列表,play的主要功能在于将事先归并为一组的主机装扮成事先通过ansible中的task定义好的角色。从根本上
来讲,所谓的task无非是调用ansible的一个module。将多个play组织在一个playbook中,即可以让它们联合起来按事先编排的机制完成某一任务。
2、playbook核心元素:
Hosts:执行的远程主机列表。
Tasks:任务集。
Varniables:内置变量或自定义变量在playbook中调用。
Templates:模板,即使用模板语法的文件,比如配置文件等。
Handlers 和 notity 结合使用:由特定条件触发的操作,满足条件方才执行,否则不执行。
tags:标签,指定某条任务执行,用于选择运行 playbook 中的部分代码。
3、playbook语法:
(1)playbook使用yaml语法格式,后缀可以是yaml,也可以是yml。在单 一 一 个playbook文件中,可以连续三个连子号(---)区分多个play,还有
选择性的连续三个点号(...)用来表示play的结尾,也可省略。
(2)次行开始正常写playbook的内容,一般都会写上描述该playbook的功能。
(3)使用#号注释代码。
(4)缩进必须统一,不能空格和tab混用。
(5)缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行实现的。
(6)YAML文件内容和Linux系统大小写判断方式保持一致,是区分大小写的,k/v的值均需大小写敏感。k/v的值可同行写也可以换行写,同行使用":"
分隔,v可以是个字符串,也可以是一个列表。
(7)一个完整的代码块功能需要最少元素包括 name: task。
4、创建playbook文件:
[root@controller-node1 ~]# vim playbook01.yml
--- #固定格式
- hosts: slave-node2 #定义需要执行主机
remote_user: root #远程用户
vars: #定义变量
http_port: 8080 #变量
soft_name: httpd
tasks: #定义一个任务的开始
- name: create new file #定义任务的名称
file: name=/tmp/playtest.txt state=touch #调用模块,具体要做的事情
- name: create new user
user: name=test02 system=yes shell=/sbin/nologin
- name: install package
yum: name={{ soft_name }} state=installed
- name: config httpd
template: src=/root/httpd.conf dest=/etc/httpd/conf/httpd.conf
notify: #定义执行一个动作(action)让handlers来引用执行,与handlers配合使用
- restart apache #notify要执行的动作,这里必须与handlers中的name定义内容一致
- name: copy index.html
copy: src=/root/index.html dest=/var/www/html/index.html
- name: start httpd
service: name=httpd state=started
handlers: #处理器:更加tasks中notify定义的action触发执行相应的处理动作
- name: restart apache #要与notify定义的内容相同
service: name=httpd state=restarted #触发要执行的动作
5、文件准备:
(1)html文件准备:
[root@controller-node1 ~]# echo "playbook test file" >index.html
(2)配置文件准备:
[root@controller-node1 ~]# grep ^Listen httpd.conf
6、执行playbook:
[root@controller-node1 ~]# ansible-playbook playbook01.yml
playbook常用选项:
格式:ansible-playbook <filename.yml> ... [options]
[root@controller-node1 ~]# ansible-playbook -h
--check or -C:只检测可能会发生的改变,但不真正执行操作。
--list-hosts:列出运行任务的主机。
--list-tags:列出playbook文件中定义所有的tags。
--list-tasks:列出playbook文件中定义的所以任务集。
--limit:主机列表只针对主机列表中的某个主机或者某个组执行。
-f:指定并发数,默认为5个。
-t:指定tags运行,运行某一个或者多个tags(前提playbook中有定义tags)。
-v:显示过程 -vv -vvv 更详细。
7、验证playbook执行的结果:
(1)验证创建的文件和用户:
[root@controller-node1 ~]# ansible slave-node2 -m shell -a 'ls /tmp/playtest.txt && id test02'
(2)验证安装的httpd服务及更改的配置文件:
[root@controller-node1 ~]# curl 172.16.1.92:8080
8、template之for if:
(1)示例一:
(2)示例二:
(3)示例三: