Ansible

前言

天天说运维,究竟是干什么的?先看看工作流程呗。一般来说,运维工程师在一家企业里属于个位数的岗位,甚至只有一个。面对生产中NNN台服务器,NN个人员,工作量也是非常大的。
所以嘛,图中的我好歹也会配置盔甲的。

image

这就是我主要干的事情(呵呵)

image

就算你会很厉害的脚本,面对成百上千,甚至上万的主机,效率问题将会困扰你的。
因此,有没有解放的工具呢?

Ansible 是什么

Ansible 简单的说是一个配置管理系统(configuration management system)。你只需要可以使用 ssh 访问你的服务器或设备就行。它也不同于其他工具,因为它使用推送的方式,而不是像 puppet 等 那样使用拉取安装agent的方式。你可以将代码部署到任意数量的服务器上!

Ansible能做什么

ansible可以帮助我们完成一些批量任务,或者完成一些需要经常重复的工作。
比如:同时在100台服务器上安装nginx服务,并在安装后启动它们。
比如:将某个文件一次性拷贝到100台服务器上。
比如:每当有新服务器加入工作环境时,你都要为新服务器部署某个服务,也就是说你需要经常重复的完成相同的工作。
这些场景中我们都可以使用到ansible。

Ansible特性

 模块化:调用特定的模块,完成特定任务
 有Paramiko,PyYAML,Jinja2(模板语言)三个关键模块
 支持自定义模块
 基于Python语言实现
 部署简单,基于python和SSH(默认已安装),agentless
 安全,基于OpenSSH
 支持playbook编排任务
 幂等性:一个任务执行1遍和执行n遍效果一样,不因重复执行带来意外情况
 无需代理不依赖PKI(无需ssl)
 可使用任何编程语言写模块
 YAML格式,编排任务,支持丰富的数据结构
 较强大的多层解决方案

Ansible架构

image

Ansible工作原理

image

Ansible主要组成部分功能说明

 PLAYBOOKS:
            任务剧本(任务集),编排定义Ansible任务集的配置文件,由Ansible顺序依次执行,通常是JSON格式的YML文件
 INVENTORY:
            Ansible管理主机的清单/etc/anaible/hosts
 MODULES:
            Ansible执行命令的功能模块,多数为内置的核心模块,也可自定义,ansible-doc –l 可查看模块
 PLUGINS:
            模块功能的补充,如连接类型插件、循环插件、变量插件、过滤插件等,该功能不常用
 API:
            供第三方程序调用的应用程序编程接口
 ANSIBLE:
            组合INVENTORY、 API、 MODULES、PLUGINS的绿框,可以理解为是ansible命令工具,其为核心执行工具

注意事项

 执行ansible的主机一般称为主控端,中控,master或堡垒机
 主控端Python版本需要2.6或以上
 被控端Python版本小于2.4需要安装python-simplejson
 被控端如开启SELinux需要安装libselinux-python
 windows不能做为主控端

安装Ansible

安装方法有很多,这里仅仅以Centos yum安装为例。

Ansible默认不在标准仓库中,需要用到EPEL源。

请自行参考
https://mirrors.aliyun.com/help/centos
#yum install ansible

image

#ansible --version
ansible 2.4.2.0
  config file = /etc/ansible/ansible.cfg
  executable location = /usr/bin/ansible
  python version = 2.7.5

Ansible 功能详解

配置文件

配置文件或指令描述
/etc/ansible/ansible.cfg主配置文件,配置ansible工作特性
/etc/ansible/hosts主机清单
/etc/ansible/roles/存放角色的目录
/usr/bin/ansible主程序,临时命令执行工具
/usr/bin/ansible-doc查看配置文档,模块功能查看工具
/usr/bin/ansible-galaxy下载/上传优秀代码或Roles模块的官网平台
/usr/bin/ansible-playbook定制自动化任务,编排剧本工具
/usr/bin/ansible-pull远程执行命令的工具
/usr/bin/ansible-vault文件加密工具
/usr/bin/ansible-console基于Console界面与用户交互的执行工具

Ansible 配置文件

Ansible 配置文件/etc/ansible/ansible.cfg (一般保持默认)
 [defaults]
 #inventory = /etc/ansible/hosts # 主机列表配置文件
 #library = /usr/share/my_modules/ # 库文件存放目录
 #remote_tmp = $HOME/.ansible/tmp #临时py命令文件存放在远程主机目录
 #local_tmp = $HOME/.ansible/tmp # 本机的临时命令执行目录
 #forks = 5 # 默认并发数
 #sudo_user = root # 默认sudo 用户
 #ask_sudo_pass = True #每次执行ansible命令是否询问ssh密码
 #ask_pass = True      #连接时提示输入ssh密码
 #remote_port = 22     #远程主机的默认端口,生产中这个端口应该会不同
 #log_path = /var/log/ansible.log #日志
 #host_key_checking = False # 检查对应服务器的host_key,建议取消注释。也就是不会弹出
                                Are you sure you want to continue connecting (yes/no)? 

实验规划

实验环境:VMware Workstation Pro 14(试用版)
系统平台:
CentOS Linux release 7.4.1708 (Core)       内核  3.10.0-693.el7.x86_64
最小化安装

实验环境:VMware Workstation Pro 14(试用版)
系统平台:
CentOS release 6.9 (Final)             内核  2.6.32-696.el6.x86_64
最小化安装
主机名系统版本IP地址功能
7-web-0Centos 7.4192.168.7.200Ansible主控端
6-dns-1Centos 6.9192.168.7.254DNS服务器
6-web-1Centos 6.9192.168.7.201服务器
7-web-2Centos 7.4192.168.7.202服务器
7-db-3Centos 7.4192.168.7.203服务器

除了6-DNS-1以外,所有的主机的DNS均指向192.168.7.254

正向区域设置
#dig -t axfr hunk.tech

; <<>> DiG 9.8.2rc1-RedHat-9.8.2-0.62.rc1.el6 <<>> -t axfr hunk.tech
;; global options: +cmd
hunk.tech.      600 IN  SOA 6-dns-1.hunk.tech. admin.hunk.tech. 24 720 600 86400 10800
hunk.tech.          600 IN  NS  6-dns-1.hunk.tech.
6-dns-1.hunk.tech.  600 IN  A   192.168.7.254
6-web-1.hunk.tech.  600 IN  A   192.168.7.201
7-db-3.hunk.tech.   600 IN  A   192.168.7.203
7-web-0.hunk.tech.  600 IN  A   192.168.7.200
7-web-2.hunk.tech.  600 IN  A   192.168.7.202
hunk.tech.          600 IN  SOA 6-dns-1.hunk.tech. admin.hunk.tech. 24 720 600 86400 10800

Inventory 主机清单

Ansible必须通过Inventory 来管理主机。Ansible 可同时操作属于一个组的多台主机,组和主机之间的关系通过 inventory 文件配置。

语法格式:

单台主机
green.example.com    >   FQDN
192.168.100.10       >   IP地址
192.168.100.11:2222  >   非标准SSH端口

[webservers]         >   定义了一个组名     
alpha.example.org    >   组内的单台主机
192.168.100.10 

[dbservers]
192.168.100.10       >   一台主机可以是不同的组,这台主机同时属于[webservers] 

[group:children]     >  组嵌套组,group为自定义的组名,children是关键字,固定语法,必须填写。
dns                  >  group组内包含的其他组名
db                   >  group组内包含的其他组名

[webservers] 
www[001:006].hunk.tech > 有规律的名称列表,
这里表示相当于:
www001.hunk.tech
www002.hunk.tech
www003.hunk.tech
www004.hunk.tech
www005.hunk.tech
www006.hunk.tech

[databases]
db-[a:e].example.com   >   定义字母范围的简写模式,
这里表示相当于:
db-a.example.com
db-b.example.com
db-c.example.com
db-d.example.com
db-e.example.com

以下这2条定义了一台主机的连接方式,而不是读取默认的配置设定
localhost       ansible_connection=local
www.163.com     ansible_connection=ssh        ansible_ssh_user=hunk

最后还有一个隐藏的分组,那就是all,代表全部主机,这个是隐式的,不需要写出来的。

Inventory 参数说明

ansible_ssh_host
      将要连接的远程主机名.与你想要设定的主机的别名不同的话,可通过此变量设置.

ansible_ssh_port
      ssh端口号.如果不是默认的端口号,通过此变量设置.这种可以使用 ip:端口 192.168.1.100:2222

ansible_ssh_user
      默认的 ssh 用户名

ansible_ssh_pass
      ssh 密码(这种方式并不安全,我们强烈建议使用 --ask-pass 或 SSH 密钥)

ansible_sudo_pass
      sudo 密码(这种方式并不安全,我们强烈建议使用 --ask-sudo-pass)

ansible_sudo_exe (new in version 1.8)
      sudo 命令路径(适用于1.8及以上版本)

ansible_connection
      与主机的连接类型.比如:local, ssh 或者 paramiko. Ansible 1.2 以前默认使用 paramiko.1.2 以后默认使用 'smart','smart' 方式会根据是否支持 ControlPersist, 来判断'ssh' 方式是否可行.

ansible_ssh_private_key_file
      ssh 使用的私钥文件.适用于有多个密钥,而你不想使用 SSH 代理的情况.

ansible_shell_type
      目标系统的shell类型.默认情况下,命令的执行使用 'sh' 语法,可设置为 'csh' 或 'fish'.

ansible_python_interpreter
      目标主机的 python 路径.适用于的情况: 系统中有多个 Python, 或者命令路径不是"/usr/bin/python",比如  \*BSD, 或者 /usr/bin/python 不是 2.X 版本的 Python.
      我们不使用 "/usr/bin/env" 机制,因为这要求远程用户的路径设置正确,且要求 "python" 可执行程序名不可为 python以外的名字(实际有可能名为python26).

      与 ansible_python_interpreter 的工作方式相同,可设定如 ruby 或 perl 的路径....

上面的参数用这几个例子来展示可能会更加直观

some_host         ansible_ssh_port=2222     ansible_ssh_user=manager
aws_host          ansible_ssh_private_key_file=/home/example/.ssh/aws.pem
freebsd_host      ansible_python_interpreter=/usr/local/bin/python
ruby_module_host  ansible_ruby_interpreter=/usr/bin/ruby.1.9.3

第一条 Ansible 命令

很重要的一点,主机清单必须要先配置,由于这搭建了内部DNS服务器,所以,这里的主机使用了FQDN名称。

#cat /etc/ansible/hosts 
[web]
6-web-1.hunk.tech
7-web-0.hunk.tech
7-web-2.hunk.tech

[group:children]
dns
db

[dns]
6-dns-1.hunk.tech

[db]
7-db-3.hunk.tech

192.168.7.[200:203]
192.168.7.254
#ansible dns -m ping       # 使用ansible对dns组内的主机进行ping模块测试

image
非常抱歉哦,竟然是失败的。为什么呢?Ansible是基于ssh进行工作的,那么当ssh一台远程主要的时候,是不是需要输入密码呢?可是这一条指令并没有提示输入口令呢

#ansible dns -m ping -k
加上-k选项后,会提示输入ssh密码了。

image

另外,值得注意的是,当指令成功和失败都会有不同的颜色反映出来,配以changed :false,changed :SUCCESS,可以让我们非常清晰的知道执行的结果。

当主机数量多的时候,输入密码可不是一个好差事呢?前面的章节已经讲过主机之间可以使用基于密钥的SSH链接。为了更方便的管理主机,这个章节的实验都用这种方法。

基于key的免密码登录

#ssh-keygen
#ssh-copy-id 6-web-1.hunk.tech
#ssh-copy-id 6-DNS-1.hunk.tech
#ssh-copy-id 7-web-0.hunk.tech
#ssh-copy-id 7-web-2.hunk.tech
#ssh-copy-id 7-db-3.hunk.tech

现在就不会再提示密码的问题了。

#ansible all -m ping
6-web-1.hunk.tech | SUCCESS
7-web-0.hunk.tech | SUCCESS
7-web-2.hunk.tech | SUCCESS
7-db-3.hunk.tech | SUCCESS 
6-dns-1.hunk.tech | SUCCESS 
192.168.7.201 | SUCCESS 
192.168.7.200 | SUCCESS 

是不是很简单呀,Ansible用的指令也不是太多,可以使用man ansible和官方网站去查询。http://docs.ansible.com/ansible/latest/

Ansible常用命令语法

ansible <host-pattern> [-m module_name] [options]
指令 匹配规则的主机清单 -m 模块名 选项

--version 显示版本
-a 模块参数(如果有)
-m module 指定模块,默认为command
-v 详细过程 –vv -vvv更详细
--list-hosts 显示主机列表,可简写--list
-k, --ask-pass 提示连接密码,默认Key验证
-K,--ask-become-pass 提示使用sudo密码
-C, --check 检查,并不执行
-T, --timeout=TIMEOUT 执行命令的超时时间,默认10s
-u, --user=REMOTE_USER 执行远程执行的用户
-U, SUDO_USER, --sudo-user 指定sudu用户
-b, --become 代替旧版的sudo 切换
ansible-doc: 显示模块帮助
ansible-doc [options] [module...]

-a 显示所有模块的文档
-l, --list 列出可用模块
-s, --snippet 显示指定模块的简要说明

例子:#ansible-doc ping

由于ansible的模块有1378个(2.4.2.0),并且一直在持续更新。因此,这个指令必须要掌握的。
#ansible-doc -l |wc -l
1378

Ansible 主机 匹配列表

通配符

注意用单引号
*  匹配任意字符
#ansible '*' -m ping   等同于 #ansible all -m ping

#ansible '*dns*' -m ping
6-dns-1.hunk.tech | SUCCESS

? 匹配单个字符
#ansible '192.168.7.20?' -m ping
192.168.7.201 | SUCCESS
192.168.7.203 | SUCCESS
192.168.7.202 | SUCCESS
192.168.7.200 | SUCCESS

: 或者
#ansible '192.168.7.201:192.168.7.254' -m ping
192.168.7.201 | SUCCESS
192.168.7.254 | SUCCESS

:& 并且  (逻辑与)
#ansible 'test3:&test' --list

  hosts (1):
    192.168.7.254

:! 逻辑非。在test3组内,但是并不在test组内
#ansible 'test3:!test' --list       > 用到感叹号的时候,记得引号为单引号,否则会被bash解析为历史命令

  hosts (2):
    192.168.7.200
    192.168.7.203

使用正则表达式
~表示后面是正则匹配,注意~后面不能有空格
#ansible '~[67]-(db|dns).*\.hunk.*' --list

  hosts (2):
    6-dns-1.hunk.tech
    7-db-3.hunk.tech

这里写一条正则的坑,我们在用bash脚本的时候,匹配数字可以使用[0-9]或[[:digit:]],在用Ansible的时候,我们来看下不同的效果吧

#cat /etc/ansible/hosts 

[web]
6web-1.hunk.tech
7web-0.hunk.tech
7web-2.hunk.tech

[group:children]
dns
db

[dns]
6-dns-1.hunk.tech

[db]
7-db-3.hunk.tech
[test2]
192.168.7.[200:203]
192.168.7.254
[test]
192.168.7.254

[test3]
192.168.7.200
192.168.7.254
192.168.7.203

DNS都是可以正确解析出来的,不要怀疑
6-dns-1.hunk.tech.  600 IN  A   192.168.7.254
6-web-1.hunk.tech.  600 IN  A   192.168.7.201
7-db-3.hunk.tech.   600 IN  A   192.168.7.203
7-web-0.hunk.tech.  600 IN  A   192.168.7.200
7-web-2.hunk.tech.  600 IN  A   192.168.7.202

#ansible '~^[[:digit:]]' --list     有人说这种写法会报错,可是主机都是centos7.4,版本都是一样
  hosts (7):                         这里匹配出7台主机
    192.168.7.200
    192.168.7.201
    192.168.7.202
    192.168.7.203
    192.168.7.254
    6-dns-1.hunk.tech
    7-db-3.hunk.tech

#ansible '~^[0-9]' --list
  hosts (10):                       这里匹配出10台主机
    6web-1.hunk.tech
    6-dns-1.hunk.tech
    7-db-3.hunk.tech
    7web-0.hunk.tech
    7web-2.hunk.tech
    192.168.7.254
    192.168.7.201
    192.168.7.200
    192.168.7.203
    192.168.7.202

#ansible '~^[[:digital:]]' --list
  hosts (10):                       这里匹配出10台主机
    6web-1.hunk.tech
    7web-0.hunk.tech
    7web-2.hunk.tech
    192.168.7.200
    192.168.7.201
    192.168.7.202
    192.168.7.203
    192.168.7.254
    6-dns-1.hunk.tech
    7-db-3.hunk.tech

分享2个正则表达式的网址
https://en.wikipedia.org/wiki/Regular_expression#POSIX_basic_and_extended

python2的
https://docs.python.org/2/library/re.html

不同软件对正则的表达都不一样,还是用那些通用性强的吧。

[0-9]纯数字
[a-zA-Z0-9]数字和字母

Ansible 的命令执行过程

以 ansible db -m command -a 'ls -l /' -vvv 这条命令为例,根据显示的信息时行解读

1. 加载自己的配置文件,默认/etc/ansible/ansible.cfg
    Using /etc/ansible/ansible.cfg as config file

2.匹配主机清单
    Parsed /etc/ansible/hosts inventory source with ini plugin

3. 加载指令对应的模块文件,如command,生成.py的文件到本机的临时目录,这个目录就是在/etc/ansible/ansible.cfg定义的
    Using module file /usr/lib/python2.7/site-packages/ansible/modules/commands/command.py
    PUT /tmp/tmp4JvsLH TO /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/command.py

4. 通过ansible将模块或命令生成对应的临时py文件,并将该文件传输至远程服务器的对应执行用户$HOME/.ansible/tmp/ansible-tmp-数字/XXX.PY文件,
    这个目录就是在/etc/ansible/ansible.cfg定义的
    ( umask 77 && mkdir -p "` echo /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861 `" ....)
    sftp> put /tmp/tmp4JvsLH /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/command.py\n'

5. 给文件+x 权限
    'chmod u+x /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/ /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/command.py && sleep 0'

6. 执行并返回结果
    '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/command.py;

7. 删除临时py文件,sleep 0退出
    rm -rf "/root/.ansible/tmp/ansible-tmp-1517301292.6-155771303493861/" > /dev/null 2>&1 && sleep 0

8.断开远程主机连接
    'Shared connection to 7-db-3.hunk.tech closed.\r\n')

执行结果状态

 绿色:执行成功并且不需要做改变的操作

 ×××:执行成功并且对目标主机做变更

 红色:执行失败

可以在配置文件中定义
[colors]
#highlight = white
#verbose = blue
#warn = bright purple
#error = red
#debug = dark gray
#deprecate = purple
#skip = cyan
#unreachable = red
#ok = green
#changed = yellow
#diff_add = green
#diff_remove = red
#diff_lines = cyan

ping

功能:尝试连接到主机,验证并返回pong成功。

-对于Windows目标,请改用win_ping模块
-不使用icmp协议,使用ssh协议。
例子:
#ansible db -m ping
7-db-3.hunk.tech | SUCCESS => {
    "changed": false, 
    "ping": "pong"                > 返回pong表明成功通讯
}

command

功能:在远程节点上执行命令

-变量 和操作符号 "<", ">", "|", ";" and "&" 不能正常工作。如果需要使用,请使用shell模块
-Ansible默认不指定模块时,将使用此模块。
- chdir
        命令运行前先切换到此目录

- creates
        条件判断,如果文件存在,将不执行后面的命令。
        #ansible web -m command -a 'creates=/app echo /app not found'
        6-web-1.hunk.tech | SUCCESS | rc=0 >>
        skipped, since /app exists

= free_form
        The command module takes a free form command to run.  There is no parameter actually named 'free form'. See the
        examples!

- removes
        条件判断,如果文件不存在,将不执行后面的命令。
        #ansible web -m command -a 'removes=/app echo /app not found'
        6-web-1.hunk.tech | SUCCESS | rc=0 >>
        /app not found

- stdin
        将命令的stdin直接设置为指定的值

- warn
       如果 ansible.cfg 设置了开启警报, 将不会对这一行进行警报

shell

功能:在远程节点上执行命令。与command模快使用一致,但是,变量 和操作符号 "<", ">", "|", ";" and "&" 能正常工作

下面2个例子对比能清晰的表示出不同的地方

#ansible db -m command -a 'echo $RANDOM'
7-db-3.hunk.tech | SUCCESS | rc=0 >>
$RANDOM

#ansible db -m shell -a 'echo $RANDOM'
7-db-3.hunk.tech | SUCCESS | rc=0 >>
5159

这些复杂命令,即使使用shell也可能会失败,解决办法:写到脚本时,
copy到远程,执行,再把需要的结果拉回执行命令的机器
#ansible web -m shell -a df | awk '{print $5}'

script

功能:把脚本复制到远程节点后,在远程节点本地运行脚本

给定的脚本将通过远程节点上的shell环境进行处理。
这个模块在远程系统上不需要python,就像原始脚本一样。
#ansible web -m script -a './shell.sh'

6-web-1.hunk.tech | SUCCESS => {
        "Wed Jan 31 23:17:17 CST 2018", 
        "/usr/lib64/qt-3.3/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin"
}

copy

功能:复制文件或目录到远程节点。默认会覆盖目标文件

backup:在覆盖之前将原文件备份,备份文件包含时间信息。有两个选项:yes|no 
content:用于替代"src",可以直接设定指定文件的内容,相当于echo 重定向内容到文件
dest:必选项。要将源文件复制到的远程主机的绝对路径,如果源文件是一个目录,那么该路径也必须是个目录 
directory_mode:递归的设定目录的权限,默认为系统默认权限
force:如果目标主机包含该文件,但内容不同,如果设置为yes,则强制覆盖,如果为no,则只有当目标主机的目标位置不存在该文件时,才复制。默认为yes
others:所有的file模块里的选项都可以在这里使用
src:要复制到远程主机的文件在本地的地址,可以是绝对路径,也可以是相对路径。如果路径是一个目录,它将递归复制。在这种情况下,如果路径使用"/"来结尾,则只复制目录里的内容,如果没有使用"/"来结尾,则包含目录在内的整个内容全部复制,类似于rsync。

#ansible dns -m copy -a 'src=/tmp/abc.txt  dest=/app backup=yes'

复制目录时,斜线不要写
#ansible dns -m copy -a 'src=/tmp/dir1  dest=/app'

根据内容生成文件,相当于echo abc123 > /app/123.txt
#ansible dns -m copy -a 'content="abc123"  dest=/app/123.txt'

fetch

功能:从远程节点获取文件(只能是文件)到本地目录。默认会以主机清单中的主机名为目录存放获取到的文件

#ansible all -m fetch -a 'src=/var/log/messages dest=/app'
#tree -L 1 /app
/app
├── 6-dns-1.hunk.tech
├── 6-web-1.hunk.tech
├── 7-db-3.hunk.tech
├── 7-web-0.hunk.tech
└── 7-web-2.hunk.tech

注意,src=/var/log/mess*   这种通配符语法是不支持的

file

功能:设置远程节点的文件的文件属性

force:需要在两种情况下强制创建软链接,一种是源文件不存在但之后会建立的情况下;另一种是目标软链接已存在,需要先取消之前的软链,然后创建新的软链,有两个选项:yes|no 
group:定义文件/目录的属组 
mode:定义文件/目录的权限
owner:定义文件/目录的属主
path:必选项,定义文件/目录的路径
recurse:递归的设置文件的属性,只对目录有效
src:要被链接的源文件的路径,只应用于state=link的情况
dest:被链接到的路径,只应用于state=link的情况 
state:  操作方法
    directory:如果目录不存在,创建目录
    file:即使文件不存在,也不会被创建
    link:创建软链接
    hard:创建硬链接
    touch:如果文件不存在,则会创建一个新的文件,如果文件或目录已存在,则更新其最后修改时间
    absent:删除目录、文件或者取消链接文件。相当于rm -rf

创建空文件,类似于touch    
#ansible dns -m file -a 'path=/app/dir2/abc.txt state=touch mode=0666 owner=ftp'    

创建空目录, 类似于mkdir -p
#ansible dns -m file -a 'path=/app/dir2/dir3/dir4 state=directory mode=0666 owner=ftp'

├ dir2
│ └── dir3
│     └── dir4

创建软链接
#ansible dns -m file -a 'path=/app/abc.txt state=link src=/app/dir2/abc.txt'
lrwxrwxrwx  1 root root   17 Feb  1 00:58 abc.txt -> /app/dir2/abc.txt

hostname

功能:设置远程节点主机名

#ansible dns -m hostname -a 'name=6-dns-1.hunk.tech'

cron

功能:管理计划任务

backup:对远程主机上的原任务计划内容修改之前做备份 
cron_file:如果指定该选项,则用该文件替换远程主机上的cron.d目录下的用户的任务计划 
day:日(1-31,*,*/2,……) 
hour:小时(0-23,*,*/2,……)  
minute:分钟(0-59,*,*/2,……) 
month:月(1-12,*,*/2,……) 
weekday:周(0-7,*,……)
job:要执行的任务,依赖于state=present 
name:该任务的描述 
special_time:指定什么时候执行,参数:reboot,yearly,annually,monthly,weekly,daily,hourly 
state:确认该任务计划是创建还是删除 
user:以哪个用户的身份执行

#ansible dns -m cron -a 'name="test cron job" minute=*/2 job="/usr/bin/wall hello world"'

禁用某个计划任务

#Ansible: test cron job
*/2 * * * * /usr/bin/wall hello world

正确的写法:必须完整的写完,包括name等属性
#ansible dns -m cron -a 'disabled=yes name=None minute=*/3 job="/usr/bin/wall hello world"'

#ansible dns -m cron -a 'disabled=yes job="/usr/bin/wall hello world"'   > 这种写法是不对的,它会创建一条以下记录并禁用它
#Ansible: None
#* * * * * /usr/bin/wall hello world

删除计划任务
#ansible dns -m cron -a 'state=absent name=None'

yum

功能:使用yum包管理器来管理软件包

config_file:yum的配置文件 
disable_gpg_check:关闭gpg_check 
disablerepo:不启用某个源 
enablerepo:启用某个源
name:要进行操作的软件包的名字,也可以传递一个url或者一个本地的rpm包的路径 
state:Whether to install (`present' or `installed', `latest'), or remove (`absent' or `removed') a package.
        (可选值: present, installed, latest, absent, removed) [Default: present]

#ansible all -m yum -a 'name=tree state=present'

#ansible all -a 'rpm -q tree' -o
6-web-1.hunk.tech | SUCCESS | rc=0 | (stdout) tree-1.5.3-3.el6.x86_64
6-dns-1.hunk.tech | SUCCESS | rc=0 | (stdout) tree-1.5.3-3.el6.x86_64
7-web-0.hunk.tech | SUCCESS | rc=0 | (stdout) tree-1.6.0-10.el7.x86_64
7-web-2.hunk.tech | SUCCESS | rc=0 | (stdout) tree-1.6.0-10.el7.x86_64
7-db-3.hunk.tech | SUCCESS | rc=0 | (stdout) tree-1.6.0-10.el7.x86_64

yum_repository

功能:配置管理yum源

reposdir: repo文件存放目录
file:      repo文件名,默认为name的值
name:       唯一的repository ID
gpgkey:设置gpgkey
gpgcheck:设置gpg检查
enabled:设置开启关闭
bandwidth:控制带宽,0为无限
state:状态(present,absent
description:描述

#ansible dns -m yum_repository -a 'state=present name=epel enabled=yes gpgcheck=yes description="Aliyun EPEL" baseurl="http://mirrors.aliyun.com/epel/6/$basearch,http://mirrors.aliyuncs.com/centos/$releasever/os/$basearch/" gpgkey="https://mirrors.aliyun.com/epel/RPM-GPG-KEY-EPEL-6Server"'

service

功能:配置管理服务

arguments:给命令行提供一些选项 。可以使用别名 args
enabled:是否开机启动 yes|no
name:必选项,服务名称 
pattern:定义一个模式,如果通过status指令来查看服务的状态时,没有响应,就会通过ps指令在进程中根据该模式进行查找,如果匹配到,则认为该服务依然在运行
runlevel:运行级别
sleep:如果执行了restarted,在则stop和start之间沉睡几秒钟
state:对当前服务执行启动,停止、重启、重新加载等操作(started,stopped,restarted,reloaded)

#ansible db -m service -a 'name=httpd state=started'  > 一次只能操作一个服务

setup

功能:收集关于远程主机的信息。

在playbooks里经常会用到的一个参数gather_facts就与该模块相关

--tree :将所有主机的输出信息保存到/tmp/目录下,以/etc/ansible/hosts里的主机名为文件名
ansible all -m setup -a 'filter=ansible_distribution_version' --tree /tmp/

filter :过滤关键字
#ansible db -m setup -a 'filter=ansible_distribution_version'

gather_subset:按子集收集信息,值有all, min, hardware, network, virtual, ohai, facter。不包含请使用!号,如,!network
关键字说明返回值例子
ansible_nodename节点名"6-dns-1.hunk.tech"
ansible_fqdnFQDN名"6-dns-1.hunk.tech"
ansible_hostname主机短名称"6-dns-1"
ansible_domain主机域名后缀"hunk.teh"
ansible_memtotal_mb总物理内存"ansible_memtotal_mb": 222
ansible_swaptotal_mbSWAP总大小"1023"
ansible_processorCPU信息Intel(R) Core(TM) i5-5200U CPU @ 2.20GHz
ansible_processor_coresCPU核心数量4
ansible_processor_vcpusCPU逻辑核心数量2
ansible_all_ipv4_addresses有所IPV4地址192.168.0.200
ansible_all_ipv6_addresses所有IPV6地址
ansible_default_ipv4默认网关的网卡配置信息
ansible_eth2具体某张网卡信息不同系统名称需要变化
ansible_dnsDNS设置信
ansible_architecture系统架构x86_64
ansible_machine主机类型x86_64
ansible_kernel内核版本"2.6.32-696.el6.x86_64"
ansible_distribution发行版本"CentOS"
ansible_distribution_major_version操作系统主版本号"6"
ansible_distribution_release发行版名称"Final"
ansible_distribution_version完整版本号"7.4.1708"
ansible_pkg_mgr软件包管理方式"yum"
ansible_service_mgr进行服务方式"systemd"
ansible_os_family家族系列"RedHat"
ansible_cmdline内核启动参数
ansible_selinuxSElinux状态"disabled"
ansible_env当前环境变量参数
ansible_date_time时间相关
ansible_python_versionpython版本"2.6.6"
ansible_lvmLVM卷相关信息
ansible_mounts所有挂载点
ansible_device_links所有挂载的设备的UUID和卷标名
ansible_devices所有/dev/下的正在使用的设备的信息
ansible_user_dir执行用户的家目录"/root"
ansible_user_gecos执行用户的描述信息"The root "
ansible_user_gid执行用户的的GID0
ansible_user_id执行用户的的用户名"root"
ansible_user_shell执行用户的shell类型"/bin/bash"
ansible_user_uid执行用户的UID0

user

功能:管理用户账号

选项太多,与useradd这类系统命令差不多
http://docs.ansible.com/ansible/latest/user_module.html

name:用户名,可以使用别名user
#ansible db -m user -a 'name=hunk4 shell=/sbin/nologin system=yes comment="name is hunk"'
7-db-3.hunk.tech | SUCCESS => {
    "changed": true, 
    "comment": "name is hunk", 
    "createhome": true, 
    "group": 996, 
    "home": "/home/hunk4", 
    "name": "hunk4", 
    "shell": "/sbin/nologin", 
    "state": "present", 
    "system": true, 
    "uid": 998
}

删除用户
#ansible db -m user -a 'name=hunk4 state=absent'
7-db-3.hunk.tech | SUCCESS => {
    "changed": true, 
    "force": false, 
    "name": "hunk4", 
    "remove": false, 
    "state": "absent"
}

修改用户指定信息
#ansible db -m user -a 'name=hunk4 state=present comment=" hunk is my"'

remove:删除用户时一并删除用户家目录,需要与state=absent一起使用

#ansible db -m user -a 'name=hunk3 state=absent remove=yes'
7-db-3.hunk.tech | SUCCESS => {
    "changed": true, 
    "force": false, 
    "name": "hunk3", 
    "remove": true, 
    "state": "absent"
}

state:操作方法。(present , absent)

groups: 添加辅助组
group: 指定用户的主组

以下这个例子注意看,group和groups所代表的含义不同。
    - name: add user
      user: name={{ username }} group=ftp groups={{ groupname }}

group

功能:添加组或删除组

group模块请求的是groupadd, groupdel, groupmod 三个指令.
用法都是差不多的。

get_url

功能:从 HTTP, HTTPS, or FTP 下载文件

checksum:下载完成后进行checksum;格式: e.g. checksum="sha256:D98291AC[...]B6DC7B97".值有sha1, sha224, sha384, sha256, sha512, md5
timeout:下载超时时间,默认10s
url:下载的URL
url_password、url_username:主要用于需要用户名密码进行验证的情况
use_proxy:是事使用代理,代理需事先在环境变更中定义
force:yes目标存在时是否下载,no目标文件不存在时下载
backup:创建一个包含时间戳信息的备份文件

#ansible dns -m get_url -a 'dest=/app/ url="https://github.com/bennojoy/nginx/archive/master.zip"'

#ansible dns -m get_url -a 'dest=/app/ELS.txt checksum=sha1:8c9e20bd25525c3ed04ebaa407097fe875f02b2c url="ftp://172.18.0.1/pub/Files/ELS.txt" force=yes'
6-dns-1.hunk.tech | SUCCESS => {
    "changed": false, 
    "checksum_dest": "8c9e20bd25525c3ed04ebaa407097fe875f02b2c", 
    "checksum_src": "8c9e20bd25525c3ed04ebaa407097fe875f02b2c", 

fail

功能:自定义消息失败

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

默认返回 'Failed as requested from task'

lineinfile

功能:替换一个文件中特定的行,或者使用一个反引用的正则表达式替换一个现有的行。

只有找到的最后一行将被替换
backup:创建一个包含时间戳信息的备份文件
backrefs:  为no时,如果没有匹配,则添加一行line。如果匹配了,则把匹配内容替被换为line内容。
            为yes时,如果没有匹配,则文件保持不变。如果匹配了,把匹配内容替被换为line内容。
insertafter:配合state=present。该行将在指定正则表达式的最后一个匹配之后插入。一个特殊的价值是在EOF; EOF用于在文件的末尾插入行。如果指定的正则表达式没有匹配,则将使用EOF
insertBefore:state=present。该行将在指定正则表达式的最后一个匹配之前插入。 BOF用于在文件的开头插入行。如果指定的正则表达式不匹配,则该行将被插入到文件的末尾。不能使用backrefs
valiate:在保存sudoers文件前,验证语法,如果有错,执行时,会报出来,重新编辑playbook
regexp: 正则表达式
# Before 2.3, option 'dest', 'destfile' or 'name' was used instead of 'path'
- lineinfile:
    path: /etc/selinux/config
    regexp: '^SELINUX='
    line: 'SELINUX=enforcing'

- lineinfile:
    path: /etc/sudoers
    state: absent
    regexp: '^%wheel'

- lineinfile:
    path: /etc/hosts
    regexp: '^127\.0\.0\.1'
    line: '127.0.0.1 localhost'
    owner: root
    group: root
    mode: 0644

- lineinfile:
    path: /etc/httpd/conf/httpd.conf
    regexp: '^Listen '
    insertafter: '^#Listen '
    line: 'Listen 8080'

- lineinfile:
    path: /etc/services
    regexp: '^# port for http'
    insertbefore: '^www.*80/tcp'
    line: '# port for http by default'

# Add a line to a file if it does not exist, without passing regexp
- lineinfile:
    path: /tmp/testfile
    line: '192.168.1.99 foo.lab.net foo'

# Fully quoted because of the ': ' on the line. See the Gotchas in the YAML docs.
- lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%wheel\s'
    line: '%wheel ALL=(ALL) NOPASSWD: ALL'

# Yaml requires escaping backslashes in double quotes but not in single quotes
- lineinfile:
    path: /opt/jboss-as/bin/standalone.conf
    regexp: '^(.*)Xms(\\d+)m(.*)$'
    line: '\1Xms${xms}m\3'
    backrefs: yes

# Validate the sudoers file before saving
- lineinfile:
    path: /etc/sudoers
    state: present
    regexp: '^%ADMIN ALL='
    line: '%ADMIN ALL=(ALL) NOPASSWD: ALL'
    validate: '/usr/sbin/visudo -cf %s'

replace

功能:替换一个文件中符合匹配的所有行,或者使用一个反引用的正则表达式替换所有的行。

ansible-galaxy

连接 https://galaxy.ansible.com 下载相应的roles,此网站是Ansible爱好者将日常使用较好的playbooks打包上传,其他人可以免费下载
到Ansible PlayBooks并立即投入使用。

ansible-galaxy 语法:

ansible-galaxy [delete|import|info|init|install|list|login|remove|search|setup] [--help] [options] 

 列出已安装的galaxy
#ansible-galaxy list geerlingguy.mysql
- geerlingguy.mysql, 2.8.1

 安装galaxy
ansible-galaxy install geerlingguy.redis

 删除galaxy
ansible-galaxy remove geerlingguy.redis

进入网站后找到这时标记的地方

image

把名字复制下来

image

就可以在你的ansible主机上进行安装剧本了。

#ansible-galaxy install geerlingguy.mysql
- downloading role 'mysql', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-mysql/archive/2.8.1.tar.gz
- extracting geerlingguy.mysql to /root/.ansible/roles/geerlingguy.mysql
- geerlingguy.mysql (2.8.1) was installed successfully

安装的剧本默认是存放在家目录的隐藏文件中。
/root/.ansible

#tree -L 2
.
└── geerlingguy.mysql
    ├── defaults
    ├── handlers
    ├── LICENSE
    ├── meta
    ├── README.md
    ├── tasks
    ├── templates
    ├── tests
    └── vars

当然,本机的galaxy命令也提供在线搜索剧本。

#ansible-galaxy search --author geerlingguy

Found 90 roles matching your search:

 Name                              Description
 ----                              -----------
 geerlingguy.raspberry-pi          Configures a Raspberry Pi.
 geerlingguy.php-redis             PhpRedis support for Linux
 geerlingguy.drupal-console        Drupal Console
 geerlingguy.adminer               Installs Adminer for Database management.
 geerlingguy.blackfire             Blackfire installation for Linux
 geerlingguy.tomcat6               Tomcat 6 for RHEL/CentOS and Debian/Ubuntu.
 geerlingguy.php-pear              PHP PEAR library installation.

Ansible-vault

功能:管理加密解密yml文件

ansible-vault [create|decrypt|edit|encrypt|rekey|view]
 ansible-vault encrypt hello.yml 加密
 ansible-vault decrypt hello.yml 解密
 ansible-vault view hello.yml 查看加密问题
 ansible-vault edit hello.yml 编辑加密文件
 ansible-vault rekey hello.yml 修改口令
 ansible-vault create new.yml 创建新文件

Ansible-console

可交互执行命令,支持tab补全。

#ansible-console
Vault password:默认是当前登录账号密码

root@all (5)[f:5]$
列出所有的内置命令: ?或help

执行用户@当前操作的主机组(all) (当前组的主机数量5)[f:并发数5]
设置并发数: forks n
root@all (5)[f:5]$ forks 10

切换组: cd 主机组
root@all (5)[f:10]$ cd db

列出当前组主机列表: list
root@all (5)[f:10]$ list
6-web-1.hunk.tech
7-web-0.hunk.tech
7-web-2.hunk.tech
6-dns-1.hunk.tech
7-db-3.hunk.tech

执行一行指令
root@web (3)[f:10]$ setup filter=ansible_distribution_version

Ansible-playbook

语法:

ansible-playbook <filename.yml> ... [options]
常见选项
--check 只检测可能会发生的改变,但不真正执行操作
--list-hosts 列出运行任务的主机
--list-tasks 列出此playbook中的所有任务
--list-tags 列出此playbook中的所有的tags
--limit 主机列表 只针对主机列表中的主机执行
--step 一步一步执行脚本
--flush-cache  清除fact缓存
-C 文件名     执行前先检查语法。
-D 显示出执行前后的变化内容
-v 显示过程 -vv -vvv 更详细

第一个playbook

---

- hosts: all
  remote_user: root

  tasks:
    - name: test yml
      command: /usr/bin/wall "hello world"

语法检查

image

真正执行

image

Playbook采用YAML语言编写

Playbook工作流程

image

YAML语法简介

这里只涉及到playbook相关的语法,更多请参考官网http://www.yaml.org

语法非常严格,请仔细仔细再仔细。

在单一档案中,可用连续三个连字号(---)区分多个档案。另外,还有选择性的连续三个点号( ... )用来表示档案结尾
 次行开始正常写Playbook的内容,一般建议写明该Playbook的功能
 使用#号注释代码
 缩进必须是统一的,不能空格和tab混用,一般缩进2个空格
 缩进的级别也必须是一致的,同样的缩进代表同样的级别,程序判别配置的级别是通过缩进结合换行来实现的
 YAML文件内容和Linux系统大小写判断方式保持一致,是区别大小写的,key/value的值均需大小写敏感
 key/value的值可以写在同一行,也可换行写。同一行使用 , 逗号分隔
 value可是个字符串,也可是另一个列表
 一个完整的代码块功能需最少元素需包括 name和task
 一个name只能包括一个task
 使用| 和 > 来分隔多行,实际上这只是一行。
        include_newlines: |
            exactly as you see
            will appear these three
            lines of poetry

        ignore_newlines: >
            this is really a
            single line of text
            despite appearances
 Yaml中不允许在双引号中出现转义符号,所以都是以单引号来避免转义符错误           
 YAML文件扩展名通常为yml或yaml

List:列表

其所有元素均使用"-"打头

- web
- dns
-空格web

Dictionary:字典(键值对)

通常由多个key与value构成

多行写法:
name: hunk
blog: "http://http://blog.51cto.com/191226139"
name:空格hunk   > 这个冒号后面必须是一个空格

同一行写法:
需要使用{ }
{name: hunk, blog: "http://http://blog.51cto.com/191226139"}  > 逗号后建议使用留一个空格

布尔值的表示法:
yes/no  true/false

create_key: yes
needs_agent: no
knows_oop: True
likes_emacs: TRUE
uses_cvs: false

一些特别要注意的语法

Ansible 使用 "{{ var }}" 来引用变量. 如果一个值以 "{"开头, YAML 将认为它是一个字典, 所以我们必须引用它, 像这样:

foo: "{{ variable }}"

使用引号来包裹任何包含冒号:的value值, 像这样:

foo: "somebody said I should put a colon here: so I did"

Playbook核心元素

hosts

hosts 行的内容是一个或多个组或主机的 patterns,以逗号为分隔符。通常是/etc/ansible/hosts定义的主机列表

remote_user 就是远程执行任务的账户名:

---
- hosts: web,dns
  remote_user: root

tasks

任务集

  tasks:
    - name: install httpd
      yum: name=httpd

    - name: start httpd
      service: name=httpd state=started

    - name: check http port
      shell: ss -ntl|grep 80 > /tmp/httpd.txt

    - name: fetch
      fetch: src=/tmp/httpd.txt dest=/app

执行过程

image

执行结果
#tree /app
/app
├── 6-web-1.hunk.tech
│   └── tmp
│       └── httpd.txt
├── 7-web-0.hunk.tech
│   └── tmp
│       └── httpd.txt
├── 7-web-2.hunk.tech
│   └── tmp
│       └── httpd.txt

#cat /app/6-web-1.hunk.tech/tmp/httpd.txt 
LISTEN     0      128                      :::80                      :::*    

一个yml文件里可以设计多个playbook,不过呢,为了更清晰的管理,建议应该独立存放不同任务需求,方便以后调用。

这里要注意双引号的情况

    - name: change httpd.conf
       shell: "/bin/sed -i.bakr '/^Listen 80/cListen 8080' /etc/httpd/conf/httpd.conf"

Handlers 和 notity

由特定条件触发的操作,满足条件方才执行,否则不执行。

Handlers也是task列表,这些task与前述的tasks并没有本质上的不同,用于当关注的资源发生变化时,才会采取一定的操作

还是拿上个例子的playbook修改下。

---
- hosts: ~^7.*web
  remote_user: root

  tasks:
    - name: install httpd
      yum: name=httpd

    - name: change httpd.conf
      copy: src=/app/httpd.conf dest=/etc/httpd/conf/ backup=yes
      notify: restart httpd             > 在 notify 中定义内容一定要和handlers中定义的 - name 内容一样,这样才能达到触发的效果,否则会不生效。
    - name: start httpd
      service: name=httpd state=started

    - name: wall http status
      shell: /usr/bin/wall `ss -nltp|grep httpd`

  handlers:
    - name: restart httpd           > 只有接收到通知才会执行这里的任务
      service: name=httpd state=restarted
这里准备了一个httpd2.4版本的配置文件
#grep ^Listen /app/httpd.conf
Listen 8080
这个配置文件不可用于httpd2.2版本,因此需要在执行时指定匹配的主机用正则判断一下。
---
#ansible-playbook installhttpd.yml --limit ~^7.*web > 支持正则表达式
PLAY [web]

TASK [Gathering Facts] 

ok: [7-web-0.hunk.tech]
ok: [7-web-2.hunk.tech]

tags

指定某条任务执行,用于选择运行playbook中的部分代码。 ansible具有幂等性,因此会自动跳过没有变化的部分,

即便如此,有些代码为测试其确实没有发生变化的时间依然会非常地长。此时,如果确信其没有变化,就可以通过

tags跳过此些代码片断。可以为每个tasks设置tags,这样方便调用。

    - name: change httpd.conf
      copy: src=/app/httpd.conf dest=/etc/httpd/conf/
      tags: copyconf    > tags:后除了一个空格外,不要有其他空格,中间可以使用_下划线
      notify: restart httpd

可以使用多个tags

#ansible-playbook –t copyconf useradd.yml
#ansible-playbook –t copyconf,start_httpd useradd.yml
如果命令或脚本的退出码不为零,可以使用如下方式替代
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand || /bin/true

 或者使用ignore_errors来忽略错误信息:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True

变量

变量命名

变量名仅能由字母、数字和下划线组成,且只能以字母开头

变量定义方式

1. # ansible setup facts 远程主机的所有变量都可直接调用。setup模块就是用来获取远程主机的相关信息的。一般以ansible_开头的就是变量可以调用

2. 在/etc/ansible/hosts中定义

普通变量:主机组中主机单独定义,优先级高于公共变量
公共(组)变量:针对主机组中所有主机定义统一变量

[web]
6-web-1.hunk.tech hname=nginx http_prot=8080   > 主机普通变量
7-web-0.hunk.tech hname=httpd http_prot=8081   > 主机普通变量
7-web-2.hunk.tech hname=httpd24 http_prot=8082  > 主机普通变量

[web:vars]   注意:vars是关键字,针对web组内所有主机设置的变量
hname=web    > 主机公共(组)变量,

3. 通过命令行指定变量,优先级最高
    #ansible-playbook –e 变量名=变量值
        http_port=80

4. 在playbook中定义
vars:                 > 关键字
  - var1: value1
  - var2: value2

5. vars_files指定变量文件
    vars_files:
    - /app/vars.yml

6. 在role中定义

7. 通过{{ variable_name }} 调用变量,且变量名前后必须有空格,有时用"{{ variable_name }}"才生效

8. register  注册变量
   把某一条任务执行的结果保存下来,可以在接下的任务中调用或者做些判断。这里是定义了结果存到什么名字的变量里。常与ignore_errors配合设置成True
   整个变量可以使用在template中,动作行中,或者是when语句中
   使用-v参数来查看存储的内容或者使用debug: var=注册名
    register变量的命名不能用 - 中横线

各种变量优先级:
命令行 -e > vars_files指定变量文件 > 主机清单普通变量 > 主机清单公共(组)变量

一些例子:

通过playbook中使用vars: 定义调用


---
- hosts: web
  remote_user: root
  vars:                     > 关键字
    - username: hunk88      > 键值对
    - groupname: groupabc   > 键值对

  tasks:
    - name: add group
      group: name={{ groupname }}    > 变量调用
    - name: add user
      user: name={{ username }}      > 变量调用
...

通过playbook中使用setup中的变量调用

---
- hosts: web
  remote_user: root

  tasks:
    - name: create  file
      copy: dest=/app/ip.txt content="{{ ansible_all_ipv4_addresses }}"  > 调用了setup 模块收集的ansible_all_ipv4_addresses变量值
...

#cat /app/ip.txt 
["192.168.5.102", "172.18.103.79", "192.168.7.201"]

通过主机清单定义普通变量并调用

[web]
6-web-1.hunk.tech hname=nginx http_prot=8080
7-web-0.hunk.tech hname=httpd http_prot=8081
7-web-2.hunk.tech hname=httpd24 http_prot=8082

---
- hosts: web
  remote_user: root

  tasks:
    - name: set hostname
      hostname: name={{ hname }}-{{ http_prot }}

结果:
#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
nginx-8080

7-web-0.hunk.tech | SUCCESS | rc=0 >>
httpd-8081

7-web-2.hunk.tech | SUCCESS | rc=0 >>
httpd24-8082

通过主机清单定义公共(组)变量并调用

[web]
6-web-1.hunk.tech http_prot=8080
7-web-0.hunk.tech http_prot=8081
7-web-2.hunk.tech http_prot=8082

[web:vars]
hname=web
make="-"

tasks:
    - name: change hostname
      hostname: name={{ hname }}{{ make }}{{ http_prot }}

结果:
#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
web-8080

7-web-0.hunk.tech | SUCCESS | rc=0 >>
web-8081

7-web-2.hunk.tech | SUCCESS | rc=0 >>
web-8082

通过独立的变量文件调用

#vim /app/vars.yml
hname: nginx    > 注意键值对的格式
prot: 9527

playbook:

- hosts: web
  remote_user: root
  vars_files:
    - /app/vars.yml

  tasks:
    - name: set hostname
      hostname: name={{ hname }}-{{ prot }}

结果:

#ansible web -m shell -a 'echo $HOSTNAME'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

7-web-2.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

7-web-0.hunk.tech | SUCCESS | rc=0 >>
nginx-9527

通过register注册变量并调用

- hosts: web
  tasks:
  - shell: /bin/cat /etc/centos-release
    register: release               > 将上一条结果保存到release变量
  - name: show release
    debug: var=release

以下是用debug模块查看输出结果

ok: [7-web-0.hunk.tech] => {
    "release": {
        "changed": true,                       > 显示是否已更改
        "cmd": "/bin/cat /etc/centos-release", > 执行的命令
        "delta": "0:00:00.006148", 
        "end": "2018-02-03 22:47:49.010194",   > 执行结束的时间
        "failed": false,
        "rc": 0,                               > 命令的返回码
        "start": "2018-02-03 22:47:49.004046", > 执行开始的时间
        "stderr": "",                          > 如果有错误,则输出错误的信息
        "stderr_lines": [],                    > 逐行输出
        "stdout": "CentOS Linux release 7.4.1708 (Core) ", > 命令的输出
        "stdout_lines": [
            "CentOS Linux release 7.4.1708 (Core) "
        ]
    }
}

调用:
- hosts: web
  tasks:
  - shell: /bin/cat /etc/centos-release
    ignore_errors: true
    register: release

  - name: show Centos 6
    command: /usr/bin/wall "Centos 6"
    when: release.stdout | match("CentOS release 6.9 (Final)")

  - name: show Centos 7
    command: /usr/bin/wall "Centos 7"
    when: release.stdout | search("release 7")  > 模糊匹配

判断文件是否存在的实例:

- hosts: all
  tasks:
    - name: check file
      shell: ls /app/ip.txt
      ignore_errors: true
      register: release

    - name: show file not found
      command: /usr/bin/wall "file not found"
      when: release.rc != 0

    - name: show file found
      command: /usr/bin/wall "file found"
      when: release.rc == 0

when条件判断

与shell编程中的条件判断类似

在task后添加when子句即可使用条件测试;when语句支持Jinja2表达式语法

前面的案例使用了正则来判断主机的系统版本号,这里直接使用when调用系统变量来判断

match:精确匹配
search:模糊匹配
支持正则表达式

when: release.stdout | match('.*release 6.9.*$')
when: release.stdout | match('CentOS release 6.9 \(Final\)') > 精确匹配
when: release.stdout | search("release 7")  > 模糊匹配
when: ansible_distribution_major_version == "7"

判断变量是否定义

is defined: 变量已经定义
is undefined: 变量未定义

tasks:
    - shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
      when: foo is defined

    - fail: msg="Bailing out. this play requires 'bar'"
      when: bar is undefined

with_items

当有需要重复性执行的任务时,可以使用迭代机制。类似于循环

 对迭代项的引用,固定变量名为 item"
 要在task中使用with_items给定要迭代的元素列表

列表格式:
字符串
字典

例子:

- hosts: web
  remote_user: root
  vars:               > 定义了2个变量
    - user1: hunk4    > 键值对
    - user2: hunk5

  tasks:
    - name: create user
      user: name={{ item }}    > 调用with_items的值,这是固定的关键字,不能换成其他的。
      with_items:              > with_items:列表开始    
        - "{{ user1 }}"        > 调用vars中定义的2个变量,注意需要用双引号引起来。
        - "{{ user2 }}"

结果:
#ansible web -a 'tail -n3 /etc/passwd'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
user2:x:503:503::/home/user2:/bin/bash
hunk4:x:504:504::/home/hunk4:/bin/bash
hunk5:x:505:505::/home/hunk5:/bin/bash

7-web-0.hunk.tech | SUCCESS | rc=0 >>
user2:x:1003:1003::/home/user2:/bin/bash
hunk4:x:1004:1004::/home/hunk4:/bin/bash
hunk5:x:1005:1005::/home/hunk5:/bin/bash

7-web-2.hunk.tech | SUCCESS | rc=0 >>
user2:x:1003:1003::/home/user2:/bin/bash
hunk4:x:1004:1004::/home/hunk4:/bin/bash
hunk5:x:1005:1005::/home/hunk5:/bin/bash

当然,也可以不像上面那样,直接在with_items下直接给到列表内容。

另外,每个- name 下面的with_items可以同时存在,不相互冲突。如下面的:
- name: X1
  with_items
    - a
    - b

- name: X2
  with_items
    - c
    - d

等价的写法:

方法1,多行:
  tasks:
    - name: with_items
      command: wall "{{ item }}"
      with_items:
        - 0
        - 2
        - 4
        - 6
        - 8
        - 10
      when: item > 5

方法2,单行:
  tasks:
    - name: with_items
      command: wall "{{ item }}"
      with_items: [ 0, 2, 4, 6, 8, 10 ]    > 使用[ ] 来存放列表
      when: item > 5

批量循环创建文件夹

本着为了以后更好的维护变量的设计,这次把变量独立到到个文件中
#vim /app/vars.yml
all_data:                
  - A
  - B 
  - C 
  - D

playbook:
- hosts: dns
  remote_user: root
  vars_files:
    - /app/vars.yml

  tasks:
    - name: create dir
      file: "path=/tmp/{{ item }} state=directory"
      with_items:
        - "{{ all_data }}"

#tree /tmp
/tmp
├── A
├── B
├── C
└── D

with_items嵌套子变量

这次来了个综合一点的例子,本着为了以后更好的维护变量的设计,这次把变量独立到到个文件中

#cat /app/vars.yml 

user1: hunk3       > 键值对
user2: hunk4
group1: A
group2: B

playbook

- hosts: web
  remote_user: root
  vars_files:              > 导入变量文件
    - /app/vars.yml

  tasks:
    - name: create groups
      group: name={{ item }}   > 使用with_items创建组
      with_items:
        - "{{ group1 }}"       > with_items列表的值来自变量文件中的变量名
        - "{{ group2 }}"

    - name: create users
      user: name={{ item.username }} group={{ item.groupname }}   > 嵌套子变量,格式为:item.with_items的列表中的键名
      with_items:
       - { username: "{{ user1 }}", groupname: "{{ group1 }}" }   > 别晕,最外层的{} 就是键值对的语法,里面有2个键值对,用逗号,分隔。{{ }}是引入的变量键名,注意要用双引号。
       - { username: "{{ user2 }}", groupname: "{{ group2 }}" }

结果:

image

#ansible web -m shell -a 'id hunk3;id hunk4'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
uid=502(hunk3) gid=502(A) groups=502(A)
uid=503(hunk4) gid=503(B) groups=503(B)

7-web-0.hunk.tech | SUCCESS | rc=0 >>
uid=1002(hunk3) gid=1002(A) groups=1002(A)
uid=1003(hunk4) gid=1003(B) groups=1003(B)

7-web-2.hunk.tech | SUCCESS | rc=0 >>
uid=1002(hunk3) gid=1002(A) groups=1002(A)
uid=1003(hunk4) gid=1003(B) groups=1003(B)

模板templates

功能:根据模块文件动态生成对应的配置文件

 Jinja2语言,使用字面量,有下面形式
字符串:使用单引号或双引号
数字:整数,浮点数
列表:[item1, item2, ...]
元组:(item1, item2, ...)
字典:{key1:value1, key2:value2, ...}
布尔型:true/false
 算术运算:+, -, *, /, //, %, **
 比较操作:==, !=, >, >=, <, <=
 逻辑运算:and, or, not
 流表达式:For If When
 templates文件必须存放于templates目录下,且命名为 .j2 结尾
 yaml/yml 文件需和templates目录平级,目录结构如下:
./
├── temnginx.yml
└── templates
     └── nginx.conf.j2

Playbook中template 的 算术运算

.j2文件

server {
   worker_connectios {{ ansible_processor_vcpus *2 }};
}

yml文件

- hosts: dns
  remote_user: root

  tasks:
    - template: src=/app/1.conf.j2 dest=/app/2.conf

生成的配置文件

server {
   worker_connectios 4;   > ansible_processor_vcpus=2,所以2*2=4
}

Playbook中template 的 for 循环

语法:

{% for i in nginx_vhosts %}
    语句块
{% endfor %}

{% if vhost.root is defined %}
root {{ vhost.root }};
{% endif %}

以实例来讲:

- hosts: web
  remote_user: root
  vars:
    prot_list:
      - 8080
      - 8081
      - 8082

  tasks:
    - name: create vhost
      template: src=/app/1.conf.j2 dest=/app/2.conf    > for 循环是写在.j2文件中的。

一般来讲是配合template模板来使用,文件的后缀名为.j2

先建立一个标准模板文件,这个文件需要跟dest使用的格式一致,只不过是将会变化的内容使用for循环的格式来写

{% for prot in prot_list %}
server {
        listen prot;
}

{% endfor %}

是不是与shell编程的for循环很像?

image

#ansible-playbook for1.yml
结果:
#cat /app/2.conf 

server {
    listen 8080;
}

server {
    listen 8081;
}

server {
    listen 8082;
}

再来一个多行多主机的模板:

- hosts: web
  remote_user: root
  vars:
    vhosts:
      - web:
        prot: 8080
        server: web1.hunk.tech
        root: /app/webroot1
      - web:
        prot: 8081
        server: web2.hunk.tech
        root: /app/webroot2
      - web:
        prot: 8082
        server: web2.hunk.tech
        root: /app/webroot2
  tasks:
    - name: create vhost
      template: src=/app/1.conf.j2 dest=/app/2.conf

j2文件:

{% for vhost in vhost_list %}
server {
        listen {{ vhost.prot }};
        servername {{ vhost.server }};
        root {{ vhost.root }};
}

{% endfor %}

执行:
#ansible-playbook for1.yml
结果:

#cat /app/2.conf 
server {
    listen 8080;
    servername web1.hunk.tech;
    root /app/webroot1;
}

server {
    listen 8081;
    servername web2.hunk.tech;
    root /app/webroot2;
}

server {
    listen 8082;
    servername web2.hunk.tech;
    root /app/webroot2;
}

Playbook中template 的 if 判断

语法:

{% if i is defined %}
        语句块
{% endif %}

playbook文件:

- hosts: web
  remote_user: root
  vars:
    vhost_list:
      - web1:                 
        prot: 8080
        name: web1.hunk.tech  > 注意,web1是定义了name的值
      - web2:                 > 注意,web2是没有定义了name的值
        prot: 8081

  tasks:
    - template: src=/app/1.conf.j2 dest=/app/2.conf

j2文件:

{% for vhost in vhost_list %}
server {
        listen {{ vhost.prot }};
{% if vhost.name is defined %}      > 如果vhost.name有应以,刚填入下面的内容
        servername {{ vhost.name }};
{% endif %}
}

{% endfor %

执行:
#ansible-playbook if.yml

结果:
#cat /app/2.conf 
server {
    listen 8080;
    servername web1.hunk.tech;
}

server {
    listen 8081;
}

roles

用于层次性、结构化地组织playbook。 roles能够根据层次型结构自动装载变量文件、tasks以及handlers等。

要使用roles只需要在playbook中使用import_tasks指令即可。(include也可以用,官方明确声明此命令将会淘汰)

简单来讲,roles就是通过分别将变量、文件、任务、模板及处理器放置于单独的目录中,并可以便捷地

include它们的一种机制。角色一般用于基于主机构建服务的场景中,但也可以是用于构建守护进程等场景中

 复杂场景:建议使用roles,代码复用度高

创建role的步骤

(1) 创建以roles命名的目录
(2) 在roles目录中分别创建以各角色名称命名的目录,如webservers等
(3) 在每个角色命名的目录中分别创建files、 handlers、meta、 tasks、 templates和vars目录;
    用不到的目录可以创建为空目录,也可以不创建
(4) 在playbook文件中,调用各自角色

建议的目录结构


├── roles                   > 必须是这个名字
│   ├── git                 > 具体项目名称
│   │   ├── default         > 设定默认变量时使用此目录中的main.yml文件
│   │   │   └── main.yml            > 至少应该包含一个名为main.yml的文件
│   │   ├── files           > 存放有copy或script模块等调用的文件
│   │   ├── handlers        > 定义触发器
│   │   │   └── main.yml            > 至少应该包含一个名为main.yml的文件
│   │   ├── meta            > 定义当前角色的特殊设定及其依赖关系
│   │   │   └── main.yml            > 至少应该包含一个名为main.yml的文件
│   │   ├── tasks           > 定义任务
│   │   │   └── main.yml            > 至少应该包含一个名为main.yml的文件
│   │   ├── templates       > template模块查找所需要模板文件目录
│   │   │   └── main.yml            > 至少应该包含一个名为main.yml的文件
│   │   └── vars            > 定义变量;;其他的文件需要在此文件中通过include进行包含
│   │       └── main.yml            > 至少应该包含一个名为main.yml的文件

还是拿一个实例来说:

如果要在一台初始化的主机上面安装httpd服务,有以下过程:(这里不考虑编译安装情况,假设yum脚本里不会创建组和用户)

1.创建用于httpd服务的组
2.创建用于httpd服务的用户
3.安装httpd软件包
4.启动httpd服务

把这些过程体现在ansible上面就是对应的具体的tasks,因此,将需要在roles/tasks/下面创建分别用于这些过程的独立yml文件

1.创建用于httpd服务的
#vim groupadd.yml
- name: groupadd apache
  group: name=apache 

2.创建用于httpd服务的用户
#vim useradd.yml
- name: useradd apache
  user: name=apache group=apache shell=/sbin/nologin system=yes

3.安装httpd软件包
#vim install_httpd.yml
- name: yum install httpd
  yum: name=httpd

4.启动httpd服务
#vim start_httpd.yml
- name: start httpd
  service: name=httpd state=started

每个具体的小任务有了,那么就得有一个主的配置文件,默认剧本就会读取它,从而确定其他任务的关系。

注意,文件名必须是main.yml

注意,顺序不能颠倒,步骤是从上而下顺序执行,就像编排电影剧本一样。有没有当导演的感觉?
#vim main.yml
- import_tasks: groupadd.yml
- import_tasks: useradd.yml
- import_tasks: install_httpd.yml
- import_tasks: start_httpd.yml

最后,创建一个执行的playbook文件,这个文件与roles目录是同级目录。

#vim httpd_roles.yml
---
- hosts: web
  remote_user: root

  roles:
    - httpd              > 注意,这个- 后面跟就是roles目录下的子目录名称

当然,也可以写成
  roles:
    - role: httpd        > 注意,这个- role: 是不可以改变名称的,后面跟就是roles目录下的子目录名称

image

至此,此时的目录结构为:

├── httpd_roles.yml                 > 执行的playbook文件
└── roles                           > roles角色目录
    ├── httpd                       > 项目文件夹
    │   ├── default
    │   ├── files
    │   ├── handlers
    │   ├── meta
    │   ├── tasks                   > 任务文件夹
    │   │   ├── groupadd.yml
    │   │   ├── install_httpd.yml
    │   │   ├── main.yml
    │   │   ├── start_httpd.yml
    │   │   └── useradd.yml
    │   ├── templates
    │   └── vars
#ansible web -m shell -a 'ss -nlt|grep 80'
6-web-1.hunk.tech | SUCCESS | rc=0 >>
LISTEN     0      128         :::80
7-web-2.hunk.tech | SUCCESS | rc=0 >>
LISTEN     0      128         :::80
7-web-0.hunk.tech | SUCCESS | rc=0 >>
LISTEN     0      128         :::80

调用其他任务

为了更好的复用代码,可以将一些公共的代码集中在一个目录里,按需要在以后的roles进行调用

├── pubtasks
│   └── create_nginx_user_and_group.yml
#cat create_nginx_user.yml
- name: create nginx group
  group: name=nginx

- name: create nginx user
  user: name=nginx shell=/usr/sbin/nologin comment="nginx service" group=nginx createhome=no

调用的时候,在tasks中引入即可

- hosts: dns
  remote_user: root

  tasks:
    - name: test
      import_tasks: /app/yml/pubtasks/create_nginx_user_and_group.yml

同理,在别的文件中引入import_role也是可以的。

roles中的tags

前面的章节针对tasks有tags,而roles中也可以运用此方法

- hosts: web
  remote_user: root

  roles:
    - { role: httpd, tags: [ 'web', 'apache22' ] }  > 其实,这是字典的一种写法,前面章节有讲到,只不过是写在一行里了。

所以,下面的写法是同等的
  roles:
    - role: httpd
      tags: 
        - web
        - apache22

基于条件判断的roles的tags

- { role: nginx, tags: [ 'nginx', 'web' ], when: ansible_distribution_major_version == "6" }

当roles中有了tags后,执行时候加上-t tags名称即可

# ansible-playbook  httpd_roles.yml -t web
# ansible-playbook  httpd_roles.yml -t apache22

也可以同时调用多个tags
# ansible-playbook  httpd_roles.yml -t "web,db"
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值