runc: implement --console-socket
This allows for higher-level orchestrators to be able to have access to the master pty file descriptor without keeping the runC process running. This is key to having (detach && createTTY) with a _real_ pty created inside the container, which is then sent to a higher level orchestrator over an AF_UNIX socket. This patch is part of the console rewrite patchset. Signed-off-by: Aleksa Sarai <asarai@suse.de>
This commit is contained in:
parent
f1324a9fc1
commit
7df64f8886
|
@ -29,6 +29,11 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "console-socket",
|
||||||
|
Value: "",
|
||||||
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||||
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "pid-file",
|
Name: "pid-file",
|
||||||
Value: "",
|
Value: "",
|
||||||
|
|
5
exec.go
5
exec.go
|
@ -29,6 +29,10 @@ following will output a list of processes running in the container:
|
||||||
|
|
||||||
# runc exec <container-id> ps`,
|
# runc exec <container-id> ps`,
|
||||||
Flags: []cli.Flag{
|
Flags: []cli.Flag{
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "console-socket",
|
||||||
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||||
|
},
|
||||||
cli.StringFlag{
|
cli.StringFlag{
|
||||||
Name: "cwd",
|
Name: "cwd",
|
||||||
Usage: "current working directory in the container",
|
Usage: "current working directory in the container",
|
||||||
|
@ -127,6 +131,7 @@ func execProcess(context *cli.Context) (int, error) {
|
||||||
enableSubreaper: false,
|
enableSubreaper: false,
|
||||||
shouldDestroy: false,
|
shouldDestroy: false,
|
||||||
container: container,
|
container: container,
|
||||||
|
consoleSocket: context.String("console-socket"),
|
||||||
detach: detach,
|
detach: detach,
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: context.String("pid-file"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
package libcontainer
|
package libcontainer
|
||||||
|
|
||||||
import "io"
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
// Console represents a pseudo TTY.
|
// Console represents a pseudo TTY.
|
||||||
type Console interface {
|
type Console interface {
|
||||||
|
@ -11,8 +16,59 @@ type Console interface {
|
||||||
Path() string
|
Path() string
|
||||||
|
|
||||||
// Fd returns the fd for the master of the pty.
|
// Fd returns the fd for the master of the pty.
|
||||||
Fd() uintptr
|
File() *os.File
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConsoleData represents arbitrary setup data used when setting up console
|
const (
|
||||||
// handling. It is
|
TerminalInfoVersion uint32 = 201610041
|
||||||
|
TerminalInfoType uint8 = 'T'
|
||||||
|
)
|
||||||
|
|
||||||
|
// TerminalInfo is the structure which is passed as the non-ancillary data
|
||||||
|
// in the sendmsg(2) call when runc is run with --console-socket. It
|
||||||
|
// contains some information about the container which the console master fd
|
||||||
|
// relates to (to allow for consumers to use a single unix socket to handle
|
||||||
|
// multiple containers). This structure will probably move to runtime-spec
|
||||||
|
// at some point. But for now it lies in libcontainer.
|
||||||
|
type TerminalInfo struct {
|
||||||
|
// Version of the API.
|
||||||
|
Version uint32 `json:"version"`
|
||||||
|
|
||||||
|
// Type of message (future proofing).
|
||||||
|
Type uint8 `json:"type"`
|
||||||
|
|
||||||
|
// Container contains the ID of the container.
|
||||||
|
ContainerID string `json:"container_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ti *TerminalInfo) String() string {
|
||||||
|
encoded, err := json.Marshal(*ti)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(encoded)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTerminalInfo(containerId string) *TerminalInfo {
|
||||||
|
return &TerminalInfo{
|
||||||
|
Version: TerminalInfoVersion,
|
||||||
|
Type: TerminalInfoType,
|
||||||
|
ContainerID: containerId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTerminalInfo(encoded string) (*TerminalInfo, error) {
|
||||||
|
ti := new(TerminalInfo)
|
||||||
|
if err := json.Unmarshal([]byte(encoded), ti); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if ti.Type != TerminalInfoType {
|
||||||
|
return nil, fmt.Errorf("terminal info: incorrect type in payload (%q): %q", TerminalInfoType, ti.Type)
|
||||||
|
}
|
||||||
|
if ti.Version != TerminalInfoVersion {
|
||||||
|
return nil, fmt.Errorf("terminal info: incorrect version in payload (%q): %q", TerminalInfoVersion, ti.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ti, nil
|
||||||
|
}
|
||||||
|
|
|
@ -36,8 +36,8 @@ type linuxConsole struct {
|
||||||
slavePath string
|
slavePath string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxConsole) Fd() uintptr {
|
func (c *linuxConsole) File() *os.File {
|
||||||
return c.master.Fd()
|
return c.master
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *linuxConsole) Path() string {
|
func (c *linuxConsole) Path() string {
|
||||||
|
|
|
@ -197,8 +197,7 @@ func setupConsole(pipe *os.File, config *initConfig, mount bool) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// While we can access console.master, using the API is a good idea.
|
// While we can access console.master, using the API is a good idea.
|
||||||
consoleFile := os.NewFile(linuxConsole.Fd(), "[master-pty]")
|
if err := utils.SendFd(pipe, linuxConsole.File()); err != nil {
|
||||||
if err := utils.SendFd(pipe, consoleFile); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
5
run.go
5
run.go
|
@ -31,6 +31,11 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
||||||
Value: "",
|
Value: "",
|
||||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "console-socket",
|
||||||
|
Value: "",
|
||||||
|
Usage: "path to an AF_UNIX socket which will receive a file descriptor referencing the master end of the console's pseudoterminal",
|
||||||
|
},
|
||||||
cli.BoolFlag{
|
cli.BoolFlag{
|
||||||
Name: "detach, d",
|
Name: "detach, d",
|
||||||
Usage: "detach from the container's process",
|
Usage: "detach from the container's process",
|
||||||
|
|
16
tty.go
16
tty.go
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/term"
|
"github.com/docker/docker/pkg/term"
|
||||||
"github.com/opencontainers/runc/libcontainer"
|
"github.com/opencontainers/runc/libcontainer"
|
||||||
|
"github.com/opencontainers/runc/libcontainer/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type tty struct {
|
type tty struct {
|
||||||
|
@ -100,6 +101,19 @@ func (t *tty) recvtty(process *libcontainer.Process, detach bool) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *tty) sendtty(socket *os.File, ti *libcontainer.TerminalInfo) error {
|
||||||
|
if t.console == nil {
|
||||||
|
return fmt.Errorf("tty.console not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a fake file to contain the terminal info.
|
||||||
|
console := os.NewFile(t.console.File().Fd(), ti.String())
|
||||||
|
if err := utils.SendFd(socket, console); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ClosePostStart closes any fds that are provided to the container and dup2'd
|
// ClosePostStart closes any fds that are provided to the container and dup2'd
|
||||||
// so that we no longer have copy in our process.
|
// so that we no longer have copy in our process.
|
||||||
func (t *tty) ClosePostStart() error {
|
func (t *tty) ClosePostStart() error {
|
||||||
|
@ -135,5 +149,5 @@ func (t *tty) resize() error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return term.SetWinsize(t.console.Fd(), ws)
|
return term.SetWinsize(t.console.File().Fd(), ws)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
@ -121,13 +122,13 @@ func setupIO(process *libcontainer.Process, rootuid, rootgid int, createTTY, det
|
||||||
// requirement that we set up anything nice for our caller or the
|
// requirement that we set up anything nice for our caller or the
|
||||||
// container.
|
// container.
|
||||||
if detach {
|
if detach {
|
||||||
// TODO: Actually set rootuid, rootgid.
|
|
||||||
if err := dupStdio(process, rootuid, rootgid); err != nil {
|
if err := dupStdio(process, rootuid, rootgid); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &tty{}, nil
|
return &tty{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// XXX: This doesn't sit right with me. It's ugly.
|
||||||
return createStdioPipes(process, rootuid, rootgid)
|
return createStdioPipes(process, rootuid, rootgid)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,10 +181,15 @@ type runner struct {
|
||||||
detach bool
|
detach bool
|
||||||
listenFDs []*os.File
|
listenFDs []*os.File
|
||||||
pidFile string
|
pidFile string
|
||||||
|
consoleSocket string
|
||||||
container libcontainer.Container
|
container libcontainer.Container
|
||||||
create bool
|
create bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *runner) terminalinfo() *libcontainer.TerminalInfo {
|
||||||
|
return libcontainer.NewTerminalInfo(r.container.ID())
|
||||||
|
}
|
||||||
|
|
||||||
func (r *runner) run(config *specs.Process) (int, error) {
|
func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
process, err := newProcess(*config)
|
process, err := newProcess(*config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -194,16 +200,32 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
|
process.Env = append(process.Env, fmt.Sprintf("LISTEN_FDS=%d", len(r.listenFDs)), "LISTEN_PID=1")
|
||||||
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
|
process.ExtraFiles = append(process.ExtraFiles, r.listenFDs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootuid, err := r.container.Config().HostUID()
|
rootuid, err := r.container.Config().HostUID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
rootgid, err := r.container.Config().HostGID()
|
rootgid, err := r.container.Config().HostGID()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detach := r.detach || r.create
|
||||||
|
|
||||||
|
// Check command-line for sanity.
|
||||||
|
if detach && config.Terminal && r.consoleSocket == "" {
|
||||||
|
r.destroy()
|
||||||
|
return -1, fmt.Errorf("cannot allocate tty if runc will detach without setting console socket")
|
||||||
|
}
|
||||||
|
// XXX: Should we change this?
|
||||||
|
if (!detach || !config.Terminal) && r.consoleSocket != "" {
|
||||||
|
r.destroy()
|
||||||
|
return -1, fmt.Errorf("cannot use console socket if runc will not detach or allocate tty")
|
||||||
|
}
|
||||||
|
|
||||||
startFn := r.container.Start
|
startFn := r.container.Start
|
||||||
if !r.create {
|
if !r.create {
|
||||||
startFn = r.container.Run
|
startFn = r.container.Run
|
||||||
|
@ -212,7 +234,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
// with detaching containers, and then we get a tty after the container has
|
// with detaching containers, and then we get a tty after the container has
|
||||||
// started.
|
// started.
|
||||||
handler := newSignalHandler(r.enableSubreaper)
|
handler := newSignalHandler(r.enableSubreaper)
|
||||||
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, r.detach || r.create)
|
tty, err := setupIO(process, rootuid, rootgid, config.Terminal, detach)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.destroy()
|
r.destroy()
|
||||||
return -1, err
|
return -1, err
|
||||||
|
@ -229,6 +251,39 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defer tty.Close()
|
defer tty.Close()
|
||||||
|
|
||||||
|
if config.Terminal && detach {
|
||||||
|
conn, err := net.Dial("unix", r.consoleSocket)
|
||||||
|
if err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
unixconn, ok := conn.(*net.UnixConn)
|
||||||
|
if !ok {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
return -1, fmt.Errorf("casting to UnixConn failed")
|
||||||
|
}
|
||||||
|
|
||||||
|
socket, err := unixconn.File()
|
||||||
|
if err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
defer socket.Close()
|
||||||
|
|
||||||
|
err = tty.sendtty(socket, r.terminalinfo())
|
||||||
|
if err != nil {
|
||||||
|
r.terminate(process)
|
||||||
|
r.destroy()
|
||||||
|
return -1, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := tty.ClosePostStart(); err != nil {
|
if err := tty.ClosePostStart(); err != nil {
|
||||||
r.terminate(process)
|
r.terminate(process)
|
||||||
r.destroy()
|
r.destroy()
|
||||||
|
@ -241,7 +296,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
||||||
return -1, err
|
return -1, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if r.detach || r.create {
|
if detach {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
status, err := handler.forward(process, tty)
|
status, err := handler.forward(process, tty)
|
||||||
|
@ -295,6 +350,7 @@ func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, e
|
||||||
shouldDestroy: true,
|
shouldDestroy: true,
|
||||||
container: container,
|
container: container,
|
||||||
listenFDs: listenFDs,
|
listenFDs: listenFDs,
|
||||||
|
consoleSocket: context.String("console-socket"),
|
||||||
detach: context.Bool("detach"),
|
detach: context.Bool("detach"),
|
||||||
pidFile: context.String("pid-file"),
|
pidFile: context.String("pid-file"),
|
||||||
create: create,
|
create: create,
|
||||||
|
|
Loading…
Reference in New Issue