探索ansible runner的源码及执行api原理

简介:

前沿:

     ansible的文档说的不清不楚,文档一点也不实在,有些范例都做不通,走不通。今天中午吃饭的时候,和同事 祖天彪(这名够霸道吧),聊了很长时间ansible在实际项目中碰到的问题,尤其是运维平台页面上。  下午的时候,找他去玩耍,这哥们正好在看ansible的api源码,也就是 ansible runner ~    就这样一块交流一两个小时,把runner 这部分的核心代码给过了一遍。 中文的文档真的很少, 我这边就简单的阐述下,看完后的一些总结。 可能有些地方不对,或者是没有说明白,还请大家见谅。


1
2
3
4
5
6
7
8
9
10
11
12
import ansible.runner
runner = ansible.runner.Runner(
    host_list= "/etc/ansible/xiaorui.py"
    module_name= 'command' ,
    module_args= 'ip a' ,
    pattern= 'web' ,                    
)
 
datastructure = runner.run()
print  '\n'
print  '\n'
print  datastructure


    

上面是一个简单的ansible api的执行的例子。 我们可以看到他调用的runner模块。


runner目录下面有个__init__.py文件,__init__.py的作用, 相当于class中的def __init__(self):函数,用来初始化模块。 把所在目录当作一个package处理。不懂的去看,python包相关的基础。 

原文:http://rfyiamcool.blog.51cto.com/1030776/1420147


下面讲解下 Runner这个类。

class Runner(object):

    ''' core API interface to ansible '''


    # see bin/ansible for how this is used...


    def __init__(self,

        host_list=C.DEFAULT_HOST_LIST,      这里不仅可以放 静态的hosts文件,也可以放 inventory的脚本,脚本要777权限。


        module_path=None,               这个是ansible的路径,一般不用写


        module_name=C.DEFAULT_MODULE_NAME,      模块的名字,模块的位置要选定在/usr/share/absible下,不然会识别不到。   要注意下~


        module_args=C.DEFAULT_MODULE_ARGS,  # ex: "src=/tmp/a dest=/tmp/b"   模块的参数


        forks=C.DEFAULT_FORKS,              # parallelism level   进程的数目,他的逻辑是这样,你如果填入了20个进程,他会判断你的list_hosts是否有20个,没有的话,他就会根据主机的数目来派生进程,如果超过20个,那就用multiprocess进程池pool来调度。mulitiprocess本身有个isalive的东西,来判断分离出去进程的存活状态。


        timeout=C.DEFAULT_TIMEOUT,       这个就是超时的时间


        pattern=C.DEFAULT_PATTERN,          # which hosts?  ex: 'all', 'acme.example.org'   这个是做相关的匹配,是关于inventory的匹配


        remote_user=C.DEFAULT_REMOTE_USER,  # ex: 'username'  远端用户的选择


        remote_pass=C.DEFAULT_REMOTE_PASS,  # ex: 'password123' or None if using key 远端密码的选择


        remote_port=None,                   # if SSH on different ports   远端端口的选择


        private_key_file=C.DEFAULT_PRIVATE_KEY_FILE, # if not using keys/passwords  还可以用指定key


        sudo_pass=C.DEFAULT_SUDO_PASS,      # ex: 'password123' or None   sudo之后的密码的推送


        background=0,                       # async poll every X seconds, else 0 for non-async  看字眼就知道他是做什么的了,他非常的像 saltstack的 event, 当派生出了一个任务后,

        产生一个ansible_job_id,然后时不时的去拿数据


        transport=C.DEFAULT_TRANSPORT,      # 'ssh', 'paramiko', 'local'  这里是选择你的链接得到方式,默认是用的 paramiko


        conditional='True',                 # run only if this fact expression evals to true     这个是什么呢?  相当与 puppet saltstack 里面的require,状态的判断。  

                   

        callbacks=None,                     # used for output  回调的输出


        sudo=False,                         # whether to run sudo or not 是否是sudo


        sudo_user=C.DEFAULT_SUDO_USER,      # ex: 'root'  sudo的时候,用到的用户名


        ):




再来关注下这个,我和同事的关注点,不太一样,他以前经常用salt,他不想写hosts文件,或者是inventory文件,想直接 ansible  ip地址 -m shell "ip a" 类似这样的使用,而我的想法是,想给inventory传递参数,  inventory在被ansbile调用的时候,是不能传递参数的, 他后面的那个参数,只是获取 json的那个key。 

简单的说, ansible -i nima.py "bj_nginx" -m shell "dir"  这里的bj_nginx 是不能传递给nima里面的,而是获取nima返回的那片json的。


1
self.inventory        = utils. default (inventory, lambda: ansible.inventory.Inventory(host_list))

咱们进到,inventory的目录

[root@devops-ruifengyun inventory ]$ pwd

/usr/lib/python2.7/dist-packages/ansible/inventory


试图找下,官网支持不支持针对inventory的传递参数,host_list传递给了 Inventory类,他的逻辑也很干练,如果 host_list  如果没有传入的话,默认走的是 /etc/ansible/hosts文件,如果你指定了,他会从你的文件读入的,  首先如果是str,根据逗号来区分并且去除空格,放到list里面。接着是list了,。。。  霹雳拉ye,oh。。


原文:http://rfyiamcool.blog.51cto.com/1030776/1420147


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
       if  type (host_list)  in  str unicode  ]:
           if  host_list.find( "," ) ! =  - 1 :
               host_list  =  host_list.split( "," )
               host_list  =  [ h  for  in  host_list  if  and  h.strip() ]
 
       if  type (host_list)  = =  list :
           self .parser  =  None
           all  =  Group( 'all' )
           self .groups  =  all  ]
           for  in  host_list:
               if  x.find( ":" ) ! =  - 1 :
                   tokens  =  x.split( ":" , 1 )
                   all .add_host(Host(tokens[ 0 ], tokens[ 1 ]))
               else :
                   all .add_host(Host(x))
       elif  os.path.exists(host_list):
           if  os.path.isdir(host_list):
               # Ensure basedir is inside the directory
               self .host_list  =  os.path.join( self .host_list, "")
               self .parser  =  InventoryDirectory(filename = host_list)
               self .groups  =  self .parser.groups.values()
           elif  utils.is_executable(host_list):
               self .parser  =  InventoryScript(filename = host_list)
               self .groups  =  self .parser.groups.values()
           else :
               data  =  file (host_list).read()
               if  not  data.startswith( "---" ):
                   self .parser  =  InventoryParser(filename = host_list)
                   self .groups  =  self .parser.groups.values()
               else :
                   raise  errors.AnsibleError( "YAML inventory support is deprecated in 0.6 and removed in 0.7, see the migration script in examples/scripts in the git checkout" )
 
           utils.plugins.vars_loader.add_directory( self .basedir(), with_subdir = True )
       else :
           raise  errors.AnsibleError( "Unable to find an inventory file, specify one with -i ?" )
 
   def  _match( self str , pattern_str):
   原文:http: / / rfyiamcool.blog. 51cto .com / 1030776 / 1420147




最后,咱们的流程跑到这里。。。


 elif utils.is_executable(host_list):

     self.parser = InventoryScript(filename=host_list)

     self.groups = self.parser.groups.values()


咱们再来看看InventoryScript做了什么东西。。。。

wKioL1OIc96iihenAATsklD8dHg199.jpg


找到了,  这就是我给 host_list传递字符串的时候,会提示没有该文件,因为他的subprocess的shell状态是 False,这样的话,不能识别 分号和空格。 只要在subprocess.Popen追加一个shell=True就可以了。

 

既然ansible runner代码,基本过了一遍,那么 inventory 模块的参数传递,很明了了。。。

 

原文:http://rfyiamcool.blog.51cto.com/1030776/1420147


wKioL1OIeDvh_yz1AASJsHr_qOw236.jpg


1
2
3
4
5
6
7
8
9
10
11
  30      def  __init__( self , filename = C.DEFAULT_HOST_LIST):
  31 
  32          self .filename  =  filename
  33          cmd  =  self .filename,  "--list"  ]
  34          try :
  35              sp  =  subprocess.Popen(cmd, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
  36          except  OSError, e:
  37              raise  errors.AnsibleError( "problem running %s (%s)"  %  ( ' ' .join(cmd), e))
  38          (stdout, stderr)  =  sp.communicate()
  39          self .data  =  stdout
  40          self .groups  =  self ._parse()


接着,他用调用了他自己类下的 _parse() 函数 ~

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
def  _parse( self ):
 
     all_hosts  =  {}
     self .raw   =  utils.parse_json( self .data)
     all        =  Group( 'all' )
     groups     =  dict ( all = all )
     group      =  None
 
     if  'failed'  in  self .raw:
         raise  errors.AnsibleError( "failed to parse executable inventory script results" )
 
     for  (group_name, data)  in  self .raw.items():
 
         group  =  groups[group_name]  =  Group(group_name)
         host  =  None
 
         if  not  isinstance (data,  dict ):
             data  =  { 'hosts' : data}
 
         if  'hosts'  in  data:
 
             for  hostname  in  data[ 'hosts' ]:
                 if  not  hostname  in  all_hosts:
                     all_hosts[hostname]  =  Host(hostname)
                 host  =  all_hosts[hostname]
                 group.add_host(host)
 
         if  'vars'  in  data:
             for  k, v  in  data[ 'vars' ].iteritems():
                 group.set_variable(k, v)
         all .add_child_group(group)
 
     # Separate loop to ensure all groups are defined
     for  (group_name, data)  in  self .raw.items():
         if  isinstance (data,  dict and  'children'  in  data:
             for  child_name  in  data[ 'children' ]:
                 if  child_name  in  groups:
                     groups[group_name].add_child_group(groups[child_name])
     return  groups




        上面的代码已经很好理解了,就是把刚才通过subprocess获取的数据,用json模块,json.loads一下。这里会取出两个大key,一个是主机类型标签的hosts 这个key,和vars ,这个vars是给模板或者其他功能的数据。 

(上次咱们提过的,ansible 通过cmdb,获取inventory的数据  http://rfyiamcool.blog.51cto.com/1030776/1416808  

        

        有很多人会说,这也太搓了,太scha了,居然是通过外部调用获取的数据,但是我觉得这也是很多人着迷ansible的地方,不管你会不会python,你只要会任何一个语言,shell、php、java、nodejs、ruby只要能标准的stdout输出,那就行了。 


剩下的不需要你再度的介入,因为对于ansible来说,我拿到的数据都是标准的输出,所以大家不管用怎么语言,一定要做好异常的处理,让他print干干净净的数据,这样ansible 这个小孩才能完美的加载数据。


希望大家通过这篇文章,对于ansible api更有深度的了解,一些的帮助,如果文章描述的有问题,请即使的通知我,我会即使的更正修改。






 本文转自 rfyiamcool 51CTO博客,原文链接:http://blog.51cto.com/rfyiamcool/1420147,如需转载请自行联系原作者


相关文章
|
1月前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
71 2
|
2月前
|
存储 数据可视化 JavaScript
可视化集成API接口请求+变量绑定+源码输出
可视化集成API接口请求+变量绑定+源码输出
63 4
|
4月前
|
存储 Linux API
Linux源码阅读笔记08-进程调度API系统调用案例分析
Linux源码阅读笔记08-进程调度API系统调用案例分析
|
4月前
|
Linux API
Linux源码阅读笔记07-进程管理4大常用API函数
Linux源码阅读笔记07-进程管理4大常用API函数
|
4月前
|
JSON 算法 API
京东以图搜图功能API接口调用算法源码python
京东图搜接口是一款强大工具,通过上传图片即可搜索京东平台上的商品。适合电商平台、比价应用及需商品识别服务的场景。使用前需了解接口功能并注册开发者账号获取Key和Secret;准备好图片的Base64编码和AppKey;生成安全签名后,利用HTTP客户端发送POST请求至接口URL;最后解析JSON响应数据以获取商品信息。
|
4月前
|
API 开发者 Python
API接口:原理、实现及应用
本文详细介绍了API接口在现代软件开发中的重要性及其工作原理。API接口作为应用程序间通信的桥梁,通过预定义的方法和协议实现数据和服务的共享。文章首先解释了API接口的概念,接着通过Python Flask框架示例展示了API的设计与实现过程,并强调了安全性的重要性。最后,本文还讨论了API接口在Web服务和移动应用程序等领域的广泛应用场景。
|
4月前
|
JSON API 网络架构
Django 后端架构开发:DRF 高可用API设计与核心源码剖析
Django 后端架构开发:DRF 高可用API设计与核心源码剖析
94 0
|
4月前
|
Kubernetes 负载均衡 API
在K8S中,api-service 和 kube-schedule 高可用原理是什么?
在K8S中,api-service 和 kube-schedule 高可用原理是什么?
|
5月前
|
Serverless 网络安全 Python
Ansible原理和安装
Ansible原理和安装
85 1
|
4月前
|
消息中间件 Linux 网络安全
之所以能早点下班,多亏看有了这篇 Ansible 工作原理图解!
之所以能早点下班,多亏看有了这篇 Ansible 工作原理图解!
下一篇
DataWorks