Merge pull request #967 from crosbymichael/task-ns
Namespace tasks via runc --root
This commit is contained in:
commit
9c0897d524
@ -40,6 +40,10 @@ func main() {
|
|||||||
Name: "debug",
|
Name: "debug",
|
||||||
Usage: "enable debug output in logs",
|
Usage: "enable debug output in logs",
|
||||||
},
|
},
|
||||||
|
cli.StringFlag{
|
||||||
|
Name: "namespace,n",
|
||||||
|
Usage: "namespace that owns the task",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(context *cli.Context) error {
|
||||||
if context.GlobalBool("debug") {
|
if context.GlobalBool("debug") {
|
||||||
@ -61,10 +65,11 @@ func main() {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
var (
|
server := grpc.NewServer()
|
||||||
server = grpc.NewServer()
|
sv, err := shim.New(path, context.GlobalString("namespace"))
|
||||||
sv = shim.New(path)
|
if err != nil {
|
||||||
)
|
return err
|
||||||
|
}
|
||||||
logrus.Debug("registering grpc server")
|
logrus.Debug("registering grpc server")
|
||||||
shimapi.RegisterShimServer(server, sv)
|
shimapi.RegisterShimServer(server, sv)
|
||||||
if err := serve(server, "shim.sock"); err != nil {
|
if err := serve(server, "shim.sock"); err != nil {
|
||||||
|
@ -16,7 +16,9 @@ import (
|
|||||||
"github.com/containerd/containerd/api/services/shim"
|
"github.com/containerd/containerd/api/services/shim"
|
||||||
"github.com/containerd/containerd/api/types/mount"
|
"github.com/containerd/containerd/api/types/mount"
|
||||||
"github.com/containerd/containerd/api/types/task"
|
"github.com/containerd/containerd/api/types/task"
|
||||||
|
shimb "github.com/containerd/containerd/linux/shim"
|
||||||
"github.com/containerd/containerd/log"
|
"github.com/containerd/containerd/log"
|
||||||
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/plugin"
|
"github.com/containerd/containerd/plugin"
|
||||||
runc "github.com/containerd/go-runc"
|
runc "github.com/containerd/go-runc"
|
||||||
|
|
||||||
@ -87,11 +89,15 @@ type Runtime struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) Create(ctx context.Context, id string, opts plugin.CreateOpts) (t plugin.Task, err error) {
|
func (r *Runtime) Create(ctx context.Context, id string, opts plugin.CreateOpts) (t plugin.Task, err error) {
|
||||||
path, err := r.newBundle(id, opts.Spec)
|
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
s, err := newShim(r.shim, path, r.remote)
|
path, err := r.newBundle(namespace, id, opts.Spec)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
s, err := newShim(r.shim, path, namespace, r.remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
os.RemoveAll(path)
|
os.RemoveAll(path)
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -136,6 +142,10 @@ func (r *Runtime) Create(ctx context.Context, id string, opts plugin.CreateOpts)
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) Delete(ctx context.Context, c plugin.Task) (*plugin.Exit, error) {
|
func (r *Runtime) Delete(ctx context.Context, c plugin.Task) (*plugin.Exit, error) {
|
||||||
|
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
lc, ok := c.(*Task)
|
lc, ok := c.(*Task)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("container cannot be cast as *linux.Container")
|
return nil, fmt.Errorf("container cannot be cast as *linux.Container")
|
||||||
@ -153,7 +163,7 @@ func (r *Runtime) Delete(ctx context.Context, c plugin.Task) (*plugin.Exit, erro
|
|||||||
return &plugin.Exit{
|
return &plugin.Exit{
|
||||||
Status: rsp.ExitStatus,
|
Status: rsp.ExitStatus,
|
||||||
Timestamp: rsp.ExitedAt,
|
Timestamp: rsp.ExitedAt,
|
||||||
}, r.deleteBundle(lc.containerID)
|
}, r.deleteBundle(namespace, lc.containerID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) Tasks(ctx context.Context) ([]plugin.Task, error) {
|
func (r *Runtime) Tasks(ctx context.Context) ([]plugin.Task, error) {
|
||||||
@ -162,6 +172,25 @@ func (r *Runtime) Tasks(ctx context.Context) ([]plugin.Task, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var o []plugin.Task
|
var o []plugin.Task
|
||||||
|
for _, fi := range dir {
|
||||||
|
if !fi.IsDir() {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
tasks, err := r.loadContainers(ctx, fi.Name())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
o = append(o, tasks...)
|
||||||
|
}
|
||||||
|
return o, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Runtime) loadContainers(ctx context.Context, ns string) ([]plugin.Task, error) {
|
||||||
|
dir, err := ioutil.ReadDir(filepath.Join(r.root, ns))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var o []plugin.Task
|
||||||
for _, fi := range dir {
|
for _, fi := range dir {
|
||||||
if !fi.IsDir() {
|
if !fi.IsDir() {
|
||||||
continue
|
continue
|
||||||
@ -169,12 +198,12 @@ func (r *Runtime) Tasks(ctx context.Context) ([]plugin.Task, error) {
|
|||||||
id := fi.Name()
|
id := fi.Name()
|
||||||
// TODO: optimize this if it is call frequently to list all containers
|
// TODO: optimize this if it is call frequently to list all containers
|
||||||
// i.e. dont' reconnect to the the shim's ever time
|
// i.e. dont' reconnect to the the shim's ever time
|
||||||
c, err := r.loadContainer(filepath.Join(r.root, id))
|
c, err := r.loadContainer(ctx, filepath.Join(r.root, ns, id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.G(ctx).WithError(err).Warnf("failed to load container %s", id)
|
log.G(ctx).WithError(err).Warnf("failed to load container %s/%s", ns, id)
|
||||||
// if we fail to load the container, connect to the shim, make sure if the shim has
|
// if we fail to load the container, connect to the shim, make sure if the shim has
|
||||||
// been killed and cleanup the resources still being held by the container
|
// been killed and cleanup the resources still being held by the container
|
||||||
r.killContainer(ctx, id)
|
r.killContainer(ctx, ns, id)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
o = append(o, c)
|
o = append(o, c)
|
||||||
@ -229,8 +258,12 @@ func (r *Runtime) forward(events shim.Shim_EventsClient) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) newBundle(id string, spec []byte) (string, error) {
|
func (r *Runtime) newBundle(namespace, id string, spec []byte) (string, error) {
|
||||||
path := filepath.Join(r.root, id)
|
path := filepath.Join(r.root, namespace)
|
||||||
|
if err := os.MkdirAll(path, 0700); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
path = filepath.Join(path, id)
|
||||||
if err := os.Mkdir(path, 0700); err != nil {
|
if err := os.Mkdir(path, 0700); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -246,26 +279,27 @@ func (r *Runtime) newBundle(id string, spec []byte) (string, error) {
|
|||||||
return path, err
|
return path, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) deleteBundle(id string) error {
|
func (r *Runtime) deleteBundle(namespace, id string) error {
|
||||||
return os.RemoveAll(filepath.Join(r.root, id))
|
return os.RemoveAll(filepath.Join(r.root, namespace, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) loadContainer(path string) (*Task, error) {
|
func (r *Runtime) loadContainer(ctx context.Context, path string) (*Task, error) {
|
||||||
id := filepath.Base(path)
|
namespace, err := namespaces.NamespaceRequired(ctx)
|
||||||
s, err := loadShim(path, r.remote)
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
id := filepath.Base(path)
|
||||||
|
s, err := loadShim(path, namespace, r.remote)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = r.handleEvents(s); err != nil {
|
if err = r.handleEvents(s); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := ioutil.ReadFile(filepath.Join(path, configFilename))
|
data, err := ioutil.ReadFile(filepath.Join(path, configFilename))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &Task{
|
return &Task{
|
||||||
containerID: id,
|
containerID: id,
|
||||||
shim: s,
|
shim: s,
|
||||||
@ -275,13 +309,14 @@ func (r *Runtime) loadContainer(path string) (*Task, error) {
|
|||||||
|
|
||||||
// killContainer is used whenever the runtime fails to connect to a shim (it died)
|
// killContainer is used whenever the runtime fails to connect to a shim (it died)
|
||||||
// and needs to cleanup the container resources in the underlying runtime (runc, etc...)
|
// and needs to cleanup the container resources in the underlying runtime (runc, etc...)
|
||||||
func (r *Runtime) killContainer(ctx context.Context, id string) {
|
func (r *Runtime) killContainer(ctx context.Context, ns, id string) {
|
||||||
log.G(ctx).Debug("terminating container after failed load")
|
log.G(ctx).Debug("terminating container after failed load")
|
||||||
runtime := &runc.Runc{
|
runtime := &runc.Runc{
|
||||||
// TODO: should we get Command provided for initial container creation?
|
// TODO: should we get Command provided for initial container creation?
|
||||||
Command: r.runtime,
|
Command: r.runtime,
|
||||||
LogFormat: runc.JSON,
|
LogFormat: runc.JSON,
|
||||||
PdeathSignal: unix.SIGKILL,
|
PdeathSignal: unix.SIGKILL,
|
||||||
|
Root: filepath.Join(shimb.RuncRoot, ns),
|
||||||
}
|
}
|
||||||
if err := runtime.Kill(ctx, id, int(unix.SIGKILL), &runc.KillOpts{
|
if err := runtime.Kill(ctx, id, int(unix.SIGKILL), &runc.KillOpts{
|
||||||
All: true,
|
All: true,
|
||||||
@ -302,10 +337,10 @@ func (r *Runtime) killContainer(ctx context.Context, id string) {
|
|||||||
if err := runtime.Delete(ctx, id); err != nil {
|
if err := runtime.Delete(ctx, id); err != nil {
|
||||||
log.G(ctx).WithError(err).Warnf("delete container %s", id)
|
log.G(ctx).WithError(err).Warnf("delete container %s", id)
|
||||||
}
|
}
|
||||||
// try to unmount the rootfs is it was not held by an external shim
|
// try to unmount the rootfs in case it was not owned by an external mount namespace
|
||||||
unix.Unmount(filepath.Join(r.root, id, "rootfs"), 0)
|
unix.Unmount(filepath.Join(r.root, ns, id, "rootfs"), 0)
|
||||||
// remove container bundle
|
// remove container bundle
|
||||||
if err := r.deleteBundle(id); err != nil {
|
if err := r.deleteBundle(ns, id); err != nil {
|
||||||
log.G(ctx).WithError(err).Warnf("delete container bundle %s", id)
|
log.G(ctx).WithError(err).Warnf("delete container bundle %s", id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,16 +22,16 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newShim(shimName string, path string, remote bool) (shim.ShimClient, error) {
|
func newShim(shimName string, path, namespace string, remote bool) (shim.ShimClient, error) {
|
||||||
if !remote {
|
if !remote {
|
||||||
return localShim.Client(path)
|
return localShim.Client(path, namespace)
|
||||||
}
|
}
|
||||||
socket := filepath.Join(path, "shim.sock")
|
socket := filepath.Join(path, "shim.sock")
|
||||||
l, err := sys.CreateUnixSocket(socket)
|
l, err := sys.CreateUnixSocket(socket)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
cmd := exec.Command(shimName)
|
cmd := exec.Command(shimName, "--namespace", namespace)
|
||||||
cmd.Dir = path
|
cmd.Dir = path
|
||||||
f, err := l.(*net.UnixListener).File()
|
f, err := l.(*net.UnixListener).File()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,9 +57,9 @@ func newShim(shimName string, path string, remote bool) (shim.ShimClient, error)
|
|||||||
return connectShim(socket)
|
return connectShim(socket)
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadShim(path string, remote bool) (shim.ShimClient, error) {
|
func loadShim(path, namespace string, remote bool) (shim.ShimClient, error) {
|
||||||
if !remote {
|
if !remote {
|
||||||
return localShim.Client(path)
|
return localShim.Client(path, namespace)
|
||||||
}
|
}
|
||||||
socket := filepath.Join(path, "shim.sock")
|
socket := filepath.Join(path, "shim.sock")
|
||||||
return connectShim(socket)
|
return connectShim(socket)
|
||||||
|
@ -4,6 +4,7 @@ package shim
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
shimapi "github.com/containerd/containerd/api/services/shim"
|
shimapi "github.com/containerd/containerd/api/services/shim"
|
||||||
"github.com/containerd/containerd/api/types/task"
|
"github.com/containerd/containerd/api/types/task"
|
||||||
@ -15,23 +16,28 @@ import (
|
|||||||
"google.golang.org/grpc/metadata"
|
"google.golang.org/grpc/metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Client(path string) (shimapi.ShimClient, error) {
|
func Client(path, namespace string) (shimapi.ShimClient, error) {
|
||||||
pid, err := runc.ReadPidFile(filepath.Join(path, "init.pid"))
|
pid, err := runc.ReadPidFile(filepath.Join(path, "init.pid"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cl := &client{
|
s, err := New(path, namespace)
|
||||||
s: New(path),
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cl := &client{
|
||||||
// used when quering container status and info
|
s: s,
|
||||||
|
}
|
||||||
|
// used when quering container status and info
|
||||||
cl.s.initProcess = &initProcess{
|
cl.s.initProcess = &initProcess{
|
||||||
id: filepath.Base(path),
|
id: filepath.Base(path),
|
||||||
pid: pid,
|
pid: pid,
|
||||||
runc: &runc.Runc{
|
runc: &runc.Runc{
|
||||||
Log: filepath.Join(path, "log.json"),
|
Log: filepath.Join(path, "log.json"),
|
||||||
LogFormat: runc.JSON,
|
LogFormat: runc.JSON,
|
||||||
|
PdeathSignal: syscall.SIGKILL,
|
||||||
|
Root: filepath.Join(RuncRoot, namespace),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
return cl, nil
|
return cl, nil
|
||||||
|
@ -49,7 +49,7 @@ type initProcess struct {
|
|||||||
terminal bool
|
terminal bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func newInitProcess(context context.Context, path string, r *shimapi.CreateRequest) (*initProcess, error) {
|
func newInitProcess(context context.Context, path, namespace string, r *shimapi.CreateRequest) (*initProcess, error) {
|
||||||
for _, rm := range r.Rootfs {
|
for _, rm := range r.Rootfs {
|
||||||
m := &mount.Mount{
|
m := &mount.Mount{
|
||||||
Type: rm.Type,
|
Type: rm.Type,
|
||||||
@ -65,6 +65,7 @@ func newInitProcess(context context.Context, path string, r *shimapi.CreateReque
|
|||||||
Log: filepath.Join(path, "log.json"),
|
Log: filepath.Join(path, "log.json"),
|
||||||
LogFormat: runc.JSON,
|
LogFormat: runc.JSON,
|
||||||
PdeathSignal: syscall.SIGKILL,
|
PdeathSignal: syscall.SIGKILL,
|
||||||
|
Root: filepath.Join(RuncRoot, namespace),
|
||||||
}
|
}
|
||||||
p := &initProcess{
|
p := &initProcess{
|
||||||
id: r.ID,
|
id: r.ID,
|
||||||
|
@ -20,13 +20,19 @@ import (
|
|||||||
|
|
||||||
var empty = &google_protobuf.Empty{}
|
var empty = &google_protobuf.Empty{}
|
||||||
|
|
||||||
|
const RuncRoot = "/run/containerd/runc"
|
||||||
|
|
||||||
// New returns a new shim service that can be used via GRPC
|
// New returns a new shim service that can be used via GRPC
|
||||||
func New(path string) *Service {
|
func New(path, namespace string) (*Service, error) {
|
||||||
|
if namespace == "" {
|
||||||
|
return nil, fmt.Errorf("shim namespace cannot be empty")
|
||||||
|
}
|
||||||
return &Service{
|
return &Service{
|
||||||
path: path,
|
path: path,
|
||||||
processes: make(map[int]process),
|
processes: make(map[int]process),
|
||||||
events: make(chan *task.Event, 4096),
|
events: make(chan *task.Event, 4096),
|
||||||
}
|
namespace: namespace,
|
||||||
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type Service struct {
|
type Service struct {
|
||||||
@ -40,10 +46,11 @@ type Service struct {
|
|||||||
eventsMu sync.Mutex
|
eventsMu sync.Mutex
|
||||||
deferredEvent *task.Event
|
deferredEvent *task.Event
|
||||||
execID int
|
execID int
|
||||||
|
namespace string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) Create(ctx context.Context, r *shimapi.CreateRequest) (*shimapi.CreateResponse, error) {
|
func (s *Service) Create(ctx context.Context, r *shimapi.CreateRequest) (*shimapi.CreateResponse, error) {
|
||||||
process, err := newInitProcess(ctx, s.path, r)
|
process, err := newInitProcess(ctx, s.path, s.namespace, r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user