自己動手實現一個 kubectl exec
kubectl exec
可以說是非常高頻使用的,如果你想自己瞭解相關原理,不妨自己動手寫一個。
知識儲備:
-
websocket 阮一峯這篇《WebSocket 教程 - 阮一峯的網絡日誌》寫的比較詳細。
-
kubectl exec 原理
-
https://itnext.io/how-it-works-kubectl-exec-e31325daa910
-
https://erkanerol.github.io/post/how-kubectl-exec-works/
如果你英文閱讀能還可以,這兩篇文章從原理方面介紹了 exec 是如何工作的。
瞭解了以上知識之後,接下來我們就開始動手吧。
首先來初始化一下項目,這裏使用 go mod 作爲依賴管理工具。k8s 的 client-go 對機器版本是有要求的,所以在初始化的時候最好去官方那邊找一下可用的版本。如果遇到mod/k8s.io/client-go@v10.0.0+incompatible/kubernetes/scheme/register.go:22:2: unknown import path "k8s.io/api/admissionregistration /v1alpha1": cannot find module providing package k8s.io/api/admissionregistration/v1alpha1
這種報錯,可以嘗試強制指定版本,這個也是從 kubebuilder 那裏學到的。
go mod init k8sdemo
module k8sdemo
go 1.13
require (
github.com/gorilla/websocket v1.4.2
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586
k8s.io/api v0.17.2
k8s.io/apimachinery v0.17.2
k8s.io/client-go v0.17.2
)
client-go 的 example 目錄也有相關對象的 CURD 示例,我們可以先從這裏入手,先熟悉相關操作,可以看到首先從 kuebconfig 讀取配置,然後初始化各種 client 的一個集合,最後創建了一個 deployment 實例。
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
deploymentsClient := clientset.AppsV1().Deployments(apiv1.NamespaceDefault)
接下來我們看一下 kubectl exec 究竟發送了什麼請求,可以看到關鍵在於exec?command=date&container=nginx&stdin=true&stdout=true&tty=true
kubectl exec nginx-8486565b79-4hb5t -it -v 10 bash
curl -k -v -XPOST -H "User-Agent: kubectl/v1.16.2 (linux/amd64) kubernetes/c97fe50"
-H "X-Stream-Protocol-Version: v4.channel.k8s.io"
-H "X-Stream-Protocol-Version: v3.channel.k8s.io"
-H "X-Stream-Protocol-Version: v2.channel.k8s.io"
-H "X-Stream-Protocol-Version: channel.k8s.io"
'https://192.168.2.2:6443/api/v1/namespaces/default/pods/nginx-8486565b79-4hb5t/exec
?command=date&container=nginx&stdin=true&stdout=true&tty=true'
現在就開始做吧,從上面的 URL 可以看到 exec 是屬於 pod 的資源,
// 初始化pod所在的corev1資源組,發送請求
// PodExecOptions struct 包括Container stdout stdout Command 等結構
// scheme.ParameterCodec 應該是pod 的GVK (GroupVersion & Kind)之類的
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name("nginx-8486565b79-4hb5t").
Namespace("default").
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Command: []string{"bash"},
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
// remotecommand 主要實現了http 轉 SPDY 添加X-Stream-Protocol-Version相關header 併發送請求
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
// 建立鏈接之後從請求的sream中發送、讀取數據
if err = exec.Stream(remotecommand.StreamOptions{
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
Tty: false,
}); err != nil {
fmt.Print(err)
}
以上只實現了單個命令,實際上我們更多的是使用 - it 進入交互式終端,這個應該怎麼做呢?
// 這裏引入了ssh包 來做終端響應 golang.org/x/crypto/ssh/termina
// 檢查是不是終端
if !terminal.IsTerminal(0) || !terminal.IsTerminal(1) {
fmt.Errorf("stdin/stdout should be terminal")
}
// 這個應該是處理Ctrl + C 這種特殊鍵位
oldState, err := terminal.MakeRaw(0)
if err != nil {
fmt.Println(err)
}
defer terminal.Restore(0, oldState)
// 用IO讀寫替換 os stdout
screen := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stdout}
完整示例
package main
import (
"flag"
"fmt"
"io"
"os"
"path/filepath"
"golang.org/x/crypto/ssh/terminal"
corev1 "k8s.io/api/core/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/client-go/tools/remotecommand"
"k8s.io/client-go/util/homedir"
)
func main() {
var kubeconfig *string
if home := homedir.HomeDir(); home != "" {
kubeconfig = flag.String("kubeconfig", filepath.Join(home, ".kube", "vm"), "(optional) absolute path to the kubeconfig file")
} else {
kubeconfig = flag.String("kubeconfig", "", "absolute path to the kubeconfig file")
}
flag.Parse()
config, err := clientcmd.BuildConfigFromFlags("", *kubeconfig)
if err != nil {
panic(err)
}
clientset, err := kubernetes.NewForConfig(config)
if err != nil {
panic(err)
}
// 初始化pod所在的corev1資源組,發送請求
// PodExecOptions struct 包括Container stdout stdout Command 等結構
// scheme.ParameterCodec 應該是pod 的GVK (GroupVersion & Kind)之類的
req := clientset.CoreV1().RESTClient().Post().
Resource("pods").
Name("nginx-8486565b79-4hb5t").
Namespace("default").
SubResource("exec").
VersionedParams(&corev1.PodExecOptions{
Command: []string{"bash"},
Stdin: true,
Stdout: true,
Stderr: true,
TTY: false,
}, scheme.ParameterCodec)
// remotecommand 主要實現了http 轉 SPDY 添加X-Stream-Protocol-Version相關header 併發送請求
exec, err := remotecommand.NewSPDYExecutor(config, "POST", req.URL())
// 檢查是不是終端
if !terminal.IsTerminal(0) || !terminal.IsTerminal(1) {
fmt.Errorf("stdin/stdout should be terminal")
}
// 這個應該是處理Ctrl + C 這種特殊鍵位
oldState, err := terminal.MakeRaw(0)
if err != nil {
fmt.Println(err)
}
defer terminal.Restore(0, oldState)
// 用IO讀寫替換 os stdout
screen := struct {
io.Reader
io.Writer
}{os.Stdin, os.Stdout}
// 建立鏈接之後從請求的sream中發送、讀取數據
if err = exec.Stream(remotecommand.StreamOptions{
Stdin: screen,
Stdout: screen,
Stderr: screen,
Tty: false,
}); err != nil {
fmt.Print(err)
}
}
原文鏈接:https://vsxen.github.io/2020/06/20/kubectl-exec/
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/VmEcIYfsUbgh-p4RCssGFQ