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/[email protected]
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 :
[email protected]:/# 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 accountJAVA
、Golang
、front end
、docker
、k8s
Knowledge of dry goods .