Python使用SSH代理访问远程Docker

Docker 20.10.17

Python 2.7

1 前言

Python中有个叫docker-py的客户端库用来操作docker,关于docker-py的基本使用可以参考https://pypi.org/project/docker/。本篇文章主要记录一下前段时间工作中涉及到的一个问题的解决方案,仅供有同样需求的同学参考。这个问题就是:如何使用Python操作其它机器上远程的Docker服务?

使用过docker-py客户端的同学,肯定都知道,创建client实例的时候,需要在构造函数中传入base_url这个参数,或者指定指定环境变量DOCKER_HOST,例如:

import docker
client = docker.DockerClient(base_url='unix://var/run/docker.sock')

其中unix协议表示使用的是本地的UNIX Domain Socket,这是Docker用于同一台主机的进程通讯,顾名思义要想使用这种方式客户端需要跟Docker进程在同一台主机上。当然docker-py还支持别的协议,通过源码注释我们可以看到它还支持tcp的方式,例如:

import docker
client = docker.DockerClient(base_url='tcp://127.0.0.1:1234')

看到这里,上面的问题通过tcp访问远程的Docker服务不就可以了吗,但是这里需要注意的是,默认的Docker服务是不会启用TCP的监听端口的,需要在启动服务式做一些改造,修改启动脚本/etc/systemd/system/docker.service.d/tcp.conf

ExecStart=/usr/bin/dockerd -H unix:///var/run/docker.sock -H tcp://0.0.0.0:2375

这种方式需要修改Docker服务,而且暴露端口可能会引起一些安全问题,我们的生产环境中通常都是使用的默认的UNIX Domain Socket的方式,显然TCP的方式不适合我们,那还有什么方式呢?继续查看源码,我们看到,原来docker-py是支持ssh的,例如:

import docker
client = docker.DockerClient(base_url='ssh:/root@127.0.0.1:22')

当时我看到这里仿佛看到了新大路,之前的问题迎刃而解,但通过实验发现行不通

这需要docker-py客户端所在的机器要与Docker服务的机器进行免密登录,设置互信,显然这种方式也不是很合适。

那有没有那种不需要进行免密操作,又能通过ssh用户名密码进行连接呢?通过上面的报错信息,我们可以看到,docker-py的ssh协议底层使用的是paramiko,这个库熟悉啊,之前实现Python远程主机终端不就是用的这个库嘛,可以指定用户名密码连接的,再深挖一下源码一看:

稍微改造一下上面的代码,加上用户名密码的认证,我们就可以通过ssh访问远程主机了。所以本篇文章将通过两个方案实现访问远程docker:通过改造SSH用户密码认证访问远程Docker,通过SSH命令行隧道方式访问远程Docker。

2 通过改造SSH用户密码认证访问远程Docker

这种方式自己实现的代码逻辑比较简单,两个关键类需要重写SSHHTTPAdapter和DockerClient。思路是:

  1. 解析base_url中的用户密码信息
  2. 通过用户名密码创建SSH连接
  3. 让DockerClient使用我们创建的SSHHTTPAdapter

2.1 解析base_url

定义一个函数用来解析base_url,例如:ssh://Username:Password@127.0.0.1:22

def docker_urlparse(url):# fix for url with '#'mark = '_a5s7m3_'r = urlparse.urlparse(url.replace('#', mark))hostname = r.hostname.replace(mark, '#')username = r.username.replace(mark, '#')password = r.password.replace(mark, '#')port = r.portscheme = r.schemereturn {'hostname': hostname,'port': port,'username': username,'password': password,'scheme': scheme}

2.2 重写SSHHTTPAdapter

重写SSHHTTPAdapter的目的是能够通过用户名密码创建连接,主要是重写方法_create_paramiko_client

class SSHHTTPAdapter(transport.SSHHTTPAdapter):def __init__(self, ssh_params, timeout=60, pool_connections=DEFAULT_NUM_POOLS,max_pool_size=DEFAULT_MAX_POOL_SIZE, shell_out=False):self.ssh_params = ssh_paramsdel ssh_params['scheme']super(SSHHTTPAdapter, self).__init__('', timeout, pool_connections, max_pool_size, shell_out)def _create_paramiko_client(self, _):logging.getLogger("paramiko").setLevel(logging.WARNING)self.ssh_client = paramiko.SSHClient()ssh_config_file = os.path.expanduser("~/.ssh/config")if os.path.exists(ssh_config_file):conf = paramiko.SSHConfig()with open(ssh_config_file) as f:conf.parse(f)host_config = conf.lookup(self.ssh_params['hostname'])self.ssh_conf = host_configif 'proxycommand' in host_config:self.ssh_params["sock"] = paramiko.ProxyCommand(self.ssh_conf['proxycommand'])if 'hostname' in host_config:self.ssh_params['hostname'] = host_config['hostname']if self.ssh_params['port'] is None and 'port' in host_config:self.ssh_params['port'] = self.ssh_conf['port']if self.ssh_params['username'] is None and 'user' in host_config:self.ssh_params['username'] = self.ssh_conf['user']self.ssh_client.load_system_host_keys()self.ssh_client.set_missing_host_key_policy(paramiko.WarningPolicy())

2.3 重写DockerClient

使用自定义的SSHHTTPAdapter创建对应的adapter实例,然后绑定到DockerClient的api上,具体实现如下:

class SSHDockerClient(DockerClient):def __init__(self, *args, **kwargs):base_url = kwargs.get('base_url')ssh_params = docker_urlparse(base_url)adapter = SSHHTTPAdapter(ssh_params)kwargs['base_url'] = ''kwargs['version'] = MINIMUM_DOCKER_API_VERSIONsuper(SSHDockerClient, self).__init__(*args, **kwargs)self.api.mount('http+docker://ssh', adapter)self.api.base_url = 'http+docker://ssh'

2.4 验证测试

base_url格式:ssh://<用户名>:<密码>@<主机>:<端口>

client = SSHDockerClient(base_url='ssh://docker:xxxxx@127.0.0.1:22')
client.version()

3 通过SSH命令行隧道方式访问远程Docker

网上还有一些资料,是使用SSH命令随带的方式代理远程Docker的Unix Domain Socket,我一开始也是用的这种方式。

3.1 命令行使用

命令行执行SSH命令创建隧道

ssh -nNT -L /tmp/docker.sock:/var/run/docker.sock root@127.0.0.1

然后客户端直接这样用

import docker
client = docker.DockerClient(base_url='unix:///tmp/docker.sock')

3.2 使用代码封装命令

上面需要单独执行命令行,不符合需求,我们可以将命令用代码封装起来,在Python中使用subprocess单独启动一个命令行进程,然后再创建对应的客户端实例。思路很简单:

  1. 使用subprocess单独运行ssh命令
  2. 等待本地的/tmp/docker.sock可用
  3. 使用/tmp/docker.sock创建客户端实例

具体代码实现:

class ProxyDockerClient(DockerClient):remote_sock = '/var/run/docker.sock'local_socks_dir = '/tmp/docker-client-proxy'proxy_process = None  # type: subprocessdef __init__(self, *args, **kwargs):base_url = kwargs.get('base_url')ssh_params = docker_urlparse(base_url)kwargs['base_url'] = self.ssh_proxy(ssh_params)super(ProxyDockerClient, self).__init__(*args, **kwargs)def ssh_proxy(self, ssh_params):if ssh_params['scheme'] == 'ssh':local_sock = os.path.join(self.local_socks_dir, ssh_params['hostname'], os.path.basename(self.remote_sock))local_sock_dir = os.path.dirname(local_sock)shutil.rmtree(local_sock_dir, ignore_errors=True)os.makedirs(local_sock_dir)base_url = 'unix://%s' % local_sockself.proxy_process = subprocess.Popen(args=['sshpass', '-p', ssh_params['password'], 'ssh', '-nNT', '-L','%s:%s' % (local_sock, self.remote_sock),'%s@%s' % (ssh_params['username'], ssh_params['hostname'])],stdout=subprocess.PIPE, stderr=subprocess.STDOUT,close_fds=True)connected = self._wait_connectable(local_sock)if not connected:self.proxy_process.kill()raise RuntimeError("Can't connected!")return base_url@staticmethoddef _wait_connectable(sock_address, retries=20):sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)connected = Falseretry_times = 0while not connected and retry_times < retries:try:sock.connect(sock_address)connected = Trueexcept socket.error, msg:logging.error(msg)finally:retry_times += 1time.sleep(0.5)return connected

3.3 验证测试

base_url格式:ssh://<用户名>:<密码>@<主机>:<端口>

client = ProxyDockerClient(base_url='ssh://docker:xxxxx@127.0.0.1:22')
client.version()

注意:这种方式在CentOS下需要使用非root用户,并且将/var/run/docker.sock分配非root权限,否则会报错。

centos不能使用root用户 https://bugzilla.redhat.com/show_bug.cgi?id=1527565
https://rancher.com/docs/rke/latest/en/troubleshooting/ssh-connectivity-errors/

4 总结

对比两种方案的实现,建议使用第一种,第二种是我最早一版本的实现,当时测试机器是Ubuntu,并没有发现问问题,后来切换到CentOS出现问题了,排查了很久才找到原因。关于**如何使用Python操作其它机器上远程的Docker服务?**这个问题的解决方案就总结这么多,同学们有什么更好的方案可以一块交流学习。

Python使用SSH代理访问远程Docker相关推荐

  1. python通过代理访问网页_【已解决】Python中使用代理访问网络

    [问题] 在用Python的urllib2等库,访问网络,发现某些网址访问很慢,比如: 但是,当使用代理(此处用的是gae)后,发现访问速度就快很多了. 所以,希望给Python的访问网络,增加代理的 ...

  2. 关于termux在手机上搭载Linux系统,python,ssh

    之前在学Linux的时候无意间接触到了termux,下面聊聊它!! 本节内容 01 关于termux 02 手机上termux的安装 03 在termux装linux 04 利用termux装pyth ...

  3. Putty通过ssh代理连接远程服务

    版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/catoop/article/details/81478332 Putty通过ssh代理连接远程跨网络 ...

  4. 在使用反向代理访问的服务器上配置远程jupyterNotebook

    在使用反向代理访问的服务器上配置远程jupyterNotebook 使用场景 配置流程 简化的访问方式 使用场景 自己的电脑没有GPU或者配置较低,想要使用远程的linux服务器进行深度学习,而且需要 ...

  5. SSH远程管理,构建密钥对验证的SSH体系,设置SSH代理功能。

    SSH服务及配置文件 SSH(Secure Shell)是一种安全通道协议,主要用来实现字符界面的远程登录,对通信双方的数据实行了加密处理,提供了更好的安全性.OpenSSH是实现SSH协议的开源软件 ...

  6. python os.remove拒绝访问_「进阶Python」第八讲:代理模式

    本文完整代码请查看github项目:advance-python,或者直接访问链接: https://github.com/Jackpopc/advance-python/blob/master/6- ...

  7. arm服务器获取文件路径中文,ssh 访问远程服务器文件路径

    ssh 访问远程服务器文件路径 内容精选 换一换 在IntelliJ上选择"项目",找到".idea"文件夹,单击右键选择"新建>文件" ...

  8. 远程服务器的url怎么配置文件,Linux常用命令(5)--SSH访问远程服务器、SCP服务器间文件拷贝...

    一.使用"ssh"命令,登录访问远程服务器 Linux是一个支持多用户操作的系统,在同一时刻允许多个用户同时访问系统,共享系统提供的服务.那么用户如何从不同的机器上访问同一台Lin ...

  9. linux ssh禁止用户访问任何目录,怎么限制远程ssh用户访问特定的文件

    比如我要实现以下目标,通过配置linux限制SSH用户指定目录 user 1 只可以访问 /Media, /Documents以及它的家目录 User 2 只可以访问/Folder21, 以及它的家目 ...

最新文章

  1. 与太原工业学院商讨第十七届全国大学生智能车华北赛区承办事宜
  2. java读取文件替换字符,跳槽薪资翻倍
  3. MySql入门知识(一)
  4. JavaScript在发送ajax请求时,URL域名地址是使用绝对地址还是相对地址?什么是浏览器跨域访问操作,js如何实现?
  5. MyGeneration【ui-原】
  6. hasOwnProperty()
  7. 设计模式之单件模式(Singleton Pattern)
  8. 【已解决】手机“此设备已安装证书授权中心,您的安全网络流量可能被监控”怎么办?
  9. (一)apache atlas源代码编译与打包
  10. 设计模式(5)——单例模式的七种实现方式
  11. 数据库系统概念 第五章 习题答案
  12. 干货!国外关于高速PCB设计的技术书籍和资料介绍
  13. 华为交换机console口如何设置密码
  14. C#中 OUT 的用法
  15. 程序员代码面试指南 CD101 单调栈结构
  16. Java开源 之随机生成中文姓名,手机号,邮编,住址
  17. 南非世界杯 小组赛 荷兰vs丹麦
  18. win7讲述人修复_win7讲述人无法正常启动
  19. P3375 【模板】KMP字符串匹配(woc我太想她了)
  20. VUE学习-脚手架vue cli(六)

热门文章

  1. 360搜索彩蛋自动化开发实践
  2. USB芯片完成的工作。
  3. C++ 删除指定字符串中的某些字符
  4. 鸿蒙天钟壁纸,AI加持的另类彩电之华为智慧屏带你体验鸿蒙
  5. 用CSS添加鼠标样式
  6. SSM框架整合+配置
  7. Web前端-2-html-列表
  8. 【Acwing】第57场周赛 题解
  9. IOderWire2012(IP公务电话系统)
  10. 龙珠直播前50信息爬取并保存图片和天池排名爬取