Add create and start command for container lifecycle
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
75fb70be01
commit
3fe7d7f31e
|
@ -0,0 +1,72 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
var createCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique on
|
||||
your host.`,
|
||||
Description: `The create command creates an instance of a container for a bundle. The bundle
|
||||
is a directory with a specification file named "` + specConfig + `" and a root
|
||||
filesystem.
|
||||
|
||||
The specification file includes an args parameter. The args parameter is used
|
||||
to specify command(s) that get run when the container is started. To change the
|
||||
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||
"runc spec --help" for more explanation.`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "console",
|
||||
Value: "",
|
||||
Usage: "specify the pty slave path for use with the container",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "no-pivot",
|
||||
Usage: "do not use pivot root to jail process inside rootfs. This should be used whenever the rootfs is on top of a ramdisk",
|
||||
},
|
||||
},
|
||||
Action: func(context *cli.Context) {
|
||||
bundle := context.String("bundle")
|
||||
if bundle != "" {
|
||||
if err := os.Chdir(bundle); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
}
|
||||
spec, err := loadSpec(specConfig)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
notifySocket := os.Getenv("NOTIFY_SOCKET")
|
||||
if notifySocket != "" {
|
||||
setupSdNotify(spec, notifySocket)
|
||||
}
|
||||
if os.Geteuid() != 0 {
|
||||
fatalf("runc should be run as root")
|
||||
}
|
||||
status, err := startContainer(context, spec, true)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
// exit with the container's exit status so any external supervisor is
|
||||
// notified of the exit with the correct exit status.
|
||||
os.Exit(status)
|
||||
},
|
||||
}
|
|
@ -29,6 +29,13 @@ const (
|
|||
|
||||
// Destroyed is the status that denotes the container does not exist.
|
||||
Destroyed
|
||||
|
||||
// Stopped is the status that denotes the container does not have a created or running process.
|
||||
Stopped
|
||||
|
||||
// Initialized is the status where the container has all the namespaces created but the user
|
||||
// process has not been start.
|
||||
Initialized
|
||||
)
|
||||
|
||||
func (s Status) String() string {
|
||||
|
@ -43,6 +50,10 @@ func (s Status) String() string {
|
|||
return "paused"
|
||||
case Destroyed:
|
||||
return "destroyed"
|
||||
case Stopped:
|
||||
return "stopped"
|
||||
case Initialized:
|
||||
return "initialized"
|
||||
default:
|
||||
return "unknown"
|
||||
}
|
||||
|
|
|
@ -195,7 +195,6 @@ func (c *linuxContainer) Start(process *Process) error {
|
|||
}
|
||||
// generate a timestamp indicating when the container was started
|
||||
c.created = time.Now().UTC()
|
||||
|
||||
c.state = &runningState{
|
||||
c: c,
|
||||
}
|
||||
|
@ -1034,31 +1033,47 @@ func (c *linuxContainer) refreshState() error {
|
|||
if paused {
|
||||
return c.state.transition(&pausedState{c: c})
|
||||
}
|
||||
running, err := c.isRunning()
|
||||
t, err := c.runType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if running {
|
||||
switch t {
|
||||
case Initialized:
|
||||
return c.state.transition(&initializedState{c: c})
|
||||
case Running:
|
||||
return c.state.transition(&runningState{c: c})
|
||||
}
|
||||
return c.state.transition(&stoppedState{c: c})
|
||||
}
|
||||
|
||||
func (c *linuxContainer) isRunning() (bool, error) {
|
||||
func (c *linuxContainer) runType() (Status, error) {
|
||||
if c.initProcess == nil {
|
||||
return false, nil
|
||||
return Stopped, nil
|
||||
}
|
||||
pid := c.initProcess.pid()
|
||||
// return Running if the init process is alive
|
||||
if err := syscall.Kill(c.initProcess.pid(), 0); err != nil {
|
||||
if err := syscall.Kill(pid, 0); err != nil {
|
||||
if err == syscall.ESRCH {
|
||||
// It means the process does not exist anymore, could happen when the
|
||||
// process exited just when we call the function, we should not return
|
||||
// error in this case.
|
||||
return false, nil
|
||||
return Stopped, nil
|
||||
}
|
||||
return false, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", c.initProcess.pid())
|
||||
return Stopped, newSystemErrorWithCausef(err, "sending signal 0 to pid %d", pid)
|
||||
}
|
||||
return true, nil
|
||||
// check if the process that is running is the init process or the user's process.
|
||||
// this is the difference between the container Running and Created.
|
||||
environ, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/environ", pid))
|
||||
if err != nil {
|
||||
return Stopped, newSystemErrorWithCausef(err, "reading /proc/%d/environ", pid)
|
||||
}
|
||||
check := []byte("_LIBCONTAINER")
|
||||
for _, v := range bytes.Split(environ, []byte("\x00")) {
|
||||
if bytes.Contains(v, check) {
|
||||
return Initialized, nil
|
||||
}
|
||||
}
|
||||
return Running, nil
|
||||
}
|
||||
|
||||
func (c *linuxContainer) isPaused() (bool, error) {
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"runtime/debug"
|
||||
|
@ -219,6 +220,9 @@ func (l *LinuxFactory) Type() string {
|
|||
// StartInitialization loads a container by opening the pipe fd from the parent to read the configuration and state
|
||||
// This is a low level implementation detail of the reexec and should not be consumed externally
|
||||
func (l *LinuxFactory) StartInitialization() (err error) {
|
||||
// start the signal handler as soon as we can
|
||||
s := make(chan os.Signal, 1024)
|
||||
signal.Notify(s, syscall.SIGCONT)
|
||||
fdStr := os.Getenv("_LIBCONTAINER_INITPIPE")
|
||||
pipefd, err := strconv.Atoi(fdStr)
|
||||
if err != nil {
|
||||
|
@ -260,7 +264,7 @@ func (l *LinuxFactory) StartInitialization() (err error) {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return i.Init()
|
||||
return i.Init(s)
|
||||
}
|
||||
|
||||
func (l *LinuxFactory) loadState(root string) (*State, error) {
|
||||
|
|
|
@ -61,7 +61,7 @@ type initConfig struct {
|
|||
}
|
||||
|
||||
type initer interface {
|
||||
Init() error
|
||||
Init(s chan os.Signal) error
|
||||
}
|
||||
|
||||
func newContainerInit(t initType, pipe *os.File) (initer, error) {
|
||||
|
|
|
@ -127,6 +127,9 @@ func runContainer(config *configs.Config, console string, args ...string) (buffe
|
|||
if err != nil {
|
||||
return buffers, -1, err
|
||||
}
|
||||
if err := container.Signal(syscall.SIGCONT); err != nil {
|
||||
return buffers, -1, err
|
||||
}
|
||||
ps, err := process.Wait()
|
||||
if err != nil {
|
||||
return buffers, -1, err
|
||||
|
|
|
@ -5,6 +5,7 @@ package libcontainer
|
|||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
"github.com/opencontainers/runc/libcontainer/keys"
|
||||
|
@ -23,7 +24,7 @@ func (l *linuxSetnsInit) getSessionRingName() string {
|
|||
return fmt.Sprintf("_ses.%s", l.config.ContainerId)
|
||||
}
|
||||
|
||||
func (l *linuxSetnsInit) Init() error {
|
||||
func (l *linuxSetnsInit) Init(s chan os.Signal) error {
|
||||
// do not inherit the parent's session keyring
|
||||
if _, err := keyctl.JoinSessionKeyring(l.getSessionRingName()); err != nil {
|
||||
return err
|
||||
|
@ -49,5 +50,7 @@ func (l *linuxSetnsInit) Init() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
signal.Stop(s)
|
||||
close(s)
|
||||
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
|
||||
}
|
||||
|
|
|
@ -6,6 +6,8 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/opencontainers/runc/libcontainer/apparmor"
|
||||
|
@ -17,7 +19,7 @@ import (
|
|||
)
|
||||
|
||||
type linuxStandardInit struct {
|
||||
pipe io.ReadWriter
|
||||
pipe io.ReadWriteCloser
|
||||
parentPid int
|
||||
config *initConfig
|
||||
}
|
||||
|
@ -42,7 +44,7 @@ func (l *linuxStandardInit) getSessionRingParams() (string, uint32, uint32) {
|
|||
// the kernel
|
||||
const PR_SET_NO_NEW_PRIVS = 0x26
|
||||
|
||||
func (l *linuxStandardInit) Init() error {
|
||||
func (l *linuxStandardInit) Init(s chan os.Signal) error {
|
||||
ringname, keepperms, newperms := l.getSessionRingParams()
|
||||
|
||||
// do not inherit the parent's session keyring
|
||||
|
@ -150,6 +152,18 @@ func (l *linuxStandardInit) Init() error {
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return system.Execv(l.config.Args[0], l.config.Args[0:], os.Environ())
|
||||
// check for the arg before waiting to make sure it exists and it is returned
|
||||
// as a create time error
|
||||
name, err := exec.LookPath(l.config.Args[0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// close the pipe to signal that we have completed our init
|
||||
l.pipe.Close()
|
||||
// wait for the signal to exec the users process
|
||||
<-s
|
||||
// clean up the signal handler
|
||||
signal.Stop(s)
|
||||
close(s)
|
||||
return syscall.Exec(name, l.config.Args[0:], os.Environ())
|
||||
}
|
||||
|
|
|
@ -110,11 +110,11 @@ func (r *runningState) status() Status {
|
|||
func (r *runningState) transition(s containerState) error {
|
||||
switch s.(type) {
|
||||
case *stoppedState:
|
||||
running, err := r.c.isRunning()
|
||||
t, err := r.c.runType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if running {
|
||||
if t == Running {
|
||||
return newGenericError(fmt.Errorf("container still running"), ContainerNotStopped)
|
||||
}
|
||||
r.c.state = s
|
||||
|
@ -129,16 +129,38 @@ func (r *runningState) transition(s containerState) error {
|
|||
}
|
||||
|
||||
func (r *runningState) destroy() error {
|
||||
running, err := r.c.isRunning()
|
||||
t, err := r.c.runType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if running {
|
||||
if t == Running {
|
||||
return newGenericError(fmt.Errorf("container is not destroyed"), ContainerNotStopped)
|
||||
}
|
||||
return destroy(r.c)
|
||||
}
|
||||
|
||||
type initializedState struct {
|
||||
c *linuxContainer
|
||||
}
|
||||
|
||||
func (i *initializedState) status() Status {
|
||||
return Initialized
|
||||
}
|
||||
|
||||
func (i *initializedState) transition(s containerState) error {
|
||||
switch s.(type) {
|
||||
case *runningState:
|
||||
i.c.state = s
|
||||
case *initializedState:
|
||||
return nil
|
||||
}
|
||||
return newStateTransitionError(i, s)
|
||||
}
|
||||
|
||||
func (i *initializedState) destroy() error {
|
||||
return destroy(i.c)
|
||||
}
|
||||
|
||||
// pausedState represents a container that is currently pause. It cannot be destroyed in a
|
||||
// paused state and must transition back to running first.
|
||||
type pausedState struct {
|
||||
|
@ -161,11 +183,11 @@ func (p *pausedState) transition(s containerState) error {
|
|||
}
|
||||
|
||||
func (p *pausedState) destroy() error {
|
||||
isRunning, err := p.c.isRunning()
|
||||
t, err := p.c.runType()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !isRunning {
|
||||
if t != Running && t != Created {
|
||||
if err := p.c.cgroupManager.Freeze(configs.Thawed); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
2
main.go
2
main.go
|
@ -86,6 +86,7 @@ func main() {
|
|||
}
|
||||
app.Commands = []cli.Command{
|
||||
checkpointCommand,
|
||||
createCommand,
|
||||
deleteCommand,
|
||||
eventsCommand,
|
||||
execCommand,
|
||||
|
@ -98,6 +99,7 @@ func main() {
|
|||
resumeCommand,
|
||||
runCommand,
|
||||
specCommand,
|
||||
startCommand,
|
||||
stateCommand,
|
||||
updateCommand,
|
||||
}
|
||||
|
|
8
run.go
8
run.go
|
@ -68,17 +68,14 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
notifySocket := os.Getenv("NOTIFY_SOCKET")
|
||||
if notifySocket != "" {
|
||||
setupSdNotify(spec, notifySocket)
|
||||
}
|
||||
|
||||
if os.Geteuid() != 0 {
|
||||
return fmt.Errorf("runc should be run as root")
|
||||
}
|
||||
|
||||
status, err := startContainer(context, spec)
|
||||
status, err := startContainer(context, spec, false)
|
||||
if err == nil {
|
||||
// exit with the container's exit status so any external supervisor is
|
||||
// notified of the exit with the correct exit status.
|
||||
|
@ -88,7 +85,7 @@ command(s) that get executed on start, edit the args parameter of the spec. See
|
|||
},
|
||||
}
|
||||
|
||||
func startContainer(context *cli.Context, spec *specs.Spec) (int, error) {
|
||||
func startContainer(context *cli.Context, spec *specs.Spec, create bool) (int, error) {
|
||||
id := context.Args().First()
|
||||
if id == "" {
|
||||
return -1, errEmptyID
|
||||
|
@ -111,6 +108,7 @@ func startContainer(context *cli.Context, spec *specs.Spec) (int, error) {
|
|||
console: context.String("console"),
|
||||
detach: detach,
|
||||
pidFile: context.String("pid-file"),
|
||||
create: create,
|
||||
}
|
||||
return r.run(&spec.Process)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/codegangsta/cli"
|
||||
)
|
||||
|
||||
var startCommand = cli.Command{
|
||||
Name: "start",
|
||||
Usage: "start signals a created container to execute the users defined process",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique on
|
||||
your host.`,
|
||||
Description: `The start command signals the container to start the user's defined process.`,
|
||||
Action: func(context *cli.Context) {
|
||||
container, err := getContainer(context)
|
||||
if err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
if err := container.Signal(syscall.SIGCONT); err != nil {
|
||||
fatal(err)
|
||||
}
|
||||
},
|
||||
}
|
|
@ -198,6 +198,7 @@ type runner struct {
|
|||
pidFile string
|
||||
console string
|
||||
container libcontainer.Container
|
||||
create bool
|
||||
}
|
||||
|
||||
func (r *runner) run(config *specs.Process) (int, error) {
|
||||
|
@ -220,7 +221,7 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
|||
r.destroy()
|
||||
return -1, err
|
||||
}
|
||||
tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach)
|
||||
tty, err := setupIO(process, rootuid, rootgid, r.console, config.Terminal, r.detach || r.create)
|
||||
if err != nil {
|
||||
r.destroy()
|
||||
return -1, err
|
||||
|
@ -245,7 +246,15 @@ func (r *runner) run(config *specs.Process) (int, error) {
|
|||
return -1, err
|
||||
}
|
||||
}
|
||||
if r.detach {
|
||||
if !r.create {
|
||||
if err := process.Signal(syscall.SIGCONT); err != nil {
|
||||
r.terminate(process)
|
||||
r.destroy()
|
||||
tty.Close()
|
||||
return -1, err
|
||||
}
|
||||
}
|
||||
if r.detach || r.create {
|
||||
tty.Close()
|
||||
return 0, nil
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue