First, make a request for Docker exec:
Docker exec-it 5504F937F7BB SH
The output of the corresponding docker-d (started Docker daemon) is :
INFO[0211] Post/v1.20/containers/5504f937f7bb/exec info[0211] post/v1.20/exec/ Fc9c11ae6ac4827ea507e885c888bdb37c8f7b906347b9272adf8d580a6417df/start info[0211] post/v1.20/exec/ Fc9c11ae6ac4827ea507e885c888bdb37c8f7b906347b9272adf8d580a6417df/resize?h=69&w=236//exit exiting Docker EXEC initiates a TTY after 2015/10/29 00:47:51 Http:response. Writeheader on hijacked connectioninfo[0294] get/v1.20/exec/ Fc9c11ae6ac4827ea507e885c888bdb37c8f7b906347b9272adf8d580a6417df/json
I created a main function of Docker exec myself, simulating the client execution process of Docker exec (Ingress execution):
Package Mainimport (_ "FMT" "Github.com/docker/docker/api/client") func main () {client. Doeexc ()}
Then is the process of simulating the Docker exec request, I have re-wrote one, skip the Flag module interpretation of parameters, directly on the static parameters
package clientimport ("Encoding/json" "FMT" "io" "Github.com/sirupsen/logrus" "github.com/docker/docker/ Api/types "" Github.com/docker/docker/cli "flag " Github.com/docker/docker/pkg/mflag "" github.com/docker/ Docker/pkg/promise "" Github.com/docker/docker/pkg/term "_ " Github.com/docker/docker/runconfig ") type execconfig struct {user string // User that will run the commandPrivileged bool // Is the container in privileged modeTty bool // attach standard streams to a tty. container string // name of the container (To execute in) attachstdin bool // attach the standard input, makes possible user interactionattachstderr bool // Attach the standard outputAttachStdout bool // Attach the standard errorDetach bool // Execute in detach modeCmd []string // execution commands and args}var commonflags = &cli.commonflags{flagset: new (flag. Flagset)}var clientflags = &cli. Clientflags{flagset: new (flag. Flagset), common: commonflags}func doeexc () {stdin, stdout, stderr := Term. Stdstreams () newcli := newdockercli (stdin, stdout, stderr, clientflags) If err := newcli.init (); err != nil {fmt. Printf ("docker&Nbsp;cli init err %s \n ", err)}cmdexec (NEWCLI)}func cmdexec (newcli * DOCKERCLI) error {execConfig := &ExecConfig{User: "",privileged: false,tty: true,cmd: []string{"sh"},Container: "5504F937F7BB", Detach: false,attachstdin: The true,attachstderr: true,attachstdout: true,}//corresponds to the first output of the daemon serverresp, err := newcli.call ("POST", "/containers/" +execconfig.container+ "/exec", execconfig, nil) if Err != nil {return err}defer serverresp.body.close () var response types. Containerexeccreateresponseif err := json. Newdecoder (Serverresp.body). Decode (&response); err != nil {reTurn err}execid := response.id//daemon will assign a execidfmt.printf ("response id %d \n", &NBSP;EXECID) if execid == "" {fmt. fprintf (newcli.out, "Exec id empty") Return nil}//temp struct for execstart so that we don ' t need to transfer all the Execconfigexecstartcheck := &types. execstartcheck{detach: execconfig.detach,tty: execconfig.tty,}if ! Execconfig.detach {if err := newcli. Checkttyinput (Execconfig.attachstdin, execconfig.tty); err != nil {return err}} else {if _, _, err := readbody (Newcli.call ("POST", "/exec/" +execID+ "/ Start ", execstartcheck, nil)); err != nil {return err}// for now don ' T&NBSP;PRINT&NBSP;THIS&NBSP;-&NBSP;WAIT&NBSP;FOR&NBSP;WHEN&NBSP;WE&NBSP;SUPPORT&NBSP;EXEC&NBSp;wait ()// fmt. fprintf (cli.out, "%s\n", execid) return nil}// This place is the process of IO interaction, that is, the binding TTY, output stream redirection processes var ( Out, stderr io. Writerin io. Readcloserhijacked = make (Chan io. Closer) errch chan error)// block the return Until the chan gets closeddefer func () {logrus. DEBUGF ("End of cmdexec (), waiting for hijack to finish.") If _, ok := <-hijacked; ok {fmt. Fprintln (newcli.err, "hijack did not finish (Chan still open)")}} () if execconfig.attachstdin {in = newcli.in}if execconfig.attachstdout {out = Newcli.out}if execconfig.attachstderr {if execconfig.tty {stderr = newcli.out} else {stderr = newcli.err}}//This is the most critical execution function errch = promise. Go (func () error {return newcli.hijack ("POST", "/exec/" +execid+ "/start", Execconfig.tty, in, out, stderr, hijacked, execconfig)})// Acknowledge the hijack before startingselect {case closer := <-hijacked:// make sure that hijack gets closed when returning. (result// in Closing hijack chan and freeing server ' s goroutines.if closer != Nil {defer closer. Close ()}case err := <-errch:if err != nil {logrus. DEBUGF ("error hijack: %s", err) return err}}if execconfig.tty && Newcli.isterminalin {if err := newcli.monitorttysize (execid, true); err != nil {fmt. fprintf (newcli.err, "Error monitoring tty sizE: %s\n ", err)}}if err := <-errch; err != nil {logrus. DEBUGF ("error hijack: %s", err) Return err}var status intif _, status, err = getexecexitcode (newcli, execid); err != nil {return err}if status != 0 {return newcli. Statuserror{statuscode: status}}return nil}
to let us analyze the execution flow of the most critical newcli.hijack
func (CLI&NBSP;*DOCKERCLI) hijack (Method, path string, setrawterminal bool, in io. Readcloser, stdout, stderr io. Writer, started chan io. closer, data interface{}) error {defer func () {if started != nil {close (started)}} () Params, err := cli.encodedata (data) if err != nil { Return err}req, err := http. Newrequest (method, fmt. Sprintf ("%s/v%s%s", cli.basepath, api. Version, path), params) if err != nil {return err}// add cli Config ' S http headers before we set the docker headers// then the user can ' t change our headersfor k, v := range Cli.configfile.httpheaders {req. Header.set (k, v)}//req. Header.set ("User-agent", "docker-client/" +dockerversion. Version+ " (" +runtime. goos+ ")") req. Header.set ("Content-type", "Text/plain") req. Header.set ("Connection", "Upgrade") req. Header.set ("Upgrade", "TCP") req. Host = cli.addrdial, err := cli.dial () //newly established connection// when we set up a TCP connection for hijack, there could be long periods// of inactivity (A long running command with no output) that in certain// network setups may cause econntimeout, leaving the client in an unknown// state. setting tcp keepalive on the socket connection will prohibit// ECONNTIMEOUT unless the Socket connection truly is brokenif tcpconn, ok := dial. (*net. Tcpconn); ok {tcpconn.setkeepalive (True) tcpconn.setkeepaliveperiod (30&NBSp;* time. Second)}if err != nil {if strings. Contains (Err. Error (), "connection refused") {return fmt. Errorf ("cannot connect to the docker daemon. is ' Docker daemon ' Running on this host ")} Return err}clientconn := httputil. Newclientconn (Dial, nil) defer clientconn. Close ()// server hijacks the connection, error ' connection closed ' Expectedclientconn. Do (req) //the second rwc, br := clientconn corresponding to the output of the daemon. Hijack () //clear out the buffer clears the buffer data DEFER&NBSP;RWC. Close () If started != nil {started <- rwc}var receivestdout chan errorvar oldstate *term. state //if a pseudo terminal needs to be assigned if in != nil && setrawterminal && cli.isterminalin &&&Nbsp;os.getenv ("Noraw") == "" {oldstate, err = term. Setrawterminal (CLI.INFD) if err != nil {return err}defer term. Restoreterminal (cli.infd, oldstate)}if stdout != nil | | stderr != nil {receivestdout = promise. Go (func () (Err error) {defer func () {if in != nil {if Setrawterminal && cli.isterminalin {term. Restoreterminal (cli.infd, oldstate)}// for some reason this close call blocks on darwin. As the client exists right after, simply discard the Close// until we find a better solution.if runtime. goos != "Darwin" {in. Close ()}}} () &nbsRedirection of the P; //io output stream is here// when tty is on, use regular copyif Setrawterminal && stdout != nil {_, err = io. Copy (STDOUT,&NBSP;BR)} else {_, err = stdcopy. Stdcopy (STDOUT,&NBSP;STDERR,&NBSP;BR)}logrus. DEBUGF ("[Hijack] end of stdout") Return err})}sendstdin := promise. The redirect of the Go (func () error { //io input stream is here If in != nil {io. Copy (rwc, in) Logrus. DEBUGF ("[Hijack] end of stdin")}IF&NBSP;CONN,&NBSP;OK&NBSP;:=&NBSP;RWC. (Interface {closewrite () error}); ok {if err := conn. Closewrite (); err != nil {logrus. DEBUGF ("Couldn ' t send eof: %s", err)}}// discard errors due to Pipe interruptionreturn nil}) //blocked here if stdout ! = nil | | stderr != nil {if err := <-receivestdout; err != nil {logrus. DEBUGF ("error receivestdout: %s", err) return err}}if !cli.isterminalin {if Err := <-sendstdin; err != nil {logrus. DEBUGF ("error sendstdin: %s", err) Return err}}return nil}
Summary, after redirection, if it is not executed in the background, it is stuck there until the process is killed.
Specific execution flow of Docker exec