Ansible从零到入门再到实践

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dnnoqdtF-1614567338812)(…/imgs/logo.jpeg)]

为什么选择学习Ansible这个工具,而不选择其他的工具。

最主要的原因是我需要的工具必须要简单,包括安装简单,使用简单。

安装简单

这个工具安装简单(下面的章节会详细讲解安装方法),而且只要安装在一台机器上就可以了。被管理的机器不需要安装客户端。像SaltStack,Puppet不仅要安装服务端还要安装客户端,很麻烦。

使用简单

只需要一条命令就可以完成复杂的操作。安装完成以后,基本不用做任何配置。就可以直接使用。为什么说基本不用配置呢,因为被管理的机器还是要配置的(后面章节会详细讲解如何添加被控机器,不用担心,超级简单),要不然我怎么知道我要控制哪些机器呢?

下面是Ansible,Puppet, SaltStack 之间的对比

技术对比

在这里插入图片描述

优劣势对比

在这里插入图片描述



准备2台机器(够学习使用了)

一个安装Ansible软件,另一个作为被控制端,所以自用同样的步骤安装另一台机器。

我们需要创建2台虚拟机。

使用VMware安装CentOS7点击这里

基础准备工作也就完成了,接下来就是就正式开启自动化运维学习之路。废话不多说,马上开始Ansible的学习。



CentOS 7安装Ansible

ansible在第三方源中,所以我们要安装第三方源

[root@localhost ~]#  yum install -y epel-release

然后再安装ansible

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

如果出现报错:Cannot retrieve metalink for repository: epel/x86_64. Please verify its path and try again

请修改epel源的配置文件

[root@localhost ~]# vi /etc/yum.repos.d/epel.repo

将第二行开头 “#” 号去掉,第三行开头加一个 “#” 号。保存退出。

在这里插入图片描述

然后如下执行

[root@localhost ~]# yum clean all

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

等待安装完成即可。

安装完成后,查看版本。

[root@localhost ~]# ansible --version
ansible 2.9.16
  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, Jul 13 2018, 13:06:57) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]

包含版本信息,工具配置文件路径,python版本等等。这说明已经安装成功

Ubuntu安装Ansible

$ sudo apt update
$ sudo apt install software-properties-common
$ sudo apt-add-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible

其他发行版本请查看官方文档

https://docs.ansible.com/ansible/latest/index.html



### 初次使用

用来管理的机器已经安装好了,被管理的机器还要安装什么吗?
郑重的告诉你,被管理的机器什么都不用安装,就是这么任性。

虽然被管理的机器什么都不用安装,但是我们要把被管理机器的IP,用户名,密码收集起来。
然后告诉管理机器。要不然管理机器不知道去管理谁。我们要怎么把被管理机器告诉告诉管理机器呢?

Ansible有个管理清单,只要把被管理的机器添加进去就可以了。

这个清单文件是:/etc/ansible/hosts前面做对比介绍的时候说过,
Ansible是通过ssh协议通信的,所以,我们要把被管理的机器IP,用户名,密码告诉管理机器。也就是把被管理机器的这些信息写进清单里面。

我们把被管理的机器的IP,用户名,密码按照下面格式填入/etc/ansible/hosts文件中

把下面的内容添加到/etc/ansible/hosts的最后一行,然后保存退出。

192.168.233.167  ansible_ssh_user=root  ansible_ssh_pass=123456

**说明:**为什么要添加到最后一行。里面其他的内容可以删掉么?里面原有的内容是可以删除的,但是不建议删除。因为清单有个分组的概念(后面会讲到分组的概念).
而原有的这些注释的内容,就是给我们的实例。我们按照这个实例根据需求配置我们自己的分组。

联通测试

被管理的主机已经添加到清单里面了,接下来就测试一下,管理机和被管理机是否联通。用一条命令即可,下面的命令在管理机操作

[root@localhost ~]# ansible 192.168.233.167 -m ping
192.168.233.167 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false, 
    "ping": "pong"
}

说明管理机与被管理机已经可以正常通信了。返回的信息颜色是绿色的,执行的命令返回是正确的。
如果执行命令失败,显示是红色的。所以很容易就判断出执行的命令返回是正确的还是错误。

细心的同学可能发现了,我们在配置清单的时候,把被管理机的用户名和密码全部填写进去了。万一管理被入侵,其他的被管理机器一下全暴露了。



免密码配置

这里我们就引入了密钥的概念。管理机和被管理机通信使用密钥代替密码,这样就提高了安全性。具体操作步骤如下:

步骤一: 在管理机器生成公钥

[root@localhost ~]# ssh-keygen 
Generating public/private rsa key pair.
Enter file in which to save the key (/root/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /root/.ssh/id_rsa.
Your public key has been saved in /root/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:IrRLAe0wt1pDfZjbzI16ss79M6Hp/O/7aLSXyGQtcBo root@localhost.localdomain
The key's randomart image is:
+---[RSA 2048]----+
|  .. . o         |
|  o.+ + .        |
|   *o. * o       |
|   .=o. = E .    |
|   o+...S  = .   |
|  .. oo.. o = .  |
|    .  + o * + . |
|     ..oo o =.o  |
|     .o.+oo*=+.  |
+----[SHA256]-----+
[root@localhost ~]#

步骤二: 把公钥复制到被管理机器

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

Number of key(s) added: 1

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

输入被管理机器密码即可。

聪明的同学可能马上就想到了,一台机器这样复制还行,被管理的机器少则几十台,多则几百几千台。一个一个的复制岂不是要累死了。几百几千台当然不可能这样操作了。我们用个小工具sshpass,写个小脚本来完成这个重复的工作。

其实前面在/etc/ansible/hosts配置user和password的时候,ansible就是调用sshpass来自动输入ssh密码的。

sshpas需要单独安装,首先要下载:http://sourceforge.net/projects/sshpass/

安装sshpass会用到gcc,所以要先安装gcc

[root@localhost ~]# yum install gcc -y

[root@localhost ~]# tar -xvf sshpass-1.06.tar.gz

[root@localhost ~]# cd sshpass-1.06/

[root@localhost ~]# ./configure

[root@localhost ~]# make && make install

接下来写脚本copysk.sh。

#!/bin/bash
#
password=123456
for ip in $(cat ip)
do
sshpass -p ${password} ssh-copy-id root@${ip}
done

脚本文件copysk.sh和ip文件在同一目录下。然后执行脚本即可

[root@localhost ~]# bash copysk.sh

如果不知道如何写脚本,这上面的复制修改password的值,再把ip文件中的IP改为你的IP即可。

密钥已经配置好了,管理机上的清单文件也要改一下。

打开清单文件如下:

[root@localhost ~]# vi /etc/ansible/hosts
192.168.1.1
192.168.1.2

把IP后面的ansible_ssh_user 和 ansible_ssh_pass删除,只保留前面的IP就行了。



清单分组

前面说到清单分组的概念,如果忘记也没关系。接下来说一下,为什么要分组,如何把被管理的机器分组。

首先说一下为什么要分组。

比如说我管理了100台机器。其中的10台做web服务器。5台做数据库服务器,剩下的全部用来做数据运算。
今天领导让我把10台web服务器更新一下,我如果把web服务器的IP一个一个单独的拿出来,单独的去操作。
这样做是不是特别傻。

所以ansible的开发者就想到了一个分组的概念。
把功能一类的机器分成一组,然后通过组名管理组内的若干台机器。

比如分一个web服务器组,包含主机192.168.1.10、192.168.1.11.
我们可以在清单里进行如下配置:

[web]
192.168.1.10
192.168.1.11

如此配置就可对web组进行管理,示例如下:

[root@localhost ~]# ansible web -m ping
192.168.1.10 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
192.168.1.11 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}

到此为止,ansible安装好了,该配置的也已经配置好了。具体的实际应用到现在一点都没有说到。
下面的内容将结合实际生产应用来教大家如何使用ansible

ansible是基于模块化设计的,所以有很多的模块可以供我们使用。

比如前面用到的命令:

ansible web -m ping  #就是调用**ping**模块测试管理机与被管理机是否能ping通

ansible-doc -l  #命令可以列出所有的模块

ansible-doc ping  #就可查看ping模块的用法

ansible-doc  copy  #就可以查看copy模块的用法。

如果你英文还不错,完全可以查看此文档学习每个模块的用法。
英文不太好的话,也不用担心。

后面会把常用的模块都拿出来,一 一讲解,并给出一个或多个例子供参考。

虽然模块很多,但是用到的并不多,接下来就开始常用模块的学习。



Ansible模块学习之command 模块

command模块顾名思义,就是执行命令的模块。这模块可以帮助我们在被管理机器上执行命令

例如:

查看被管理机器root用户目录下的文件

[root@localhost ~]#  ansible 192.168.146.129 -m command -a "ls /root"

在这里插入图片描述

这个模块很简单,就像本地执行命令一样,但是执行的命令中含有重定向、管道符等操作时,会显示红色报错信息。

比如**"<", “>”, “|”, “;” 和 “&” ** 这些符号。

如果必须用到怎么办呢?

当然有办法,接下来就来介绍Ansible模块学习之shell 模块



Ansible模块学习之shell 模块

shell模块同样是在远程机器上执行命令但是不同的是,shell 模块在远程主机中执行命令时,会经过远程主机上的/bin/sh程序处理。也因此shell模块支持更多的操作符,比如介绍command模块提到的重定向”<”, “>”, 管道符“|”。

例1: 我想查看远程机器var目录下面一共有多少个文件

[root@localhost ~]# ansible 192.168.146.129 -m shell -a "ls /var |wc -l"

在这里插入图片描述

返回22,说明远程机器/var目录下一共有22个文件

例2: 我想查看远程机器/var目录下面是否有test文件或文件夹

[root@localhost ~]# ansible 192.168.146.129 -m shell -a "ls /var |grep test"

在这里插入图片描述

返回红色失败信息,说明远程机器的/var目录下没有test文件或文件夹

上面的2个例子都是使用shell模块并用到了管道 “|”。如果使用command模块并使用管道符号会有什么样的效果呢?我们来看一下。

例3: 使用command模块查看远程机器上/var目录下一共有多少个文件

[root@localhost ~]# ansible 192.168.146.129 -m command -a "ls /var |wc -l"

在这里插入图片描述

结果是只执行了管道“|”前面的命令,最后一行显示执行管道后面的命令直接报错。

这时候你可能会想shell模块可以满足我使用一些复杂的了命令了。但是还满足不了我的需求,我要执行在远程机器上执行脚本怎么办呢?我要把脚本拷贝到远程机器上再执行吗?

虽然方法可以,但是这也太麻烦了。根本符合自动化理念。

其实开发者早就想到这个问题了。这也印证那句话:“我们现在考虑的问题,90%以上都是前人考虑过的。”因此开发者就开发了script模块来解决远程执行脚本的问题。接下来就讲解一下script模块。



Ansible模块学习之script模块

看到script就知道这个模块和脚本有关系。没错,script模块功能就是本地的脚本,可以在远程机器上执行。也就是说,脚本一直在本地,不用拷贝到远程机器上。我们做个小实验来介绍script模块的用法。

首先在管理机器上我们写个很简单的脚本test.sh,在/root目录下。脚本内容如下:

#!/bin/bash
touch /tmp/testfile
echo "123" > /tmp/testfile

脚本的内容很简单,就是在tmp目录下创建一个testfile文件,然后把”123“写入test file文件中。

接下来我们就使用script模块,把本地的脚本在远程机器上执行

[root@localhost ~]# ansible 192.168.146.129 -m script -a "/root/test.sh"

在这里插入图片描述

我们可以看到已经正常执行完成了。我们在使用shell或者command模块来查看远程机器是否有testfile文件,里面的内容是不是”123“.

[root@localhost ~]# ansible 192.168.146.129 -m command -a "cat /tmp/testfile"

在这里插入图片描述

可以看见远程机器上有对应的文件,文件中的内容也是对的,

上面的小实验就介绍了script用法。非常简单。

小贴士: 在本地执行脚本的时候要给脚本执行的权限才能执行。使用script模块时,脚本即使没有执行权限也可以在远程机器上执行的。

细心的同学可能发现了,并且产生疑问。

我们在本地执行命令的时候还会加个参数啥的,比如:ls -l ,rm -rf 等等。

为什么我们上面的使用各个模块的时候没有添加其他的参数呢?是没有参数吗?

这里要说明 一下。上面三个模块都有参数,但是基本上不会使用到。反正到现在,使用上面的三个模块我也没加过任何参数。所以我就直接省略掉。避免一些同学看到这么简单模块都要加参数。而丧失学习兴趣。

后面的模块讲解,我们也只会讲解常用的模块,不常用的模块简单提一下,或者直接就不讲。同样,模块的参数也是只讲常用的,不常用的就不讲,或一笔带过。所以你在看这本书的时候不要有这样的担心。我看了一堆的东西,结果没什么用。这是你不用担心的。我的目的就是写出有用的东西。不会写一些没用的东西来凑字数。也不会用一些高大上的词汇来显示自己很牛的样子。

我的目的是让你一点都不懂到入门。如果你入门了,我的目的就达到了。接下来的深入学习你就知道该如何去学习,去搜索资料。



Ansible模块学习之user模块

做运维的都知道,同一台机器可以有很多用户登陆。我们要创建用户,删除用户,给用户附加组等等。这些都会用到user模块。下面对user模块和参数的使用结合实例进行讲解

name参数: 这个是必须参数,指定要操作用户的名称

group参数: 指定用户所在的基本组

shell参数: 指定用户默认登陆shell

uid参数: 指定用户uid,一般不做设置

comment参数: 用作用户的注释信息,

state参数: 指定用户是否在远程机器,有2个可选值。默认是present,用户存在远程机器上,absent删除远程机器上的用户

remove参数: 当我们删除用户,也就是state参数值是absent的时候。默认是不会删除家目录

的。也就是remove值默认是no,如果要删除家目录remove值yes

password参数: 用于设置用户密码 ,但是这个密码不是明文的,而是加密后的

和/etc/shadow文件中密码字段一样

参数介绍就到这里,下面将用实例讲解各参数的用法

例1: 创建名称为test的用户

[root@localhost ~]# ansible 192.168.146.129 -m user -a "name=test"

在这里插入图片描述

可以从返回参数看出创建了用户test,uid是1000,groupid也是1000,家目录是/home/test 使用的shell 是/bin/bash

如果test用户已经存在了,则不做任何操作,如下:

[root@localhost ~]# ansible 192.168.146.129 -m user -a "name=test"

在这里插入图片描述

可以看到changed的值是false,说明没有做任何改变。

例2: 我要删除刚才创建的test用户。并且把它的家目录也一起删了

[root@localhost ~]# ansible 192.168.146.129 -m user -a "name=test state=absent remove=yes"

在这里插入图片描述

如果我不想删除用户的家目录怎么操作呢?直接把remove=yes去掉就可以保留用户的家目录了

例3: 安装apache要创建apache用户,但是这个用户是不需要登陆的,我们要如何做呢?这里就要指定登陆shell了,而指定的shell值是nologin。意思是不允许apache这个用户登陆

[root@localhost ~]# ansible 192.168.146.129 -m user -a "name=apache shell=nologin"

在这里插入图片描述

这样就创建apache用户了,而且无法用这个用户登陆系统。因为无法登陆系统所以是没有家目录的。

如果指定apache用户使用/bin/sh作为默认shell如下操作即可

[root@localhost ~]# ansible 192.168.146.129 -m user -a "name=apache shell=/bin/sh"

例4: 刚才创建了test用户,我需要做个说明,这是一个测试用户,如下操作:

[root@localhost ~]# ansible 192.168.146.129 -m user -a "user=test comment=Thistestuser"

然后我们查看一下远程机器上的/etc/passwd文件

[root@localhost ~]# ansible 192.168.146.129 -m shell -a "grep test /etc/passwd"

在这里插入图片描述

可以看到 Thistestuser 字段

用户创建好了,是时候给用户添加个密码了。这里用到了password参数,前面做参数说明的时候讲到了,这个密码不是明文的。是加密的,因此我们要把密码的明文经过加密处理才能使用。

比如: 我想给test用户设置密码为123456,应该怎么做呢?下面跟我一步一步的操作即可。

首先进入到python命令行界面。用python的crypt模块对字符串进行加密,最后返回的字符串就是我们要用到的,注意字符串是用单引号引起来的,我们只要单引号里面的字符串

具体操作步骤如下:

[root@localhost ~]# python

Python 2.7.5 (default, Jun 17 2014, 18:11:42)

[GCC 4.8.2 20140120 (Red Hat 4.8.2-16)] on linux2

Type "help", "copyright", "credits" or "license" for more information.

>>> import crypt

>>> crypt.crypt('123456')

'$6$xXHy1LuASc7e9tbO$38vx7.ZvpllcavRxvQ1z1GMBwGx5C3eTR2H8Xofbiq8/hwaj8cvWfM6D63agNMWeL6vu2kQ2lszum0JBQRejD/'

在这里插入图片描述

加密后的密码已经生成了,我们直接拿来用就可以了。

例5: 给test用户设置密码为123456

ansible 192.168.146.129 -m user -a 'user=test password="$6$xXHy1LuASc7e9tbO$38vx7.ZvpllcavRxvQ1z1GMBwGx5C3eTR2H8Xofbiq8/hwaj8cvWfM6D63agNMWeL6vu2kQ2lszum0JBQRejD/" '

在这里插入图片描述

这里有个地方必须要注意的,执行的整条命令是单引号引起来的,密码内容是双引号引起来的。这样做的原因是,加密后的密码内容包含了一些特殊符号,必须要用双引号引起来,避免产生歧义。

user模块常用的参数和参数值的使用已经讲完了,接下来讲解file模块



Ansible模块学习之file模块

file模块一看就知道这个模块和文件操作相关。比如创建文件或文件夹,删除文件或文件夹,修改文件或文件夹的权限。

下面对file模块常用参数介绍一下,然后结合实例讲解用法。

path参数: 这参数是必须的,它指明了被管理的文件或文件夹的路径

state参数: 这个参数很灵活,不同的模块都会有state参数,模块不一样它的值也不一样,后面回结合实例说明state参数不同的值作用和它的作用。

src参数: 做软链接或硬链接的时候指定链接的源文件

例1: 在/tmp目录下创建文件mytest 权限位0644

[root@localhost ~]# ansible 192.168.233.167 -m file -a "path=/tmp/mytest state=touch mode=0644"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "dest": "/tmp/mytest", 
    "gid": 0, 
    "group": "root", 
    "mode": "0644", 
    "owner": "root", 
    "size": 0, 
    "state": "file", 
    "uid": 0
}

例2: 删除刚才创建的mytest文件

[root@localhost ~]# ansible 192.168.233.167 -m file -a "path=/tmp/mytest state=absent"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "path": "/tmp/mytest", 
    "state": "absent"
}

例3: 在/tmp目录下创建文件夹dirtest 权限是0755

[root@localhost ~]# ansible 192.168.233.167 -m file -a "path=/tmp/dirtest state=directory mode=0755"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "gid": 0, 
    "group": "root", 
    "mode": "0755", 
    "owner": "root", 
    "path": "/tmp/dirtest", 
    "size": 4096, 
    "state": "directory", 
    "uid": 0
}

例4: 创建软连接相当Linux命令"ln -s"

[root@localhost ~]# ansible 192.168.233.167 -m file -a "src=/tmp/dirtest dest=/tmp/testlink state=link"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "dest": "/tmp/testlink", 
    "gid": 0, 
    "group": "root", 
    "mode": "0777", 
    "owner": "root", 
    "size": 12, 
    "src": "/tmp/dirtest", 
    "state": "link", 
    "uid": 0
}

以上就是file模块常用的几种方式了。

主要就是state值的区别,下面总结一下:

touch: 创建文件

absent: 删除文件或目录

directory: 创建目录

link: 创建软链接。如果创建硬链接state的值就是hard

注意: mode值的前面的0不要省略掉,要写成"0755"或"0644"而不是"755"和"644"。

具体为什么我也不知道,官方是这样说的。可能会引起bug吧。(纯属个人猜测)



Ansible模块学习之copy模块

copy模块是把本机的文件拷贝到远程机器,跟scp命令有点类似

常用参数如下:

src参数: 本地文件路径。必须参数

dest参数: 远程机器路径。必须参数

backup参数: 是否备份,若值是yes表示备份,如果不需要备份省略该参数。省略该参数后,若远程机器有同名的文件,远程机器上的文件会直接覆盖

mode参数: 复制后的文件权限,比如:“0755”,“0644”。如果没有该参数则复制后的文件与源文件权限相同

例1: 把本地拷贝到远程机器不做备份,

[root@localhost ~]# ansible 192.168.233.167 -m copy -a "src=/root/test dest=/tmp/test"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "checksum": "4e1243bd22c66e76c2ba9eddc1f91394e57f9f83", 
    "dest": "/tmp/test", 
    "gid": 0, 
    "group": "root", 
    "md5sum": "d8e8fca2dc0f896fd7cb4cb0031ba249", 
    "mode": "0644", 
    "owner": "root", 
    "size": 5, 
    "src": "/root/.ansible/tmp/ansible-tmp-1611939995.84-4445-83659486471571/source", 
    "state": "file", 
    "uid": 0
}

例2: 把本地文件拷贝到远程机器并备份

[root@localhost ~]# ansible 192.168.233.167 -m copy -a "src=/root/test dest=/tmp/test  backup=yes"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "backup_file": "/tmp/test.2953.2021-01-29@17:08:14~", 
    "changed": true, 
    "checksum": "1b7d20fc0014599208b7ccea75f6f0c959af283b", 
    "dest": "/tmp/test", 
    "gid": 0, 
    "group": "root", 
    "md5sum": "80b9455d8672a7e9a2c5120462b2963d", 
    "mode": "0644", 
    "owner": "root", 
    "size": 10, 
    "src": "/root/.ansible/tmp/ansible-tmp-1611940092.92-4528-54138861592249/source", 
    "state": "file", 
    "uid": 0
}

此时,返回值会多一个"back_file"

如果本地文件内容和远程文件一样,则不会复制。返回内容也是深绿色的
ansible在复制文件的时候会判断文件内容是否相同

关于ansible执行返回的颜色简单说明一下:

绿色: 表示执行成功但是没做任何修改
黄色: 表示执行成功并做了修改
红色: 表示执行失败
浅绿色: 表示跳过此次操作

有一个参数需要拿出来单独说一下,用到的可能不多,但可能会用到。

remote_src参数: 这个参数表示操作都是在远程机器上操作

此时的src表示的也是远程机器的路径,而不是本地的。dest也是远程机器的路径.

例1: 远程机器上的文件拷贝

[root@localhost ~]# ansible 192.168.233.167 -m copy -a "src=/tmp/dirtest dest=/root/dirtest backup=yes remote_src=yes"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709", 
    "dest": "/root/dirtest", 
    "gid": 0, 
    "group": "root", 
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e", 
    "mode": "0644", 
    "owner": "root", 
    "size": 0, 
    "src": "/tmp/dirtest", 
    "state": "file", 
    "uid": 0
}

上面示例因为remote_src=yes, 所以src=/tmp/dirtestdest=/root/dirtest的两个路径都是在远程机器上,



Ansible模块学习之fetch模块

copy模块是把本机的文件拷贝到远程机器,而fetch模块是把远程机器的文件拉取到本地

fetch模块刚好和copy模块的操作是相反的。因此参数的含义也是相反的

src参数: 远程机器上的文件或文件夹

dest参数: 保存到本地的目录

例1: 拷贝远程文件到本地

[root@localhost ~]# ansible 192.168.233.167 -m fetch -a "src=/etc/hosts dest=/home/"
192.168.233.167 | CHANGED => {
    "changed": true, 
    "checksum": "68991e742192b6cc45ad7b95eb88ea289658f65c", 
    "dest": "/home/192.168.233.167/etc/hosts", 
    "md5sum": "32d544b36aefb5a7f800e75cca57ce8b", 
    "remote_checksum": "68991e742192b6cc45ad7b95eb88ea289658f65c", 
    "remote_md5sum": null
}

在本地/home目录下会生成远程机器IP的文件夹,拉取的文件就在该目录下。并且保留目录结构

[root@localhost ~]# tree /home/192.168.233.167/
/home/192.168.233.167/
└── etc
    └── hosts

这个模块很简单,就不过多赘述。



Ansible模块学习之cron模块

Cron模块用来管理crontab的,包括添加、删除、更新操作系统的crontab任务计划

name参数: 计划任务名称

job参数: 指定计划的任务中需要实际执行的命令或者脚本

user参数: 指定计划任务属于哪个用户,默认是root用户

state参数: 当计划任务有名称时,根据计划任务名称修改删除对应的任务,删除计划任务state值为absent

backup参数: 对已有的任务修改或删除时,是否保存

disabled参数: 当计划任务有名称时,根据计划任务名称关闭(注释)对应的计划任务

minute参数:设置计划任务中分钟设定位的值,取值范围(0-59,*, */2)

hour参数: 设置计划任务中小时设定位的值,取值范围(0-23,*,*/2)

day参数: 设置计划任务中**天(日)**设定位的值,取值范围(1-31,*,*/2)

month参数:设置计划任务中月份设定位的值,取值范围(1-12,*,*/2)

weekday参数: 设置计划任务中周几设定位的值,取值范围(0-6 for Sunday-Saturday, *)

例1: 创建名称为ntpdate的计划任务,每天凌晨1点5分同步时间

[root@localhost ~]# ansible 192.168.233.167 -m cron -a "name='ntpdate' minute=5 hour=1 job='ntpdate ntp.aliyun.com'"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "envs": [], 
    "jobs": [
        "ntpdate"
    ]
}

查看一下添加后效果

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "crontab -l"
192.168.233.167 | CHANGED | rc=0 >>
#Ansible: ntpdate
5 1 * * * ntpdate ntp.aliyun.com

已经添加成功

例2: 把刚才创建的计划给关闭掉

[root@localhost ~]# ansible 192.168.233.167 -m cron -a "name='ntpdate' minute=5 hour=1 job='ntpdate ntp.aliyun.com' disabled=yes"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "envs": [], 
    "jobs": [
        "ntpdate"
    ]
}

查看一下结果

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "crontab -l"
192.168.233.167 | CHANGED | rc=0 >>
#Ansible: ntpdate
#5 1 * * * ntpdate ntp.aliyun.com

已经被注释掉了

注意:如果使用disabled参数没有设置时间参数,或时间参数设置错误时,计划任务会被注释掉的同时,时间也会被修改,如下:

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "crontab -l"
192.168.233.167 | CHANGED | rc=0 >>
#Ansible: ntpdate
#* * * * * ntpdate ntp.aliyun.com

可以看到,时间全部变为 "*"了

例3: 把刚才添加的ntpdate任务删除并备份

[root@localhost ~]# ansible 192.168.233.167 -m cron -a "name='ntpdate' state=absent backup=yes"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "backup_file": "/tmp/crontabRvJZBw", 
    "changed": true, 
    "envs": [], 
    "jobs": []
}

显示已经把名称为"ntpdate"的计划任务保存到/tmp/crontabRvJZBw

我们先用 crontab -l 查看一下计划任务

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "crontab -l"
192.168.233.167 | CHANGED | rc=0 >>

显示为空,说明计划任务已经被删除了。

我们再看一下备份文件

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "cat /tmp/crontabRvJZBw"
192.168.233.167 | CHANGED | rc=0 >>
#Ansible: ntpdate
5 1 * * * ntpdate ntp.aliyun.com

确实是我们在例1中创建的计划任务

建议:删除和关闭计划任务的时候,把backup=yes一起加上,即使操作错了还有备份

还有一个参数这里提一下special_time参数

special_time参数

计划任务的时间设定格式为@reboot或者@hourly,@reboot表示重启时执行,@hourly表示每小时执行一次,相当于设置成"0 * * * *" ,这种@开头的时间设定格式则需要使用special_time参数进行设置.

special_time参数的可用值有:

reboot: 重启后

yearly: 每年

annually: 每年,与yearly相同

monthly: 每月

weekly : 每周

daily: 每天

hourly: 每时



Ansible模块学习之yum&apt模块

yum模块是RHEL系的操作系统的软件包管理工具,但是这个模块只用在Python2.x中,Python3.x中要用dnf模块。这里不做讲解,和yum模块用法一样。

apt模块是Debian系的操作系统的软件包管理工具。

常用的参数如下,且用法一下,这里就不分开讲了。根据被管理机的系统自己选择使用相应的模块即可

name参数: 软件包名称,比如:nginx

state参数: 软件包状态,present表示安装,absent表示删除

update_cache参数: 安装软件前是否更新缓存,如果update_cache=true表示安装前更新,默认是不更新

例1: 安装nginx

[root@localhost ~]# ansible 192.168.233.167 -m apt -a "name=nginx state=present"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "cache_update_time": 1612164115, 
    "cache_updated": false, 
    "changed": true, 
    "stderr": "", 
    "stderr_lines": [], 
     ...

例2: 删除刚才安装的nginx

[root@localhost ~]# ansible 192.168.233.167 -m apt -a "name=nginx state=absent"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "stderr": "", 
    "stderr_lines": [], 
    "stdout": "Reading package lists...\nBuil
    ...

yum和apt模块就写这么多。



Ansible模块学习之service模块

service模块用来管理远程主机上的服务,支持的init系统包括BSD init、OpenRC、SysV、Solaris SMF、,

systemd,upstart。对于Windows目标,请改用[win_service]模块

name参数: 服务名称,比如nginx

state参数: 指定服务状态,可选值为:started、stopped、restarted、 reloaded

enabled参数: 值为yes时,指定服务设置为开机启动,值为no时,指定服务不开机启动

例1: 启动nginx服务

[root@localhost ~]# ansible 192.168.233.167 -m service -a "name=nginx state=started"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "name": "nginx", 
    "state": "started", 
    "status": {
        "ActiveEnterTimestamp": "Mon 2021-02-01 15:44:12 CST", 
        "ActiveEnterTimestampMonotonic": "344563040949", 
        "ActiveExitTimestamp": "Mon 2021-02-01 15:45:51 CST", 
        "ActiveExitTimestampMonotonic": "344661708600", 
        "ActiveState": "inactive", 
        "After": "network.target system.slice basic.target sysinit.target systemd-journald.socket", 
    ...

例2: 停止nginx服务

[root@localhost ~]# ansible 192.168.233.167 -m service -a "name=nginx state=stopped"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "name": "nginx", 
    "state": "stopped", 
    "status": {
        "ActiveEnterTimestamp": "Mon 2021-02-01 15:57:59 CST", 
        "ActiveEnterTimestampMonotonic": "345389576503", 
        "ActiveExitTimestamp": "Mon 2021-02-01 15:45:51 CST", 
        "ActiveExitTimestampMonotonic": "344661708600", 
        "ActiveState": "active", 
        "After": "network.target system.slice basic.target sysinit.target systemd-journald.socket",
     ...

例3: 将nginx设置为开机启动

[root@localhost ~]# ansible 192.168.233.167 -m service -a "name=nginx enabled=yes"
192.168.233.167 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": true, 
    "enabled": true, 
    "name": "nginx", 
    "status": {
        "ActiveEnterTimestampMonotonic": "0", 
        "ActiveExitTimestampMonotonic": "0", 
        "ActiveState": "inactive", 
        "After": "network.target basic.target sysinit.target system.slice systemd-journald.socket", 
    ...


初时ansible-playbook

什么是playbook

playbook是由单个ad-hoc编排而成,并且加入一些逻辑判断,和变量引用。它非常适合部署复杂的应用程序。

playbook是以yaml格式表示,因此在写playbook的时候要遵守yaml语法格式。

之所以使用YAML,是因为与其他常见的数据格式(例如XML或JSON)相比,人类更容易读写。

学习playbook之前,我们先简单学习一下yaml语法。

YAML语法

所有YAML文件(无论是否与Ansible有关)都可以选择以---和开头...。表示文档的开始和结束。

列表中的所有成员都是以相同的缩进级别开头的行,并以(破折号和空格)开头:"- "

---
# A list of  fruits
- Apple
- Orange
- Strawberry
- Mango

创建的文件以yaml或yml为后缀。比如: tasks.yaml

字典以简单的形式表示(冒号后面必须有一个空格):key: value

---
#学生信息
student:
  name: xiaoming
  age: 18
  sex: male

字典和列表结合使用

---
#学生信息
- student_1:
  name: xiaoming
  age: 18
  sex: male
  hobbies:
    - listenMusic
    - readingBook
    - playGames
- student_2:
  name: xiaohua
  age: 16
  sex: female
  hobbies:
    - running
    - makeFriend
    - sleeping
    

如果值有多行内容可以使用 |,使用这个 | 将忽略掉缩进,如下:

- name: add IP resolution
      blockinfile: 
        path: /etc/hosts
        block: | 
          192.168.1.1 node1
          192.168.1.2 node2

blockinfile是ansible的一个模块,它的作用是把一段单行或多行文本添加到文件中

上面的yaml语句的意思是:把IP解析添加到/etc/hosts文件中

yaml语法特别简单,而且符合人类的读写习惯。其他的就不多说了

验证语法可以到 http://www.yamllint.com/

接下我们我们回到ansible中,在学习playbook的过程中,进一步熟悉yaml语法



playbook组成

hosts: 远程主机或主机组

user: 执行任务的用户

sudo: 如果用户不是root需要sudo权限

gather_facts: 获取远程主机facts信息。默认是打开的(就是不写这个参数。关闭的时候这个必须写,且值是no

task: 任务开始

name: 任务名称,在屏幕上显示的信息

我们写一个test.yaml来感受一下。test.yaml内容如下

---
- hosts: 192.168.333.167  #远程机器的ip也可以是组名
  remote_user: root  #远程机器的用户
  #sudo: yes
  #gather_facts: no
  tasks:
    - name: copy file  # 任务名称,在执行任务的时候显示的TASK
      copy: 
        src: /root/testFile  #本地文件
        dest: /tmp/   # 远程机器目录

执行一下

[root@localhost ~]# ansible-playbook test.yaml 

PLAY [192.168.233.167] ********************************************************************************************************

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

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

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


一个最基本的playbook就完成了



handlers 使用

handlersnotify是结合使用的,那么,在什么时候会用到呢?

举一个例子,我们在修改nginx监听端口的时候要nginx -s reload一下才能生效。

我们按住正常的流程是先修改 nginx端口,再重启nginx。
示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: modify nginx listen port
    replace:
      path: /etc/nginx/sites-enabled/default #ubuntu用apt安装的nginx端口配置文件位置
      regexp: 'listen 80' #匹配需要修改的内容
      replace: 'listen 880' #修改匹配到的内容
      #backup: yes 这里不做备份,因为ubuntu用apt安装nginx配置有点特殊,会导致nginx无法重启
  - name: restart nginx
    service:
      name: nginx
      state: restarted

这样写是没问题的。执行一下看看:

[root@localhost ~]# ansible-playbook modify.yaml 

PLAY [192.168.233.167] ********************************************************************************************************

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

TASK [modify nginx listen port] ***********************************************************************************************
changed: [192.168.233.167]

TASK [restart nginx] *********************************************************************************************************
changed: [192.168.233.167]

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

执行也是ok的。

查看一下结果

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "ss -tpln|grep 880"
192.168.233.167 | CHANGED | rc=0 >>
LISTEN     0      128          *:880                      *:*                   users:(("nginx",pid=40284,fd=6),("nginx",pid=40283,fd=6),("nginx",pid=40282,fd=6))

可以看到端口已经改为880了。

那么用handlers 和notify应该怎么写playbook呢?我们写一个handlers.yaml文件,如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: modify nginx listen port
    replace:
      path: /etc/nginx/sites-enabled/default #ubuntu用apt安装的nginx端口配置文件位置
      regexp: 'listen 80' #匹配需要修改的内容
      replace: 'listen 880' #修改匹配到的内容
      #backup: yes 这里不做备份,因为ubuntu用apt安装nginx配置有点特殊,会导致nginx无法重启
    notify: restart nginx #注意notify的层级和replace是同一层的
  
  handlers: #handlers的层级是和"- name"同一层级的
  - name: restart nginx
    service:
      name: nginx
      state: restarted

注意: notify的 名称一定要和handlers的 name 相同

执行一下看看结果:

[root@localhost ~]# ansible-playbook handlers.yaml 

PLAY [192.168.233.167] ********************************************************************************************************

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

TASK [modify nginx listen port] ***********************************************************************************************
changed: [192.168.233.167]

RUNNING HANDLER [restart nginx] ***********************************************************************************************
changed: [192.168.233.167]

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

可以看到多了一个RUNNING HANDLER,这个就是handlers的用法。

有的同学就纳闷了,这个跟正常流程的没有区别啊。对,这样看来确实没有区别。

我们知道,ansible在执行时会进行判断,目标与想要修改的状态是否一致,如果不一致则修改,一致则不修改。这个也叫做幂等性

因此ansible的同一条命令是可以重复执行的。不过有一种操作例外,就是往文件里面添加字符,每次操作都会添加一次

这个跟handlers有什么关系呢?

handlers的触发机制是,只有notify的任务执行返回的状态是 changed的时候才会触发handlers。否则不会触发handlers的。

如果按照正常流程写一个重启nginx的任务,那么就是无论端口是否修改它都会重启一次nginx,重启是为了使新的配置生效的。

配置文件都没有修改,还去重启的话,这样是没有必要的。这也就是为什么使用handlers。

重新运行刚才的handlers.yaml文件看看结果

[root@localhost ~]# ansible-playbook handlers.yaml 

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [modify nginx listen port] *********************************************************************************************************
ok: [192.168.233.167]

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

可以看到最下面的changed的值是0,说明没有做修改,也没调用handlers重启nginx



tags的使用

看名字就知道是打标签了,给什么打标签呢?当然是给任务打标签了。

为什么要给任务打标签呢?

想象一下,我写了一个playbook里面包含了很多个任务(我自己就写过一个任务里面包含20多个任务),但是我只想执行其中的一个任务呢?

这时就用到了tags,我们先写个tags.yaml的demo看看。

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: task1
    file:
      path: /root/test_one
      state: touch
    tags: t1  # 注意tags标签的层级,是和file模块同一层级
  - name: task2
    file:
      path: /root/test_two
      state: touch
    tags: t2
  - name: task3
    file:
      path: /root/test_three
      state: touch
    tags: t3

上面yaml有3个任务:task1 task2和task3 对应的tags是t1 t2 t3。

如果正常执行的话,3个任务会被依次执行,但是我现在只想执行第3个任务task3。

执行如下:

[root@localhost ~]# ansible-playbook --tag=t3 tags.yaml  

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [task3] *********************************************************************************************************
changed: [192.168.233.167]

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

在执行ansible-playbook的时候添加参数–tag=t3 就只会执行tags=t3的任务。执行结果TASK [task3]也证明了这一点。

既然能选择执行哪个tags的任务,当然也能跳过某个tags的任务,命令如下:

[root@localhost ~]# ansible-playbook --skip-tag=t3 tags.yaml  

列出所有的tags

[root@localhost ~]# ansible-playbook --list-tags tags.yaml  

playbook: tags.yaml

  play #1 (192.168.233.167): 192.168.233.167	TAGS: []
      TASK TAGS: [t1, t2, t3]

共3个tags 分别是t1、t2、t3



playbook中的变量

在写playbook中使用变量可以使我们的playbook更加灵活

自定义变量

变量名应该由字母、数字、下划线组成,变量名需要以字母开头,ansible内置的关键字不能作为变量名

定义变量的格式:变量名:变量值

可以用关键字 vars来定义变量

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    testfile: mytestfile
  tasks:
  - name: create file
    file:
      path: /root/{{ testfile }} #引用变量名 在双花括号中,且变量名和花括号直接加一个空格
      state: touch

多个变量写法

vars:
  testfile1: mytestfile
  testfile2: youtestfile

或者

vars:
  - testfile1: mytestfile
  - testfile2: youtestfile

两种写法都可以,根据你自己喜好。

除了在playbook中定义变量外,还可以在单独一个文件中定义变量

比如:写一个名为my_vars.yaml的变量文件如下:

nginx_port: "listen 880"  #变量中间有空格的用双引号,没有空格的可以不用引号#              nginx_conf_path: /etc/nginx/nginx.conf

每个变量占一行

引用方法如下:

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    testfile: mytestfile
  var_files:   #用var_files关键字引入变量文件
  - /root/my_vars.yaml # 引入变量文件的路径
  tasks:
  - name: create file
    file:
      path: /root/{{ testfile }} #引用变量名 在双花括号中,且变量名和花括号直接加一个空格
      state: touch
  - name: modify nginx listen port
    replace:
      path: /etc/nginx/sites-enabled/default #ubuntu用apt安装的nginx端口配置文件位置
      regexp: 'listen 80' #匹配需要修改的内容
      replace: {{ nginx_port }} #引用变量文件中的nginx_port值
      #backup: yes 这里不做备份,因为ubuntu用apt安装nginx配置有点特殊,会导致nginx无法重启
    notify: restart nginx #注意notify的层级和replace是同一层的
  - name: fetch file
    fetch:
      src: "{{ nginx_conf_path }}" #调用了my_vars变量文件中的nginx_conf_path变量
      dest: /root/
  
  handlers: #handlers的层级是和"- name"同一层级的
  - name: restart nginx
    service:
      name: nginx
      state: restarted

vars和vars_files可以同时使用,但是建议只使用其中一种方式

如果两种方式都是用,且引用变量名相同时,会优先使用vars_files引用的文件中定义的变量



ansible变量

上面所讲的都是我们自己定义的变量,在ansible中除了自定义变量外,它自身也有自己的变量

通过setup模块我们查看一下

[root@localhost ~]# ansible 192.168.233.167 -m setup
192.168.233.167 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.233.167"
        ], 
        "ansible_all_ipv6_addresses": [
            "fe80::20c:29ff:fe31:441"
        ], 
        "ansible_apparmor": {
            "status": "enabled"
        }, 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "07/29/2019", 
        "ansible_bios_version": "6.00", 
        "ansible_cmdline": {
            "BOOT_IMAGE": "/vmlinuz-4.4.0-87-generic", 
            "ro": true, 
            "root": "/dev/mapper/ubuntu--vg-root"
        }, 
        "ansible_date_time": {
            "date": "2021-02-03", 
            "day": "03", 
            "epoch": "1612322512", 
            "hour": "11", 
            "iso8601": "2021-02-03T03:21:52Z", 
            "iso8601_basic": "20210203T112152183883", 
            "iso8601_basic_short": "20210203T112152", 
            "iso8601_micro": "2021-02-03T03:21:52.183883Z", 
            "minute": "21", 
            "month": "02", 
            "second": "52", 
            "time": "11:21:52", 
            "tz": "CST", 
            "tz_offset": "+0800", 
            "weekday": "Wednesday", 
            "weekday_number": "3", 
            "weeknumber": "05", 
            "year": "2021"
        }, 
        "ansible_default_ipv4": {
            "address": "192.168.233.167", 
            "alias": "ens33", 
            "broadcast": "192.168.233.255", 
            "gateway": "192.168.233.2", 
            "interface": "ens33", 
            "macaddress": "00:0c:29:31:04:41", 
            "mtu": 1500, 
            "netmask": "255.255.255.0", 
            "network": "192.168.233.0", 
            "type": "ether"
        }, 
        "ansible_default_ipv6": {}, 
        "ansible_device_links": {
            "ids": {
                "dm-0": [
                    "dm-name-ubuntu--vg-root", 
                    "dm-uuid-LVM-2wodXdFENDdoEyQu26BNJoBxQ7Vi3OpA8J1ctCb9mfzmBcg6fkMoq4qNzWbXc24U"
                ], 
                "dm-1": [
                    "dm-name-ubuntu--vg-swap_1", 
                    "dm-uuid-LVM-2wodXdFENDdoEyQu26BNJoBxQ7Vi3OpA0SBFA2ppfr2CudF072rlk5JqgxEt6VZA"
                ], 
                "sda5": [
                    "lvm-pv-uuid-e0VDvn-DMEP-FQ7u-cuFZ-8RQu-y76e-UdUbFX"
                ], 
                "sr0": [
                    "ata-VMware_Virtual_SATA_CDRW_Drive_01000000000000000001"
                ]
            }, 
            "labels": {
                "sr0": [
                    "Ubuntu-Server\\x2016.04.3\\x20LTS\\x20amd64"
                ]
            }, 
            "masters": {
                "sda5": [
                    "dm-0", 
                    "dm-1"
                ]
            }, 
            "uuids": {
                "dm-0": [
                    "4f69b277-6b7e-4bd8-80f0-4f85a04eadda"
                ], 
                "dm-1": [
                    "e1b06976-3417-4a0c-8d43-9cf1481e43ae"
                ], 
                "sda1": [
                    "64a1b32e-3914-493a-9353-03afe9700551"
                ], 
                "sr0": [
                    "2017-08-01-11-30-13-00"
                ]
            }
        }, 
        "ansible_devices": {
            "dm-0": {
                "holders": [], 
                "host": "", 
                "links": {
                    "ids": [
                        "dm-name-ubuntu--vg-root", 
                        "dm-uuid-LVM-2wodXdFENDdoEyQu26BNJoBxQ7Vi3OpA8J1ctCb9mfzmBcg6fkMoq4qNzWbXc24U"
                    ], 
                    "labels": [], 
                    "masters": [], 
                    "uuids": [
                        "4f69b277-6b7e-4bd8-80f0-4f85a04eadda"
                    ]
                }, 
                "model": null, 
                "partitions": {}, 
                "removable": "0", 
                "rotational": "1", 
                "sas_address": null, 
                "sas_device_handle": null, 
                "scheduler_mode": "", 
                "sectors": "36741120", 
                "sectorsize": "512", 
                "size": "17.52 GB", 
                "support_discard": "0", 
                "vendor": null, 
                "virtual": 1
            }, 
       ...

信息较多,我们只截取部分,有兴趣的同学可以自己研究一下。

在playbook执行过程中我们首先看到[Gathering Facts]任务,这个任务会自动执行setup模块收集远程机器的信息。

在我们写playbook的时候默认都会执行[Gathering Facts]任务,如果不想或不需要收集远程机器的信息我们可以把这个任务关闭,
写法如下:

---
- hosts: 192.168.233.167
  remote_user: root
  gather_facts: no
  tasks:
  - name: task1
    file:
      path: /root/test_one
      state: touch
    tags: t1  # 注意tags标签的层级,是和file模块同一层级
  - name: task2
    file:
      path: /root/test_two
      state: touch
    tags: t2

加上gather_facts: no在执行playbook中就不会在执行[Gathering Facts]任务,这样也就提高了playbook的执行速度。

这里列出几个常用的变量名称

ansible_os_family: 获取远程机器属于哪个家族的,比如RedHat、Debian

ansible_distribution_version: 远程机器发行版本

ansible_default_ipv4: 获取远程机器ip

我们写一个playbook看看上面三个变量对应的值

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    ip: "{{ ansible_default_ipv4['address'] }}"
    family: "{{ ansible_os_family }}"
    vers: "{{ ansible_distribution_version }}"
  tasks:
  - name: show ansible vars 
    debug:
      msg: "{{ item }}"
    loop:
      - "{{ ip }}"
      - "{{ family }}"
      - "{{ vers }}"

再看一下执行结果

[root@localhost ~]# ansible-playbook ansible_vars.yml 

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [show ansible vars] *********************************************************************************************************
ok: [192.168.233.167] => (item=192.168.233.167) => {
    "msg": "192.168.233.167"  
}
ok: [192.168.233.167] => (item=Debian) => {
    "msg": "Debian"
}
ok: [192.168.233.167] => (item=16.04) => {
    "msg": "16.04"
}

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

用debug模块分别显示了上面三个变量的值,远程机器不同,可能显示的结果也不同。



注册变量

其实ansible每次执行后都会有返回值,执行Ad-hoc(就是临时执行的命令)的时候可以看到,但是执行playbook的时候是看不到的。

在我们写playbook的时候如果某一步操作想引用前一步执行后的返回值怎么办呢?

这时候我们可以借助ansible的关键字register,把返回的内容注册的一个自定义的变量中,

先写一个register.yaml,内容如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: free -m
    shell: free -m
    register: free  #返回内容注册到变量free
  - name: show register vars
    debug:
      var: free  #显示变量free的内容

这个yaml文件的意思是第一个任务使用shell模块执行free -m命令,再把返回的结果注册到变量free。第二个任务用debug模块通过关键字var把刚才注册到free变量的内容显示出来

看一下执行结果:

[root@localhost ~]# ansible-playbook register.yml 

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [free -m] *********************************************************************************************************
changed: [192.168.233.167]

TASK [show register vars] *********************************************************************************************************
ok: [192.168.233.167] => {
    "free": {
        "changed": true, 
        "cmd": "free -m", 
        "delta": "0:00:00.006214", 
        "end": "2021-02-03 14:32:45.998207", 
        "failed": false, 
        "rc": 0, 
        "start": "2021-02-03 14:32:45.991993", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "              total        used        free      shared  buff/cache   available\nMem:           1982         326         232           9        1423        1414\nSwap:          2047          34        2013", 
        "stdout_lines": [
            "              total        used        free      shared  buff/cache   available", 
            "Mem:           1982         326         232           9        1423        1414", 
            "Swap:          2047          34        2013"
        ]
    }
}

PLAY RECAP *********************************************************************************************************
192.168.233.167            : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

可以看到变量free的内容,"stdout_lines"的内容就是执行free -m的结果。



交互式变量

之前我们写的playbook都是直接执行完成,中间没有任何交互的,但是在执行playbook的时候想要临时输入一下变量需要怎么做呢?只要通过关键字prompt即可实现。我(官方没有这个叫法)把这种变量叫做交互式变量,要先通过vars_prompt定义好哪些变量需要交互实现

先看一个例子:

---
- hosts: 192.168.233.167
  remote_user: root
  vars_prompt:
    - name: uname
      prompt: "Input uname"
      private: no  #关闭private
    - name: passwd
      prompt: "Password"
  tasks:
  - name: out prompt
    debug:
      msg: you uname is {{ uname }} password is {{ passwd }}

参数 private默认是打开的,该参数打开输入内容是不可见的,关闭后输入的内容可以在屏幕显示

执行结果如下:

[root@localhost ~]# ansible-playbook  123.yml 
Input uname: xiaoming   #关闭private,显示输入内容
Password:               #默认打开private,不显示输入内容

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [out prompt] *********************************************************************************************************
ok: [192.168.233.167] => {
    "msg": "you uname is xiaoming password is 123456"
}

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

交互式变量就讲这么多。

当然还有其他设置变量的方式,个人觉得掌握以上几种足够工作中使用了。感兴趣的同学,可以网上去找其他设置变量的方式。这里就不全列出了



playbook的循环

学过编程的同学知道在编程过程中经常会用到for 循环、while循环来处理数据。在ansible-playbook中也有循环,虽然没有那么复杂,但是用起来很方便。

细心的同学马上想起在讲变量的用到过循环。不记得也没关系,我们再来看一下

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    ip: "{{ ansible_default_ipv4['address'] }}"
    family: "{{ ansible_os_family }}"
    vers: "{{ ansible_distribution_version }}"
  tasks:
  - name: show ansible vars 
    debug:
      msg: "{{ item }}"
    loop:
      - "{{ ip }}"
      - "{{ family }}"
      - "{{ vers }}"

通过关键字loop 对 ip、family、vers三个变量进行循环,除了loop还可以使用with_items达到同样的循环效果。只要把上的loop改成with_items。

返回的结果也是分别显示出来

[root@localhost ~]# ansible-playbook loop.yaml 

PLAY [192.168.233.167] *********************************************************************************************************

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

TASK [show ansible vars] *********************************************************************************************************
ok: [192.168.233.167] => (item=192.168.233.167) => {
    "msg": "192.168.233.167"
}
ok: [192.168.233.167] => (item=Debian) => {
    "msg": "Debian"
}
ok: [192.168.233.167] => (item=16.04) => {
    "msg": "16.04"
}

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

再来一个应用场景,需要删除多个文件

如果不使用循环,那么删除3个文件就要写3个任务,使用循环的话一个任务就可以了

示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: remove install file
    file:
      path: "{{ item.filepath }}"
      state: absent
    with_items:
      - { filepath: "/root/test_one" }
      - { filepath: "/root/test_two" }

再来一个更复杂一点的场景,需要拷贝多个文件,但是文件在本地路径不一样,拷到远程机器的目录也不一样。

比如:本地文件/root/A 拷贝到远程机器/home目录下,本地文件/var/B拷贝到远程机器的/tmp目录下,yaml文件示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: copy file
    copy:
      src: "{{ item.src }}"
      dest: "{{ item.dest}}"
    with_items:
      - { src: "/root/A", dest: "/home/A" }
      - { src: "/var/B", dest: "/tmp/B" }

上面的playbook还可以用另一个关键字with_together来实现,写法有所区别,

示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: copy file 
    copy:
      src: "{{ item.0 }}"
      dest: "{{ item.1}}"
    with_together:
      - ["/root/A", "/var/B" ]
      - ["/home/A", "/tmp/B" ]

以上两种方法都可以,根据自己喜好即可。

还有一种虽然使用了循环,但是没有用到loopwith_items关键字,我们先来看一下

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - name: remove install file
    apt:
      name: ['vim', 'lrzsz']
      state: present

虽然没有循环的关键字,但是name的值是一个列表,ansible在执行的时候会自动循环



条件判断when

在编程的过程经常会看到用if做条件判断,但是在ansible-playbook中用关键字when来做判断,而它的用法也很简单,先看一个小例子

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: yum install nginx
    yum: 
      name: nginx
      state: present
    when: ansible_distribution == "CentOS"
  - name: apt install nginx
    apt:
      name: nginx
      state: present
    when: ansible_distribution == "Ubuntu"

上面yaml的意思很简单,当远程机器是CentOS的时候使用yum安装nginx,当远程机器是Ubuntu使用apt安装nginx

执行结果如下:

[root@localhost ~]# ansible-playbook when.yaml 

PLAY [192.168.233.167] ********************************************************************************************************************

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

TASK [yum install nginx] ******************************************************************************************************************
skipping: [192.168.233.167]

TASK [apt install nginx] ******************************************************************************************************************
ok: [192.168.233.167]

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

可以看到TASK [yum install nginx]状态的skipping,因为远程机器的系统是Ubuntu的。

这个是最简单的用法用来区分远程机器的不同发行版本。

如果远程机器是CentOS6和CentOS7或者Ubuntu14,16,18的版本我们就需要组合条件来判断了

示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install nginx
    apt:
      name: nginx
      state: present
    when: ansible_distribution == "Ubuntu" && ansible_distribution_major_version == "16"

或者换成列表的方式书写条件

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install nginx
    apt:
      name: nginx
      state: present
    when: 
      - ansible_distribution == "Ubuntu" 
      - ansible_distribution_major_version == "16"

以上两种方式都可以

条件判断常用的就这些,当然还有其他的。感兴趣的同学可以去网上找找



条件判断和block

试想,如果每个任务都写一个when去判断,这种做法是不是很麻烦呢。一个两个任务还行,十个八个呢?

先不管写那么多的when判断语句累不累,一下子写那么多判断语句,自己估计看着的都傻了吧。

还好ansible的开发者已经帮忙我们想好解决方案了,就是用block关键字把符合条件的任务当作 “块”

只要条件符合,“块” 内的所有任务都会执行,而不需要每个任务都去写个判断

且看示例:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks:
  - block:
    - name: yum install nginx
      yum: 
        name: nginx
        state: present
    - name: yum install vim
      yum:
        name: vim
        state: present
    when: ansible_distribution == "CentOS" #注意when的层级,是和block同一层级
  - block:
    - name: apt install nginx
      apt:
        name: nginx
        state: present
    - name: apt install vim
      apt:
        name: vim
        state: present
    when:  == "Ubuntu"  #注意when的层级,是和block同一层级

这个playbook相信你可以很轻松的看明白是什么意思。

唯一需要注意的就是when和block是相同层级的



忽略错误ignore_errors

我们在执行playbook的是按照任务顺序一步步执行的,如果某一步执行错误,playbook就会自动停止,后面的任务不会再继续执行。

有时候我们希望某一步任务执行错误,但是后面的任务还要继续执行。

这个时候要用到关键字ignore_errors,一看就知道它的作用是忽略错误。

默认是关闭的,需要忽略的时候要打开,
示例如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: copy
    shell: cp 123   #这个命令执行会报错
    ignore_errors: true
  - name: show df -h
    shell: df -h

看一下执行结果

[root@localhost ~]# ansible-playbook ig.yaml 

PLAY [192.168.233.167] ********************************************************************************************************************

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

TASK [copy] ******************************************************************************************************************
fatal: [192.168.233.167]: FAILED! => {"changed": true, "cmd": "cp 123", "delta": "0:00:00.003605", "end": "2021-02-04 11:35:22.464331", "msg": "non-zero return code", "rc": 1, "start": "2021-02-04 11:35:22.460726", "stderr": "cp: missing destination file operand after '123'\nTry 'cp --help' for more information.", "stderr_lines": ["cp: missing destination file operand after '123'", "Try 'cp --help' for more information."], "stdout": "", "stdout_lines": []}
...ignoring

TASK [show df -h] *************************************************************************************************************************
changed: [192.168.233.167]

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

可以看到ignored=1 后面的df -h的任务正常执行了

ignore_errors使用很简单,不做多说。



include的使用

编程的时候经常会调用其他代码模块的函数,当前比较火的Python 就是用关键字 import调用(或者叫做导入)模块。

在写shell脚本的时候我们也会you [. /path/test.sh] 的语法调用其他的shell脚本

在ansible-playbook中也有相应的调用,它是用关键字include来实现的

我们先写2个playbook,一个安装LAMP一个安装LNMP.

LAMP.yaml

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install LAMP
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - apache2 
      - mysql-server
      - php-fpm

LNMP.yaml

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install LNMP
    apt:
      name: "{{ item }}"
      state: present
    loop:
      - nginx 
      - mysql-server
      - php-fpm

可以看到无论在LAMP或LNMP中都会安装mysql-server和php-fpm,因此我们把这2个任务放到一个单独的yaml文件中,再写一个install_mysql_php.yaml文件,
内容如下:( 复习一下循环 )

- apt:
    name: "{{ item }}"
    state: present
  loop:
    - mysql-server
    - php-fpm

或者你不用循环,直接写单独的2个任务,如下:

- apt:
    name: mysql-server
    state: present
- apt:
    name: php-fpm
    state: present

以上2种写法都可以,但是要注意一点,这个yaml文件只包含任务,可以理解为任务列表。

而我们之前写的LAMP.yaml和LNMP.yaml文件也要做相应的修改,修改后如下:

LAMP.yaml

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install LAMP
    apt:
      name: apache2
      state: present
  - include: install_mysql_php.yaml

LNMP.yaml

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - name: apt install LNMP
    apt:
      name: nginx
      state: present
  - include: install_mysql_php.yaml

includewhen结合是经常使用的,因为经常远程机器的系统都有不同发行版,有的是CentOS,有的是Ubuntu,有的是SUSE,而针对不同的发行版我们就要写不同的playbook,写法如下:

---
- hosts: 192.168.233.167
  remote_user: root
  tasks: 
  - include: centos.yml  
    when: ansible_os_family == "RedHat"                                           
  - include: ubuntu.yml     
    when: ansible_os_family == "Debian" 

其实还有include_tasksimport_tasks也可以达到相同的调用效果,个人感觉一个include就够用了

好奇的同学可以到网上找相关资料研究一下。



template模块

为什么使用template?

在实际的工作中由于每台服务器的环境配置都可能不同,

但是往往很多服务的配置文件都需要根据服务器环境进行不同的配置,比如Nginx最大进程数、Redis最大内存等。

为了解决这个问题可以使用Ansible的template模块,该模块和copy模块作用基本一样,都是把管理端的文件复制到客户端主机上,但是区别在于template模块可以通过变量来获取配置值,支持多种判断、循环、逻辑运算等,而copy只能原封不动的把文件内容复制过去

直接来个示例吧,本地创建模板文件template.j2(因为ansible用的jinja2引擎,所以用j2后缀)

发行版: {{ distribution }}
系统版本: {{ distribution_version }}
主机名: {{ hostname }}
内核版本: {{ kernel }}
IP地址: {{ ip }}

写个playbook调用该模板

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    - distribution: "{{ ansible_distribution }}"
    - distribution_version: "{{ ansible_distribution_version }}"
    - hostname: "{{ ansible_hostname }}"
    - kernel: "{{ ansible_kernel }}"
    - ip: "{{ ansible_default_ipv4['address'] }}"
  tasks:
  - name: use template module
    template:
      src: /root/template.j2
      dest: /tmp/hostinfo

说明: playbook中"{{ }}"中是ansible gather_facter获取到的变量值。

template.j2文件中"{{ }}"中的变量是playbook中定义的,不要搞混淆了

jinja2 模板中的变量是调用playbook中定义的变量。

查看一下远程机器的hostinfo文件

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "cat /tmp/hostinfo"
192.168.233.167 | CHANGED | rc=0 >>
发行版: Ubuntu
系统版本: 16.04
主机名: ubuntu
内核版本: 4.4.0-87-generic
IP地址: 192.168.233.167

贴士: Jinja是一种现代且设计友好的Python模板语言而ansible 是使用python开发的,所以在ansible中无论是正常使用变量,还是template模块使用变量都是使用jinja模板。

学过flask的应该很熟悉,jinja的使用也非常灵活,可以做逻辑运算,做for循环,做if判断。在这里就不做多讲解有兴趣的同学可以网上查找资料

再写个使用template模块使用的例子。也是简单的例子。但是会加上简单的运算、循环和逻辑判断

现在,我们需要部署一台nginx服务器,部署需求是

1、nginx的worker_processes个数是cpu核数 x 2

2、部署2个vhosts,一个监听80端口,另一个监听8080端口

3、一个server_name是web1.test.com 另一个server_name是web2.test.com

4、2个vhosts的root路径分别是/var/www/web1和/var/www/web2

整个可能有点复杂,但是也不难。先写playbook

---
- hosts: 192.168.233.167
  remote_user: root
  vars:
    cpu_cores: "{{ ansible_processor_cores }}"
    nginx_vhosts:
      - web1:
        listen_port: 80
        server_name: "web1.test.com"
        root: "/var/www/web1"
      - web2:
        listen_port: 8080
        server_name: "web2.test.com"
        root: "/var/www.web2"
  tasks:
    - name: set nginx config from template
      template: 
        src: nginx.conf.j2
        dest: /etc/nginx/nginx.conf
        backup: yes

再写nginx.conf.j2

worker_processes {{ cpu_cores * 2}} #cpu核心数x2
{% for vhost in nginx_vhosts %}  #循环playbook中nginx_vhosts中的变量
  server {
      listen {{ vhost.listen_port }}
      {% if vhost.server_name is defined %} #判断playbook中是否定义server_name变量
      server_name {{ vhost.server_name }}
      {% endif %}  #if语句结束
      root {{ vhost.root }}
  }
{% endfor %}  #for语句结束

说明: 上面模板是nginx配置文件的部分内容,测试使用。正确的做法是把原来的nginx.conf文件修改成nginx.conf.j2文件

看一下结果

[root@localhost ~]# ansible 192.168.233.167 -m shell -a "cat /etc/nginx/nginx.conf"
192.168.233.167 | CHANGED | rc=0 >>
worker_processes 2  #用的是虚拟机只给一个核这里x2,所以值为2
  server {
      listen 80
            server_name web1.test.com
            root /var/www/web1
  }
  server {
      listen 8080
            server_name web2.test.com
            root /var/www.web2
  }

template就说这么多吧



role的使用

roles是根据已知文件结构自动加载某些vars_files,任务和处理程序的方法。按角色对内容进行分组还可以轻松与其他用户共享角色。

项目结构示例:

site.yml
webservers.yml
fooservers.yml
roles/
    common/
        tasks/
        handlers/
        files/
        templates/
        vars/
        defaults/
        meta/
    webservers/
        tasks/
        defaults/
        meta/

roles必须至少包含这些目录,使用时,每个目录必须包含一个main.yml文件

  • tasks: 存放角色要执行的任务的文件。
  • handlers: 调用handlers的文件放在该文件夹
  • defaults: 角色的默认变量
  • vars: 角色的其他变量
  • files: 存放文件。比如要copy的文件,并且在此目录下的文件,再写copy任务时,src 只要写文件名即可,不用写全路径
  • templates: 包含可以通过此角色部署的模板。
  • meta: 为此角色定义一些元数据。

roles的存放路径默认是/etc/ansible/roles

写完roles后可以用ansible-playbook -C roles_name.yaml检查语法是否有误

下面是我自己写的一个roles小demo功能非常简单,可以去看下了解roles具体该如何写。

Demo地址:

Gitee:https://gitee.com/wsl12105/playbook_demo.git

Github:https://github.com/wsl12105/playbook_demo.git

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

运维0到1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值