1. 先决条件

docker开放远程API端口

2. gin框架实现

type GetCommandResultRequire struct {
	IpAddr        string `json:"ip_addr"` //传入要控制容器的ip地址
	ContainerUuid string `json:"container_uuid"` //容器id
}

func GetCommendResult(c *gin.Context) {

	var sendCommandRequire GetCommandResultRequire
	sendCommandRequire.IpAddr = c.Query("ip_addr")
	sendCommandRequire.ContainerUuid = c.Query("container_uuid")

	//升级接口
	ws, err := upgrader.Upgrade(c.Writer, c.Request, nil)
	if err != nil {
		fmt.Print("升级websocket连接错误:", err)
		return
	}
	defer ws.Close()

    execHr := new(types.HijackedResponse)
	dockerCli, err := ConnectDocker(sendCommandRequire.IpAddr)
	if err != nil {
		tools.SetErr(c, 500, err, err.Error())//此处是自己实现的gin返回
		return
	}

	//启动携程,持续接收信息、传入命令、异常关闭docker链接
	go func() {
		for {
			_, message, err := ws.ReadMessage()
			if err != nil {
				logger.Error("接口断开:", err)
				execHr.Close()
				ws.Close()
				return
			}
			logger.Info(string(message))
			//发送命令
			err = SendCommend(string(message), execHr)
			if err != nil {
				logger.Error("命令发送错误")
			}
		}
	}()

	err = ExecContainer(dockerCli, execHr, ws, sendCommandRequire.ContainerUuid)
	if err != nil {
		tools.SetErr(c, 500, err, err.Error())
		return
	}
	logger.Info("链接关闭")
}

//定义一个函数,链接容器,并持续输出。
func ExecContainer(dockerCli *client.Client, execHr *types.HijackedResponse, conn *websocket.Conn, containerId string) error {

	ctx := context.Background()
	// 在指定容器中执行/bin/bash命令
	ir, err := dockerCli.ContainerExecCreate(ctx, containerId, types.ExecConfig{
		AttachStdin:  true,
		AttachStdout: true,
		AttachStderr: true,
		Cmd:          []string{"/bin/sh"},
		Tty:          true,
	})
	if err != nil {
		return err
	}

	// 保持链接
	*execHr, err = dockerCli.ContainerExecAttach(ctx, ir.ID, types.ExecStartCheck{Detach: false, Tty: true})
	if err != nil {
		return err
	}

	//关闭I/O
	defer execHr.Close()

	// 输入一个测试命令(非必要)
	_, err = execHr.Conn.Write([]byte("ls\r"))
	if err != nil {
		return err
	}

	// 输出
	scanner := bufio.NewScanner(execHr.Conn)
	for scanner.Scan() {
		fmt.Println(scanner.Text())
		text := scanner.Text()
		b, _ := json.Marshal(text)
		err = conn.WriteMessage(websocket.TextMessage, b)
		if err != nil {
			log.Println("写入错误", err)
			//return err
			continue
		}
	}
	logger.Info("容器输出结束")
	return nil
}

//写一个函数,向容器中发送命令。
func SendCommend(cmdString string, execHr *types.HijackedResponse) (err error) {

	_, err = execHr.Conn.Write([]byte(cmdString + "\r"))
	if err != nil {
		return
	}
	return nil
}

3. 测试用html文件

<!DOCTYPE html>
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
    <title>TestWebsocket</title>
 
    <script type="text/javascript">
        var Server_Com;
        function LinkServer() {
            // 声明连接
            
            if ("WebSocket" in window) {
                Server_Com = new WebSocket("ws://127.0.0.1:18000/v1/m6e/docker/container/exec?ip_addr=10.10.239.32&container_uuid=48bcf6811212");
                console.log("新建连接到->127.0.0.1:18000");
            }
 
            // 建立连接后发送
            Server_Com.onopen = function() {
 
                Server_Com.send("Hello Server!"); // Web Socket 已连接上,使用 send() 方法发送数据
                console.log("已连接上服务器");
            }
 
            // 接收服务器消息
            Server_Com.onmessage = function(event) {
                var recv_msg = event.data;
 
                if (recv_msg == "Hello Client!") {
                    console.log("接收到服务器的问候: " + recv_msg); // 用于提示收到信息
                } else {
                    document.getElementById("Time").textContent = document.getElementById("Time").textContent + "\r\n" + recv_msg; // 实时更新显示服务器发回的时间
                    console.log("接收到服务器数据: " + recv_msg);
                }
            }
        }

        function SendMessage(){
            var inputMessage = document.getElementById("sendcmd").value;
            Server_Com.send(inputMessage);
        }
    </script>
</head>
 
<body>
    <p>接收到的信息:</p>
    <p id="Time">crow-exec测试</p>
    <button onclick="SendMessage()">发送消息</button>
    <input id="sendcmd" name="sendcmd"></input>
    <button onclick="LinkServer()">连接</button>
 
</body>
</html>

4. 需要完善

vi 编辑器无法实现,需要进一步完善。


gin框架使用websocket实现进入容器内部执行命令-LMLPHP

03-23 14:12