ansible基本使用

ansible基本使用

标签:ansible

ansible是以款配置简单方便的自动化运维工具,基于python开放,可以实现线上主机的批量管理配置、批量应用部署。默认通过SSH协议来管理机器。

ansible安装

1
2
3
4
5
6
7
8
9
yum install ansible
#查看是否安装成功
ansible --version
ansible 2.8.4
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, Jun 20 2019, 20:27:34) [GCC 4.8.5 20150623 (Red Hat 4.8.5-36)]

ansible基本配置

ansible默认配置路径是在/etc/ansible下

1
2
3
4
5
[root@localhost ansible]# tree
.
├── ansible.cfg #ansible配置文件
├── hosts #主机节点文件
└── roles #角色目录

配置ansible.cfg

1
host_key_checking = False #第一次登录主机要hosts文件,禁用掉就不需要验证了

配置hosts

ansible基于SSH所以可以使用免密钥和用户密码方式登录

1
2
3
4
[test]
192.168.1.150 ansible_ssh_user=jenkins ansible_ssh_pass=jenkins
192.168.1.113 ansible_ssh_user=root ansible_ssh_pass=root
#这里使用的是密码登录

ansible模块使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@localhost ansible]# ansible test -m ping
192.168.1.113 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
192.168.1.150 | SUCCESS => {
"ansible_facts": {
"discovered_interpreter_python": "/usr/bin/python"
},
"changed": false,
"ping": "pong"
}
#ansible ping 模块探测主机是否存活,这里节点主机只要开放SSH端口就可以并不是LCMP协议的ping
#这里的test跟hosts配置文件中的test对应
1
2
3
4
5
6
7
[root@localhost ansible]# ansible test -m command -a "pwd"
192.168.1.113 | CHANGED | rc=0 >>
/root

192.168.1.150 | CHANGED | rc=0 >>
/home/jenkins
#ansible 批量执行命令不支持管道
1
2
3
4
5
6
7
[root@localhost ansible]# ansible test -m shell -a "ls | grep nginx"
192.168.1.113 | CHANGED | rc=0 >>
nginx-1.14.2.tar.gz

192.168.1.150 | CHANGED | rc=0 >>
nginx-1.14.2.tar.gz
#ansible 批量执行命令支持管道

ansible-playbook使用

ansible-playbook是定制主机配置可以按照YAML格式的语法来定义多个任务,支持同步或异步执行

核心元素

1
2
3
4
5
Tasks:任务,由模板定义的操作列表
Variables:变量
Templates:模板,即使用模板语法的文件
Handlers:处理器 ,当某条件满足时,触发执行的操作
Roles:角色

playbook示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
[root@localhost ansible]# cat firewalld.yml 
---
- hosts: test #要执行的主机
remote_user: root #执行用户
vars:
- server: nginx #定义的变量

tasks: #执行任务
- name: rm nginx #任务名称
file: path=~/nginx-1.14.2.tar.gz state=absent #使用ansible模块来执行任务
- name: copy fire
copy: src=/opt/nginx-1.14.2.tar.gz dest=~/

- hosts: 192.168.1.150

tasks:
- name: nginx
service: name={{nginx}} state=status #变量引用

playbook规范

playbook方便了批量执行任务但是当执行脚本果断时会造成脚本过多难以管理,所以ansible-playbook定义了一套脚本管理规范

只需要将写好的模块放在roles文件中就可以直接这么写

1
2
3
4
5
6
7
8
---
- hosts: test
roles:
- nginx

- hosts: 192.168.1.150
roles:
- nginx

我们看一下roles文件

1
2
3
4
5
6
7
8
9
10
[root@localhost ansible]# tree roles
roles
└── nginx
├── files
├── handlers
├── meta
├── tasks
│   └── main.yml
├── templates
└── vars
1
2
3
[root@localhost nginx]# cat tasks/main.yml
- debug:
msg: "hello role!"

这样不管在主机的任何地方主要使用引用nginx就可以实现这个模块不要重复去写,当人你也可以不放在roles目录中,你可以自定义目录

1
2
3
4
5
6
[root@localhost ~]# tree test
test
├── nginx
│   └── tasks
│   └── main.yml
└── test.yml
1
2
3
4
[root@localhost test]# cat test.yml 
- hosts: test
roles:
- "/root/test/nginx"

使用绝对路径就可以引用这个写好的模块

ansible二次开发

ansible是python写的,使用python可以直接继承ansible的类进行二次开发,当然如果嫌弃麻烦可以直接用os模块调用,这里不做演示

官方给出的API文档

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#!/usr/bin/env python

import json
import shutil
from ansible.module_utils.common.collections import ImmutableDict
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
from ansible import context
import ansible.constants as C

class ResultCallback(CallbackBase):
"""A sample callback plugin used for performing an action as results come in

If you want to collect all results into a single object for processing at
the end of the execution, look into utilizing the ``json`` callback plugin
or writing your own custom callback plugin
"""
def v2_runner_on_ok(self, result, **kwargs):
"""Print a json representation of the result

This method could store the result in an instance attribute for retrieval later
"""
host = result._host
print(json.dumps({host.name: result._result}, indent=4))

# since the API is constructed for CLI it expects certain options to always be set in the context object
context.CLIARGS = ImmutableDict(connection='local', module_path=['/to/mymodules'], forks=10, become=None,
become_method=None, become_user=None, check=False, diff=False)

# initialize needed objects
loader = DataLoader() # Takes care of finding and reading yaml, json and ini files
passwords = dict(vault_pass='secret')

# Instantiate our ResultCallback for handling results as they come in. Ansible expects this to be one of its main display outlets
results_callback = ResultCallback()

# create inventory, use path to host config file as source or hosts in a comma separated string
inventory = InventoryManager(loader=loader, sources='/etc/ansible/hosts')

# variable manager takes care of merging all the different sources to give you a unified view of variables available in each context
variable_manager = VariableManager(loader=loader, inventory=inventory)

# create data structure that represents our play, including tasks, this is basically what our YAML loader does internally.
play_source = dict(
name = "Ansible Play",
hosts = 'test',
gather_facts = 'no',
tasks = [
dict(action=dict(module='shell', args='ls'), register='shell_out'),
#dict(action=dict(module='debug', args=dict(msg='{{shell_out.stdout}}')))
]
)

# Create play object, playbook objects use .load instead of init or new methods,
# this will also automatically create the task objects from the info provided in play_source
play = Play().load(play_source, variable_manager=variable_manager, loader=loader)

# Run it - instantiate task queue manager, which takes care of forking and setting up all objects to iterate over host list and tasks
tqm = None
try:
tqm = TaskQueueManager(
inventory=inventory,
variable_manager=variable_manager,
loader=loader,
passwords=passwords,
stdout_callback=results_callback, # Use our custom callback instead of the ``default`` callback plugin, which prints to stdout
)
result = tqm.run(play) # most interesting data for a play is actually sent to the callback's methods
finally:
# we always need to cleanup child procs and the structures we use to communicate with them
if tqm is not None:
tqm.cleanup()

# Remove ansible tmpdir
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

网上查看资料封装修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# -*- coding: UTF-8 -*-
import json
import shutil
from ansible.module_utils.common.collections import ImmutableDict
from ansible.parsing.dataloader import DataLoader
from ansible.vars.manager import VariableManager
from ansible.inventory.manager import InventoryManager
from ansible.playbook.play import Play
from ansible.executor.task_queue_manager import TaskQueueManager
from ansible.plugins.callback import CallbackBase
from ansible import context
import ansible.constants as C


class ResultCallback(CallbackBase):
"""
重写callbackBase类的部分方法
"""
def __init__(self, *args, **kwargs):
super(ResultCallback,self).__init__(*args, **kwargs) #python2
#super().__init__(*args, **kwargs) #python3
self.host_ok = {}
self.host_unreachable = {}
self.host_failed = {}
self.task_ok = {}
def v2_runner_on_unreachable(self, result):
self.host_unreachable[result._host.get_name()] = result

def v2_runner_on_ok(self, result, **kwargs):
self.host_ok[result._host.get_name()] = result

def v2_runner_on_failed(self, result, **kwargs):
self.host_failed[result._host.get_name()] = result

class MyAnsiable2():
def __init__(self,
connection='local', # 连接方式 local 本地方式,smart ssh方式
remote_user=None, # 远程用户
ack_pass=None, # 提示输入密码
sudo=None, sudo_user=None, ask_sudo_pass=None,
module_path=None, # 模块路径,可以指定一个自定义模块的路径
become=None, # 是否提权
become_method=None, # 提权方式 默认 sudo 可以是 su
become_user=None, # 提权后,要成为的用户,并非登录用户
check=False, diff=False,
listhosts=None, listtasks=None,listtags=None,
verbosity=3,
syntax=None,
start_at_task=None,
inventory=None):

# 函数文档注释
"""
初始化函数,定义的默认的选项值,
在初始化的时候可以传参,以便覆盖默认选项的值
"""
context.CLIARGS = ImmutableDict(
connection=connection,
remote_user=remote_user,
ack_pass=ack_pass,
sudo=sudo,
sudo_user=sudo_user,
ask_sudo_pass=ask_sudo_pass,
module_path=module_path,
become=become,
become_method=become_method,
become_user=become_user,
verbosity=verbosity,
listhosts=listhosts,
listtasks=listtasks,
listtags=listtags,
syntax=syntax,
start_at_task=start_at_task,
)

# 三元表达式,假如没有传递 inventory, 就使用 "localhost,"
# 确定 inventory 文件
self.inventory = inventory if inventory else "localhost,"

# 实例化数据解析器
self.loader = DataLoader()

# 实例化 资产配置对象
self.inv_obj = InventoryManager(loader=self.loader, sources=self.inventory)

# 设置密码,可以为空字典,但必须有此参数
self.passwords = {}

# 实例化回调插件对象
self.results_callback = ResultCallback()

# 变量管理器
self.variable_manager = VariableManager(self.loader, self.inv_obj)


def run(self, hosts='localhost', gether_facts="no", module="ping", args=''):
play_source = dict(
name = "Ad-hoc",
hosts = hosts,
gather_facts = gether_facts,
tasks = [
# 这里每个 task 就是这个列表中的一个元素,格式是嵌套的字典
# 也可以作为参数传递过来,这里就简单化了。
{"action":{"module": module, "args": args}},
])

play = Play().load(play_source, variable_manager=self.variable_manager, loader=self.loader)

tqm = None
try:
tqm = TaskQueueManager(
inventory=self.inv_obj ,
variable_manager=self.variable_manager,
loader=self.loader,
passwords=self.passwords,
stdout_callback=self.results_callback)

result = tqm.run(play)
finally:
if tqm is not None:
tqm.cleanup()
shutil.rmtree(C.DEFAULT_LOCAL_TMP, True)

def playbook(self,playbooks):
from ansible.executor.playbook_executor import PlaybookExecutor

playbook = PlaybookExecutor(playbooks=playbooks, # 注意这里是一个列表
inventory=self.inv_obj,
variable_manager=self.variable_manager,
loader=self.loader,
passwords=self.passwords)

# 使用回调函数
playbook._tqm._stdout_callback = self.results_callback

result = playbook.run()


def get_result(self):
result_raw = {'success':{},'failed':{},'unreachable':{}}

# print(self.results_callback.host_ok)
for host,result in self.results_callback.host_ok.items():
result_raw['success'][host] = result._result
for host,result in self.results_callback.host_failed.items():
result_raw['failed'][host] = result._result
for host,result in self.results_callback.host_unreachable.items():
result_raw['unreachable'][host] = result._result

# 最终打印结果,并且使用 JSON 继续格式化
print(json.dumps(result_raw, indent=4))
if __name__ == "__main__":
ansible2 = MyAnsiable2(inventory='/etc/ansible/hosts', connection='smart')
ansible2.run(hosts= "test", module="shell", args='ip a |grep "inet"')
ansible2.get_result()


#ansible2 = MyAnsiable2(inventory='/etc/ansible/hosts', connection='smart')

#传入playbooks 的参数,需要是一个列表的数据类型,这里是使用的相对路径
#相对路径是相对于执行脚本的当前用户的家目录
#ansible2.playbook(playbooks=['test.yml'])
#ansible2.get_result()

封装之后就可以以直接调用了