From 252072672f3c6fa0b04a2e49afe782be9c12c767 Mon Sep 17 00:00:00 2001 From: LidolLxf <13660354959@163.com> Date: Fri, 20 Oct 2023 17:42:05 +0800 Subject: [PATCH] =?UTF-8?q?bcs-webconsole=E6=8C=81=E4=B9=85=E5=8C=96?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E5=91=BD=E4=BB=A4=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bcs-webconsole/console/api/service.go | 65 +++++++++++++++++++ .../console/podmanager/cleanup.go | 59 +++++++++++++++++ .../console/podmanager/constants.go | 5 ++ .../console/repository/bkrepo.go | 25 +++++++ .../bcs-webconsole/console/repository/cos.go | 10 +++ .../bcs-webconsole/console/repository/repo.go | 1 + 6 files changed, 165 insertions(+) diff --git a/bcs-services/bcs-webconsole/console/api/service.go b/bcs-services/bcs-webconsole/console/api/service.go index 52c2026452..aae033d7d0 100644 --- a/bcs-services/bcs-webconsole/console/api/service.go +++ b/bcs-services/bcs-webconsole/console/api/service.go @@ -35,6 +35,7 @@ import ( "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/i18n" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/metrics" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/podmanager" + "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/repository" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/rest" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/sessions" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/tracing" @@ -147,6 +148,14 @@ func (s *service) CreateWebConsoleSession(c *gin.Context) { return } + // 创建.bash_history文件 + go func(podCtx *types.PodContext) { + errCreate := CreateBashHistory(podCtx) + if errCreate != nil { + logger.Warnf("create bash history fail: %s", errCreate.Error()) + } + }(podCtx) + podCtx.ProjectId = authCtx.ProjectId podCtx.Username = authCtx.Username podCtx.Source = consoleQuery.Source @@ -610,3 +619,59 @@ func APIError(c *gin.Context, msg string) { // nolint c.AbortWithStatusJSON(http.StatusBadRequest, data) } + +// getBashHistory将文件放在本地 historyLocalDir +func getBashHistory(podName string) ([]byte, error) { + filename := podName + podmanager.HistoryFileName + + // 使用cos存储的文件 + storage, err := repository.NewProvider(config.G.Repository.StorageType) + if err != nil { + return nil, err + } + + // 10 分钟下载时间 + ctx, cancel := context.WithTimeout(context.Background(), time.Minute*10) + defer cancel() + + // 下载cos文件 + fileInput, err := storage.DownloadFile(ctx, podmanager.HistoryRepoDir+filename) + if err != nil { + return nil, err + } + + // 读取.bash_history内容byte格式 + output, err := io.ReadAll(fileInput) + if err != nil { + return nil, err + } + + return output, nil +} + +// CreateBashHistory 创建.bash_history文件 +func CreateBashHistory(podCtx *types.PodContext) error { + // 往容器写文件 + pe, err := podCtx.NewPodExec() + if err != nil { + return err + } + pe.Command = []string{"cp", "/dev/stdin", "/root/.bash_history"} + stdin := &bytes.Buffer{} + pe.Stderr = &bytes.Buffer{} + // 读取保存的.bash_history文件 + historyFileByte, err := getBashHistory(podCtx.PodName) + if err != nil { + return err + } + _, err = stdin.Write(historyFileByte) + if err != nil { + return err + } + pe.Stdin = stdin + err = pe.Exec() + if err != nil { + return err + } + return nil +} diff --git a/bcs-services/bcs-webconsole/console/podmanager/cleanup.go b/bcs-services/bcs-webconsole/console/podmanager/cleanup.go index abccec452b..2c6739a971 100644 --- a/bcs-services/bcs-webconsole/console/podmanager/cleanup.go +++ b/bcs-services/bcs-webconsole/console/podmanager/cleanup.go @@ -14,6 +14,7 @@ package podmanager import ( + "bytes" "context" "fmt" "strconv" @@ -23,10 +24,14 @@ import ( "github.com/go-redis/redis/v8" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/kubernetes/scheme" + "k8s.io/client-go/tools/remotecommand" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/components/k8sclient" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/config" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/metrics" + "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/repository" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/sessions" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/storage" "github.com/Tencent/bk-bcs/bcs-services/bcs-webconsole/console/types" @@ -164,6 +169,12 @@ func (p *CleanUpManager) cleanUserPodByCluster(clusterId string, namespace strin continue } + // 命令行持久化保存 + errCommand := persistenceCommand(k8sClient, pod.Name, pod.Namespace, pod.Spec.Containers[0].Name, clusterId) + if errCommand != nil { + logger.Errorf("persistenceCommand, err: %s", errCommand) + } + // 删除pod if err := k8sClient.CoreV1().Pods(namespace).Delete(p.ctx, pod.Name, metav1.DeleteOptions{}); err != nil { logger.Errorf("delete pod(%s) failed, err: %s", pod.Name, err) @@ -176,6 +187,54 @@ func (p *CleanUpManager) cleanUserPodByCluster(clusterId string, namespace strin return nil } +// persistenceCommand 命令行持久化保存 +func persistenceCommand(k8sClient *kubernetes.Clientset, podName, namespace, containerName, clusterId string) error { + req := k8sClient.CoreV1().RESTClient().Post().Resource("pods").Name(podName).Namespace(namespace). + SubResource("exec") + + req.VersionedParams(&v1.PodExecOptions{ + Command: []string{"/bin/bash", "-c", "cat /root/.bash_history"}, + Container: containerName, + Stdin: false, + Stdout: true, + Stderr: true, // kubectl 默认 stderr 未设置, virtual-kubelet 节点 stderr 和 tty 不能同时为 true + TTY: false, + }, scheme.ParameterCodec) + + k8sConfig := k8sclient.GetK8SConfigByClusterId(clusterId) + executor, err := remotecommand.NewSPDYExecutor(k8sConfig, "POST", req.URL()) + if err != nil { + return err + } + + stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} + err = executor.Stream(remotecommand.StreamOptions{ + Stdout: stdout, + Stderr: stderr, + }) + if err != nil { + return err + } + + // 文件名 + filename := podName + HistoryFileName + + // 上传cos + storage, err := repository.NewProvider(config.G.Repository.StorageType) + if err != nil { + return err + } + + ctx := context.Background() + + // 推送至cos存储桶 + err = storage.UploadFileByReader(ctx, stdout, HistoryRepoDir+filename) + if err != nil { + return err + } + return nil +} + // cleanConfigMap 清理configmap func (p *CleanUpManager) cleanConfigMap(clusterId string, namespace string, alivePodMap map[string]struct{}) error { k8sClient, err := k8sclient.GetK8SClientByClusterId(clusterId) diff --git a/bcs-services/bcs-webconsole/console/podmanager/constants.go b/bcs-services/bcs-webconsole/console/podmanager/constants.go index e84afc74c9..6e23f4d509 100644 --- a/bcs-services/bcs-webconsole/console/podmanager/constants.go +++ b/bcs-services/bcs-webconsole/console/podmanager/constants.go @@ -31,4 +31,9 @@ const ( // UserCtxExpireTime Context 过期时间, 12个小时 UserCtxExpireTime = 3600 * 12 clusterExpireSeconds = 3600 * 24 * 7 + + // HistoryFileName .bash_history文件结尾 + HistoryFileName = "-history.txt" + // HistoryRepoDir .bash_history repo远程存放的目录 + HistoryRepoDir = "/bash-history/latest/" ) diff --git a/bcs-services/bcs-webconsole/console/repository/bkrepo.go b/bcs-services/bcs-webconsole/console/repository/bkrepo.go index deb7e82f01..505e3d8f55 100644 --- a/bcs-services/bcs-webconsole/console/repository/bkrepo.go +++ b/bcs-services/bcs-webconsole/console/repository/bkrepo.go @@ -59,6 +59,31 @@ func (b *bkrepoStorage) UploadFile(ctx context.Context, localFile, filePath stri return nil } +// UploadFileByReader upload file to bkRepo by Reader +func (b *bkrepoStorage) UploadFileByReader(ctx context.Context, r io.Reader, filePath string) error { + // 上传文件API PUT /generic/{project}/{repoName}/{fullPath} + rawURL := fmt.Sprintf("%s/generic/%s/%s/%s", config.G.Repository.Bkrepo.Endpoint, + config.G.Repository.Bkrepo.Project, config.G.Repository.Bkrepo.Repo, filePath) + + req, err := http.NewRequestWithContext(ctx, http.MethodPut, rawURL, r) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/octet-stream") + req.Header.Set("X-BKREPO-OVERWRITE", "true") + + resp, err := b.client.Do(req) + if err != nil { + return err + } + if resp.StatusCode != 200 { + body, _ := io.ReadAll(resp.Body) + klog.Errorf("Upload file failed, resp: %s\n", string(body)) + return fmt.Errorf("upload file failed, Err code: %v", resp.StatusCode) + } + return nil +} + // IsExist 是否存在 func (b *bkrepoStorage) IsExist(ctx context.Context, filePath string) (bool, error) { rawURL := fmt.Sprintf("%s/generic/%s/%s%s", config.G.Repository.Bkrepo.Endpoint, diff --git a/bcs-services/bcs-webconsole/console/repository/cos.go b/bcs-services/bcs-webconsole/console/repository/cos.go index b43b853b26..fd2ba60003 100644 --- a/bcs-services/bcs-webconsole/console/repository/cos.go +++ b/bcs-services/bcs-webconsole/console/repository/cos.go @@ -45,6 +45,16 @@ func (c *cosStorage) UploadFile(ctx context.Context, localFile, filePath string) return nil } +// UploadFileByReader upload file to cos by Reader +func (c *cosStorage) UploadFileByReader(ctx context.Context, r io.Reader, filePath string) error { + _, err := c.client.Object.Put(ctx, filePath, r, nil) + if err != nil { + return fmt.Errorf("upload file failed: %v", err) + } + + return nil +} + // ListFile list current folder files func (c *cosStorage) ListFile(ctx context.Context, folderName string) ([]string, error) { var marker string diff --git a/bcs-services/bcs-webconsole/console/repository/repo.go b/bcs-services/bcs-webconsole/console/repository/repo.go index 5a2266b807..653830ef74 100644 --- a/bcs-services/bcs-webconsole/console/repository/repo.go +++ b/bcs-services/bcs-webconsole/console/repository/repo.go @@ -22,6 +22,7 @@ import ( // Provider 对象存储接口 type Provider interface { UploadFile(ctx context.Context, localFile, filePath string) error + UploadFileByReader(ctx context.Context, r io.Reader, filePath string) error ListFile(ctx context.Context, folderName string) ([]string, error) ListFolders(ctx context.Context, folderName string) ([]string, error) IsExist(ctx context.Context, filePath string) (bool, error)