使用WebSocket实现与Kubernetes内部容器的交互式通信的故事

首先

k8s_dashboard.png

作为Kubernetes Dashboard的一个功能,提供了Shell功能。该功能允许用户在浏览器中以交互方式执行容器内的命令。

k8s_shell.gif

假设我们在基础架构中使用Kubernetes,并提供一种让用户在其中创建容器的服务,为了提高用户的便利性,我们希望通过Shell访问容器。然而,有些情况下我们可能希望用户避免访问Kubernetes仪表板。因此,为了提供Shell功能,我们需要创建另一个客户端,并在与Kubernetes仪表板不同的路径下提供访问方式。

undefined

实现执行此类操作的客户端可通过模仿Kubernetes Dashboard的行为来实现。然而,有关此操作的信息很少,实现起来十分困难。因此,本文将介绍如何重新创建此类操作的方法,并带有示例程序的解释。

目标

我們將使用C#來創建一個傳輸和接收的程序,在我們預先配置好的Kubernetes容器內執行命令。這個程序可以以互動方式操作。

dotnet_shell.gif

利用环境 – 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()));
    }
}
dotnet_shell.gif

結尾

在本文中,我们介绍了如何使用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の備忘録

广告
将在 10 秒后关闭
bannerAds