编程知识 cdmana.com

Connect docker container through browser

Preface

Use within the company Jenkins do CI/CD when , There are often cases of project construction failure , In general, through Jenkins The output of the build console can be used to understand the possible problems , But there are special cases where development needs to be done in Jenkins Troubleshooting on the server , At this time, we can only find operation and maintenance to debug , For the experience of developers, I investigated web terminal, Can provide the container terminal to the development for troubleshooting when the build fails .

Effect display

Support color highlight , Support tab Key completion , Support copy and paste , The experience is basically the same as the ordinary terminal Agreement .

be based on docker Of web terminal Realization

docker exec call

The first thing I think about is through docker exec -it ubuntu /bin/bash Command to open a terminal , Then the standard input and output are passed through websocket Interact with the front end .

And found that docker offer API and SDK developable , adopt Go SDK Can be very convenient in docker Create a terminal process in :

  • install sdk
go get -u github.com/docker/docker/client@8c8457b0f2f8

This project is new tag Not followed go mod server semantics , So if you just go get -u github.com/docker/docker/client The default installation is 2017 One of the years tag edition , I'm directly here master I found one on the branch commit ID, Specific reason reference issue

  • call exec
package main

import (
    "bufio"
    "context"
    "fmt"
    "github.com/docker/docker/api/types"
    "github.com/docker/docker/client"
)

func main() {
    //  initialization  go sdk
    ctx := context.Background()
    cli, err := client.NewClientWithOpts(client.FromEnv)
    if err != nil {
        panic(err)
    }

    cli.NegotiateAPIVersion(ctx)

    //  Execute in the specified container /bin/bash command 
    ir, err := cli.ContainerExecCreate(ctx, "test", types.ExecConfig{
        AttachStdin:  true,
        AttachStdout: true,
        AttachStderr: true,
        Cmd:          []string{"/bin/bash"},
        Tty:          true,
    })
    if err != nil {
        panic(err)
    }

    //  Attach to the created above /bin/bash In progress 
    hr, err := cli.ContainerExecAttach(ctx, ir.ID, types.ExecStartCheck{Detach: false, Tty: true})
    if err != nil {
        panic(err)
    }
    //  close I/O
    defer hr.Close()
    //  Input 
    hr.Conn.Write([]byte("ls\r"))
    //  Output 
    scanner := bufio.NewScanner(hr.Conn)
    for scanner.Scan() {
        fmt.Println(scanner.Text())
    }
}

This is the time docker The input and output of the terminal are available , The next step is through websocket To interact with the front end .

Front page

When we're in linux terminal Knock up and down ls On command , To see is :

root@a09f2e7ded0d:/# ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

In fact, the string returned from standard output is :

[0m[01;34mbin[0m   [01;34mdev[0m  [01;34mhome[0m  [01;34mlib64[0m  [01;34mmnt[0m  [01;34mproc[0m  [01;34mrun[0m   [01;34msrv[0m  [30;42mtmp[0m  [01;34mvar[0m
[01;34mboot[0m  [01;34metc[0m  [01;34mlib[0m   [01;34mmedia[0m  [01;34mopt[0m  [01;34mroot[0m  [01;34msbin[0m  [01;34msys[0m  [01;34musr[0m

In this case , There's already one called xterm.js The library of , Designed to simulate Terminal Of , We need to do terminal display through this library .

var term = new Terminal();
term.open(document.getElementById("terminal"));
term.write("Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ");

Through official examples , You can see that it will display special characters accordingly :

In this case, you just need to be in websocket When you connect to the server , Use... To get the terminal output term.write() Write out , Then the front-end input as the input of the terminal can achieve the function we need .

The idea is right , But there's no need to write ,xterm.js One has been provided websocket Plug ins are here to do this , We just need to pass the standard input and output through websocket Transmission is OK .

  • install xterm.js
npm install xterm
  • be based on vue Write the front page
<template>
  <div ref="terminal"></div>
</template>

<script>
//  introduce css
import "xterm/dist/xterm.css";
import "xterm/dist/addons/fullscreen/fullscreen.css";

import { Terminal } from "xterm";
//  Adaptive plug-ins 
import * as fit from "xterm/lib/addons/fit/fit";
//  Full screen plug-in 
import * as fullscreen from "xterm/lib/addons/fullscreen/fullscreen";
// web Link plugin 
import * as webLinks from "xterm/lib/addons/webLinks/webLinks";
// websocket plug-in unit 
import * as attach from "xterm/lib/addons/attach/attach";

export default {
  name: "Index",
  created() {
    //  Installing a plug-in 
    Terminal.applyAddon(attach);
    Terminal.applyAddon(fit);
    Terminal.applyAddon(fullscreen);
    Terminal.applyAddon(webLinks);

    //  Initialize the terminal 
    const terminal = new Terminal();
    //  open websocket
    const ws = new WebSocket("ws://127.0.0.1:8000/terminal?container=test");
    //  Bound to the dom On 
    terminal.open(this.$refs.terminal);
    //  Add plug-ins 
    terminal.fit();
    terminal.toggleFullScreen();
    terminal.webLinksInit();
    terminal.attach(ws);
  }
};
</script>

Back end websocket Support

stay go Is not provided in the standard library of websocket Modular , Here we use the official order websocket library .

go get -u github.com/gorilla/websocket

The core code is as follows :

// websocket Handshake configuration , Ignore Origin testing 
var upgrader = websocket.Upgrader{
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

func terminal(w http.ResponseWriter, r *http.Request) {
    // websocket handshake 
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Error(err)
        return
    }
    defer conn.Close()

    r.ParseForm()
    //  Get the container ID or name
    container := r.Form.Get("container")
    //  perform exec, Get the connection to the container terminal 
    hr, err := exec(container)
    if err != nil {
        log.Error(err)
        return
    }
    //  close I/O flow 
    defer hr.Close()
    //  Withdraw from the process 
    defer func() {
        hr.Conn.Write([]byte("exit\r"))
    }()

    //  Forward input / Output to websocket
    go func() {
        wsWriterCopy(hr.Conn, conn)
    }()
    wsReaderCopy(conn, hr.Conn)
}

func exec(container string) (hr types.HijackedResponse, err error) {
    //  perform /bin/bash command 
    ir, err := cli.ContainerExecCreate(ctx, container, types.ExecConfig{
        AttachStdin:  true,
        AttachStdout: true,
        AttachStderr: true,
        Cmd:          []string{"/bin/bash"},
        Tty:          true,
    })
    if err != nil {
        return
    }

    //  Attach to the created above /bin/bash In progress 
    hr, err = cli.ContainerExecAttach(ctx, ir.ID, types.ExecStartCheck{Detach: false, Tty: true})
    if err != nil {
        return
    }
    return
}

//  Forward the output of the terminal to the front end 
func wsWriterCopy(reader io.Reader, writer *websocket.Conn) {
    buf := make([]byte, 8192)
    for {
        nr, err := reader.Read(buf)
        if nr > 0 {
            err := writer.WriteMessage(websocket.BinaryMessage, buf[0:nr])
            if err != nil {
                return
            }
        }
        if err != nil {
            return
        }
    }
}

//  Forward the input from the front end to the terminal 
func wsReaderCopy(reader *websocket.Conn, writer io.Writer) {
    for {
        messageType, p, err := reader.ReadMessage()
        if err != nil {
            return
        }
        if messageType == websocket.TextMessage {
            writer.Write(p)
        }
    }
}

summary

The above is a simple docker web terminal function , After that, you just need to pass it through the front end container ID or container name You can open the specified container for interaction .

Complete code :https://github.com/monkeyWie/...

I am a MonkeyWie, Welcome to scan ! To share in public official account JAVAGolang front end dockerk8s Knowledge of dry goods .

wechat

版权声明
本文为[mokeyWie]所创,转载请带上原文链接,感谢

Scroll to Top