自动化运维之ansible

一、ansible介绍 

(1)介绍

        ansible 简单的自动化IT工具。  

        功能:

                   1、自动化部署APP;

                   2、自动化管理配置项;

                   3、自动化持续交付;

                   4、自动化的云服务管理。

        特点:

                   1、关注点并非配置管理、应用部署或IT流程工作流,而是提供一个统一的界面来协调所有的IT自动化功能;

                   2、受管理节点无需安装额外的远程控制软件,由平台通过SSH对其进行管理;

                   3、模块化开发,其模块支持JSON等标准输出格式,可采用任何编程语言重写;

                   4、用户不用编写脚本或者代码管理应用,同时还能搭建工作流实现IT任务的自动化执行。降低自动化技术门槛以及对传统IT的依赖,从而加快项目的交付速度。

         优缺点:

                    1、优点:轻量级,不需要客户端安装agent;更新时,只需要在操作机上进行一次更新即可,批量任务执行可以写成脚本,而且不用分发到远程就可以执行;而且使用python编写,维护更方便;支持sudo。

                    2、缺点:对于几千台、上万台机器的操作,还不清楚性能、效率情况如何,需要进一步了解。

(2)ansible工作原理

         ①、ansible基本架构如下:

 注解:

ansible core :ansible 自身核心模块

host inventory:主机库,定义可管控的主机列表

connection plugins:连接插件,一般默认基于ssh协议链接

modules: core modules (自带模块)、custom modules(自定义模块)

playbooks :剧本,按照所设定编排的顺序执行完成安排任务

      ②、ansible工作原理:

     

     ③、ansible执行过程

ansible命令的执行过程:

  • 加载自己的配置文件/etc/ansible/ansible.cfg
  • 加载自己对应的模块。如:command、ping、shell
  • 通过ansible将模块或命令生成对应的python文件,并将该文件传输至远程服务器的对应执行用户 $HOME/.ansible/tmp/ansible-tmp-数字/xxx.py文件
  • 给文件执行权限 +x
  • 执行并返回结果
  • 删除临时py文件,退出。

    ④、ansible的执行状态(执行后输出的颜色)

二、部署ansible

(1)安装前的准备工作

         查看系统环境

[root@localhost ~]# cat /etc/redhat-release
CentOS Linux release 7.6.1810 (Core)
[root@localhost ~]# uname -a
Linux localhost.localdomain 3.10.0-957.el7.x86_64 #1 SMP Thu Nov 8 23:39:32 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

(2)ansible安装前提

         ①、对管理主机要求

             机器上需要安装Pyhon2.6(windows系统不可以做控制主机),

         ②、对托管主机要求

             托管节点需要安装Python 2.4及以上版本。但如果版本过低,则需要额外安装python-simplejson

(3)ansible安装方式

         ①、源码安装

              源码安装需要Python2.6以上版本,其一拉模块通过pip或easy_install进行安装。

         ②、pip安装

              pip是专门用来管理python模块的工具,ansible会将每次正式发布都更新到pip仓库中。所以通过pip安装或跟更新ansible,会拿到最新稳定版。

         ③、yum安装

               1、安装epel源并查看

[root@localhost ~]# yum -y install epel-release
[root@localhost ~]# ll /etc/yum.repos.d/epel*
-rw-r--r--. 1 root root  951 Oct  3  2017 /etc/yum.repos.d/epel.repo
-rw-r--r--. 1 root root 1050 Oct  3  2017 /etc/yum.repos.d/epel-testing.repo

             2、安装ansible并查看

[root@localhost ~]# yum install ansible -y
[root@localhost ~]# ansible --version
ansible 2.9.10
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Oct 30 2018, 23:45:53) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

   (4)ansible组成介绍

[root@localhost ~]# tree /etc/ansible/
/etc/ansible/
├── ansible.cfg    #ansible的配置文件
├── hosts       #ansible的主仓库,用来储存需要管理的远程主机的相关信息
└── roles

1 directory, 2 files

三、ansible的八个命令

  (1)ansible

   ansible是指令核心部分,其主要用于执行单条命令。默认后面需要跟主机和选项部分,默认不指定模块时,使用的是command模块。默认模块在ansible.cfg中进行修改。

ansible参数(使用-h可查看):

-a 'Arguments' , --args='Arguments' 命令行参数
-m name 执行模块的名字,默认使用commond模块。执行单行命令可以不使用 -m 参数。
-i PATH ,指定库存主机文件的路径,默认为/etc/ansible/hosts
-u USERNAME , 执行用户,使用这个远程用户名而不是当前用户。
-U sudo到哪个用户,默认为root
-k 登陆密码。
-K 提示密码使用sudo

 (2)ansible-doc

  该指令用于查看模块信息,常用参数:

-l:列出所有已安装的模块
例:ansible-doc -l
-s 模块名:查看具体某模块的用法。
例:ansible-doc -s commond

  (3)ansible-galaxy

   该指令用于方便从https://galaxy.ansible.com/站点下载第三方扩展模块,类似于yum 命令

#列出所有已安装的galaxy
ansible-galaxy list

#安装一些模块
ansible-galaxy install aerisclound.docker

#卸载一些模块
ansible-galaxy remove aerisclound.docker

  (4)ansible-lint

 该指令是对playbook的语法进行检查的一个工具。

ansible-lint playbook.yml

  (5)ansible-playbook

该指令是使用最多的指令,其通过读取playbook文件后,执行相应的动作。

  (6)ansible-pull

适用于:有数量巨大的机器配置,即使使用非常高的线程还需要花费很长时间

            在没有网络连接的机器上运行ansible,比如启动后安装

  (7)ansible-vault

      该指令用于加密/解密配置文件。主要用于playbooks文件里涉及到配置密码或者其他变量时,可使用该指令进行加密。加密后,通过cat命令查看到的会是一个密码串类的文件,编辑时需要输入设定的密码。这样的playbook文件执行时,需要加上--ask-vault-pass参数,输入密码后才能正常执行。 

#加密文件 site.yml


ansible-vault encrypt  site.yml   #加密

ansible-vault  decrypt   site.yml  #解密

ansible-vault  view   site.yml  #查看

ansible-vault  edit   site.yml  #编辑加密文件

ansible-vault  rekey  site.yml  #修改文件的密码

ansible-vault  create new.yml   #创建新文件

(8)ansible-console

 此工具可交互执行命令,支持tab,ansible 2.0+新增

使用示例:

[root@localhost test]# ansible-console
Welcome to the ansible console.
Type help or ? to list commands.

root@all (2)[f:5]$ list
192.168.10.11
192.168.10.12
root@all (2)[f:5]$ cd 192.168.10.11
root@192.168.10.11 (1)[f:5]$ list
192.168.10.11
root@192.168.10.11 (1)[f:5]$ yum name=httpd state=present

192.168.10.11 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "changes": {
        "installed": [
            "httpd"
        ]
    }, 
    ……

root@192.168.10.11 (1)[f:5]$ service name=httpd state=started
192.168.10.11 | CHANGED => {
    "changed": true, 
    "name": "httpd", 
    "state": "started", 
    ……

常用子命令:

  • 设置并发数: focks n (forks 10)
  • 切换组: cd 主机组 (也可以切换主机)
  • 列出当前组主机列表:list
  • 列出所有的内置命令: ?或help 

四、Inventory与Patterns介绍

  官方所说的Inventory就是hosts文件,默认情况下位于/etc/ansible/hosts。

   Patterns(模式)部分我们可以理解为正则表达式,通过Patterns可以匹配Inventory中的部分主机。

(1)Hosts and Groups(主机和组)

 对于/etc/ansible/hosts,可以有以下几种形式:

1、简单的主机和组

   一个简单的例子:

abc.mc2-csbu-demo.com
[one]
cy.mc2-csbu-demo.com
zbs.mc2-csbu-demo.com
[two]
wm.mc2-csbu-demo.com
ljc.mc2-csbu-demo.com

如上,中括号中的名字代表组名,可以根据需要将主机分成具有标识的组。

            主机部分可以使用域名、主机名、IP地址标识;使用前两者时,需要主机可以反向解析到相应的IP地址,一般来说,该配置使用IP地址较多。

2、端口和别名

例:如果某些主机的ssh运行在自定义的端口上,当ansible使用Paramiko进行ssh连接时,不会使用SSH配置文件中列出的端酒,但是如果修改ansible使用openssh进行ssh连接时,/etc/ansible/hosts就如下:

[abc]
192.168.0.10:5309

且如果想为静态IP设置别名,类似于SaltStack中的minion配置文件中的id参数配置。/etc/ansible/hosts如下:

jumper ansible_ssh_port = 5555 ansible_ssh_host=192.168.0.10

 以上示例表示了指代IP为192.168.0.10,ssh连接端口为5555的主机。

3、指定主机范围

例:

[abc]
192.168.0.[10:50]
[efg]
www[10:50].mc2-csbu-demo.com

如上制定了两个组,abc组中有40台主机,从192.18.0.10到192.168.0.50。 

 4、使用主机变量

Hosts部分中经常用到的变量如下:


ansible_ssh_host  #要连接的主机名
ansible_ssh_port  #端口号默认为22
ansible_ssh_user  #ssh连接时默认使用的用户名
ansible_ssh_pass  #ssh连接时的密码
ansible_sudo_pass  #使用sudo连接用户时的密码
ansible_ssh_private_key_file  #密钥文件如果不想使用ssh-agent管理时可以使用这个选项。
ansible_shell_type #shell的类型默认sh
ansible_connection #ssh连接的类型:local、ssh、paramiko。在ansible1.2之前默认为paramiko,后来智能选择,优先使用ControlPersist的ssh(支持的前提下)
ansible_python_interpreter #用来指定python解释器的路径,也可以指定其他的


示例:

[test]
192.168.0.10 ansible_ssh_user=root ansible_ssh_pass='root'
192.168.0.20 ansible_ssh_user=user01 ansible_ssh_pass='user01'
192.168.0.30 ansible_ssh_user=user02 ansible_ssh_port=7777 ansible_ssh_pass='user02'

以上实例中,定义了三台主机,分别定义了 用户、密码和端口。

  5、组内变量

一个例子如下:

[test]
host01
host02
[test:vars]
ntp_server=ntp.mc2-csbu-demo.com
proxy=proxy.mc2-xsbu-demo.com

上面的test组中包含两个主机,通过对test组只当vars变量,相应的host01和host02相当于相应的ntp_server和proxy变量参数值。 

6、组的包含与组内变量

一个例子如下:

[shanghai]
xuhui
songjiang
[shanxi]
yanta
qujaing
[zhongguo:children]
shanghai
shanxi
[zhongguo:vars]
some_server=www.mc2-csbu-demo.com
halon_system_timeout=30
self_dedtruct_countdown=60
escape_pods=2
[world:children]
zhongguo
hanguo

 在以上实例中,上海组中有松江和徐汇;陕西组有雁塔、曲江;又指定了中国组,包含上海组和陕西组;同时为中国组中的所有主机指定四个变量;最后设置了一个世界组,包含中国组、韩国。

(2)Patterns(主机与组正则匹配部分)

Patterns意味着在ansible中管理哪些主机,也可以理解为与哪台主机进行通信。ansible的用法如下:

ansible <pattern_goes_here> -m <module_name> -a <arguments>

一个示例:

ansible webservers -m service -a "name=httpd state=restarted"

如上示例中,webservers组局势Pattern部分。该命令的意思是对webserver组或者主机重启httpd服务。

1、表示所有主机可以使用all或者*

2、通配符与逻辑或

利用通配符或者:的例子如下

one.361way.com


#冒号表示or--逻辑或 即两个主机
one.361way.com:two.361way.com  



#通配符指定一组具有规则特征的主机或主机名
*.361way.com
192.168.0.*



#以上的用法,在多个组之间同样适用
webservers:dbservers //表示两个组之间的所有主机

3、逻辑非与逻辑and

一个例子:

webserver:!dbserver
#目标主机必须在组webserver中但不在dbserver组中。

webserver:&dbserver
#目标主机必须即在webserver组中又在dbserver组中。


webserver:dbserver:&staging:!phone
#目标主机可以在webserver组中或在dbserver组中,还必须存在于staging组中,但又不存在于phone组中

4、混合高级用法

 一个简单的例子:

*.361way.com:*.org

~(web|db).*\.9lit\.org  #该表达式表示*web.*.9lit.com和*db.*.9lit.com

混合高级用法在ansible-playbook中的用法 

     a、在ansible-playbook命令中,可以使用变量组成表达式,但是必须如下使用

ansible-palybook -e webserver:!{{excluded}}:&{{required}}

     b、在ansible和ansible-playbook中,还可以通过一个参数“-limit”来明确指定排除某些主机或组:

ansible-playbook site.yml --limit datacennter2

(3)配置文件ansible.cfg

     ansible.cfg文件的默认存放位置在/etc/ansible/下。但也可以存放于其他位置,默认的读ansible.cfg文件的顺序是:当前命令运行的目录下的ansible.cfg > 用户家目录下的ansible.cfg>/etc/ansible/ansible.cfg。

  • ansible.cfg 配置文件可以存放在多个地方,先找到哪个就使用哪个的配置;
  • 其 ansible.cfg 配置的所有内容均可在命令行通过参数的形式传递或定义在 playbook 中。

默认的ansible.cfg文件中的大部分选项前都有注释。

  1.     [defaults]:配置下定义常规的连接类配置,如 inventory、library、remote_tmp、local_tmp、forks…
  2. [paramiko_connection]:该部分不常用。
  3. [ssh_connection]:Ansible默认使用SSH协议连接对端主机,该部署主要是SSH连接的一些配置,但配置项较少,多数默认即可。

具体的一些选项可以参考:ansible.cfg配置文件详解

                                            ansible.cfg 配置文件详解

(4)  ansible的简单使用

           在使用ansible之前,需要进行ansible免密登录,有两种方法:

            方法一:

                       ansible免密登录

            方法二(该方法是因为我在hosts文件中写入了一个外网地址,即一个弹性云服务器,所以方法一没有用。):

                       首先在本机生成私钥:ssh-keygen

                       其次,将私钥分发给所有主机:ssh-copy-id root@主机IP

                       此方法如果报错,可能是因为需要一个包:yum install openssh-clientsansible

    我的hosts文件:

    配置完成后,就可以成功使用absible了            

五、ansible常用模块

       ansible官方的分类,将模块按照功能分类为:云模块、命令模块、数据库模块、文件模块、资产模块、消息模块、监控模块、网络模块、通知模块、包管理模块、源码控制模块、系统模块、单元模块、web设施模块、windows模块。

       可以使用ansible-doc -l命令查看所有模块,可以使用ansible-doc -s module来查看某个模块的参数,也可以使用ansible-doc help module来查看该模块更详细的信息。

       下面是一些常用的模块:

(1)ping模块

     该模块用来测试主机之间是否可通,不涉及参数:

 (2)setup模块

       setup模块,主要用于获取主机信息,在playbooks里经常会用到的一个参数gather_facts就与该模块相关。setup模块下经常使用的一个参数是filter参数,具体使用如下:

 #查看网卡信息

[root@localhost ansible]# ansible test1 -m setup -a 'filter=ansible_eth0'
47.100.187.247 | SUCCESS => {
    "ansible_facts": {
        "ansible_eth0": {
            "active": true, 
            "device": "eth0", 
            "features": {
                "busy_poll": "off [fixed]", 
                "fcoe_mtu": "off [fixed]", 
                "generic_receive_offload": "on", 
                "generic_segmentation_offload": "on", 
                "highdma": "on [fixed]", 
                "hw_tc_offload": "off [fixed]", 
                "l2_fwd_offload": "off [fixed]", 
                "large_receive_offload": "off [fixed]", 
                "loopback": "off [fixed]", 
                "netns_local": "off [fixed]", 
                "ntuple_filters": "off [fixed]", 
                "receive_hashing": "off [fixed]", 
                "rx_all": "off [fixed]", 
                "rx_checksumming": "on [fixed]", 
                "rx_fcs": "off [fixed]", 
                "rx_gro_hw": "off [fixed]", 
                "rx_udp_tunnel_port_offload": "off [fixed]", 
                "rx_vlan_filter": "off [fixed]", 
                "rx_vlan_offload": "off [fixed]", 
                "rx_vlan_stag_filter": "off [fixed]", 
                "rx_vlan_stag_hw_parse": "off [fixed]", 
                "scatter_gather": "on", 
                "tcp_segmentation_offload": "on", 
                "tx_checksum_fcoe_crc": "off [fixed]", 
                "tx_checksum_ip_generic": "on", 
                "tx_checksum_ipv4": "off [fixed]", 
                "tx_checksum_ipv6": "off [fixed]", 
                "tx_checksum_sctp": "off [fixed]", 
                "tx_checksumming": "on", 
                "tx_fcoe_segmentation": "off [fixed]", 
                "tx_gre_csum_segmentation": "off [fixed]", 
                "tx_gre_segmentation": "off [fixed]", 
                "tx_gso_partial": "off [fixed]", 
                "tx_gso_robust": "off [fixed]", 
                "tx_ipip_segmentation": "off [fixed]", 
                "tx_lockless": "off [fixed]", 
                "tx_nocache_copy": "off", 
                "tx_scatter_gather": "on", 
                "tx_scatter_gather_fraglist": "off [fixed]", 
                "tx_sctp_segmentation": "off [fixed]", 
                "tx_sit_segmentation": "off [fixed]", 
                "tx_tcp6_segmentation": "on", 
                "tx_tcp_ecn_segmentation": "on", 
                "tx_tcp_mangleid_segmentation": "off", 
                "tx_tcp_segmentation": "on", 
                "tx_udp_tnl_csum_segmentation": "off [fixed]", 
                "tx_udp_tnl_segmentation": "off [fixed]", 
                "tx_vlan_offload": "off [fixed]", 
                "tx_vlan_stag_hw_insert": "off [fixed]", 
                "udp_fragmentation_offload": "on", 
                "vlan_challenged": "off [fixed]"
            }, 
            "hw_timestamp_filters": [], 
            "ipv4": {
                "address": "172.24.41.142", 
                "broadcast": "172.24.63.255", 
                "netmask": "255.255.192.0", 
                "network": "172.24.0.0"
            }, 
            "macaddress": "00:16:3e:16:a2:8f", 
            "module": "virtio_net", 
            "mtu": 1500, 
            "pciid": "virtio2", 
            "promisc": false, 
            "timestamping": [
                "rx_software", 
                "software"
            ], 
            "type": "ether"
        }, 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false
}

#查询主机内存信息

#查询所有的主机的相关信息并存储到某个目录中。

[root@localhost ansible]# ansible all -m setup --tree /tmp/facts
[root@localhost ansible]# ll /tmp/facts/
总用量 80
-rw-r--r--. 1 root root 16277 12月 23 01:20 192.168.10.10
-rw-r--r--. 1 root root 16278 12月 23 01:20 192.168.10.11
-rw-r--r--. 1 root root 16277 12月 23 01:20 192.168.10.12
-rw-r--r--. 1 root root 29118 12月 23 01:20 47.100.187.247

 (3)file模块

     file模块主要用于远程主机上的文件操作,file模块的如下选项(该选项都可以使用ansible-doc -s file):


  • force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要取消之前的软链接,并重新创建新的软链接;有两个选项(yes|no)。
  • group:定义文件/目录的属组
  • mode:定义文件/目录的权限
  • owner:定义文件/目录的属组
  • path:必选项,定义文件/目录的路径
  • recurse:递归的设置文件的属性,只对目录有效。
  • src:要被链接的源文件的路径,只适用于state=link的情况
  • dest:被链接到的路径,只适用于state=link的情况
  • state:选项:  absent 删除    directory 目录不存在创建  file 不创建文件检查文件是否存在hard 硬链接  link 软链接   touch 文件不存在创建,如果文件存在更新创建时间

使用示例:

#创建软链接

 操作结果:

 #删除某个文件

(4)copy模块 

复制文件到远程主机,copy模块包含如下选项(该选项可以使用ansible-doc -s copy):


backup:在覆盖之前将源文件备份,备份时间包含时间信息。有两个选项:yes| no

content:用于替代"src",可以直接设定指定文件的值。

dest:必选项。要将源文件复制到远程主机的绝对路径。如果源文件是一个目录,那么该路径也必须是一个目录。

directory_mode:递归的设定目录的权限,默认为系统默认权限。

force:如果目标主机中包含该文件,但内容不同,如果设置为yes,则强制覆盖;如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes。

others:所有的file模块里的选项都可以在这里使用。

src:要复制到远程主机的文件在本机的位置,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将被递归复制。如上情况下,如果该路径以”/“结尾,且只复制当前路径下的文件;如果不以”/“结尾,则包含整个目录在内的整个内容全部复制。


#将一个本地的文件复制到目标主机,并设置用户foo,组为foo,且权限为0644。

结果:

 # 修改上述文件的内容

(5)command模块

该模块通过-a跟上要执行的命令可以直接执行,但命令中包含某些字符("$HOME","<",">","|","&")命令会执行不成功。选项如下(可以使用ansible-doc -s command):


creates: 创建一个文件,当该文件存在时,则该命令不执行。

free_from:要执行的linux指令。

chdir:在执行指令前,先切换到该指定的目录。

removes:删除一个文件名,当该文件不存在,则该选项不执行。

executable:切换shell来执行指令,该执行路径必须是一个绝对路径。


注:commond模块不是调用shell的指令,所有没有bash的环境变量,也不能使用shell的一些操作方法。

#查看远程主机的内存

(6)shell模块 

        用法和command一样,不过是其通过/bin/bash进行执行,所以shell模块可以执行任何命令,就像在本机执行一样。

(7)raw模块

       用法和shell模块一样,可以执行任意命令,就像在本机执行一样。

 (8)script模块

    将管理端的shell在被管理主机上执行,其原理是先将shell复制到远程主机,再在远程主机上执行,原理类似于raw模块。

   注:raw模块于command、shell模块不同的是其没有chdir、creates、removes等参数。

(9)service模块

     该模块用来管理服务。

     注意:假如想要管理远程主机中的某个服务,那么这个服务必须能被 BSD init, OpenRC, SysV, Solaris SMF, systemd, upstart 中的任意一种所管理,否则 service 模块也无法管理远程主机的对应服务。这样说可能不容易理解,那么我们换个方式来解释,假设你在使用 centos6,那么你的 centos6 中的 nginx 必须能够通过 “service nginx start” 启动,如果你的 nginx 无法通过 “service nginx start” 进行启动,那么它也同样无法通过 ansible 的 service 模块启动。假设你在使用 centos7,那么你的 centos7 中的 nginx 则必须能够通过 “systemctl start nginx” 启动,如果它无法通过 “systemctl start nginx” 进行启动,那么它也同样无法通过 ansible 的 service 模块进行启动。centos6 中默认通过 sysv 管理服务,centos7 中默认通过 systemd 管理服务。所以,如果服务无法通过 BSD init, OpenRC, SysV, Solaris SMF, systemd, upstart 中的任意一种所管理,那么它也无法被ansible 的 service 模块管理。

   该模块包含的选项:


arguments:给命令行提供一下选项。

enabled:是否设置为开机自启。

name:必选项,服务名称

pattern:定义一个模式,如果通过status指令来查看服务的状态时,没有响应,就会通过ps

指令再进程中根据该模式进行查找,如果匹配到,则认为该服务依然运行。

runlevel:运行级别

sleep:如果执行了restarted,则在stop和start之间沉睡几秒钟。

state:对当前服务执行启动,停止,重启,重新加载等操作。(started、stopped、restarted、reloaded)


#将docker设置为开机自启动,并开启服务

  结果:

 (10)cron模块

   该模块用于管理计划任务,该模块包含以下选项:


backup:对远程主机上的原计划任务内容进行备份。

cron_file:如果指定该选项,则用该文件替换远程主机上的cron.d目录下的用户的任务计划。

day:日(1-31,*,*/2,……)

hour:小时

minute:分钟

month:月

weekday:周

job:要执行的任务,依赖于state=present

name:该任务的描述

special_time:指定什么时候执行,参数:reboot、yearly、annually、monthly、weekly、daily、hourly。

state:确认该任务是执行还是删除。

user:以哪个身份执行。


 #创建一个计划任务

#删除刚刚创建的计划任务。

(11)filesystem模块(未理解,后续填坑)

(12)yum模块

 使用yum包管理器来管理软件包,其选项大致有: 


config_file:yum的配置文件

disable_gpg_check:关闭 gpg_check

disablerepo:不启动某个源

enablerepo:启动某个源

name:要进行操作的软件包的名字,也可以传递一个url或一个本地的rpm包的路径。

state:状态 (present、absent、latest)


#安装httpd

 结果:

(13)user模块与group模块

         user模块的请求是useradd、userdel、usermod三个指令;group模块请求的是groupadd、groupdel、groupmod指令。具体参数可以使用ansible-doc -s user/group命令获取。

       1、user模块

         #创建用户,并指定家目录和shell

      #删除刚刚创建的用户

       2、group模块

#创建一个新的用户组

#删除刚刚创建的用户组

(14)synchronize模块

该模块使用rsync同步文件。使用该模块,系统必须安装rsync 包,否则无法使用这个模块大致参数如下:


archive:归档,相当于同时开启recursive(递归)、links、perms、times、owner、group、-D选项都为yes,默认该项开启。

checksum:跳过检测sum值,默认为关闭。

compress:是否开启压缩。

copy_links:复制链接文件,默认为no

delete:删除不存在的文件,默认为no

更多参数可使用ansible-doc -s synchronize来查看 


一个比较好的示例,可参考:anbile模块之synchronize 

 (15)mount模块(后续填坑……)

mount模块用来配置挂载点,选项可以使用ansible-doc -s mount命令来查看。

(16)Fetch模块

     功能:从远程主机提取文件至ansible的主控端,copy相反,目前不支持目录

[root@localhost ansible]# ansible all -m fetch -a "src=/etc/redhat-release dest=/etc/ansible/abc/"
192.168.10.11 | CHANGED => {
    "changed": true, 
    "checksum": "dd9a53b0d396d3ab190cfbc08dca572d3e741a03", 
    "dest": "/etc/ansible/abc/192.168.10.11/etc/redhat-release", 
    "md5sum": "712356bf79a10f4c45cc0a1772bbeaf6", 
    "remote_checksum": "dd9a53b0d396d3ab190cfbc08dca572d3e741a03", 
    "remote_md5sum": null
}
[root@localhost ansible]# tree /etc/ansible/abc/
/etc/ansible/abc/
└── 192.168.10.11
    └── etc
        └── redhat-release

(17)unarchive模块

 该模块可用来解包解压缩。

 它有两种用法:

1、将ansible主机上的压缩包传到远程主机后解压缩至特定目录,设置copy=yes
2、将远程主机上的某个压缩包解压缩到指定路径下,设置copy=no。


常见参数:

  • copy:默认为yes,当copy=yes,拷贝的文件是从ansible主机复制到远程主机上, 当copy=no,拷贝的文件是在远程主机上需要源文件。
  • remote_src:和copy功能一样且互斥,yes表示是在远程主机,no表示文件在ansible主机上。
  • mode/src/dest:设置解压缩之后的文件权限/源路径/目标路径

(18)archive模块

 该模块可用来将某些文件打包压缩


参数:

format:用于打包的压缩方式,例如:bz2、gzip 


[root@localhost ansible]# ansible 192.168.10.11 -m archive -a 'path=/etc/redhat-release dest=/home/etc.tar.bz2 format=bz2 owner=root mode=0600'
192.168.10.11 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "archived": [
        "/etc/redhat-release"
    ], 
    "arcroot": "/etc/", 
    "changed": true, 
    "dest": "/home/etc.tar.bz2", 
    "expanded_exclude_paths": [], 
    "expanded_paths": [
        "/etc/redhat-release"
    ], 
    "gid": 0, 
    "group": "root", 
    "missing": [], 
    "mode": "0600", 
    "owner": "root", 
    "secontext": "unconfined_u:object_r:home_root_t:s0", 
    "size": 82, 
    "state": "file", 
    "uid": 0
}

(19)hostname模块

该模块可用来管理主机的名字

[root@localhost ansible]# ansible 192.168.10.11 -m command -a "hostname"
192.168.10.11 | CHANGED | rc=0 >>
git
[root@localhost ansible]# ansible 192.168.10.11 -m hostname -a "name=ansible-webserver"
192.168.10.11 | CHANGED => {
    "ansible_facts": {
        "ansible_domain": "", 
        "ansible_fqdn": "ansible-webserver", 
        "ansible_hostname": "ansible-webserver", 
        "ansible_nodename": "ansible-webserver", 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "name": "ansible-webserver"
}
[root@localhost ansible]# ansible 192.168.10.11 -m command -a "hostname"
192.168.10.11 | CHANGED | rc=0 >>
ansible-webserver

(20)cron模块

该模块用来设置计划任务

支持时间:minute、hour、day、month、weekday

#创建计划任务
ansible all -m cron -a 'hour=2 minute=30 weekday=1-5 name="backup mysql" job="/root/mysql_backup.sh" '
ansible all -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1 &> /dev/null' name=time"

#禁用计划任务
ansible all -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1 &> /dev/null' name=time disabled=yes"


#启用计划任务
ansible all -m cron -a "minute=*/5 job='/usr/sbin/ntpdate 172.20.0.1 &> /dev/null' name=time disabled=no"


#删除任务
ansible all -m cron -a "name='backup mysql' state=absent"
ansible all -m cron -a "name='time' state=absent"

(21)比肩与sed的模块

ansible在使用sed进行替换时,经常会遇到需要转义的问题,所以,ansible自身提供两个模块:lineinfile和replace模块。

1、Lineinfile模块

范例:

ansible 192.168.10.11 -m lineinfile -a "path=/etc/selinux/config regexp='^SELINUX=' line='SELINUX=enforcing'"

ansible 192.168.10.11 -m lineinfile -a 'dest=/etc/fstab state=absent regexp="^#"'

2、Replace模块

范例:

ansible 192.168.10.11 -m replace -a "path=/etc/fstab regexp='^(UUID.*)' replace='#\1'"

ansible 192.168.10.11 -m replace -a "path=/etc/fstab regexp='^#(.*)' replace='\1'"

六、ansible playbook简单介绍

(1)yaml语法

        对于ansible,每一个yaml文件都是从一个列表开始。表中的每一项都是一个键值对,通常他们被称为一个“哈希”或“字典”。

       所有的YAML文件开始行都是“---”。这个YAML文件的一部分,表示这个文件开始。列表中的所有成员都开始于相同的缩进级别,并且使用一个“- ”作为开头。(一个短杠和一个空格)

       一个字典是由一个简单的“键: 值‘的形式出现的(冒号之后必须需要空格)。

      字典也可以使用缩进形式来表示,例如:

 {name:Lucille , job:Developer, skill:Eliter}

      YAML中容易踩坑的两个点,如下: 

1、例如,某个键值对的值中包含:

foo:this is a test : yes

以上示例应该写作: foo:"foo:this is a test : yes"

2、关于引用变量

ansible中经常使用如下”{{var}}“进行变量引用。如果一个值以{开头,那么YAML就会认为他是一个字典。所以,应用如下:

foo:"{{var}}"

(2)ansible-playbook介绍

     ansible playbook是定义的一个配置文件,文件中写入需要安装的服务,配置文件,变量等信息,使得任务可以按照实现定义好的机制完成;它是一系列ansible命令的集合,其利用yaml语言编写,运行过程ansible-playbook命令genuine自上而下的顺序依次完成;playbook通过ansible-playbook命令使用它的参数,与ansible命令相似。

(3)ansible-playbook命令参数

[root@localhost ~]# ansible-playbook -h
usage: ansible-playbook [-h] [--version] [-v] [-k]
                        [--private-key PRIVATE_KEY_FILE] [-u REMOTE_USER]
                        [-c CONNECTION] [-T TIMEOUT]
                        [--ssh-common-args SSH_COMMON_ARGS]
                        [--sftp-extra-args SFTP_EXTRA_ARGS]
                        [--scp-extra-args SCP_EXTRA_ARGS]
                        [--ssh-extra-args SSH_EXTRA_ARGS] [--force-handlers]
                        [--flush-cache] [-b] [--become-method BECOME_METHOD]
                        [--become-user BECOME_USER] [-K] [-t TAGS]
                        [--skip-tags SKIP_TAGS] [-C] [--syntax-check] [-D]
                        [-i INVENTORY] [--list-hosts] [-l SUBSET]
                        [-e EXTRA_VARS] [--vault-id VAULT_IDS]
                        [--ask-vault-pass | --vault-password-file VAULT_PASSWORD_FILES]
                        [-f FORKS] [-M MODULE_PATH] [--list-tasks]
                        [--list-tags] [--step] [--start-at-task START_AT_TASK]
                        playbook [playbook ...]

Runs Ansible playbooks, executing the defined tasks on the targeted hosts.

positional arguments:
  playbook              Playbook(s)

optional arguments:
  --ask-vault-pass      ask for vault password
  --flush-cache         clear the fact cache for every host in inventory
  --force-handlers      run handlers even if a task fails
  --list-hosts          outputs a list of matching hosts; does not execute
                        anything else
  --list-tags           list all available tags
  --list-tasks          list all tasks that would be executed
  --skip-tags SKIP_TAGS
                        only run plays and tasks whose tags do not match these
                        values
  --start-at-task START_AT_TASK
                        start the playbook at the task matching this name
  --step                one-step-at-a-time: confirm each task before running
  --syntax-check        perform a syntax check on the playbook, but do not
                        execute it
  --vault-id VAULT_IDS  the vault identity to use
  --vault-password-file VAULT_PASSWORD_FILES
                        vault password file
  --version             show program's version number, config file location,
                        configured module search path, module location,
                        executable location and exit
  -C, --check           don't make any changes; instead, try to predict some
                        of the changes that may occur
  -D, --diff            when changing (small) files and templates, show the
                        differences in those files; works great with --check
  -M MODULE_PATH, --module-path MODULE_PATH
                        prepend colon-separated path(s) to module library (def
                        ault=~/.ansible/plugins/modules:/usr/share/ansible/plu
                        gins/modules)
  -e EXTRA_VARS, --extra-vars EXTRA_VARS
                        set additional variables as key=value or YAML/JSON, if
                        filename prepend with @
  -f FORKS, --forks FORKS
                        specify number of parallel processes to use
                        (default=5)
  -h, --help            show this help message and exit
  -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY
                        specify inventory host path or comma separated host
                        list. --inventory-file is deprecated
  -l SUBSET, --limit SUBSET
                        further limit selected hosts to an additional pattern
  -t TAGS, --tags TAGS  only run plays and tasks tagged with these values
  -v, --verbose         verbose mode (-vvv for more, -vvvv to enable
                        connection debugging)

Connection Options:
  control as whom and how to connect to hosts

  --private-key PRIVATE_KEY_FILE, --key-file PRIVATE_KEY_FILE
                        use this file to authenticate the connection
  --scp-extra-args SCP_EXTRA_ARGS
                        specify extra arguments to pass to scp only (e.g. -l)
  --sftp-extra-args SFTP_EXTRA_ARGS
                        specify extra arguments to pass to sftp only (e.g. -f,
                        -l)
  --ssh-common-args SSH_COMMON_ARGS
                        specify common arguments to pass to sftp/scp/ssh (e.g.
                        ProxyCommand)
  --ssh-extra-args SSH_EXTRA_ARGS
                        specify extra arguments to pass to ssh only (e.g. -R)
  -T TIMEOUT, --timeout TIMEOUT
                        override the connection timeout in seconds
                        (default=10)
  -c CONNECTION, --connection CONNECTION
                        connection type to use (default=smart)
  -k, --ask-pass        ask for connection password 提示输入ssh登录密码,当使用密码验证登录的时候用。
  -u REMOTE_USER, --user REMOTE_USER
                        connect as this user (default=None)   #ssh链接的用户名,可以再ansible.cfg文件配置。

Privilege Escalation Options:
  control how and which user you become as on target hosts

  --become-method BECOME_METHOD
                        privilege escalation method to use (default=sudo), use
                        `ansible-doc -t become -l` to list valid choices.
  --become-user BECOME_USER
                        run operations as this user (default=root)
  -K, --ask-become-pass
                        ask for privilege escalation password
  -b, --become          run operations with become (does not imply password
                        prompting)

(4)playbook的构成

  playbook是由多个或一个”play“组成的列表,play的主要功能在于将实现归并为一组的主机装扮成事先通过ansible中的tasks定义好的角色。

 playbooks的组成:

Target section:  定义将要执行playbook的远程主机组

variable section:定义playbook运行时需要的变量

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

Handler section:定义task执行完成以后需要调用的任务

而其大多数情况下,对应的目录层为五个:

vars      变量层

tasks     任务层

handlers  触发条件

files     文件

template  模板 

以下是playbook的四层结构:

1、Hosts和Users

    playbook中的每一个部分都是为了让某个或某些主机以某个指定的用户身份执行任务。

hosts  用于指定要执行指定任务的主机其可以是一个或多个由冒号分隔主机组。

           user:执行该任务组的用户

           remote_user 用于指定远程主机上的执行任务的用户,与user相同.但remote_user也可用于各tasks中。也可以通过指定其通过sudo的方式在原还曾主机上执行的任务其可用于play全局或某任务。

           此外可以在sudo时使用sudo_user指定sudo时切换的用户。

           sudo :如果设置为yes,执行该任务组的用户在执行任务时,获取root权限。

           sudo_user:如果设置user为tom,sudo为yes,sudo_user为jerry。则tom用户会获取jerry的权限。

           connection:通过什么方式连接到远程主机,默认为ssh。

           gather_facts:除非明确说明不需要在远程主机上执行setup模块,否则默认会自动执行。如果确实不需要setup模块传递过来的变量,可以启动该选项。

示例:

- hosts: webserver
  tasks:
     - name: test ping 
       remote_user: test
       sudo: yes

2、任务列表和action

  play的主机部分是task list。

     task list中的各个任务按次序逐个在hosts中指定的所有主机上执行即在所有主机上完成第一个任务之后再开始第二个。在运行自上而下的某playbook时中途发生错误所有已执行任务都将回滚,因此再更正之后重新执行一次。

     task的目的是使用指定的参数执行模块而在模块参数中可以使用变量。模块执行是幂等的这意味着多次执行时安全的因为其结果均一致。每个task都应该有它的名字用于playbook的执行结果输出,建议其内容尽可能能清晰地描述任务执行步骤,如果没有提供名字则所有action地结果将用于输出。

     定义tasks的可以使用“action:module options”或“module:options”的格式。推荐使用后者以实现向后兼容。如果action一行内容过多也可以使用在行首使用几个空白字符进行换行。

tasks:
  - name:make sure apache is running
    service: name=httpd state=tunning

在众多模块中只有command和shell模块仅需要给定一个列表而不需要“key=value”格式。

tasks:
  - name:disable selinux
    command: /sbin/setenforce 0

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/someconnand || /bin/true 

 或者使用ignore_errers来忽略错误

tasks:
  - name: run this command and ignore the result
    shell: /usr/bin/someconnand || /bin/true 
    ignore_errors: True

3、handlers

 用于关注的资源发生变化时采取一定的操作。

  “notify”这个action可用于在每个play的最后被触发这样这个避免多次有改变发生时每次否要执行指定的操作,取而代之的仅在所有变化发生完成后一次性完成指定操作。

   在notify中列出的操作称为hanler也即notify中调用handler中定义的操作。

    注:在notify中定义的内容一定要和tasks中定义的-name 内容一样,这样才能达到触发效果,否则会不生效。

- name: template configure file
  template: src=template.j2 dest=/etc/foo.conf
  notify:
  - restart memcached
  - restart apache

 handler是task列表这些task与前述的task并没有本质不同。

handlers:
   - name: restart memcache
     service: name=mamcache state=restarted
   - name: restart apache
     service: name=apache state=restarted

4、tags

     tags用于让用户选择运行或略过playbook中的部分代码。ansible具有幂等性因此会跳过没有变化的部分。即便如此,有些代码为测试其确实没有发生变化的实践依然会非常长。

     ansible中可以对play、role、include、task等打一个tag(标签),然后:

    当ansible-playbook中有-t参数时,只会执行-t指定的tag。

    当ansible-playbook有--skip-tags参数时,则除了--skip-tags指定的tag外,执行其他所有。

基本应用:

---
- hosts:192.168.10.10
  tags:
    - one  #为一个play打一个tag
  tasks:
    - name:exec ifconfig 
      command:/sbin/ifconfig
      tags:                #为一个task打tag
        - exec_ifconfig

 对include语句打tag

---
- hosts:192.168.10.10
  gather_facts: no
  tasks:
     - include: foo.yml
       tags:   #方法一
         - one
     - include: bar.yml tags=two  #方法二

如上打tag的方式,执行如下:

ansible-playbook  test.yml          #foo.yml和bar.yml都执行

ansible-playbook test.yml  -t one  #只执行foo.yml

ansible-plsybook test.yml  -t  two #只执行bar.yml

同一个对象,可以打多个tag

---
- hosts:192.168.10.10
  gather_facts: no
  tasks:
     - include: foo.yml
       tags:   #方法一
         - one 
     - include: bar.yml tags=two,one #方法二

 如上,执行结果

ansible-playbook test.yml      #foo.yml和bar.yml都执行

ansible-playbook test.yml -t one #两个include都有标签one 所以foo.yml和bar.yml都执行

ansible-plsybook test.yml  -t  two #只执行bar.yml

对roles打tag

---
- hosts: 192.168.10.10
  gather_facts: no
  roles:
    - {role: foo,tags:one}
    - {role: tar,tags: [one,two]} #同时对一个role打两个tag

 可以对多个对象打同一个tag

---
- hosts:192.168.10.10
  gather_facts: no
  tasks:
     - name: exec ifconfig
       command: /sbin/ifconfig
       tags:
         - exec_cmd
     
     - name: exec ls
       command: /bin/ls
       tags:
          - exec_cmd  

5、变量的参数

 ①、var参数

   #变量直接写入文件中

运行

 运行结果

 ②、vars_files参数

#变量定义在文件中

运行

 运行结果:

 ③、vars_prompt参数

#交互模式使用变量

[root@localhost ansible]# cat files/test1.txt 
{{ http }}
[root@localhost ansible]# cat test3.yml 
---

- hosts: webservers
  user: root
  vars_prompt:
    - name: http
      prompt: please enter something
      private: yes   #值为yes 不在屏幕显示,设置为no则输入内容会在屏幕展示
  tasks:
    - name: just a test
      template: src=files/test1.txt dest=/tmp/test3.txt

执行

 执行结果:

6、示例 

以安装httpd web服务为例:

[root@localhost playbook]# cat install_web.yml 
---
- hosts: webservers
  remote_user: root
  gather_facts: False
  vars:
    packages: httpd
  tasks:
    - name: install httpd
      yum: name={{ packages }} state=present
    
    - name: Configuration httpd
      copy: src=/root/httpd.conf dest=/etc/httpd/conf/httpd.conf
      tags: httpd_conf
      notify:
          - restart httpd    

    - name: Start httpd
      service: name=httpd state=started enabled=no
      tags: start
    
    - name: Add centos user
      user: name={{ item }} state=present
      tags: adduser
      with_items:
         - centos 
         - admin
  handles:
     - name: restart httpd
       service: name=httpd state=restart 

条件判断playbook实例:

[root@localhost playbook]# cat hello.yml 
---
- hosts: webservers
  gather_facts: true
  tasks:
    - name: say redhat hello task
      shell: echo "redhat" `date` by `hostname` >> /tmp/hello.log
      when: ansible_os_family == "RedHat"
  
    - name: say other linux hello task
      shell: echo "not redhat" `date` by `hostname` >> /tmp/hell0.log
      when: ansible_os_family != "RedHat"

 循环实例:

[root@localhost playbook]# cat onebyone.yml 
---
- hosts: webservers
  gather_facts: true
  tasks:
    - name: say redhat hello task
      shell: echo {{ item }} `date` by `hostname` >> /tmp/hello01.log
      with_items:
         - message item1
         - message item2
         - message item3
         - message item4
         - message item5

七、playbook常用模块

    在playbooks中使用模块跟在命令中使用有些不同。主要是因为在playbooks中有许多从setup模块中获取的数据要处理。有些模块在命令行中进行使用,是因为他们需要访问变量,还有一些可以在命令行中使用的模块在playbooks中使用时,拥有了更加强大的功能。

(1)template模块

    template模块是ansible中最常用的模块之一。它可以自主设计一个框架式的配置文件,如何把ansible需要的值插入到合适的位置。其中jinja2模块尤其复杂,其中可以包含条件、循环、宏。

    template模块与copy模块类似,不同点在于template模块可以做变量替换。

    template模块中jinja2常用的循环:

{% for ip in ansible_all_ipv4_address %}

   {{ ip }};

{% endfor %}

#循环列出主机IP,ansible_all_ipv4_address为变量

#访问变量属性有两种方式,一种是用“obj.aatr”的方式,另一种是类似字典的方式:"obj['attr']"

#注意,上面的'{{..}}'是jinja的用来打印变量标记。如果要在其他标签中访问变量,则不能在变量名旁边加花括号。  

template模块中jinja2常用的判断:

{% if node in group_names %}

{% set zone_type = 'master' %} 

{% set zone_dir = 'data' %} 

{% else zone_type = 'slave' %} 

{% set zone_dir = 'slaves' %} 

{% endif %} 

{% if node not in group_names %} 

masters {192.168.1.100;}

{% endif %} 

#if判断,如果在主机组(hosts里面定义的)设置为主dns,否则设置为从dns。

#当然,也可以和python一样,也可以使用elif和else。

ansible使用了Jinja2的模板解析引擎。在模板中可使用到的附加变量:

     1、ansible_managed 在配置文件ansible.cfg的defaults中定义,包含模板名称,主机,模板文件的修改实践和所有者的UID信息。

     2、template_host   模板主机的节点名称

     3、template_uid     模板所有者UID

     4、template_path,template_fullpath   模板的绝对路径

     5、template_run_date   模板被渲染的时间

该模块的选项:

backup:默认为no,是否备份目标文件。

dest: 要操作的文件在远程节点上的路径

force:默认为yes,当目标文件与源文件内容不一样的时候,目标文件会被替换。若改为no,该文件只能不存在的时候被传输一次。

src :模板文件的路径,可以是绝对路径也可以是相对路径。

(2)set_fact模块

     set_fact模块可以让你在远程受管机器上去执行脚本的过程来计算我们需要的值,这些值可以被用在模板或者变量中。这些值有点类似于setup模块中的参数,只不过setup模块是以单台主机为单位的。

tasks:
   - name:Calculate InnoDB buffer pool size
     set_fact: innodb_buffer_pool_size_mb="{{ ansible_mentotal_mb / 2 }}"

(3)pause模块

       暂停模块可以让执行者在playbooks中暂停一段时间,可以指定一个时间段,或者提醒用户继续。在命令行中没有什么用,但在playbook中,很有用处。

暂停模板通常被用作当我们需要用户来提供一个确认来继续的时候,或者在一个特殊时间点手动确认。比如,更新一个WEB应用程序之后,我们需要在用户接受用户的连接之前,手工确认一切已经OK。这也可以提示用户一些可以出现的问题,并提供选项继续。Ansible会打印出服务器的名字,要求用户确认之后继续。这种方式可以让用户在部署的时候,灵活的控制整个节奏,并监控整个交互过程。

#暂停5分钟
- pause: 5 minutes=5


#暂停,直到您可以验证应用程序的更新是否成功
- pause: prompt="Make sure org.foo.FooOverload exception is not present"

(4)wait_for模块

       wait_for模块用来检测一个TCP端口是否准备好接收远程连接,这是由远程主机来完成的。如果只指定了端口,或者主机参数被设置为localhost,它会尝试连接远程受管主机。可以用local_action参数来指定从控制机器来运行命令,并使用ansible_hostname作为主机参数来连接远程受管主机。这个模块在后台运行某些程序,或者启动某些进程需要一些时间的时候特别有用。

示例:

#10秒后在当前主机开始检查8000端口,直到端口启动后返回

- wait_for: port=8000 delay=10

#检查path=/tmp/foo直到文件存在后继续

- wait_for: path=/tmp/foo

#直到/var/lock/file.lock移除后继续

- wait_for: path=/var/lock/file.lock state=absent

#直到/proc/3466/status移除后继续

- wait_for: path=/proc/3466/status  state=absent

一个实例:

---
- hosts: webapps
  tasks:
   - name: Install tomcat
     yum: name=tomcat state=installed

   - name: Start tomcat
     service: name=tomcat6 state=started

   - name: wait for Tomcat to start
     wait_for: port=8080 state=started

(5)assemble模块

      assemble组装模块把多个受管主机的文件合并成一个文件,当配置文件不允许包含的时候,非常有用。特别在设置root用户的authorized_keys文件的时候。

示例:

- assemble: src=/etc/someapp/fragments dest=/etc/someapp/someapp.conf


- assemble: src=/etc/someapp/fragments dest=/etc/someapp/someapp.conf delimiter='### START FRAGMENT ###'

(6)add_host模块

    add_host添加主机模块是playbook中一个强大的模块,它可以让使用者动态的添加受管主机到一个play中。我们可以使用uri模块从CMDB中获得主机信息然后添加他们。它还可以将主机添加到组之中,如果组不存在的话还会自动创建它。这个模块仅需要主机名和组名两个参数,跟主机库存清单的配置一样,还可以添加扩展的参数像ansible_ssh_user、ansible_ssh_port等。

示例:

- add_host: name={{ ip_from_ec2 }} groups=just_created foo=42


- add_host: name={{ new_ip }}:{{ new_port }}

- add_host: hostname={{ new ip }}
            ansible_ssh_host={{ inventory_hostname }}
            ansible_ssh_port={{ new_port }}

(7)group_by模块

   group_by模块可以让使用者根据主机的真实特性进行分组,真实特性可以通过add_fact来实现。group_by模块只接受一个参数,key,同样组名的机器被分到一个组里面。如果我们在这里使用变量,就可以把同一个操作系统类型、同一个拓扑结构、或者其他我们希望拥有同样特性的主机分为一组,组可以在子play中,模板中的目标选项被使用。

示例:
 - group_by: key=machine_{{ ansible_machine }}

 - group_by: key=virt_{{ ansible_virtualization_type }}_{{ ansible_virtalization_role }}

(8)get_url模块

   该模块主要用于从http、ftp、https服务器上下载文件(类似于wget),主要有如下选项:

   sha256sum:下载完成后进行sha256 check;

   timeout:下载超时时间,默认为10s

   url:下载的url

   url_password、url_username:主要用于需要用户名密码进行验证的情况

   use_proxy:是使用代理,代理需事先在环境变更中定义

实例:

- name: download foo.conf
  get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf mode=0440


- name: download file with sha256 check
  get_url url=http://example.com/path/file.conf dest=/etc/foo.conf sha256sum=**********************

(9)debug模块

    该模块在执行期间打印语句,对于调试变量或表达式非常有用,而不必停止播放剧本。与“when:”指令一起调试非常有用。

- debug: msg="System {{ inventory_hostname}} has uuid {{ansible_product_uuid}}"

- debug: msg="System {{ inventory_hostname}} has gateway {{ansible_default_ipv4.gateway}}"
  when: ansible_default_ipv4.gateway is defined



- shell: /usr/bin/uptime
  register: result

- debug: var=result
- name: Display all variables/facts known for a host
  debug: var=hostvars[inventory_hostname]

(10)fail模块

    此模块使用自定义消息使进度失败。当使用何时满足某个条件时,它可以用于拯救。

- fail: msg="The system may not be provisioned according to the CMDB status"
  when: cmdb_status !="to-be-staged"

八、playbook的roles和include

    在进行一个较大的项目的执行时,会导致文件较大,且修改过程也越来越麻烦。此时,可以将一些play、task或handler放到其他文件中,然后通过include指令包含进去,另一种方法是使用roles。

(1)include

    playbook可以包含其他playbook文件、task文件和handler文件。

   1、包含task文件

   如果在多个play中都需要几个相同的task,在每个play中都写一遍这些task很麻烦,所以可以将这个task单独放到一个文件中,格式如下:

cat tasks/foo.yml

---

- name: placeholder foo

  command: /bin/foo

- name: placeholder bar

  command: /bin/bar

然后在需要这些task的play中通过include包含上面的tasks/foo.yml

tasks:

  - include:  tasks/foo.yml

还可以利用include传递变量,如你部署了多个wordpress实例,通过向相同的wordpress.yml文件传递不同的值来区分实例:

tasks:

   - include: wordpress.yml user=timmy #在foo.yml 可以通过{{ user }}来使用这些变量

   - include:   wordpress.yml  user=alice

如果使用ansible1.4以上版本,include还可以写成字典格式:

tasks:

 - {include: wordpress.yml,user:timmy,ssh_kes:['keys/one.txt','keys/two.txt']} #ssh_keys是一个列表

从ansible1.0开始,还可以使用如下格式向include传递变量:

tasks:

  - include: wordpress.yml

    vars:

         remote_user: timmy

         some_list_variable:

           - alpha

           - beta

           - gamma

   2、包含handle文件

---

 - name: restart apache

    service: name=apache state=restarted

    ……

    handlers:

       - include: handlers/handlers.yml

   3、直接包含playbook文件

- name: this is a play at the top level of a file

  hosts: all

  remote_user: root

  tasks:

    - name: say hi

      tags: foo

      shell: echo "…………"

    - include: load_balancers.yml  #这些playbook文件中也至少定义了一个play

    - include:   webservers.yml

(2)roles

     如果在使用过程中,playbook增长到包含无法解决的问题,或者拥有了一块数量巨大的模块,或许此时可以使用角色。它运行我们根据定义的格式定义文件进行分组,从本质上说,它是以具有一些自动化功能的包含,角色可以帮助我们更好的组织资料库。

      角色允许使用者将变量、文件、任务、模块、handlers放到一个文件夹中,然后包含它们。在建立好一个有效的依赖关系之后,还可以在一个角色总包含另外一个角色。和包含一样,我们可以传递变量给角色。利用以上特性,可以创建角色并很容易跟其他人分享它。

    roles用来阻止playbook结构,以多层目录和文件将playbook更好的组织在一起,一个经过roles组织的playbook结构如下:

site.yml

webservers.yml

fooservers.yml

roles/

common/

#下面的子目录都不是必须提供的,没有的目录会自动忽略,不会出现问题,所以可以只有tasks/子目录也没问题

files/

templates/

tasks/

handlers/

vars/

meta/

webservers/

然后在playbook文件中包含commonhewebservers这两个role:

---

- hosts:user_group1

  roles:

     - common

     - webservers


假如我们有一个play包含了一个叫”x“的roles,则:

/path/roles/x/tasks/main.yml中的tasks都将自动添加到该play中

/path/roles/x/handlers/main.yml中的handlers都将添加到该play中

/path/roles/x/vars/main.yml中的所有变量都将添加到该play中

/path/roles/x/meta/main.yml中所有的role依赖关系都将自动添加到roles列表中

/path/roles/x/defaults/main.yml中为一些默认变量值,具有最低优先权,在没有其他任何地方指定该变量的值时,才会用到默认变量值。

task中的copy模块和script模块会自动从/path/roles/x/files寻找文件,也就是根本不用指定文件的绝对路径或相对路径,如src=file.txt则自动转换为/path/roles/x/files/file.txt

task中的template模块会自动从/path/roles/x/templates/中加载模板文件,不需要指定绝对路径或相对路径。

通过include包含文件会自动从/path/roles/x/tasks/中加载文件,无需指定。


如同include一样,也可以为role传递变量,格式如下:

---
 - hosts: webserver
 roles:
    - common
    - { role:foo_app_instance, dir:'/opt/a', port:5000 } #在foo_app_instance这个role的task文件和模板文件中通过{{ dir }} 和{{ port }}来使用变量

如果一个play中,既有tasks也有roles,那么roles会先于tasks执行,可以通过pre_tasks和post_tasks指令指定某些task先于或晚于roles执行。

---
 - hosts: webservers
 pre_tasks:
    - shell: echo "hello"   #最先执行
 
 roles:
    - { role: some_role }   #第二个执行

 tasks:
    - shell: echo 'still busy'

 post_tasks:
    - shell:  echo 'goodbye'   #最后执行

role依赖

  可以在一个role的meta/main.yml中定义该role依赖其他的role,然后调用该role的时候,会自动去拉取其他依赖的role,如一个名为myapp的role的meta/main.yml文件如下: 

---
dependencies:
 - {role: common, some_parameter:3 }
 - {role: apache, port:80 }
 - {role: postgres, dbname: blarg, other_parameter:12 }

如上,那么这些role的执行顺序为:

common<<apache<<postgres<<myapp #需要先把依赖的role执行完毕之后再进行自己

默认情况下,一个role只能被其他的role依赖一次,多次依赖不会执行,但是可以通过allow_duplicates指令来改变这种行为:

名为car的role的meta/main.yml:

---

dependencies:

   - { role: wheel, n: 1}

   - { role: wheel, n: 2}

   - { role: wheel, n: 3}

   - { role: wheel, n: 4}

名为wheel的role的meta/main.yml

---

allow_duplicates: yes

dependencies:

   - { role: tire }

   - { role:brake }

执行顺序如下:

tire(n=1)

brake (n=1)

tire(n=2)

brake (n=2)

tire(n=3)

brake (n=3)

tire(n=4)

brake (n=4)

九、ansible实战 

(1)案例描述

          给远程主机安装tomcat,进行tomcat环境搭建。

(2)分析tomcat的安装过程

       1、安装jdk  2、创建tomcat用户  3、安装tomcat  4、配置tomcat、重启等。

       准备过程:

        需要将tomcat和jdk软件通过http方式提供下载,配置过程如下:

#这个方法永久有效 可参考:https://www.cnblogs.com/renshengdezheli/p/14151106.html

yum install httpd -y 

#将两个软件放置到/var/www/html/下面的任何一个文件夹中。例如:(/var/www/html/tar)

systemctl restart httpd 

浏览器访问: http://IP/tar 

出现如下图片,表明配置成功。

#可以实现短时间分享,可参考:https://linux.cn/article-10205-1.html

搭建成功的图片:

(3)实际部署

1、定义hosts 

       在原本的/etc/ansible下面创建一个目录,放置该项目的所有文件。

       定义hosts,并确保hosts中的主机可以和安装ansible的主机免密登录。设置可参考,上述介绍中的方法。

 2、定义入口文件site.yml

 3、定义全局变量

#变量文件的名称要和主机组的名称相同。

4、定义roles

 #准备阶段,即下载tomcat与下载jdk的安装包,创建远程主机上的目录 

#tomcat安装阶段,files存放tomcat的启动脚本,templates模板存放server.xml、tomcat-users.xml和防火墙配置,handlers存放触发文件,tasks存放任务。

#主任务,主任务中包含两个任务,安装jdk和安装tomcat

#子任务一,安装jdk

 #子任务二,安装tomcat

[root@localhost tomcat]# cat ./tasks/install_tomcat.yml 
---
  - name: add group "tomcat"
    group: name=tomcat state=present

  - name: add user "tomcat"
    user: name=tomcat group=tomcat home=/opt/tomcat createhome=no
    #sudo: True

  - name: tar tomcat
    command: chdir={{ dest_path }} /bin/tar xf {{tomcat_ver}}.tar.gz -C /opt creates=/opt/{{tomcat_ver }}

  - name: Symlink install directory
    file: src=/opt/{{ tomcat_ver }} path=/opt/tomcat state=link

  - name: change ownership directory
    file: path=/opt/tomcat owner=tomcat group=tomcat state=directory recurse=yes

  - name: Configure tomcat server
    template: src=server.xml dest=/opt/tomcat/conf/

  - name: Configure tomcat users
    template: src=tomcat-users.xml dest=/opt/tomcat/conf/
    notify: restart tomcat

  - name: install tomcat init script
    copy: src=tomcat-initscript.sh dest=/etc/init.d/tomcat mode=0755

  - name: start tomcat
    #script: chdir=/etc/init.d/tomcat /bin/sh tomcat-initscript.sh
    #service: name=tomcat state=started enabled=yes
    command: /etc/init.d/tomcat start

  - name: mask firewalld
    command: systemctl mask firewalld

  - name: yum iptables
    yum: name=iptables-services state=latest

  - name: deploy iptables rules
    template: src=iptables-save dest=/etc/sysconfig/iptables
    notify: restart iptables

  - name: wait for tomcat to start
    wait_for: port={{ http_port }}

# files脚本文件内容,脚本设置以tomcat用户启动关闭

[root@localhost tomcat]# cat files/tomcat-initscript.sh 
#!/bin/sh
export JAVA_HOME=/opt/jdk1.6.0_45
export PATH=$JAVA_HOME/bin:$PATH
export CATALINA_HOME=/opt/tomcat
export CATALINA_BASE=$CATALINA_HOME

export TOMCAT_USER=tomcat

TOMCAT_USAGE="Usage: $0 {\e[00;32mstart\e[00m|\e[00;31mstop\e[00m|\e[00;32mstatus\e[00m|\e[00;31mrestart\e[00m}"

SHUTDOWN_WAIT=20
tomcat_pid() {
        echo `ps -fe | grep $CATALINA_BASE | grep -v grep | awk '{print $2}'`
}
 
start() {
        pid=$(tomcat_pid)
        if [ -n "$pid" ]
        then
                echo -e "\e[00;31mTomcat is already running (pid: $pid)\e[00m"
        else
                # Start tomcat
                echo -e "\e[00;32mStarting tomcat\e[00m"
                #ulimit -n 100000
                #umask 007
                #/bin/su -p -s /bin/sh tomcat
                if [ `user_exists $TOMCAT_USER` = "1" ]
                then
                        su $TOMCAT_USER -c $CATALINA_HOME/bin/startup.sh
                else
                        sh $CATALINA_HOME/bin/startup.sh
                fi
                status
        fi
        return 0
}
 
status(){
        pid=$(tomcat_pid)
        if [ -n "$pid" ]; then echo -e "\e[00;32mTomcat is running with pid: $pid\e[00m"
        else echo -e "\e[00;31mTomcat is not running\e[00m"
        fi
}
 
stop() {
        pid=$(tomcat_pid)
        if [ -n "$pid" ]
        then
                echo -e "\e[00;31mStoping Tomcat\e[00m"
                #/bin/su -p -s /bin/sh tomcat
                if [ `user_exists $TOMCAT_USER` = "1" ]
                then
                        su $TOMCAT_USER -c $CATALINA_HOME/bin/shutdown.sh
                    else
                        sh $CATALINA_HOME/bin/shutdown.sh
                    fi
                let kwait=$SHUTDOWN_WAIT
                count=0;
                until [ `ps -p $pid | grep -c $pid` = '0' ] || [ $count -gt $kwait ]
                do
                        echo -n -e "\e[00;31mwaiting for processes to exit\n\e[00m"
                        sleep 1
                        let count=$count+1;
                done
 
                if [ $count -gt $kwait ]
                    then
                        echo -n -e "\e[00;31mkilling processes which didn't stop after $SHUTDOWN_WAIT seconds\n\e[00m"
                        kill -9 $pid
                fi
        else
                echo -e "\e[00;31mTomcat is not running\e[00m"
        fi
 
        return 0
}
 
user_exists() {
                if id -u $1 >/dev/null 2>&1
                then
                        echo "1"
                else
                        echo "0"
                fi
}
 
case $1 in
 
        start)
          start
          ;;
 
        stop)
          stop
          ;;
 
        restart)
          stop
          start
          ;;
 
        status)
          status
          ;;
 
        *)
          echo -e $TOMCAT_USAGE
          ;;
esac
exit 0

 #模板文件内容

[root@localhost tomcat]# cat templates/iptables-save 
# sample configuration for iptables service
# you can edit this manually or use system-config-firewall
# please do not ask us to add additional ports/services to this default configuration
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ http_port }} -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ https_port }} -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT











 
[root@localhost tomcat]# cat templates/server.xml 
<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <GlobalNamingResources>
      <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
   </GlobalNamingResources>

   <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
 
    <Engine name="Catalina" defaultHost="localhost">
 
      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>
 
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
 
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
 
      </Host>
    </Engine>
  </Service>
</Server>
<?xml version='1.0' encoding='utf-8'?>
 
<tomcat-users>
<user username="{{ admin_username }}" password="{{ admin_password }}" roles="manager-gui" />
 
</tomcat-users>
# {{ ansible_managed }}
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4:512]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ http_port }} -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ https_port }} -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT











 
[root@localhost tomcat]# cat templates/tomcat-users.xml 
<?xml version='1.0' encoding='utf-8'?>

<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="on" />
  <Listener className="org.apache.catalina.core.JasperListener" />
  <Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
  <Listener className="org.apache.catalina.mbeans.ServerLifecycleListener" />
  <Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
  <GlobalNamingResources>
      <Resource name="UserDatabase" auth="Container"
              type="org.apache.catalina.UserDatabase"
              description="User database that can be updated and saved"
              factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
              pathname="conf/tomcat-users.xml" />
   </GlobalNamingResources>

   <Service name="Catalina">
    <Connector port="8080" protocol="HTTP/1.1"
               connectionTimeout="20000"
               redirectPort="8443" />
    <Connector port="8009" protocol="AJP/1.3" redirectPort="8443" />
 
    <Engine name="Catalina" defaultHost="localhost">
 
      <Realm className="org.apache.catalina.realm.UserDatabaseRealm"
             resourceName="UserDatabase"/>
 
      <Host name="localhost"  appBase="webapps"
            unpackWARs="true" autoDeploy="true"
            xmlValidation="false" xmlNamespaceAware="false">
 
        <Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
               prefix="localhost_access_log." suffix=".txt"
               pattern="%h %l %u %t &quot;%r&quot; %s %b" />
 
      </Host>
    </Engine>
  </Service>
</Server>
<?xml version='1.0' encoding='utf-8'?>
 
<tomcat-users>
<user username="{{ admin_username }}" password="{{ admin_password }}" roles="manager-gui" />
 
</tomcat-users>
# {{ ansible_managed }}
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [4:512]
-A INPUT -m state --state RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ http_port }} -j ACCEPT
-A INPUT -p tcp -m state --state NEW -m tcp --dport {{ https_port }} -j ACCEPT
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT
[root@localhost tomcat]#

#handlers内容

 5、测试部署

ansible -i hosts site.yml

#在/etc/ansible/tomcat下允许,如果不在此路径下,hosts与site.yml都需要用绝对路径

测试结果:

[root@localhost tomcat]# ansible-playbook -i hosts site.yml

PLAY [web] ****************************************************************************************************************************************************************

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

TASK [prepare : create download directory] ********************************************************************************************************************************
changed: [192.168.10.11]

TASK [prepare : Download JDK] *********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [prepare : Download Tomcat] ******************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : install jdk] ***********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : mv jdk to /opt] ********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [add group "tomcat"] *************************************************************************************************************************************************
changed: [192.168.10.11]

TASK [add user "tomcat"] **************************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tar tomcat] *********************************************************************************************************************************************************
[WARNING]: Consider using the unarchive module rather than running 'tar'.  If you need to use command because unarchive 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.
changed: [192.168.10.11]

TASK [tomcat : Symlink install directory] *********************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : change ownership directory] ********************************************************************************************************************************
changed: [192.168.10.11]

TASK [Configure tomcat server] ********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [Configure tomcat users] *********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [install tomcat init script] *****************************************************************************************************************************************
changed: [192.168.10.11]

TASK [start tomcat] *******************************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : mask firewalld] ********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : yum iptables] **********************************************************************************************************************************************
changed: [192.168.10.11]

TASK [tomcat : deploy iptables rules] *************************************************************************************************************************************
changed: [192.168.10.11]

TASK [wait for tomcat to start] *******************************************************************************************************************************************
ok: [192.168.10.11]

RUNNING HANDLER [restart tomcat] ******************************************************************************************************************************************
changed: [192.168.10.11]

RUNNING HANDLER [tomcat : restart iptables] *******************************************************************************************************************************
changed: [192.168.10.11]

PLAY RECAP ****************************************************************************************************************************************************************
192.168.10.11              : ok=21   changed=19   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

[root@localhost tomcat]# 

6、测试是否启动

 

部署成功!!!!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值