Salt Master和Minions之间可以通过SSH方式进行通信。 Salt SSH系统不会取代原有的通信系统,只是提供另外一种可选的方法不需要安装ZeroMQ和agent。但是执行效率没有ZeroMQ快。
/usr/bin/salt-ssh
#!/usr/bin/python
# EASY-INSTALL-ENTRY-SCRIPT: 'salt==2014.7.0','console_scripts','salt-ssh'
__requires__ = 'salt==2014.7.0'
import sys
from pkg_resources import load_entry_point
if __name__ == '__main__':
sys.exit(
load_entry_point('salt==2014.7.0', 'console_scripts', 'salt-ssh')()
)
/usr/lib/python2.6/site-packages/salt/scripts.py
def salt_ssh():
'''
Execute the salt-ssh system
'''
if '' in sys.path:
sys.path.remove('')
client = None
try:
client = salt.cli.SaltSSH()
client.run()
except KeyboardInterrupt, err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit('\nExiting gracefully on Ctrl-c'),
err,
hardcrash, trace=trace)
except salt.exceptions.SaltClientError as err:
trace = traceback.format_exc()
try:
hardcrash = client.options.hard_crash
except (AttributeError, KeyError):
hardcrash = False
_handle_interrupt(
SystemExit(err),
err,
hardcrash, trace=trace)
/usr/lib/python2.6/site-packages/salt/cli/__init__.py
class SaltSSH(parsers.SaltSSHOptionParser):
'''
Used to Execute the salt ssh routine
'''
def run(self):
self.parse_args()
ssh = salt.client.ssh.SSH(self.config)
ssh.run()
/usr/lib/python2.6/site-packages/salt/client/ssh/__init__.py
class SSH(object):
'''
Create an SSH execution system
'''
def __init__(self, opts):
pull_sock = os.path.join(opts['sock_dir'], 'master_event_pull.ipc')
if os.path.isfile(pull_sock) and HAS_ZMQ:
self.event = salt.utils.event.get_event(
'master',
listen=False)
else:
self.event = None
self.opts = opts
self.opts['_ssh_version'] = ssh_version()
self.tgt_type = self.opts['selected_target_option'] \
if self.opts['selected_target_option'] else 'glob'
self.roster = salt.roster.Roster(opts, opts.get('roster'))
self.targets = self.roster.targets(
self.opts['tgt'],
self.tgt_type)
priv = self.opts.get(
'ssh_priv',
os.path.join(
self.opts['pki_dir'],
'ssh',
'salt-ssh.rsa'
)
)
if not os.path.isfile(priv):
try:
salt.client.ssh.shell.gen_key(priv)
except OSError:
self.defaults = {
'user': self.opts.get(
'ssh_user',
salt.config.DEFAULT_MASTER_OPTS['ssh_user']
),
'port': self.opts.get(
'ssh_port',
salt.config.DEFAULT_MASTER_OPTS['ssh_port']
),
'passwd': self.opts.get(
'ssh_passwd',
salt.config.DEFAULT_MASTER_OPTS['ssh_passwd']
),
'priv': priv,
'timeout': self.opts.get(
'ssh_timeout',
salt.config.DEFAULT_MASTER_OPTS['ssh_timeout']
) + self.opts.get(
'timeout',
salt.config.DEFAULT_MASTER_OPTS['timeout']
),
'sudo': self.opts.get(
'ssh_sudo',
salt.config.DEFAULT_MASTER_OPTS['ssh_sudo']
),
}
if self.opts.get('rand_thin_dir'):
self.defaults['thin_dir'] = os.path.join(
'/tmp',
'.{0}'.format(uuid.uuid4().hex[:6]))
self.opts['wipe_ssh'] = 'True'
self.serial = salt.payload.Serial(opts)
self.returners = salt.loader.returners(self.opts, {})
self.fsclient = salt.fileclient.FSClient(self.opts)
self.mods = mod_data(self.fsclient)
def get_pubkey(self):
'''
Return the key string for the SSH public key
'''
priv = self.opts.get(
'ssh_priv',
os.path.join(
self.opts['pki_dir'],
'ssh',
'salt-ssh.rsa'
)
)
pub = '{0}.pub'.format(priv)
with open(pub, 'r') as fp_:
return '{0} rsa root@master'.format(fp_.read().split()[1])
def key_deploy(self, host, ret):
'''
Deploy the SSH key if the minions don't auth
'''
if not isinstance(ret[host], dict):
if self.opts.get('ssh_key_deploy'):
target = self.targets[host]
if 'passwd' in target:
self._key_deploy_run(host, target, False)
return ret
if ret[host].get('stderr', '').count('Permission denied'):
target = self.targets[host]
# permission denied, attempt to auto deploy ssh key
print(('Permission denied for host {0}, do you want to deploy '
'the salt-ssh key? (password required):').format(host))
deploy = raw_input('[Y/n] ')
if deploy.startswith(('n', 'N')):
return ret
target['passwd'] = getpass.getpass(
'Password for {0}@{1}: '.format(target['user'], host)
)
return self._key_deploy_run(host, target, True)
return ret
def _key_deploy_run(self, host, target, re_run=True):
'''
The ssh-copy-id routine
'''
argv = [
'ssh.set_auth_key',
target.get('user', 'root'),
self.get_pubkey(),
]
single = Single(
self.opts,
argv,
host,
mods=self.mods,
fsclient=self.fsclient,
**target)
if salt.utils.which('ssh-copy-id'):
# we have ssh-copy-id, use it!
stdout, stderr, retcode = single.shell.copy_id()
else:
stdout, stderr, retcode = single.run()
if re_run:
target.pop('passwd')
single = Single(
self.opts,
self.opts['argv'],
host,
mods=self.mods,
fsclient=self.fsclient,
**target)
stdout, stderr, retcode = single.cmd_block()
try:
data = salt.utils.find_json(stdout)
return {host: data.get('local', data)}
except Exception:
if stderr:
return {host: stderr}
return {host: 'Bad Return'}
if os.EX_OK != retcode:
return {host: stderr}
return {host: stdout}
def handle_routine(self, que, opts, host, target):
'''
Run the routine in a "Thread", put a dict on the queue
'''
opts = copy.deepcopy(opts)
single = Single(
opts,
opts['argv'],
host,
mods=self.mods,
fsclient=self.fsclient,
**target)
ret = {'id': single.id}
stdout, stderr, retcode = single.run()
# This job is done, yield
try:
data = salt.utils.find_json(stdout)
if len(data) < 2 and 'local' in data:
ret['ret'] = data['local']
else:
ret['ret'] = {
'stdout': stdout,
'stderr': stderr,
'retcode': retcode,
}
except Exception:
ret['ret'] = {
'stdout': stdout,
'stderr': stderr,
'retcode': retcode,
}
que.put(ret)
def handle_ssh(self):
'''
Spin up the needed threads or processes and execute the subsequent
routines
'''
que = multiprocessing.Queue()
running = {}
target_iter = self.targets.__iter__()
returned = set()
rets = set()
init = False
if not self.targets:
raise salt.exceptions.SaltClientError('No matching targets found in roster.')
while True:
if len(running) < self.opts.get('ssh_max_procs', 25) and not init:
try:
host = next(target_iter)
except StopIteration:
init = True
continue
for default in self.defaults:
if default not in self.targets[host]:
self.targets[host][default] = self.defaults[default]
args = (
que,
self.opts,
host,
self.targets[host],
)
routine = multiprocessing.Process(
target=self.handle_routine,
args=args)
routine.start()
running[host] = {'thread': routine}
continue
ret = {}
try:
ret = que.get(False)
if 'id' in ret:
returned.add(ret['id'])
except Exception:
pass
for host in running:
if host in returned:
if not running[host]['thread'].is_alive():
running[host]['thread'].join()
rets.add(host)
for host in rets:
if host in running:
running.pop(host)
if ret:
if not isinstance(ret, dict):
continue
yield {ret['id']: ret['ret']}
if len(rets) >= len(self.targets):
break
def run_iter(self):
'''
Execute and yield returns as they come in, do not print to the display
'''
for ret in self.handle_ssh():
yield ret
def cache_job(self, jid, id_, ret):
'''
Cache the job information
'''
self.returners['{0}.returner'.format(self.opts['master_job_cache'])]({'jid': jid,
'id': id_,
'return': ret})
def run(self):
'''
Execute the overall routine
'''
fstr = '{0}.prep_jid'.format(self.opts['master_job_cache'])
jid = self.returners[fstr]()
# Save the invocation information
argv = self.opts['argv']
if self.opts['raw_shell']:
fun = 'ssh._raw'
args = argv
else:
fun = argv[0] if argv else ''
args = argv[1:]
job_load = {
'jid': jid,
'tgt_type': self.tgt_type,
'tgt': self.opts['tgt'],
'user': self.opts['user'],
'fun': fun,
'arg': args,
}
# save load to the master job cache
self.returners['{0}.save_load'.format(self.opts['master_job_cache'])](jid, job_load)
if self.opts.get('verbose'):
msg = 'Executing job with jid {0}'.format(jid)
print(msg)
print('-' * len(msg) + '\n')
print('')
sret = {}
outputter = self.opts.get('output', 'nested')
for ret in self.handle_ssh():
host = ret.keys()[0]
self.cache_job(jid, host, ret[host])
ret = self.key_deploy(host, ret)
if not isinstance(ret[host], dict):
p_data = {host: ret[host]}
elif 'return' not in ret[host]:
p_data = ret
else:
outputter = ret[host].get('out', self.opts.get('output', 'nested'))
p_data = {host: ret[host].get('return', {})}
if self.opts.get('static'):
sret.update(p_data)
else:
salt.output.display_output(
p_data,
outputter,
self.opts)
if self.event:
self.event.fire_event(
ret,
salt.utils.event.tagify(
[jid, 'ret', host],
'job'))
if self.opts.get('static'):
salt.output.display_output(
sret,
outputter,
self.opts)
使用案例:
在Master端创建/etc/salt/roster
web1: 10.10.41.20
web1: host: 192.168.42.1 # The IP addr or DNS hostname user: fred # Remote executions will be executed as user fred passwd: foobarbaz # The password to use for login, if omitted, keys are used sudo: True # Whether to sudo to root, not enabled by default web2: host: 192.168.42.2
$ sudo salt-ssh -i '*' test.ping
Permission denied for host web1, do you want to deploy the salt-ssh key? (passwor
[Y/n] Y
Password for caribbean@web1:
web1:
True
You have mail in /var/spool/mail/caribbean
$ sudo salt-ssh -i '*' test.ping
web1:
True
默认情况下,salt-ssh在远程主机上运行salt执行模块,但是salt-ssh也可以直接运行原始shell命令
使用salt-ssh -r 参数
$ sudo salt-ssh '*' -r "/sbin/ifconfig"
web1:
----------
retcode:
0
stderr:
stdout:
eth0 Link encap:Ethernet HWaddr A2:C6:11:90:86:AD
inet addr:10.10.41.20 Bcast:10.10.41.255 Mask:255.255.255.0
inet6 addr: fe80::a0c6:11ff:fe90:86ad/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:455751942 errors:0 dropped:0 overruns:0 frame:0
TX packets:147447673 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:167565483715 (156.0 GiB) TX bytes:73994627760 (68.9 GiB)
Interrupt:23
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:16436 Metric:1
RX packets:1125415778 errors:0 dropped:0 overruns:0 frame:0
TX packets:1125415778 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:291575575309 (271.5 GiB) TX bytes:291575575309 (271.5 GiB)
Salt状态系统也可以用于salt-ssh, SLS文件编写方式一样
salt-ssh: config_dir: path/to/config/dir max_prox: 30 wipe_ssh: true
salt-ssh --config-dir=path/to/config/dir --max-procs=30 --wipe \* test.ping
you can callsalt-ssh \* test.ping
.
参考资料:
http://docs.saltstack.com/en/2014.7/topics/ssh/index.html
转载于:https://blog.51cto.com/john88wang/1660495