Kubernetes exec API串接分析

本篇将说明Kubernetes exec API的运作方式,并以简单范例进行开发在前后端上。虽然Kubernetes提供了不同资源的RESTful API来进行CRUD操作,但是部分API并非单纯的回传一个资料,有些是需要透过SPDY或WebSocket建立长连线串流,这种API以exec,attach为主,目标是对一个Pod执行指定指令,或者进入该Pod进行互动等等。

Exec API端点

首先了解一下Kubernetes exec API端点,由于Kubernetes官方文件并未提供相关资讯,因此这边透过kubectl指令来了解API的结构:

$ cat <<EOF | kubectl create -f -
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
spec:
containers:
- name: ubuntu
image: ubuntu:16.04
command: ['/bin/bash', '-c', 'while :; do echo Hello; sleep 1; done ']
EOF

$ kubectl -v = 8 exec -ti ubuntu bash
...
I0625 10:39:33.716271 93099 round_trippers.go:383] POST https://xxx.xxx.xxx.xxx:8443/api/v1/namespaces/default/ pods / ubuntu / execcommand = bash&container = ubuntu&container = ubuntu&stdin = true&stdout = true&tty = true
...

 

从上述得知exec API结构大致如下图所示:

其中API中的查询又可细分以下资讯:

  • command:将被执行的指令。若指令为ping 8.8.8.8,则API为command=ping&command=8.8.8.8。类型为string值。
  • container:哪个容器将被执行指令。若Pod只有一个容器,一般会用API找出名称塞到该参数中,若多个则选择让人输入名称。类型为string值。
  • stdin:是否开启标准输入,通常由使用者决定是否开启。类型为bool值。
  • stdout:是否开启标准输出,通常是預設開啟。类型为bool值。
  • stderr:是否开启标准错误输出,通常是預設開啟。类型为bool值。
  • tty:是否分配一个终端设备(Pseudo TTY,PTY)。ㄒ为bool值。

协议

执行是利用SPDY与WebSocket协定进行串流沟通的API,其中SPDY在Kubernetes官方的client-go已经有实现(参考远程命令),而kubectl正是使用SPDY,但是是SPDY目前已经规划在未来将被移弃,因此建议选择使用WebSocket来作为串流沟通。但而无论是使用哪一个协定,都要注意请求的Header必须有Connection: UpgradeUpgrade: xxx等,不然API Server会拒绝存取请求。

HTTP标头

除了SPDY与WebSocket所需要的Headers(如升级等)外,使用者与开发者还必须提供两个Headers来确保能够正确授权并沟通:

  • 授权:该Header是用来提供给API Server做认证请求的资讯,通常会是以Authorization: Bearer <token>的形式。
  • 接受:指定客户端能够接收的内容类型,一般为Accept: application/json,若输入不支持的类型将会被API以406 Not Acceptable拒绝请求。

沟通协定

一旦符合上述所有资讯后,WebSocket(或SPDY)就能够建立连线,并且与API服务器进行沟通。而当写入WebSocket时,资料将被传送到标准输入(stdin),而WebSocket的接收将会是标准输出(stdout)与输出错误(stderr).Kubernetes API服务器简单定义了一个协定来复用stdout与stderr。因此可以理解当WebSocket建立连线后,传送资料时需要再缓冲的第一个字元定义为stdin(buf [0] = 0),而接收资料时要判断stdout(buf [0] = 1)与stderr(buf [0] = 2)。其资讯如下:

标准串流
0 标准输入
1 标准输出
2 标准错误

简单下面以发送ls指令为例:

#传送`ls`指令,必须buf [0]自行塞入0字元来表示stdin.buf
= [0 108 115 10]

#接收
BUF = [1 108 115 13 10 27 91 48 109 27 91 ...]

最后需要注意Timeout问题,由于可能对WebSocket设置TCP Timeout,因此建议每一段时间发送一个stdin空讯息来保持连线。

透过WebSocket 实现Kubernetes Exec Terminal

一般我们在操作K8S (Kubernetes) 都是透过kubectl 命令,其实kubectl 所有操作都是呼叫K8S 提供的标准WebService API,然而有时候需要进Container Debug 的时候就需要透过exec 功能。有用过K8S Dashboard 应该也知道,管理者可以任意进入某一个Container Terminal,其实就是透过exec sh command 来实现。我一直很好奇K8S Dashboard 要如何在Web 实现这样的功能,查一下果然不出所料有一个WebSocket API 可以使用,于是开始研究如何自己实现像是Dashboard Terminal 这样的功能。

K8S Exec Command API WebSocket 串接

我今天的目的是要在Web做出跟K8S Dashboard Terminal一样的功能,其中必须透过WebSocker API进行串接。这一个Exec API官方说明相当少,其实有一个Kubernetes Project,叫做container-terminal  已经有实做一样的功能,但是我实际测试的时候并不work,因为K8S API呼叫的Token必须透过HTTP Header传递,然而标准的HTML5并没有夹带自订Header的方法,container-terminal所使用在Query String夹带access_token并不是标准作法。

我按下F12 光明正大偷看了K8S Dashboard 的作法,发现是透过Cookie 自行验证WebSocket 权限,看来要透过HTML5 标准来呼叫API 就要自己实做Server 了。

开始以前,我们先看看只有一条IO特性的WebSocket如何处理exec呢?其实exec就是命令呼叫,所有的程式在Linux执行一个程序都是一样的,程序启动时会分配三个档案描述子(File Descriptor),分别是StdIn (标准输入), Std Out (标准输出), Std Error Out (标准错误输出),当我们的WebSocket成功Upgrade为TCP/IP之后,K8S API必须提供一个协定来处理这三种标准描述子的收发工作。区分方法就是在资料最前端加入一个Byte,如下:

Byte 0 : 标准输入

Byte 1 : 标准输出

Byte 2 : 标准错误输出

有了这些资讯我们就可以开始实做了WebSocket 串接了。

透过WebSocket 串接K8S Exec API 实现Terminal

由于刚刚提到HTML5 WebSocket标准并不提供自订的HTTP Header,因此我的想法是在Server端透过NodeJS夹带Token来连线K8S Exec WebSocket API,因为不在Browser的NodeJS就可以自订Request Header好通过K8S的验证机制。然后另外启动一个WebSocket Server提供浏览器进行连线,当然这里的认证机制要自己实做了,可以用Cookie/Session控制即可,这样一来也不需要暴露API Token。总而言之就是一个左手接右手传的机制,这里的终端机介面移植container-terminal用了xterm.js  套件,跑起来的画面如下:

Kubernetes Exec Terminal

这样一来就可以在浏览器操作你的Container,由于是命令模式并非VM 那种传整个画面的终端机,所以用起来貌似飞快,速度就跟平常使用SSH 差不多速度,满酷炫的。

范例程式码在GitHub 有兴趣请自己玩看看啰。

https://github.com/samejack/web-k8s-exec

此外,上述提到的K8S API WebSocket 协定,其实用在Log API 也是一样的,就可以在Web 做出logs 画面的即时输出,也是很方便。闪电分享结束.......下台一鞠躬~

posted @ 2019-05-22 12:52  DaisyLinux  阅读(3572)  评论(1编辑  收藏  举报