作者 | 黄超    


640?wx_fmt=jpeg

杏仁运维工程师,关注容器技术和自动化运维。

一. 前言

在微服务大行其道的今天,容器恰巧又是微服务的主要载体,所以我们操作的对象也由最开始的「物理机」到「虚拟机」再到今天的「容器」。由于这些载体的变更,我们的使用方式也需要随之发生一些改变。比如一个最常用的登入操作,「虚拟机」下我们可能通过 ssh 的方式 ,但如果是容器呢?ssh 的方式就需要在每个容器中都运行一个 sshd 进程,这种做法可行但略显繁琐,也不太符合一个容器只运行一个进程的思想。

那么有没有一个即方便快捷又安全的登入方式呢?

有,通过 Web Terminal 的方式,通过 Web 的方式即可以避免对客户端的依赖又能够实现用户权限控制。目前,有很多开源的 Web Terminal 的项目,基本上都是通过 ssh 代理的方式调用并返回一个 shell 的虚拟终端(pty)。

640?wx_fmt=png

二. 实现容器的 Web Terminal

2.1 架构图

640?wx_fmt=png

2.2 前端 Web Termianl 页面

Linux 终端返回的内容会带很多特殊的字符,比如我输入一个 ls 指令,终端返回的结果如下:

'l'
's'
'\r\n'
'\x1b[0;0mRUNNING_PID\x1b[0m  \x1b[1;34mbin\x1b[0m          
\x1b[1;34mconf\x1b[0m         \x1b[1;34mlib\x1b[0m\r\nbash-4.3# '

这样我们就需要自己做穷举处理了,这里推荐使用一款的模拟 Terminal 的 JavaScript 库 xterm.js。这个库已经帮我们做了这些复杂操作。

<script>
  var term = new Terminal();
  term.open(document.getElementById('terminal'));
  term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
</script>

可以看到它已经将 \x1B[1;3;31mxterm.js\x1B[0m 这些特殊字符变成了红色:

640?wx_fmt=png

2.3 调用 Docker Daemon API 返回 Shell 虚拟终端

在平常的命令行操作下,我们经常会使用 docker exec -i -t <container_id> /bin/sh 来模拟一个 Shell 的伪终端。在 Web Terminal 实现里,我们需要通过 API 调用的方式来实现同样的操作。当然,我们首先要确保 Docker Daemon 的远程调用是开启的。

  1. 先调用 execCreate 来创建一个 Exec。在调用时,需要指定TtyAttachStdinAttachStdout AttachStderr 参数均为 true,Cmd 参数为 bash,这样才能获得 bash 进程的标准输入输出和错误

    request

    POST /v1.24/containers/e90e34656806/exec HTTP/1.1
    Content-Type: application/json
    
    {
      "AttachStdin": true,
      "AttachStdout": true,
      "AttachStderr": true,
      "Cmd": ["sh"],
      "DetachKeys": "ctrl-p,ctrl-q",
      "Tty": true,
      ...
    }
  2. 如果调用 execCreate 成功,调用请求会返回该 Exec 的 ID,根据这个 ID 继续调用execStart 接口。在调用时,需要指定 Detach 为 False,Tty 为 True,这样才会返回一个 HTTP 的 stream:

    request

    POST /v1.24/exec/e90e34656806/start HTTP/1.1
    Content-Type: application/json
    
    {
     "Detach": false,
     "Tty": true
    }

    response:

    HTTP/1.1 200 OK
    Content-Type: application/vnd.docker.raw-stream
    
    {{ STREAM }}
2.4 d-terminal

d-terminal 是这个系统的核心,它分成两个部分:

  1. 一部分用于处理用户端的输入和输出,以及存储和展示后端 Docker Dameon 主机的 IP 和 container_id。因为像 top 这样的监控命令需要服务端定时推送数据给客户端,所以使用了 WebSocket 协议以支持服务端推送。

  2. 另一部分用于调用 Docker Daemon 返回虚拟终端。对于终端来说,通常是你输入一个字符就会立马返回,直到你输入一个 "归位键" 终端才会把你输入的字符拼接成一个字符串并发送给 shell 解释器,并将 shell 解释器的结果返回。为了提升使用流畅性,新启用了一个线程去调用 Docker Daemon API,当然也可以使用像 epoll 这样的多路复用技术来实现。

640?wx_fmt=png

d-terminal 是使用 Python 实现的 Web 应用,核心代码如下:

@sockets.route('/echo')
def echo_socket(ws):
    ...
    # 调用 Docker API 返回一个虚拟终端
    docker_daemon_sock = get_tty()._sock
    # 启动一个与 Docker Daemon 通讯的子线
    docker_daemon_sock_thd = DockerDaemonSock(ws, docker_daemon_sock)
    docker_daemon_sock_thd.start()

    while not ws.closed:
        message = ws.receive() # 接收 terminal 的输入
        # 将用户的输入发送那个 docker daemon
        docker_daemon_sock.send(bytes(message, encoding='utf-8'))
        

# 子线程 DockerDaemonSock 类
class DockerDaemonSock(threading.Thread):
    def __init__(self, ws, docker_daemon_sock):
        super(DockerDaemonSock, self).__init__()
        self.ws = ws
        self.docker_daemon_sock = docker_daemon_sock

    def run(self):
        while not self.ws.closed:
            try:
                # 接收 docker daemon 的返回
                resp = self.docker_daemon_sock.recv(2048)
                if resp:
                    # 将 docker daemon 的返回发送给前端 terminal
                    self.ws.send(str(resp, encoding='utf-8'))
                else:
                    print("docker socket closed.")
                    self.ws.close()
            except Exception as e:
                print("docker termial socket error: {}".format(e))
                self.ws.close()

三. 总结

上述仅仅是描述了一个最基本的实现,完全是为了抛砖引玉,后续可以通过在中间层添加一些扩展,比如,用户权限的分配,与自己环境中的容器编排引擎集成等,最终作为 Pass 平台的一个基础的组成部分。

最后,上述的 demo 可去 github 具体查看。效果如下:

640?wx_fmt=gif

四. 参考

  • 一种新的进入容器的方式: WebSocket + Docker Remote API : http://www.yunweipai.com/archives/10449.html


全文完


以下文章您可能也会感兴趣:


我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。

640?wx_fmt=png


杏仁技术站

长按左侧二维码关注我们,这里有一群热血青年期待着与您相会。



GitHub 加速计划 / term / terminal
94.53 K
8.17 K
下载
The new Windows Terminal and the original Windows console host, all in the same place!
最近提交(Master分支:2 个月前 )
d04381ec "HighContrast" is not a possible requested theme. So `_UpdateBackgroundForMica()` would force the settings UI to be light or dark. To fix this, we just check if we're in high contrast mode and, if so, we don't bother setting the requested theme. 12 天前
e83434ff Turns out that having the styles for the KeyChordText and ParsedCommandLineText be empty for high contrast mode caused the issue. Since we're already using theme resources for the colors, we automatically adjust properly to whatever the high contrast theme is (Thanks XAML!). Bonus points: - we didn't need the theme dictionaries anymore, so I just moved them to the ResourceDictionary directly - ParsedCommandLineTextBlockStyle isn't used. So I removed it altogether. Validated command palette with multiple high contrast themes. See PR thread for demo. Closes #17914 12 天前
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐