Present By MagnumLuke_刘梦龙

1. Ansible 简介

Ansible 是一个 IT 自动化工具 , 他能配置系统 , 部署软件 , 编排更复杂的 IT 任务 , 如连续部署或零停机时间滚动更新

Ansible 用 Python 编写 , Ansible 允许不需要在每个节点上安装自己的组件 , 可以在不止一个地方控制整个基础架构

2. 工作原理

ansible 可以连接本机 执行命令吗_python

  • Inventory 被管理节点的列表 , 资源池
  • PlayBook 类似于 Shell 脚本 , 通过一系列命令执行自动化操作
  • Module

在 Ansible 管理体系中 , 存在 管理节点被管理节点 两种角色

被管理节点通常被成为资产

在管理节点上 , Ansible 将 AdHoc 或 PlayBook 转换为 python脚本 , 并通过 SSH 传输到被管理节点上

在被管理节点上依次执行 Python 脚本

3. Ansible 安装

3.1 先决条件

ansible 可以连接本机 执行命令吗_python_02

3.2 安装 Ansible

  • yum方式
[root@localhost ~]# yum install epel-release
[root@localhost ~]# yum install ansible
  • pip方式
[root@localhost ~]# yum install epel-release
[root@localhost ~]# yum install python2-pip
[root@localhost ~]# pip install ansible
  • 查看Ansible版本
[root@localhost ~]# ansible --version
ansible 2.9.27
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Oct 14 2020, 14:45:30) [GCC 4.8.5 20150623 (Red Hat 4.8.5-44)]

4. Ansible 简单部署

4.1 管理节点与被管理节点建立SSH信任关系

管理节点 (ansible) 中创建密钥对

[root@localhost ~]# ssh-keygen -t rsa				# 回车

将本地的公钥传输到被管理节点

[root@localhost .ssh]# ssh-copy-id root@192.168.200.135
[root@localhost .ssh]# ssh-copy-id root@192.168.200.136

4.2 场景

4.2.1 检测所有被管理主机在线状态

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

-i : 参数后面是一个列表(List) , 因此当为一个被管理节点时 , 我们后面一定要加一个英文逗号(,) , 告知是List

4.2.2 确保/tmp/a.conf发布到所有被管理节点上

[root@manager ~]# touch /tmp/a.conf
[root@manager ~]# ansible all -i 192.168.19.135,192.168.19.136 -m copy -a "src=/tmp/a.conf dest=/tmp/a.conf"
192.168.19.136 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/a.conf",
    "gid": 0,
    "group": "root",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0644",
    "owner": "root",
    "secontext": "unconfined_u:object_r:admin_home_t:s0",
    "size": 0,
    "src": "/root/.ansible/tmp/ansible-tmp-1702535672.46-6415-225212235428808/source",
    "state": "file",
    "uid": 0
}
192.168.19.135 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
    "dest": "/tmp/a.conf",
    "gid": 0,
    "group": "root",
    "md5sum": "d41d8cd98f00b204e9800998ecf8427e",
    "mode": "0644",
    "owner": "root",
    "secontext": "unconfined_u:object_r:admin_home_t:s0",
    "size": 0,
    "src": "/root/.ansible/tmp/ansible-tmp-1702535672.44-6413-170350629067151/source",
    "state": "file",
    "uid": 0
}

all : 在 Ansible 中 , 叫做 pattern , 即匹配 , 用来在 -i 参数的资产中匹配一部分 , all 代表匹配所有

-i : 指定Ansible的资产 , 即被管理主机 (也可以是文件名)

-m : 指定要运行的模板 , 比如这里的 ping模块 和 copy模块

-a : 指定模块的参数 , 这里ping模块没有指定参数 , copy模块指定了 src 和 dest 参数

5. Ansible 资产

实际场景中 , 被管理的服务器肯定不止2台 , 因此我们可以将被管理服务器写入一个文件或脚本中

5.1 静态资产

顾名思义 , 静态资产本身是一个文本文件 , 一个类似INI的文件

默认情况下 , Ansible的资产文件 位于 /etc/ansible/hosts . pip安装的可能没有这个文件 , 创建一个即可

示例

# This is the default ansible 'hosts' file.
#
# It should live in /etc/ansible/hosts
#
#   - Comments begin with the '#' character
#   - Blank lines are ignored
#   - Groups of hosts are delimited by [header] elements
#   - You can enter hostnames or ip addresses
#   - A hostname/ip can be a member of multiple groups

`Ex 1: 不属于任何组的单独主机 , 必须放在所有主机组的前面 , 也就是必须位于文件最前方
green.example.com
blue.example.com
192.168.100.1
192.168.100.10

`Ex 2: 主机组 webservers 包含4个主机 , 调用时可以通过组名匹配主机

[webservers]
alpha.example.org
beta.example.org
192.168.1.100
192.168.1.110

`有规律的主机可以使用这总方法显示 , 这代表 www001.example.com , www002.example.com , ...
www[001:006].example.com

`Ex 3: 主机组可以在 组和主机中间添加空格以便阅读

[dbservers]

db01.intranet.mydomain.net
db02.intranet.mydomain.net
10.25.1.56
10.25.1.57

# Here's another example of host ranges, this time there are no
# leading 0s:

## db-[99:101]-node.example.com

5.1.1 自定义资产

vim /etc/ansible/inventory.ini

# 没有组名的属于 all 组
1.1.1.1
2.2.2.2
3.3.3.[1:15]
test01.lml.com
test03.lml.com
test[05:09].lml.com

[web_servers]
192.168.1.2
192.168.1.3
192.168.1.5

[db_servers]
192.168.2.2
192.168.2.3
192.168.2.5
192.168.1.5

# 创建 all_server组 , 并将 db_servers 和 web_servers 添加为自己的子组 
# 选择 all_server组时 , 则代表选中了 web_servers 和 db_servers 组下的所有主机
[all_servers]
[all_servers:children]
db_servers
web_servers
  1. Ansible 的资产文件中 , 可以以IP地址的形式或者主机名的形式存在
  2. Ansible 的资产若连续 , 可以使用 [start:end] 的形式去表达
  3. 可以将服务器按照业务场景定义成组 , 比如 db_servers 和 web_servers
  4. 组合组之间可以存在继承关系 , 比如 db_servers 和 web_servers 同时继承 all_servers组

5.1.2 如何使用自定义资产

通过 -i 参数指定自定义资产的位置即可 (可以是全路径 , 也可以是相对路径)

# ansible all -i inventory.ini .........				//伪命令 , 不可执行

5.1.3 验证自定义资产

  • 列举出所有资产
ansible all -i inventory.ini --list-hosts
	hosts (19) :
	1.1.1.1
	2.2.2.2
	3.3.3.1
	......
  • 列举出选定资产 (比如db_servers)
ansible db_servers -i inventory.ini --list-hosts

5.2 资产选择器

本处使用 5.1.1 的资产进行实验

5.2.1 基本语法格式

ansible PATTERN -i inventory -m moudle -a argument

PATTERN : 可以是IP列表 , 也可以是组名 , 值得注意的是 , 资产中写的什么就要写什么 , 不能通过域名解析

-i : 资产列表 , 从哪里进行筛选

-m : 使用哪一个模块

-a : 模块所对应的参数

5.2.1.1 选择一台或几台服务器
[root@localhost ansible]# ansible 1.1.1.1 -i inventory.ini --list-host
  hosts (1):
    1.1.1.1
[root@localhost ansible]# ansible test01.lml.com -i inventory.ini --list-host
  hosts (1):
    test01.lml.com
[root@localhost ansible]# ansible 1.1.1.1,2.2.2.2 -i inventory.ini --list-host
    hosts (2):
    1.1.1.1
    2.2.2.2
5.2.1.2 选择一组服务器
[root@localhost ~]# ansible all_servers -i inventory.ini --list-host
  hosts (6):
    192.168.2.2
    192.168.2.3
    192.168.2.5
    192.168.1.2
    192.168.1.3
    192.168.1.5
# 两个子组的重复部分会被去重
5.3.1.3 使用*匹配
[root@localhost ansible]# ansible 3.3.3.1* -i inventory.ini --list-host
  hosts (7):
    3.3.3.10
    3.3.3.11
    3.3.3.12
    3.3.3.13
    3.3.3.14
    3.3.3.15
    3.3.3.1
5.3.1.4 使用逻辑进行匹配
5.3.1.4.1 并集(两个组中所有主机 , 去重)
# 显示两个组中所有的主机
[root@localhost ansible]# ansible 'web_servers:db_servers' -i inventory.ini --list-host
  hosts (6):
    192.168.1.2
    192.168.1.3
    192.168.1.5
    192.168.2.2
    192.168.2.3
    192.168.2.5
5.3.1.4.2 交集(两个组中共有主机)
# 显示同时承担web_server和db_server的主机
[root@localhost ansible]# ansible 'web_servers:&db_servers' -i inventory.ini --list-host
  hosts (1):
    192.168.1.5
5.3.1.4.3 差集 (A有B没有 , 或 B有A没有)
# 在 web_servers中 , 但是不在 db_servers中
[root@localhost ansible]# ansible 'web_servers:!db_servers' -i inventory.ini --list-host
  hosts (2):
    192.168.1.2
    192.168.1.3
# 在 db_servers中 , 但是不再 web_servers中
[root@localhost ansible]# ansible 'db_servers:!web_servers' -i inventory.ini --list-host
  hosts (3):
    192.168.2.2
    192.168.2.3
    192.168.2.5

6. Ansible Ad-Hoc Command

Ad-hoc 命令是一个概念性的名字 , 是相对于 Ansible playbook 来说的 , 类似于在命令行敲入shell 和写 shell scripts 两者之间的关系 , 可以用于执行一些临时命令

如果我们敲入一些命令去比较快的完成一些事情 , 而不需要将这些执行的命令特别保存下载 , 这样的命令就叫做 ad-hoc 命令

Ansible 提供两种方式去完成任务 ,一是 ad-hoc , 一是写 Ansible playbook (高级课程中讲) , 前者可以解决一些简单的任务 , 后者解决较复杂的任务 , 比如做配置管理或部署

6.1 命令格式

命令格式语法如下 :

ansible pattern [-i inventory] -m moudule -a argument

pattern : 资产选择器

-i : 指定资产清单文件的位置

-m : 指定本次 Ansible ad-hoc 要执行的模块 , 可以类别成 shell 中的命令

-a : 模块的参数 , 可以类比成 SHELL 中的命令参数

6.2 模块分类

Ansible 模块分为三种类型 : 核心模块(core module) , 附加模块(extra module) , 用户自定义模块(consume module)

核心模块 : 由 Ansible 的官方团队提供的

附加模块 : 由各个社区提供的 , 如 OPENSTACK , DOCKER 社区等

用户自定义模块 : 当核心模块和附加模块都无法满足需求时 , 用户可以自定义模块

6.3 联机帮助

Ansible 的核心模块和附加模块 , 数量有1000+ , 很难一次性掌握 , 因此我们可以使用 Ansible 的帮助文档 , 它本身提供的命令就是由 Ansible-doc 实现

  • 列出所有的核心模块和附加模块
# ansible-doc -l
  • 查询某个模块的使用方法
# ansible-doc modulename

6.4 常用模块

为方便演示 , 我们使用如下配置文件

管理机 : 192.168.200.132

被管理机1 : 192.168.200.133

被管理机2 : 192.168.200.134

编写如下配置文件到 /etc/ansible/hosts

[dbservers] 192.168.200.133 [webservers] 192.168.200.134

6.4.1 command & shell 模块

两个模块都是在远程服务器上执行命令

command模块 : 是 ad-hoc 的默认模块 , 在执行 ad-hoc 时 , 若不指定模块的名字则默认使用此模块

[root@localhost ansible]# ansible all -i hosts -a "echo 'hello world'"
192.168.200.133 | CHANGED | rc=0 >>
hello world
192.168.200.134 | CHANGED | rc=0 >>
hello world

[root@localhost ansible]# ansible all -i hosts -m shell -a "echo 'hello world'"
192.168.200.133 | CHANGED | rc=0 >>
hello world
192.168.200.134 | CHANGED | rc=0 >>
hello world

command模块 和 shell模块的差异

  • shell 模块可以执行 SHELL 的内置命令和特性 (比如管道符)
  • command 模块无法执行 SHELL 的内置命令和特性

Example

[root@localhost ansible]# ansible all -i hosts -m shell -a "echo 'hello' | grep -o 'e'"
192.168.200.133 | CHANGED | rc=0 >>
e
192.168.200.134 | CHANGED | rc=0 >>
e
[root@localhost ansible]# ansible all -i hosts -m command -a "echo 'hello' | grep -o 'e'"
192.168.200.133 | CHANGED | rc=0 >>
hello | grep -o e
192.168.200.134 | CHANGED | rc=0 >>
hello | grep -o e

6.4.2 script 模块

将管理节点上的脚本传到被管理节点(远程服务器)上进行执行 , 理论上此模块的执行完全不需要被管理服务器上有Python

Example

[root@localhost ansible]# echo "touch /tmp/testfile" > /root/a.sh
[root@localhost ansible]# ansible webservers -i hosts -m script -a "/root/a.sh"
192.168.200.134 | CHANGED => {
    "changed": true,
    "rc": 0,
    "stderr": "Shared connection to 192.168.200.134 closed.\r\n",
    "stderr_lines": [
        "Shared connection to 192.168.200.134 closed."
    ],
    "stdout": "",
    "stdout_lines": []
}
[root@localhost ansible]# ansible webservers -i hosts -m shell -a "ls -l /tmp"
192.168.200.134 | CHANGED | rc=0 >>
-rw-r--r--  1 root root      0 Dec 18 21:23 testfile

6.4.3 copy 模块

copy 模块主要用于管理节点和被管理节点之间的文件拷贝 , 经常用到如下参数 :

  • src 指定拷贝文件的源地址
  • dest 指定考本文件的目标地址
  • backup 拷贝文件前 , 若目标的原始文件发生变化 , 则对目标文件进行备份
  • woner 指定新拷贝文件的所有者
  • group 指定新拷贝文件的所有组
  • mode 指定新拷贝文件的权限

Example : 复制本机/etc/hosts文件到被管理机的opt文件下

[root@localhost ansible]# ansible webservers -i hosts -m copy -a "src=/etc/hosts dest=/opt"
192.168.200.134 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7335999eb54c15c67566186bdfc46f64e0d5a1aa",
    "dest": "/opt/hosts",
    "gid": 0,
    "group": "root",
    "md5sum": "54fb6627dbaa37721048e4549db3224d",
    "mode": "0644",
    "owner": "root",
    "size": 158,
    "src": "/root/.ansible/tmp/ansible-tmp-1702964365.0-3932-260266444652585/source",
    "state": "file",
    "uid": 0
}
[root@localhost ansible]# ansible webservers -i hosts -m shell -a "ls -l /opt"
192.168.200.134 | CHANGED | rc=0 >>
total 4
-rw-r--r--  1 root root 158 Dec 18 21:39 hosts
drwxr-xr-x. 2 root root   6 Oct 30  2018 rh

Example : 复制本机/etc/hosts文件到被管理机的opt文件下 并根据情况备份文件(文件名+日期)

[root@localhost ansible]# cp /etc/hosts /root/
[root@localhost ansible]# echo "hello" >> /root/hosts
[root@localhost ansible]# ansible webservers -i hosts -m copy -a "src=/root/hosts dest=/opt/hosts backup=yes"
192.168.200.134 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "backup_file": "/opt/hosts.4037.2023-12-18@21:46:21~",
    "changed": true,
    "checksum": "4b5186e9002f17136d8d0d2c9103604584d910a1",
    "dest": "/opt/hosts",
    "gid": 0,
    "group": "root",
    "md5sum": "8c096180053f7191ea6750ef94192e20",
    "mode": "0644",
    "owner": "root",
    "size": 164,
    "src": "/root/.ansible/tmp/ansible-tmp-1702964781.37-4092-35134535372962/source",
    "state": "file",
    "uid": 0
}
[root@localhost ansible]# ansible webservers -i hosts -m shell -a "ls -l /opt"
192.168.200.134 | CHANGED | rc=0 >>
total 8
-rw-r--r--  1 root root 164 Dec 18 21:46 hosts
-rw-r--r--  1 root root 158 Dec 18 21:39 hosts.4037.2023-12-18@21:46:21~
drwxr-xr-x. 2 root root   6 Oct 30  2018 rh

Example : 复制本机/etc/hosts文件到被管理机的opt文件下 同时修改文件的用户及用户组)

[root@localhost ansible]# ansible all -i hosts -m copy -a "src=/etc/hosts dest=/opt owner=nobody group=nobody"
192.168.200.134 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7335999eb54c15c67566186bdfc46f64e0d5a1aa",
    "dest": "/opt/hosts",
    "gid": 99,
    "group": "nobody",
    "md5sum": "54fb6627dbaa37721048e4549db3224d",
    "mode": "0644",
    "owner": "nobody",
    "size": 158,
    "src": "/root/.ansible/tmp/ansible-tmp-1702972402.26-5524-56470963650752/source",
    "state": "file",
    "uid": 99
}
192.168.200.133 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7335999eb54c15c67566186bdfc46f64e0d5a1aa",
    "dest": "/opt/hosts",
    "gid": 99,
    "group": "nobody",
    "md5sum": "54fb6627dbaa37721048e4549db3224d",
    "mode": "0644",
    "owner": "nobody",
    "size": 158,
    "src": "/root/.ansible/tmp/ansible-tmp-1702972402.26-5526-144681124195698/source",
    "state": "file",
    "uid": 99
}
[root@localhost ansible]# ansible all -i hosts -m shell -a "ls -l /opt"
192.168.200.133 | CHANGED | rc=0 >>
total 4
-rw-r--r--  1 nobody nobody 158 Dec 18 23:53 hosts
drwxr-xr-x. 2 root   root     6 Oct 30  2018 rh
192.168.200.134 | CHANGED | rc=0 >>
total 8
-rw-r--r--  1 nobody nobody 158 Dec 18 23:53 hosts
-rw-r--r--  1 root   root   158 Dec 18 21:39 hosts.4037.2023-12-18@21:46:21~
drwxr-xr-x. 2 root   root     6 Oct 30  2018 rh

Example : 复制本机/etc/hosts文件到被管理机的opt文件下 同时对文件的权限进行设置

[root@localhost ansible]# ansible webservers -i hosts -m copy -a "src=/etc/hosts dest=/opt/hosts mode=0755"
192.168.200.134 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "checksum": "7335999eb54c15c67566186bdfc46f64e0d5a1aa",
    "dest": "/opt/hosts",
    "gid": 99,
    "group": "nobody",
    "mode": "0755",
    "owner": "nobody",
    "path": "/opt/hosts",
    "size": 158,
    "state": "file",
    "uid": 99
}
[root@localhost ansible]# ansible webservers -i hosts -m shell -a "ls -l /opt"
192.168.200.134 | CHANGED | rc=0 >>
total 8
-rwxr-xr-x  1 nobody nobody 158 Dec 18 23:53 hosts
-rw-r--r--  1 root   root   158 Dec 18 21:39 hosts.4037.2023-12-18@21:46:21~
drwxr-xr-x. 2 root   root     6 Oct 30  2018 rh

6.4.4 yum_repsitory 模块

注意 : repo 必须是通过 Ansible 创建的才能进行管理

Yum配置文件如下

[base]
name=CentOS-$releasever - Base
mirrorlist=http://mirrorlist.centos.org/?release=$releasever&arch=$basearch&repo=os&infra=$infra
#baseurl=http://mirror.centos.org/centos/$releasever/os/$basearch/
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7

Ansible 添加 YUM 仓库 , 常用参数如下 :

  • name 仓库名称 , 就是仓库文件中第一行中括号中名称 , 必须的参数
  • description 仓库描述信息 , 添加时必须的参数
  • baseurl yum 存储库 "repodata"目录所在目录的 RUL , 添加时必须的参数 , (它可以是多个URL的列表)
  • file 仓库文件保存到本地的文件名 , (不包含.repo) , 默认是 name 的值
  • state preset 确认添加仓库文件 , absent 确认删除仓库文件
  • gpgcheck 是否检查 GPG yes|no , 没有默认值 , 使用 /etc/yum.conf 中的配置
6.4.4.1 Example : 给 dbservers 添加 epel 源
[root@localhost ansible]# ansible dbservers -i hosts -m yum_repository -a "name=epel baseurl='http://download.fedoraproject.org/pub/epel/7/$basearch' description='EPEL YUM repo'"
192.168.200.133 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "epel",
    "state": "present"
}

[root@localhost ansible]# ansible dbservers -i hosts -m shell -a "cat /etc/yum.repos.d/epel.repo"
192.168.200.133 | CHANGED | rc=0 >>
[epel]
baseurl = http://download.fedoraproject.org/pub/epel/7/
name = EPEL YUM repo
6.4.4.2 Example : 给 dbservers 删除 epel 源
[root@localhost ansible]# ansible dbservers -i hosts -m yum_repository -a "name=epel state=absent"
192.168.200.133 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "repo": "epel",
    "state": "absent"
}

[root@localhost ansible]# ansible dbservers -i hosts -m shell -a "cat /etc/yum.repos.d/epel.repo"
192.168.200.133 | FAILED | rc=1 >>
cat: /etc/yum.repos.d/epel.repo: No such file or directorynon-zero return code

6.4.5 yum 模块

等同于 Linux 上的 yum 命令 , 对远程服务器上 RPM 包进行管理 , 常用参数如下

  • name 要安装的软件包名 , 多个软件包以 ( , ) 隔开
  • state 对当前指定的软件安装 , 移除操作 (present installed latest absent removed)
  • present 确认已经安装 , 但不升级
  • installed 确认已经安装
  • latest 确保安装 , 且升级为最新
  • absent 和 removed 确认已删除
6.4.5.1 安装一个软件包
[root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=present"

[root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=installed"

[root@localhost ansible]# ansible webservers -i hosts -m yum -a "name=nginx state=latest"

# 检查是否安装
[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m shell -a "nginx -version"
192.168.200.134 | CHANGED | rc=0 >>
nginx version: nginx/1.20.1
6.4.5.2 移除一个软件包
[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m yum -a "name=nginx state=absent"
[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m yum -a "name=nginx state=removed"

[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m shell -a "nginx -version"
192.168.200.134 | FAILED | rc=127 >>
/bin/sh: nginx: command not foundnon-zero return code
6.4.5.3 安装一个软件包组
[root@manager ~]# ansible all -i /etc/ansible/hosts -m yum -a "name='@Developmenttools' state=present"

6.4.6 systemd 模块

CentOS6 之前的版本使用 service 模块 , 使用 ansible-doc service 命令查看帮助信息

管理远程节点上的 systemd 服务 , 就是由 systemd 所管理的服务 , 常用参数如下

  • daemon_reload 重新载入 systemd , 扫描新的或有变动的单元 , 值为yes
  • enabled 是否开机自启动 yes | no
  • name 必选项 , 服务名称 , 比如 httpd , vsftpd 等
  • state 对当前服务执行启动 , 停止 , 重新加载等操作 (started , stopped , restarted , reloaded)
6.4.6.1 Example : 重新加载 systemd
[root@localhost ~]# ansible webservers -i /etc/ansible/hosts -m systemd -a "daemon_reload=yes"
192.168.200.134 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": false,
    "name": null,
    "status": {}
}
6.4.6.2 Example : 启动 nginx
[root@localhost ~]# ansible  webservers -i  /etc/ansible/hosts -m systemd -a "name=nginx state=started"
6.4.6.3 Example : 关闭 nginx
[root@localhost ~]# ansible  webservers -i  /etc/ansible/hosts -m systemd -a "name=nginx state=stopped"

6.4.7 group模块

对被管理节点的组进行管理 , 常用参数如下

  • name 组名称 , 必须的
  • system 是否为系统组 , yes | no

`对被管理节点的用户进行管理 , 常用参数如下

6.4.7.1 Example : 创建普通组 db_admin
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m group -a "name=db_admin"
192.168.200.133 | CHANGED => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/bin/python"
    },
    "changed": true,
    "gid": 1001,
    "name": "db_admin",
    "state": "present",
    "system": false
}

6.4.8 user模块

用于对被管理节点上的用户进行管理 , 常用参数如下 :

  • name 必须的参数 , 指定用户名
  • password 设置用户的密码 , 这里接受的是一个加密的值 , 因为会直接存到 shadow , 默认不设置密码
  • update_password 加入设置的密码不同于原密码 , 则会更新密码 , 在1.3版本中被加入
  • home 指定用户的家目录
  • shell 设置用户的shell
  • comment 用户的描述信息
  • create_home 在创建用户时 , 是否创建其家目录 , 默认创建 , 加入不创建 , 设置为 no , 在2.5版本之前使用 createhome参数
  • group 设置用户的主组
  • groups 将用户加入到多个其他组中 , 多个用逗号隔开 , 默认会把用户从其他已经加入的组中删除
  • append 值为 yes | no , 和 groups 配合使用 , 值为yes时 , 不会把用户从其他已经加入的组中删除
  • system 值为 yes 时 , 会创建一个系统账户
  • expires 设置用户的过期时间 , 值为时间戳 , 会转为天数后 , 放在 shadow 的第8个字段里
  • generate_ssh_key 设置为yes时 , 会为用户生成密钥 , 这不会覆盖原来的密钥
  • ssh_key_type 指定用户的密钥类型 , 默认rsa , 具体的类型取决于被管理节点
  • state 删除或添加用户 , yes 为添加 , absent 为删除 , 默认值为 yes
  • remove 当与 state=absent 一起使用时 , 删除一个用户及关联的目录 , 比如家目录 , 邮箱目录 , 可选的值为 yes/no

先 生成加密密码/

[root@localhost ~]# pass=$(echo “LML123456” | openssl passwd -l -stdin)

[root@localhost ~]# echo $pass
ansible 可以连接本机 执行命令吗_运维_03MQBJpWBh$krH/eQgBQcMpyDMKxVpS.0

6.4.8.1 Example : 创建账号,设置密码
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=foo password=${pass}"
6.4.8.2 Example : 创建账号,设置密码,改密钥类型
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=Dragon password=${pass} ssh_key_type=ecdsa"
6.4.8.3 Example : 创建账号,设置密码,有效期,加组

创建Cai用户 , 并设置其有效期到2024_4_15日 , 加入到db_admin组中

[root@localhost ~]# ansible all -i /etc/ansible/hosts -m user -a "name=Cai password=${pass} group=db_admin append=yes"

6.4.9 file 模块

file模块主要用于远程主机上的文件操作 , 常用参数

  • group 定义文件/目录的属组
  • mode 定义文件/目录的而权限
  • owner 定义文件/目录属组
  • path 必选项 , 操作文件/目录 的路径
  • recurse 递归的设置文件的属性 , 只对目录有效
  • src 要被链接(软/硬)的源文件的路径 , 值用于 state=link 的情况
  • dest 被链接到的路径 , 只应用于 state=link 的情况
  • state 对文件执行什么操作 ?
  • directory 如果目录不存在则创建目录
  • file 文件不存在则不不会被创建 , 存在则返回文件的信息 , 常用于检查文件是否存在
  • link 创建软连接
  • hard 创建硬链接
  • touch 如果文件不存在 , 则会创建一个新的文件 , 如果文件或目录存在 , 则更新其最后修改时间
  • absent 删除目录 , 文件或者取消链接
6.4.9.1 Example : 文件操作
# 创建一个文件
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf state=touch"

# 改变文件所有者及权限
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf owner=nobody group=nobody mode=0644"

# 创建一个软连接
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "src=/tmp/foo.conf dest=/tmp/link.conf state=link"

# 创建一个目录
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/test_dir state=directory"

# 取消一个链接
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/link.conf state=absent"

# 删除一个文件
[root@localhost ~]# ansible all -i /etc/ansible/hosts -m file -a "path=/tmp/foo.conf state=absent"

6.4.10 cron模块

管理远程节点的 CRON 服务 , 等同于 Linux 中的计划任务 , 常用参数如下

注意 : 使用 Ansible 创建的计划任务 , 是不能使用本地 crontab -e 去编辑 , 否则 Ansible 无法再次操作此计划任务

  • name 指定一个 cron job 的名字 , 一定要指定 , 便于日后删除
  • minute 指定分钟 , 可以设置成(0-59 , * , /2 等)格式 , 默认是 * , 也就是每分钟
  • hour 指定小时 , 可以设置达成(0-23 , * , */2等)格式 , 默认是 * , 也就是每小时
  • day 指定天 , 可以设置成(1-31 , * , */2 等)格式 , 默认是 * , 也就是每天
  • month 指定月份 , 可以设置成(1-12 , * , */2 等)格式 , 默认是 * , 也就是每周
  • weekday 指定星期 , 可以设置成(0-6 for Sunday-Saturday , * 等)格式 , 默认是* , 也就是每天
  • job 要执行的内容 , 通常可以写个脚本 , 或者一段内容
  • state 指定这个 job 的状态 , 可以是新增(present) , 或者是删除(absent) , 默认为(present)
6.4.10.1 Example 1 : 创建一个 cron job 任务
[root@manager ~]# ansible all -i /etc/ansible/hosts -m cron -a "name=newJob minute='0' job='ls -alh > /dev/null'"

验证

[root@host1 ~]# crontab -l
#Ansible: newJob
0 * * * * ls -alh > /dev/null
6.4.10.2 Example 2 : 删除一个 cron job 任务
[root@manager ~]# ansible all -i /etc/ansible/hosts -m cron -a "name=newJob state=absent"

6.4.11 debug模块

6.4.11.1 Example 1 : 打印变量

debug 模块主要用于调试时使用 , 通常的作用是将一个变量的值打印出来 , 常用参数如下

  • var 直接打印一个指定的变量值
  • msg 打印一段可以格式化的字符串
# 直接打印一个变量 (-e 是传入变量)
[root@manager ~]# ansible all -i /etc/ansible/hosts -m debug -a "var=role" -e "role=web"
# 打印带字符串的 变量
[root@manager ~]# ansible all -i /etc/ansible/hosts -m debug -a "msg='role is {{role}}'" -e "role=web"

6.4.12 template 模块

template 模块使用了 jinjia2 格式作文文件模板 , 可以进行文档内变量的替换

它的每次使用都会被 ansible 标记为 “changed” 状态 , 文件以 .j2 结尾 , 常用参数如下

  • src 指定 Ansible 控制端的文件路径
  • dest 指定 Ansible 被控制端的文件路径
  • owner 指定文件的属主
  • group 指定文件的属组
  • mode 指定文件的权限
  • backup 创建一个包含时间错信息的备份文件 , 这样如果以某种方式损坏了原始文件 , 就可以将其复原 , yes/no
6.4.12.1 Example 1 : 传递文件(带变量)
# 1. 创建一个 hello_world.j2 文件
[root@manager ~]# cat hello_world.j2
Hello {{var}} !

# 2. 执行命令 , 并且设置变量 var 的值为 world
[root@manager ~]# ansible all -i /etc/ansible/hosts -m template -a "src=hello_world.j2 dest=/tmp/hello_world.world" -e "var=world"

# 3. 在被控主机上验证
Hello world !

6.4.13 lineinfile 模块

在被管理节点上 , 使用正则匹配的方式对目标文件的一行内容修改删除等操作

如果是一个文件中把所有匹配到的多行进行统一处理 , 参考 replace模块

如果相对一个文件进行一次性 添加/更新/删除等多行内容操作 , 参考 blockinfile模块

常用参数如下

  • path 目标文件路径 , 必须的
  • state 可选值 present替换(默认 | absent删除
  • regexp 在文件的每一行中查找的正则表达式 , 对于 state=present , 进找到的最后一行将被替代
  • line 要在文件中插入/替换的行 , 需要 state=present
  • create 文件不存在时 , 是否要创建文件并添加内容 , yes/no
6.4.13.1 Example 1 : 删除被控节点文件里的某一条内容
[root@manager ~]# ansible all -i /etc/ansible/hosts -m lineinfile -a "path=/etc/sudoers regexp='^%wheel' state=absent"
6.4.13.2 Example 1 : 关闭远程主机的selinux
[root@manager ~]# ansible all -i /etc/ansible/hosts -m lineinfile -a "path=/etc/selinux/config regexp='^SELINUX=' line='SELINUX=disabled' state=present"

7. Ansible-Playbook

**Ad-Hoc 的问题 **: AD-HOC每次只能在被管理节点上执行简单的命令

而日常工作中 , 我们往往面临的是一系列复杂的操作 , 例如我们可能需要安装软件 , 更新配置 , 重启服务等等一系列操作的组合 , 此时 Ad-Hoc 有些力不从心 , 使用 PLAYBOOK 可以帮助我们解决这个问题

7.1 什么是 PalyBook

Playbook 通常被大家翻译成剧本

可以认为它是 Ansible 自定义的一门语言(可以将 Playbook 比作 Linux 中的 shell) , 而 Ansible 中的 Module 可以比作为 Linux 中的各种命令

7.2 YAML 学习

安装 pip 对 yaml 文件格式进行检查

yum install python2-pip

7.2.1 YAML特点

# YAML 文件以 # 为注释
# YAML 文件以 .yml 或 .yaml结尾
# YAML 文件以 ---开始 , 以...结束 , 但是开始和结束标志都是可选的

7.2.2 基本语法

  • 大小写敏感
  • 使用缩进表示层级关系 , 一般规范2个空格
  • 缩进时使用 tab 键 , 还是空格 一定要达到统一 , 建议使用空格
  • 相同层级的元素必须左侧对齐

YAML 支持的数据结构有三种

  • 字符串
  • 列表
  • 字典
7.2.2.1 Example 1 : 字符串
---
# YAML 中的字符串可以不使用引号 , 即使里面存在空格的时候 , 当然使用单引号即双引号也没有错
this is a string
'this is a string'
"this is a string"
# YAML 中若一行写不下表达的内容 , 可以使用下面两种方法进行折行
long_line: |
	Example 1
	Example 2
	Example 3
#或
long_line: >
	Example 1
	Example 2
	Example 3
...
7.2.2.2 Example 2 : 列表
---
# 相当于 C语言 中的数组 
# 如何定义 : 以短横线开头 + 空格 + 具体的值
- red
- green
- blue

# 以上的值转为 python 的 List 会是这样的
# ['red', 'green', 'blue']
...
7.2.2.3 Example 3 : 字典
---
# 若熟悉 python 的话, 可以认为他就是 Python 中的 Dict
# 如何定义 : key + 冒号 + 空格 + 值 , 即 key: value
name: Using Ansible
code: D1234
...
7.2.2.4 Example 4 : 混合结构

以上3种结构的任意组合

---
class:
  - name: stu1
    num: 001
  - name: stu2
    num: 002
  - name: stu3
    num: 003
    
# 转换成 Python格式
# {'class': [{'name': '001', 'num': 1}, {'name': 'stu2', 'num': 2}, {'name': 'stu3', 'num': 3}]}
...
7.2.2.5 校验 YAML 语句

将 YAML文件 , 通过 Python 的 YAML 模块进行验证

# 安装 python2-pip [root@manager ~]# yum install python2-pip # 安装 pyyaml [root@manager ~]# pip install pyyaml

# cat test.yaml
---
- red
- green
- blue
...


# 对 YAML 格式文件进行检验
[root@manager ~]# python -c 'import yaml,sys; print yaml.load(sys.stdin)' < test.yaml
['red', 'green', 'blue']

7.3 定义一个 Playbook

一个 Playbook 是由一个或多个 Play 构成 , 那么一个含有多个 Play 的 Playbook 结构上应该是如下的样子

---
- key1: value1
  key2: value2
  key3: value3
...

一个含有多个 Play 的 Playbook 结构山应该是如下的样子

---
# 一个含有3个 Play 的伪 PlayBook 构成
- key1: value1
  key2: value2
  key3: value3
- key4: value1
  key5: value2
  key6: value3
- key1: value1
  key2: value2
  key3: value3
...

7.3.1 Play 的属性

Play 中的每一个 key , 比如 key1 , key2 等 , 这些 key 在 PlayBook 中被定义为 Play 的属性

这些属性具有特殊意义 , 我们不能随意的自定义 Play 的属性 , 常用属性如下

  • name 每个 play 的名字 , 用于任务执行后的回显 (一定要写)
  • hosts 每个 play 涉及的被管理服务器 , 通 ad hoc 中的 pattern
  • tasks 每个 play 中具体要完成的任务 , 以列表的形式表达
  • become 如果需要提权 , 则加上 become 相关属性
  • become_user 若提权的话 , 提权到哪个用户上
  • remote_user 指定连接用户 , 若不指定 , 则默认使用当前执行 ansible Playbook 的用户
  • 等等

7.3.2 一个完整的剧本

最常规写法

---
- name: the first play example
  hosts: all
  remote_user: root
  tasks: 
    - name: install nginx packet
      yum: name=nginx state=present
    - name: copy nginx.conf to remote server
      copy: src=nginx.conf dest=/etc/nginx/nginx.conf
    - name: start nginx service
      service: name=nginx enabled=true state=started
...

tasks 属性中任务的多种写法

# 以启动 nginx 服务 , 并增加开机启动为例

# 一行的形式
service: name=nginx enabled=true state-started

# 多行的形式
service: 
  name=nginx
  enabled=true
  state=started
  
# 多行字典的形式
service:
  name: nginx
  enabled: true
  state: started

7.3.3 具有多个 Play 的 Playbook

---
- name: manage web servers
  hosts: webservers
  remote_user: root
  tasks:
    - name: install nginx packet
      yum: name=nginx state=present
    - name: copy nginx.conf to remote server
      copy: src=nginx.conf dest=/etc/nginx/nginx.conf
    - name: start nginx server
      service: name=nginx enabled=true state=started
- name: manager db servers
  hosts: dbservers
  tasks: 
    - name: update database config
      copy: src=my.cnf dest=/etc/my.cnf
...

7.4 校验一个 PlayBook

下面校验的方法 , 只能校验 PlayBook是否正确 , 而不能校验 YAML 文件是否语法正确

之前 YAML 校验的方法

[root@manager ~]# python -c 'import yaml,sys; print yaml.load(sys.stdin)' < test.yaml

PlayBook校验

[root@manager ~]# ansible-playbook myplaybook.yml --syntax-check

playbook: myplaybook.yml

7.5 运行一个 PlayBook

[root@manager ~]# ansible-playbook myplaybook.yml

7.6 如何单步跟从调试 PlayBook

ansible-playbook myplaybook.yml --step

7.7 如何测试运行 PlayBook

// 会执行完整个 PlayBook , 但是所有 Task 中的行为都不会在远程服务器上执行 , 所有执行都是模拟行为
# ansible-playbook myplaybook.yml -C
// -C 为大写字母 C

8. Ansible 变量

8.1 Ansible 变量介绍

变量由 字母 数字 下划线 组成

只能以字母开头, 保留关键字不能作为变量名称

8.2 变量类型

根据变量的作用范围 , 大体的将变量分为

  • 全局变量
  • 剧本变量
  • 资产变量

但这只是一个比较除粗糙的划分 , 不能囊括 Ansible 中的所有变量 , 下面将分别从这三种变量入手 , 去介绍变量的使用

8.2.1 全局变量

全局变量 是 我们使用 ansible 或者使用 ansible-playbook 时 , 手动通过 -e 参数传递给 Ansible 的变量

通过 ansible 或 ansible-playbook 的 help 帮助 , 可以获取具体格式使用方式

普通的 key-value 模式

[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{key}}'" -e "key='LiuMenglong'"
192.168.200.133 | SUCCESS => {
    "msg": "name is LiuMenglong"
}

传递一个 YAML/JSON 的形式 (他们的最终形式必须是字典)

# 指定时必须加 @
[root@manager ~]# cat a.json
{"name": "LiuML", "age": "16"}

[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{name}} , age is {{age}}'" -e @a.json
[WARNING]: Found variable using reserved name: name
192.168.200.133 | SUCCESS => {
    "msg": "name is LiuML , age is 16"
}


[root@manager ~]# cat b.yaml
---
name: lisi
age: 33
...
[root@manager ~]# ansible webservers -i /etc/ansible/hosts -m debug -a "msg='name is {{name}} , age is {{age}}'" -e @b.yaml
[WARNING]: Found variable using reserved name: name
192.168.200.133 | SUCCESS => {
    "msg": "name is lisi , age is 33"
}

8.2.2 剧本变量

在 Playbook 中使用的变量叫做剧本变量 , 他的定义由多种方式 , 我们使用两种 , 文件内定义即引用文件

8.2.2.1 通过 PLAY 属性 vars 定义
---
- name: test play vars
  hosts: all
  vars: 
    user: lisi
    home: /home/lisi
...
8.2.2.2 通过 PLAY 属性 vars_file 定义
---
- name: test play vars
  hosts: all
  vars_files: 
    - vars/users.yaml
...

cat vars/users/yaml

---
user: lilei
home: /home/lilei
...
8.2.2.3 如何在 Playbook 中使用这些变量

在 PlayBook 中使用变量时 , 使用 {{ 变量名 }} 来使用变量 , 在使用变量是 , 大部分情况外侧要加双引号即 “{{ 变量 }}”

Example1:

---
- name: Test Vars using in playbook
  hosts: all
  vars: 
    user: lisi
    home: /home/lisi
  tasks: 
    - name: create the users {{user}}
      user: name={{user}} home={{home}}
...
[root@manager ~]# ansible-playbook 123456.yaml -C
[root@manager ~]# ansible-playbook 123456.yaml

注意事项 : 在下面的例子(字典)中 , {{ 变量 }} 外一定要加双引号 , 否则会yaml 解析错误

Example2

---
- name: Test Vars using in playbook
  hosts: all
  vars: 
    user: lisi
    home: /home/lisi
  tasks: 
    - name: create the users {{user}}
      user: 
        name: "{{user}}"
        home: "{{home}}"
...

8.2.3 资产变量

资产变量是和资产紧密相关的一种变量 , 资产变量分为 主机变量 和 主机组变量 , 分别针对单个主机和一组主机

8.2.3.1 主机变量
# cat hostsandhostvars
[webservers]
192.168.200.133 user=lisi age=18
192.168.200.134
  • 验证
[root@localhost ansible]# ansible webservers -i /etc/ansible/hostsandhostvars -m debug -a "var=user"
192.168.200.133 | SUCCESS => {
    "user": "lisi"
}
192.168.200.134 | SUCCESS => {
    "user": "VARIABLE IS NOT DEFINED!"
}
8.2.3.2 主机组变量
  • 主机变量优先于主机组变量
# cat hostsandhostvars
[webservers]
192.168.200.133 user=lisi age=18
192.168.200.134

[webservers:vars]
user=tom
  • 验证
[root@localhost ansible]# ansible webservers -i /etc/ansible/hostsandhostvars -m debug -a "var=user"
192.168.200.133 | SUCCESS => {
    "user": "lisi"
}
192.168.200.134 | SUCCESS => {
    "user": "tom"
}
8.2.3.3 变量的继承

变量是可以继承的

# cat host_v3
[dbservers]
192.168.200.133

[webservers]
192.168.200.134

[allserver]
[allserver:children]
webservers
dbservers


[allserver:vars]
user=lml
  • 验证
[root@localhost ansible]# ansible webservers -i /etc/ansible/host_v3 -m debug -a "var=user"
192.168.200.134 | SUCCESS => {
    "user": "lml"
}