使用WebSocket实现与Kubernetes内部容器的交互式通信的故事
首先
作为Kubernetes Dashboard的一个功能,提供了Shell功能。该功能允许用户在浏览器中以交互方式执行容器内的命令。
假设我们在基础架构中使用Kubernetes,并提供一种让用户在其中创建容器的服务,为了提高用户的便利性,我们希望通过Shell访问容器。然而,有些情况下我们可能希望用户避免访问Kubernetes仪表板。因此,为了提供Shell功能,我们需要创建另一个客户端,并在与Kubernetes仪表板不同的路径下提供访问方式。
实现执行此类操作的客户端可通过模仿Kubernetes Dashboard的行为来实现。然而,有关此操作的信息很少,实现起来十分困难。因此,本文将介绍如何重新创建此类操作的方法,并带有示例程序的解释。
目标
我們將使用C#來創建一個傳輸和接收的程序,在我們預先配置好的Kubernetes容器內執行命令。這個程序可以以互動方式操作。
利用环境 – Making use of the environment
这次使用的环境如下。
-
- Windows 10 Home Edition
-
- VirtualBox 5.2.8
- Minikube 0.31.0
Minikube是一个用于开发和测试的Kubernetes环境。本次我们将在Minikube上创建一个包含httpd容器的Pod,并尝试访问该Pod。为了简化起见,假设Pod内只包含一个容器。
在容器中执行命令的方法。
用于访问Pod并执行命令的API。
Kubernetes提供了用于访问Pod并执行命令的API,格式如下:
/api/v1/namespaces/{namespace}/pods/{name}/exec
其中{namespace}表示Pod所在的命名空间,{name}表示要访问的Pod的名称。
通过在此API的末尾作为参数添加命令,可以执行任意命令。例如,如果要执行ls命令,则可以按以下格式调用API:
/api/v1/namespaces/{namespace}/pods/{name}/exec?command=ls&stdin=true&stderr=true&stdout=true&tty=true
对于单个命令执行,执行命令后将断开通信。如果要在建立通信的状态下执行交互式命令,可以执行bash命令如下:
/api/v1/namespaces/{namespace}/pods/{name}/exec?command=/bin/bash&stdin=true&stderr=true&stdout=true&tty=true
通过进行这种API调用,可以持续进行与Pod的交互式通信。需要注意的是stdin、stderr、stdout、tty参数默认为false,但为了进行双向通信,需要将其设为true。此外,如果Pod内存在多个容器,则需要指定容器名称作为附加参数。
通信协议
与容器的交互通信使用WebSocket协议。WebSocket是在RFC6455中定义的协议,用于实现客户端和服务器之间的双向通信。通信从带有Upgrade头的GET请求开始,经过握手后建立通信,并实现双向通信。URI方案使用ws,如果使用SSL/TLS,则使用wss。
数据格式在发送和接收信息时
使用上述的API和通信协议,可以访问Pod内的容器。然而,由于Kubernetes有其自己的通信协议定义,因此在通信时需要按照此协议进行数据的发送和接收。
-
- 送受信はバイト列で行われる。
-
- 全てのバイト列の先頭バイトとして、以下を挿入する。
0: stdin(標準入力)
1: stdout(標準出力)
2: stderr(標準エラー出力)
コマンドの末尾には改行コード(\n)が必要
如果想在容器中执行“ls”命令,根据这个协议,需要发送以下字节序列。
[0, 108, 115, 13] // 十进制ASCII编码序列,第一个字节代表stdin,其余表示“ls\n”。
将这个字节序列发送给已建立WebSocket通信的容器,作为响应,会返回以1开头的字节序列,表示stdout。
创建一个简单的收发程序。
开发环境和操作概述
我們利用之前提到的API、協議和數據格式,創建了一個簡單的發送和接收程序。開發環境如下所示。
-
- IDE: Visual Studio 2017 Community
-
- 言語: C#
- フレームワーク: .NET Core 2.1
我创建的程序的操作如下所示。
-
- ユーザはコンソールを介して、アクセス先のネームスペースおよびPod名を入力する。
-
- 入力された情報を利用し、Pod内のコンテナとのWebSocket通信を確立する。
-
- ユーザからのコマンド入力および、Kubernetesからの情報送信を待ち受ける
ユーザからのコマンド入力があった場合、入力されたコマンドをバイト列に変換後、Kubernetesに送信する。
Kubernetesからの情報送信があった場合、受け取ったバイト列を文字列に変換後、コンソールに表示する。
源代码
进行以上处理的源代码如下所示。由于这是一个测试程序,因此跳过了SSL证书检查和中间证书的验证。请注意,您可以使用kubectl cluster-info命令来确认Kubernetes的IP地址和端口。此外,访问所需的令牌是使用kube-system命名空间中的名为default-token的密钥来定义的。
static void Main(string[] args)
{
string k8sUri = "wss://IP:Port"; // kubectl cluster-infoで取得したもの
string token = "Token"; // kube-systemシークレット中のdefault tokenで指定されているもの
ClientWebSocket ws = new ClientWebSocket();
ws.Options.SetRequestHeader("Authorization", "Bearer " + token);
// SSL証明書の認証をスキップ
ServicePointManager.ServerCertificateValidationCallback
= (s, certificate, chain, sslPolicyError) => true;
// 中間証明書の認証をスキップ
ws.Options.RemoteCertificateValidationCallback
= (s, certificate, chain, sslPolicyErrors) => true;
Console.Write("Kubernetes namespace: ");
string k8sNamespace = Console.ReadLine();
Console.Write("Kubernetes pod name: ");
string k8sPodName = Console.ReadLine();
string apiUri = "/api/v1/namespaces/" + k8sNamespace + "/pods/" + k8sPodName
+ "/exec?command=/bin/bash&stdin=true&stderr=true&stdout=true&tty=true";
// kubernetesとの接続を確立
ws.ConnectAsync(new Uri(k8sUri + apiUri), CancellationToken.None).Wait();
// ユーザからの入力待受用スレッド
Task inputTask = new Task(() =>
{
while (true)
{
string command = Console.ReadLine(); // ユーザからの入力を待機
List<byte> commandBin = new List<byte>(Encoding.UTF8.GetBytes(command));
commandBin.Insert(0, 0);// stdin prefixを追加
commandBin.Add(13); // \nを追加
// 入力コマンドをArraySegmentに変換した後に送信
ArraySegment<byte> buff = new ArraySegment<byte>(commandBin.ToArray());
ws.SendAsync(buff, WebSocketMessageType.Text, true, CancellationToken.None).Wait();
}
});
inputTask.Start();
// Kubernetesからの通信待受
while (ws.State == WebSocketState.Open)
{
const int MessageBufferSize = 2048;
ArraySegment<byte> buff = new ArraySegment<byte>(new byte[MessageBufferSize]);
ws.ReceiveAsync(buff, CancellationToken.None).Wait(); // Kubernetesからの通信を待機
List<byte> receivedBytes = new List<byte>(buff);
receivedBytes.RemoveAt(0); // prefixを削除
receivedBytes.RemoveAll(b => b == 0); // 末尾の0x00を削除(余分に確保されたバッファ)
Console.Write(Encoding.UTF8.GetString(receivedBytes.ToArray()));
}
}
結尾
在本文中,我们介绍了如何使用Kubernetes的API在Pod中的容器内执行任意命令。通过应用这种方法,您可以在容器中执行命令,而无需使用Kubernetes Dashboard或kubectl命令。希望这对希望实现类似功能的人们有所帮助。
请参照以下内容进行本土化转述:
请提供中国人民参考的翻译供应。
Kubernetes API関連
Executing commands in Pods using K8s API | Red Hat OpenShift Blog:本記事の内容に近い記事で、参考になりました。
Kubernetes v1 REST API Reference | OpenShift Enterprise 3.0:API呼び出し時のパラメータの内容が説明されています。
.NET Coreを利用したアプリケーションの作成
Visual Studio 2017 での .NET Core を使用した C# Hello World アプリケーションの構築 | Microsoft Docs
Minikubeの概要および、導入方法
簡単にローカルKubernetes環境を構築できるツール「Minikube」 | さくらのナレッジ
minikube で Windows 上に Kubernetes 環境を構築する | takaya030の備忘録