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