SaltStack实战应用

来源:互联网 发布:如何开通淘宝 编辑:程序博客网 时间:2024/06/10 22:35
Saltstack是一个基于Python开发的一套C/S架构配置管理工具,底层使用ZeroMQ消息队列pub/sub方式通信,使用SSL证书签发的方式进行认证管理。部署轻松,在几分钟内可运行起来,扩展性好,很容易管理上万台服务器,速度够快,服务器之间秒级通讯。

号称世界上最快的消息队列ZeroMQ使得SaltStack能够快速在成千上万台机器上进行各种操作,而且采用RSA Key方式确认身份,传输采用AES加密,这让安全性能到保障。


SaltStack的基本原理是采用C/S模式,Server端是Salt的master,Client端是minion,minion与maseter之间是通过ZeroMQ消息队列进行通信。

minion第一次启动,会在/etc/minion/pli/minion.d目录下生成monion.pem和minion.key,然后将minion.pub发给master。

maseter端通过salt-key  -L命令就会看到minion的key,接受了minion的key以后,master在/etc/salt/minion.d/目录下生成pub key,两者互信。

在Master和Minion端都是以守护进程的模式运行,一直监听配置文件里面定义的ret_port(接受minion请求)和publish_port(发布消息)的端口。SaltStack master启动后默认监听4505和4506两个端口。4505(publish_port)为saltstack的消息发布系 统,4506(ret_port)为saltstack客户端与服务端通信的端口。如果使用lsof 查看4505端口,会发现所有的minion在4505端口持续保持在ESTABLISHED状态。

ZMQ (以下 ZeroMQ 简称 ZMQ),封装好的 socket  library传输层消息队列处理库,让 Socket 编程更加简单、简洁和性能更高,可在多个线程、内核和主机盒之间弹性伸缩。它无疑是极具前景的、并且是人们更加需要的“传统”BSD 套接字之上的一层封装。ZMQ 让编写高性能网络应用程序极为简单和有趣。

类似于 Socket 的一系列接口,他跟 Socket 的区别是:普通的 socket 是端到端的(1:1的关系),而 ZMQ 却是可以N:M 的关系,人们对 BSD 套接字的了解较多的是点对点的连接,点对点连接需要显式地建立连接、销毁连接、选择协议(TCP/UDP)和处理错误等,而 ZMQ 屏蔽了这些细节,让你的网络编程更为简单。ZMQ 用于 node 与 node 间的通信,node 可以是主机或者是进程。

[root@NameNode ~]# netstat -ntupl|grep 4505
tcp        0      0      0.0.0.0:4505                0.0.0.0:*                   LISTEN      45667/python2.6     
[root@NameNode ~]# netstat -ntupl|grep 4506
tcp        0      0      0.0.0.0:4506                0.0.0.0:*                   LISTEN      45674/python2.6  




1、下载并且安装master和minion

wget    -r   -c   http://repo.saltstack.com/yum/redhat/6/x86_64/latest  所有的文件




2、基本配置

[root@NameNode ~]# salt --versions-report


 


minion端:

配置minion:
[root@DataNode ~]# vim /etc/salt/minion
master:master  IP
id: 本机IP

启动salt-minion:
[root@DataNode ~]# service salt-minion start

master端:

vim /etc/salt/master   冒号后必须有空格,否则会报错:

# The address of the interface to bind to:
#interface: 0.0.0.0

# The tcp port used by the publisher:
#publish_port: 4505

# The user under which the salt master will run. Salt will update all
# permissions to allow the specified user to run the master. The exception is
# the job cache, which must be deleted if this user is changed. If the
# modified files cause conflicts, set verify_env to False.
user: root

# The port used by the communication interface. The ret (return) port is the
# interface used for the file server, authentication, job returns, etc.
#ret_port: 4506

# If you wish to set a different value than the default one, uncomment and
# configure this setting. Remember that this value CANNOT be higher than the
# hard limit. Raising the hard limit depends on your OS and/or distribution,
# a good way to find the limit is to search the internet. For example:
#  raise max open files hard limit debian
#
#max_open_files: 100000

# The number of worker threads to start. These threads are used to manage
# return calls made from minions to the master. If the master seems to be
# running slowly, increase the number of threads. This setting can not be
# set lower than 3.
#worker_threads: 5

启动salt-master:
[root@NameNode ~]# service salt-master start 
 

确保消息发布端口正常:
[root@NameNode ~]# netstat -ntupl|grep 4505 
 

确保客户端与服务端通信正常:
[root@NameNode ~]# netstat -ntupl|grep 4506 
 


查看接受的公钥:
[root@NameNode ~]# salt-key -A     
The following keys are going to be accepted:
Unaccepted Keys:
DataNode
Proceed? [n/Y] y
Key for minion DataNode accepted.



3、SaltStack基础

Salt  minion启动时会从配置文件中获取master的地址,如果为域名,则进行解析。解析完成以后,会链接master的4506(Ret 接口)进行key认证。认证通过,会获取到master的publish_port(默认是4505),然后链接publish_port订阅拉例子master pub接口的任务。当master下发操作执行令的时候,所有的minion都能接收到,然后minion会检查本机是否匹配。如果匹配则执行。

如果想了解Salt命令就是用--help选项,这也是Linux环境中学习命令的最简单方式:



可以看到,Salt命令远程执行有五个部分组成:Salt命令本身、命令行选项;、目标定位字符串、Salt模块函数、远程执行函数的参数。




日常,如果SaltStack出现故障,在排查master和minion的过程中,可以使用debug日志级别运行在前台进行基本调试:

[root@salt-master ~]# salt -l debug 132.126.56.3 test.ping






4、SaltStack基本模块函数

1)、sys模块

sys中的list_modules函数:列出当前版本支持的模块
[root@NameNode ~]# salt '*' sys.list_modules

sys中的list_functions函数:列出当前版本支持的模块中的函数
[root@NameNode ~]# salt '*' sys.list_functions  sys
132.122.70.139:
    - sys.argspec
    - sys.doc
    - sys.list_functions
    - sys.list_modules
    - sys.list_renderers
    - sys.list_returner_functions
    - sys.list_returners
    - sys.list_runner_functions
    - sys.list_runners
    - sys.list_state_functions
    - sys.list_state_modules
    - sys.reload_modules
    - sys.renderer_doc
    - sys.returner_argspec
    - sys.returner_doc
    - sys.runner_argspec
    - sys.runner_doc
    - sys.state_argspec
    - sys.state_doc
    - sys.state_schema

sys.doc函数类似于我们Linux系统中man命令,可以很方便的查看相关模块介绍和用法:
[root@NameNode ~]# salt '*' sys.doc  cmd.run


2)、cmd模块:
[root@NameNode ~]# salt  --summary  "*"  cmd.run  "uptime"

3)、Service服务模块:
[root@NameNode ~]# salt  '*'  service.restart nginx
[root@NameNode ~]# salt  '*'  service.enable nginx
[root@NameNode ~]# salt  '*'  service.status nginx

4)、cp模块:

cp.get_file用来从master下载文件到客户端:
[root@NameNode ~]# salt  “132.122.70.139”  cp.get_file  salt://monitor.sh   /tmp/monitor.sh

cp.get_dir用来从master下载整个文件夹到客户端:
[root@NameNode ~]# salt  "132.122.70.139"  cp.get_dir   salt://monitor       /tmp/monitor



5、state模块


SLS(代表SaLt State文件)是Salt State系统的核心。SLS描述了系统的目标状态,由格式简单的数据构成。这经常被称作配置管理 首先,在master上面定义salt的主目录,默认是在/srv/salt/下面,vim /etc/salt/master。执行/etc/salt/production/路径下info_clooection目录下sls目录下的hardware_info_collect的sls文件,用 . 代替目录的 /,这也是语法。

在file_roots里设置,注意YAM的语法。


[root@NameNode ~]# salt '132.122.70.140' state.sls  info_collection.sls.hardware_info_collect


执行/etc/salt/production/路径下info_clooection目录下sls目录下的hardware_info_collect的sls文件,用 . 代替目录的 /,这也是语法。

#!/bin/env python# -*- coding: utf-8 -*-# @Time:2017/4/14 14:53import salt.configimport salt.loaderimport subprocessimport os__opts__ = salt.config.minion_config('/etc/salt/minion')__grains__ = salt.loader.grains(__opts__)def network_info():    network_dict = {}    nic_list = {}    for nic, mac in  __grains__['hwaddr_interfaces'].items():        nic_list[nic] = [mac]        try:            nic_list[nic].append( __grains__['ip_interfaces'][nic][0])        except IndexError:            nic_list[nic].append('')    network_dict['nic_device'] = nic_list    network_dict['dns'] = __grains__['dns']['nameservers']    default_gateway = subprocess.Popen("route |  grep default | awk '{print $2}'",shell=True,stdout=subprocess.PIPE)    gateway = default_gateway.stdout.read().strip("\n")    if gateway:        network_dict['gateway'] = gateway    else:        network_dict['gateway'] = ''    return network_dictdef ram_info():    ram_dict = {}    # ram_raw_info = subprocess.check_output('/usr/sbin/dmidecode -t 17',shell=True)    ram_list = []    ram_unit_list = []    ram_unit = False    ram_info_list = []    ram_raw_info = subprocess.Popen("/usr/sbin/dmidecode -t 17",shell=True,stdout=subprocess.PIPE)    # gateway = default_gateway.stdout.read().strip("\n")    dmi_memory_list = ram_raw_info.stdout.read().split('\n')    for index,line in enumerate(dmi_memory_list):        if line.startswith("Memory Device"):            ram_unit = True        if not line.startswith("Memory Device") and ram_unit:            if line.startswith('Handle'):                ram_unit = False                ram_list.append(ram_unit_list)                ram_unit_list = []            else:                ram_unit_list.append(line.strip())                if index == len(dmi_memory_list)-1:                    ram_list.append(ram_unit_list)    for ram_item in ram_list:        for item  in ram_item:            item_list = item.split(':')            if len(item_list) == 2:                k,v = item_list                if k == 'Size':                    if v.strip() != "No Module Installed":                        ram_item_dic = {}                        ram_item_dic['capacity'] = v.strip()                    else:                        break                if k == "Locator":                    ram_item_dic['slot'] = v.strip()                if k == "Type":                    ram_item_dic['model'] = v.strip()                if k == 'Type Detail':                    ram_item_dic['model'] = ram_item_dic['model'] + " " + v.strip()                if k == "Speed":                    ram_item_dic['model'] = ram_item_dic['model'] + " " + v.strip()                if k == "Manufacturer":                    ram_item_dic['manufacture'] = v.strip()                if k == "Serial Number":                    ram_item_dic['sn'] = v.strip()                    ram_info_list.append(ram_item_dic)    swap = subprocess.Popen("free  -m | grep Swap | awk '{print $2}'",shell=True,stdout=subprocess.PIPE)    swap_capacity = swap.stdout.read().strip("\n")    if swap_capacity:        ram_dict['swap'] = "%sMB" % swap_capacity    else:        ram_dict['swap'] = ""    ram_dict['Physical_Ram_Total'] = "%sMB" % __grains__['mem_total']    ram_dict['Physical_Ram_Detail'] = ram_info_list    # print (ram_info_list)    return ram_dictdef os_info():    os_dict = {}    os_dict['osmajorrelease'] = __grains__['osmajorrelease']    os_dict['os_family'] = __grains__['os_family']    os_dict['os_distribution'] = '%s %s' %(__grains__['osfullname'],  __grains__['osrelease'])    os_dict['osrelease'] = __grains__['osrelease']    os_dict['osarch'] = __grains__['osarch']    return os_dictdef cpu_info():    cpu_dict = {}    cpu_dict['cpu_mode'] = __grains__['cpu_model']    cpu_dict['num_cpus'] = __grains__['num_cpus']    cpu_dict['cpuarch']  = __grains__['cpuarch']    return cpu_dictdef fc_disk(disk_name_list, disk_and_size):    disk_fc = subprocess.Popen("ls -l /dev/disk/by-path/ |  tail -n +2 | awk '(filed=NF){if($(filed-2) !~ /part.*$/ && $(filed-2) ~/-fc-/) print $(filed-2),$NF}' | sort -nr | sed 's/^.*:0x/0x/g'",                               shell=True,stdout=subprocess.PIPE)    disk_fc_str = disk_fc.stdout.read().strip("\n")        #stdout.read is synchronous    fc_disk_dict = {}    if disk_fc_str:     #has fc storage        disk_dict = {}        disk_fc_list = disk_fc_str.split("\n")        disk_fc_new = [ i.split() for i in disk_fc_list]        for disk in disk_fc_new:            disk_dict.update({disk[0]:disk[1]})    #The keys of a dictionary are inherently unique,use a dictionary to                                                    #remove duplicate disk(Multipath disk)        diskname_fc_list = [ i.lstrip('../../')  for i in disk_dict.values()]        fc_parttion_list = list(set(disk_name_list).intersection(set(diskname_fc_list)))  #fc disk list        for fc_disk in fc_parttion_list:            fc_disk_dict.update({fc_disk:disk_and_size[fc_disk]})    return fc_disk_dictdef iscsi_disk(disk_name_list, disk_and_size):    iscsi_disk_dict = {}    disk_iscsi = subprocess.Popen("ls -l /dev/disk/by-path/ |  tail -n +2 | awk '(filed=NF){if($(filed-2) !~ /part.*$/ && $(filed-2) ~/-iscsi-/) print $(filed-2),$NF}' | sort -nr",shell=True,stdout=subprocess.PIPE)    disk_iscsi_str = disk_iscsi.stdout.read().strip("\n")    if disk_iscsi_str:   #has iscsi storage        disk_dict = {}        disk_iscsi_iqn_list = []        disk_iscsi_list = disk_iscsi_str.split("\n")        disk_iscsi_new = [ i.split() for i in disk_iscsi_list]        for target in disk_iscsi_new:            j = []            m=target[0].split("-iscsi-")            j.append(m[1])            j.append(target[1])            disk_iscsi_iqn_list.append(j)        for disk in disk_iscsi_iqn_list:            disk_dict.update({disk[0]:disk[1]})   #The keys of a dictionary are inherently unique,use a dictionary to                                                        #remove duplicate disk(Multipath disk)        diskname_iscsi_list = [ i.lstrip('../../')  for i in disk_dict.values()]        iscsi_parttion_list = list(set(disk_name_list).intersection(set(diskname_iscsi_list)))  # iscsi disk list        for iscsi_disk in iscsi_parttion_list:            iscsi_disk_dict.update({iscsi_disk:disk_and_size[iscsi_disk]})    return  iscsi_disk_dictdef local_disk(disk_name_list, disk_and_size):    disk_local = subprocess.Popen("ls -l /dev/disk/by-path/ |  tail -n +2 | awk '(filed=NF){if($(filed-2) !~ /part.*$/ && $(filed-2) !~/-fc-/ && $(filed-2) !~/-iscsi-/) print $(filed-2),$NF}'",shell=True,stdout=subprocess.PIPE)    disk_local_str = disk_local.stdout.read().strip("\n")    local_disk_dict = {}    if disk_local_str:     #local storage        disk_local_list = disk_local_str.split("\n")        disk_local_new = [ i.split() for i in disk_local_list]        diskname_local_list = [ i[1].lstrip('../../') for i in disk_local_new ]        local_parttion_list = list(set(disk_name_list).intersection(set(diskname_local_list)))  #local disk list        if local_parttion_list:            for local_disk in local_parttion_list:                local_disk_dict.update({local_disk:disk_and_size[local_disk]})        cciss_disk=os.path.exists('/proc/driver/cciss/')   #hp cciss driver disk        if cciss_disk:            cciss_disk_list = [ i for i in disk_name_list if 'cciss/'  in i and 'p'  not in i]            for cciss in cciss_disk_list:                local_disk_dict.update({cciss:disk_and_size[cciss]})    return local_disk_dictdef disk_info():    disk_dict = {}    disk_and_size = {}     #from /proc/partitions    disk_parttion = subprocess.Popen("cat /proc/partitions  | tail -n +2 | awk '{print $3,$4}'",                                     shell=True, stdout=subprocess.PIPE )    df_parttion = subprocess.Popen(        "df -P | tail -n +2 | awk '$1 ~/\/dev/ {print $1}' | sed 's/\/dev\///g'",        shell=True, stdout=subprocess.PIPE    )    disk_parttion_str = disk_parttion.stdout.read().strip("\n")    df_parttion_str = disk_parttion.stdout.read().strip("\n")    disk_parttion_list = [ i.split() for i in disk_parttion_str.strip().split("\n")] #        # list item include disk name and partition size    for i in disk_parttion_list:             #disk size dict  key: disk name  value: disk size        disk_and_size.update({i[1]:i[0]})    disk_name_list = [ i[1] for i in disk_parttion_list]    #get disk name from /proc/parttions    localdisk = local_disk(disk_name_list, disk_and_size)    fcdisk = fc_disk(disk_name_list, disk_and_size)    iscsidisk = iscsi_disk(disk_name_list, disk_and_size)    disk_dict['local'] = localdisk    disk_dict['fc'] = fcdisk    disk_dict['iscsi'] = iscsidisk    return  disk_dictdef info_collect():    host_info_dict = {}    network = network_info()    ram = ram_info()    os = os_info()    cpu = cpu_info()    disk = disk_info()    host_info_dict['os'] = os    host_info_dict['cpu'] = cpu    host_info_dict['ram'] = ram    host_info_dict['network'] = network    host_info_dict['disk'] = disk    host_info_dict['productname']  = __grains__['productname']    host_info_dict['manufacturer']  = __grains__['manufacturer']    host_info_dict['serialnumber']  = __grains__['serialnumber']    host_info_dict['hostname']  = __grains__['host']    host_info_dict['hosttype']  = 'physical' if  __grains__['virtual'] == 'physical' else 'virtual'    host_info_dict['kernelrelease']  = __grains__['kernelrelease']    host_info_dict['shell']  = __grains__['shell']    host_info_dict['saltversion']  = __grains__['saltversion']    print (host_info_dict)if __name__ == "__main__":    info_collect()


6、SaltStack高可用架构


0.16.0版本的发布,带来了minion可以连接多Master的特性. 这种方式称为多master( multi-master )配置, 使环境中的SaltStack冗余。在这种配置下,Salt Minions将连接所有配置的Salt Maste。所以,SaltSack的高可用的环境可以搭建如下:





1)、在所有minion上配置如下:


vim  /etc/salt/minion 添加下面内容master:   - 132.122.137.3  - 132.122.137.4      id: 132.126.56.2    启动服务/etc/init.d/salt-minion start



2)、在master1、master2、master3上分别设置如下:


在master1上:vim /etc/salt/masterauto_accept: True #去掉对该行的注释,并修改False为True启动服务/etc/init.d/salt-master start在master2和master3上:执行salt-maser1上的1之后,还有最关键的一步scp salt-maser1的 /etc/salt/pki/master/master.pem和/etc/salt/pki/master/master.pub,覆盖本地的启动服务/etc/init.d/salt-master start



7、SaltStack的API


如果是通过salt api 来调用saltstack,只需按照api支持的相应格式把目标主机,模块.函数,参数输入api即可。下面举例说明通过api调用原子能力的方法,调用其他的原子能力按照该格式套用即可

比如我们要调用基线中的”禁止root使用ssh远程登录” 这个原子能力:

这里我们使用curl 命令模拟程序进行api调用:

 1)、登录获取session token

[root@salt-master ~]# curl -sSk https://132.96.170.152:8080/login    -H 'Accept: application/json'    -d    username=saltapi    -d password=saltapi    -d eauth=pam

参数

必填

类型

说明

参数输入位置

eauth

yes

string

接口进行验证的方法。

由于一般我们使用PAM模块来进行用户认证。该参数值请固定输入“pam”。

http request body

username

yes

string

接口用户名.该参数请在

http request body

password

yes

string

接口用户密码。该参数请

http request body



{
    "return":[
        {
            "perms":[
                ".*"
            ],
            "start":1478071527.775191,
            "token":"2da1bc07a9c3f140c790487996ca66100a87c8c5",
            "expire":1478114727.775192,
            "user":"saltapi",
            "eauth":"pam"
        }
    ]
}


字段

字段类型

字段说明

perms

列表

用户可以使用的saltstack模块和函数

starts

float

session token生效的的时间戳

token

 

string

用户通过验证后获取的session token

expire

float

session token失效的的时间戳

user

string

接口用户名

eauth

string

接口验证用户身份的方法



2)、使用获取的token发送命令请求:

[root@salt-master ~]#  curl -sSk https://localhost:8080    -H 'Accept: application/json'    -H 'X-Auth-Token:   5d59bdd8e1427dc44275a012f4ecd6c5dc44f7af' -d      "fun=state.sls&client=local&tgt=*&arg=baseline.os.sls.atom.disable_telnet"
 

参数

必填

类型

说明

参数输入位置

X-Auth-Token

yes

string

用户登录api后,api返回给用户的session token。请通过用户登录验证接口获取该值

http request header

tgt

yes

string

应用原子能力的目标主机

http request body

 

fun

yes

string

使用的saltstack 模块.函数。该参数请取值state.sls

http request body

arg

yes

string

指定该原子能力对应的state file

http request body

client

yes

string

local

http request body

expr_form

no

string

该值指定了目标主机的匹配模式。如果不指定,默认为glob。取值请见下面详细说明

http request body


    
"fun=state.sls&client=local&tgt=*&arg=baseline.os.sls.atom.disable_telnet" ,在命令行中 有这么一段字符串:该字符串中包含了我们输入给api的值。

其中,fun=state.sls代表我们使用state.sls 这个函数,tgt=* 代表目标主机,arg= baseline.os.sls.atom.disable_telnet      就是我们所说的参数,代表我们使用的state file。client=local代表以同步方式执行。通过上面的请求可以发现,我们要执行什么原子能力,是arg这个参数的值来决定的。故我们需要知道每个原子能力对应的arg参数值。要对那些目标主机执行原子能力是tgt这个参数的值决定的。Fun 的参数的值输入state.sls 即可,因为我们都是通过state file 去实现我们的原子能力的,而state file 这种方式就必须使用state.sls这个函数。

{    "return":[        {            "salt-minion1":{                "service_|-restart vsftpd service_|-vsftpd_|-running":{                    "comment":"The service vsftpd is already running",                    "name":"vsftpd",                    "start_time":"14:48:42.446673",                    "result":true,                    "duration":43.535,                    "__run_num__":2,                    "changes":{                    },                    "__id__":"restart vsftpd service"                },                "file_|-add prohibit anonymous login configuration_|-/etc/vsftpd/vsftpd.conf_|-append":{                    "comment":"File /etc/vsftpd/vsftpd.conf is in correct state",                    "pchanges":{                    },                    "name":"/etc/vsftpd/vsftpd.conf",                    "start_time":"14:48:42.415706",                    "result":true,                    "duration":16.213,                    "__run_num__":0,                    "changes":{                    },                    "__id__":"add prohibit anonymous login configuration"                },                "file_|-delete allow anonymous login configuration_|-/etc/vsftpd/vsftpd.conf_|-replace":{                    "comment":"No changes needed to be made",                    "pchanges":{                    },                    "name":"/etc/vsftpd/vsftpd.conf",                    "start_time":"14:48:42.432250",                    "result":true,                    "duration":3.798,                    "__run_num__":1,                    "changes":{                    },                    "__id__":"delete allow anonymous login configuration"                }            },            "salt-master":{                "test_|-vsftpd is not install_|-vsftpd is not install_|-nop":{                    "comment":"Success!",                    "name":"vsftpd is not install",                    "start_time":"14:49:04.098507",                    "result":true,                    "duration":0.646,                    "__run_num__":0,                    "changes":{                    },                    "__id__":"vsftpd is not install"                }            }        }    ]}



对于返回字段,作此说明:

字段

字段类型

字段说明

return

列表

它的值是每台目标主机执行结果的详细信息

name

string

对应该STATE DECLARATIONname参数值(一个STATE DECLARATION 对应一个子操作)

comment

string

执行结果描述信息

result

 

true:操作执行成功

false:操作执行失败

none操作为test mode

 

有多种可能导致result值为false ,具体可能请见下面说明

changes

字典

描述该项操作做了哪些配置变更

__run_num__

int

该子操作执行顺序

start_time

string

表示该子操作执行的开始时间

duration

float

表示该子操作花费的时间



关注每个目标主机对应执行结果中result字段的值,true 代表操作执行成功,false 代表失败。如果发现有result 为false的,我们即可定位哪台目标主机上的操作执行失败。

同时,关注返回结果中是否包含了所有的目标主机。通过api调用salt时,如果目标主机没有响应或者目标主机连接失败,则返回结果中没有对应目标主机的信息。例如,我们选中了对3台目标主机进行操作,但结果只返回了2台目标主机的结果,那我们就可以判断有一台目标主机没有响应或者目标主机连接失败。



3)、编写python脚本请求salt api接口

自定义一个类,首先初始化时候获得token,然后使用token认证去请求相应的json文件。
salt命令在shell中使用方式是salt 客户端 方法 参数(例子:salt 'client1' cmd.run 'free -m')。
这里salt命令方法我们已经封装好了,想使用salt的什么方法就传入对应的客户端、方法、参数即可。


#!/usr/bin/env python# _*_ coding:utf-8 _*___author__ = 'junxi'import requestsimport jsontry:    import cookielibexcept:    import http.cookiejar as cookielib# 使用urllib2请求https出错,做的设置import sslcontext = ssl._create_unverified_context()# 使用requests请求https出现警告,做的设置from requests.packages.urllib3.exceptions import InsecureRequestWarningrequests.packages.urllib3.disable_warnings(InsecureRequestWarning)salt_api = "https://172.16.0.19:8001/"class SaltApi:    """    定义salt api接口的类    初始化获得token    """    def __init__(self, url):        self.url = url        self.username = "saltapi"        self.password = "salt2017"        self.headers = {            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/50.0.2661.102 Safari/537.36",            "Content-type": "application/json"            # "Content-type": "application/x-yaml"        }        self.params = {'client': 'local', 'fun': '', 'tgt': ''}        # self.params = {'client': 'local', 'fun': '', 'tgt': '', 'arg': ''}        self.login_url = salt_api + "login"        self.login_params = {'username': self.username, 'password': self.password, 'eauth': 'pam'}        self.token = self.get_data(self.login_url, self.login_params)['token']        self.headers['X-Auth-Token'] = self.token    def get_data(self, url, params):        send_data = json.dumps(params)        request = requests.post(url, data=send_data, headers=self.headers, verify=False)        # response = request.text        # response = eval(response)     使用x-yaml格式时使用这个命令把回应的内容转换成字典        # print response        # print request        # print type(request)        response = request.json()        result = dict(response)        # print result        return result['return'][0]    def salt_command(self, tgt, method, arg=None):        """远程执行命令,相当于salt 'client1' cmd.run 'free -m'"""        if arg:            params = {'client': 'local', 'fun': method, 'tgt': tgt, 'arg': arg}        else:            params = {'client': 'local', 'fun': method, 'tgt': tgt}        print '命令参数: ', params        result = self.get_data(self.url, params)        return resultdef main():    print '=================='    print '同步执行命令'    salt = SaltApi(salt_api)    print salt.token    salt_client = '*'    salt_test = 'test.ping'    salt_method = 'cmd.run'    salt_params = 'free -m'    # print salt.salt_command(salt_client, salt_method, salt_params)    # 下面只是为了打印结果好看点    result1 = salt.salt_command(salt_client, salt_test)    for i in result1.keys():        print i, ': ', result1[i]    result2 = salt.salt_command(salt_client, salt_method, salt_params)    for i in result2.keys():        print i        print result2[i]        printif __name__ == '__main__':    main()