[sandbox] Add ctr support

Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
This commit is contained in:
Maksym Pavlenko 2021-11-24 10:58:48 -08:00
parent 982de8a5d5
commit 17a2aaded3
8 changed files with 249 additions and 14 deletions

View File

@ -31,6 +31,7 @@ import (
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
"github.com/containerd/containerd/cmd/ctr/commands/pprof"
"github.com/containerd/containerd/cmd/ctr/commands/run"
"github.com/containerd/containerd/cmd/ctr/commands/sandboxes"
"github.com/containerd/containerd/cmd/ctr/commands/snapshots"
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
@ -114,6 +115,7 @@ containerd CLI
tasks.Command,
install.Command,
ociCmd.Command,
sandboxes.Command,
}, extraCmds...)
app.Before = func(context *cli.Context) error {
if context.GlobalBool("debug") {

View File

@ -0,0 +1,196 @@
/*
Copyright The containerd Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package sandboxes
import (
"fmt"
"os"
"text/tabwriter"
"github.com/containerd/containerd"
"github.com/containerd/containerd/cmd/ctr/commands"
"github.com/containerd/containerd/defaults"
"github.com/containerd/containerd/log"
"github.com/containerd/containerd/oci"
"github.com/urfave/cli"
)
// Command is a set of subcommands to manage runtimes with sandbox support
var Command = cli.Command{
Name: "sandboxes",
Aliases: []string{"sandbox", "sb", "s"},
Usage: "manage sandboxes",
Subcommands: cli.Commands{
runCommand,
listCommand,
removeCommand,
pingCommand,
},
}
var runCommand = cli.Command{
Name: "run",
Aliases: []string{"create", "c", "r"},
Usage: "run a new sandbox",
ArgsUsage: "[flags] <pod-config.json> <sandbox-id>",
Flags: []cli.Flag{
cli.StringFlag{
Name: "runtime",
Usage: "runtime name",
Value: defaults.DefaultRuntime,
},
},
Action: func(context *cli.Context) error {
var (
id = context.Args().Get(0)
runtime = context.String("runtime")
)
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
sandbox, err := client.NewSandbox(ctx, id,
containerd.WithSandboxRuntime(runtime, nil),
containerd.WithSandboxSpec(&oci.Spec{}),
)
if err != nil {
return fmt.Errorf("failed to create new sandbox: %w", err)
}
err = sandbox.Start(ctx)
if err != nil {
return fmt.Errorf("failed to start: %w", err)
}
fmt.Println(sandbox.ID())
return nil
},
}
var listCommand = cli.Command{
Name: "list",
Aliases: []string{"ls"},
Usage: "list sandboxes",
Flags: []cli.Flag{
cli.StringSliceFlag{
Name: "filters",
Usage: "the list of filters to apply when querying sandboxes from the store",
},
},
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
var (
writer = tabwriter.NewWriter(os.Stdout, 1, 8, 1, ' ', 0)
filters = context.StringSlice("filters")
)
defer func() {
_ = writer.Flush()
}()
list, err := client.SandboxStore().List(ctx, filters...)
if err != nil {
return fmt.Errorf("failed to list sandboxes: %w", err)
}
if _, err := fmt.Fprintln(writer, "ID\tCREATED\tRUNTIME\t"); err != nil {
return err
}
for _, sandbox := range list {
_, err := fmt.Fprintf(writer, "%s\t%s\t%s\t\n", sandbox.ID, sandbox.CreatedAt, sandbox.Runtime.Name)
if err != nil {
return err
}
}
return nil
},
}
var removeCommand = cli.Command{
Name: "remove",
Aliases: []string{"rm"},
ArgsUsage: "<id> [<id>, ...]",
Usage: "remove sandboxes",
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "ignore shutdown errors when removing sandbox",
},
},
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
for _, id := range context.Args() {
sandbox, err := client.LoadSandbox(ctx, id)
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to load sandbox %s", id)
continue
}
err = sandbox.Shutdown(ctx, context.Bool("force"))
if err != nil {
log.G(ctx).WithError(err).Errorf("failed to shutdown sandbox %s", id)
continue
}
log.G(ctx).Infof("deleted: %s", id)
}
return nil
},
}
var pingCommand = cli.Command{
Name: "ping",
ArgsUsage: "<id> [<id>, ...]",
Usage: "ping sandbox",
Action: func(context *cli.Context) error {
client, ctx, cancel, err := commands.NewClient(context)
if err != nil {
return err
}
defer cancel()
for _, id := range context.Args() {
sandbox, err := client.LoadSandbox(ctx, id)
if err != nil {
return fmt.Errorf("failed to load sandbox %s: %w", id, err)
}
err = sandbox.Ping(ctx)
if err != nil {
return fmt.Errorf("failed to ping %s: %w", id, err)
}
}
return nil
},
}

View File

@ -202,7 +202,8 @@ func (s *sandboxStore) List(ctx context.Context, fields ...string) ([]api.Sandbo
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
bucket := getSandboxBucket(tx, ns)
if bucket == nil {
return errors.Wrap(errdefs.ErrNotFound, "no sandbox buckets")
// We haven't created any sandboxes yet, just return empty list
return nil
}
if err := bucket.ForEach(func(k, v []byte) error {

View File

@ -25,6 +25,8 @@ import (
"github.com/containerd/containerd/identifiers"
"github.com/containerd/containerd/mount"
"github.com/containerd/containerd/namespaces"
"github.com/containerd/typeurl"
"github.com/opencontainers/runtime-spec/specs-go"
)
const configFilename = "config.json"
@ -43,7 +45,7 @@ func LoadBundle(ctx context.Context, root, id string) (*Bundle, error) {
}
// NewBundle returns a new bundle on disk
func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bundle, err error) {
func NewBundle(ctx context.Context, root, state, id string, spec typeurl.Any) (b *Bundle, err error) {
if err := identifiers.Validate(id); err != nil {
return nil, fmt.Errorf("invalid task id %s: %w", id, err)
}
@ -73,8 +75,10 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
if err := os.Mkdir(b.Path, 0700); err != nil {
return nil, err
}
if err := prepareBundleDirectoryPermissions(b.Path, spec); err != nil {
return nil, err
if typeurl.Is(spec, &specs.Spec{}) {
if err := prepareBundleDirectoryPermissions(b.Path, spec.GetValue()); err != nil {
return nil, err
}
}
paths = append(paths, b.Path)
// create working directory for the bundle
@ -100,9 +104,14 @@ func NewBundle(ctx context.Context, root, state, id string, spec []byte) (b *Bun
if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil {
return nil, err
}
// write the spec to the bundle
err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666)
return b, err
if spec := spec.GetValue(); spec != nil {
// write the spec to the bundle
err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666)
if err != nil {
return nil, fmt.Errorf("failed to write %s", configFilename)
}
}
return b, nil
}
// Bundle represents an OCI bundle

View File

@ -29,6 +29,7 @@ import (
"github.com/containerd/containerd/namespaces"
"github.com/containerd/containerd/oci"
"github.com/containerd/containerd/pkg/testutil"
"github.com/containerd/typeurl"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@ -57,11 +58,11 @@ func TestNewBundle(t *testing.T) {
GIDMappings: []specs.LinuxIDMapping{{ContainerID: 0, HostID: usernsGID}},
}
}
specBytes, err := json.Marshal(&spec)
specAny, err := typeurl.MarshalAny(&spec)
require.NoError(t, err, "failed to marshal spec")
ctx := namespaces.WithNamespace(context.TODO(), namespaces.Default)
b, err := NewBundle(ctx, work, state, id, specBytes)
b, err := NewBundle(ctx, work, state, id, specAny)
require.NoError(t, err, "NewBundle should succeed")
require.NotNil(t, b, "bundle should not be nil")

View File

@ -158,7 +158,7 @@ func (m *ShimManager) ID() string {
// Start launches a new shim instance
func (m *ShimManager) Start(ctx context.Context, id string, opts runtime.CreateOpts) (_ ShimProcess, retErr error) {
bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec.GetValue())
bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec)
if err != nil {
return nil, err
}

View File

@ -18,6 +18,7 @@ package containerd
import (
"context"
"fmt"
"time"
api "github.com/containerd/containerd/sandbox"
@ -36,8 +37,9 @@ type Sandbox interface {
Labels(ctx context.Context) (map[string]string, error)
// Start starts new sandbox instance
Start(ctx context.Context) error
// Shutdown will turn down existing sandbox instance
Shutdown(ctx context.Context) error
// Shutdown will turn down existing sandbox instance.
// If using force, the client will ignore shutdown errors.
Shutdown(ctx context.Context, force bool) error
// Pause will freeze running sandbox instance
Pause(ctx context.Context) error
// Resume will unfreeze previously paused sandbox instance
@ -74,8 +76,23 @@ func (s *sandboxClient) Start(ctx context.Context) error {
return s.client.SandboxController().Start(ctx, s.ID())
}
func (s *sandboxClient) Shutdown(ctx context.Context) error {
return s.client.SandboxController().Shutdown(ctx, s.ID())
func (s *sandboxClient) Shutdown(ctx context.Context, force bool) error {
var (
controller = s.client.SandboxController()
store = s.client.SandboxStore()
)
err := controller.Shutdown(ctx, s.ID())
if err != nil && !force {
return fmt.Errorf("failed to shutdown sandbox: %w", err)
}
err = store.Delete(ctx, s.ID())
if err != nil {
return fmt.Errorf("failed to delete sandbox from metadata store: %w", err)
}
return nil
}
func (s *sandboxClient) Pause(ctx context.Context) error {
@ -105,6 +122,10 @@ func (s *sandboxClient) Status(ctx context.Context, status interface{}) error {
// NewSandbox creates new sandbox client
func (c *Client) NewSandbox(ctx context.Context, sandboxID string, opts ...NewSandboxOpts) (Sandbox, error) {
if sandboxID == "" {
return nil, errors.New("sandbox ID must be specified")
}
newSandbox := api.Sandbox{
ID: sandboxID,
CreatedAt: time.Now().UTC(),
@ -147,6 +168,10 @@ type NewSandboxOpts func(ctx context.Context, client *Client, sandbox *api.Sandb
// WithSandboxRuntime allows a user to specify the runtime to be used to run a sandbox
func WithSandboxRuntime(name string, options interface{}) NewSandboxOpts {
return func(ctx context.Context, client *Client, s *api.Sandbox) error {
if options == nil {
options = &types.Empty{}
}
opts, err := typeurl.MarshalAny(options)
if err != nil {
return errors.Wrap(err, "failed to marshal sandbox runtime options")

View File

@ -47,6 +47,7 @@ func FromProto(p *types.Sandbox) Sandbox {
ID: p.SandboxID,
Labels: p.Labels,
Runtime: runtime,
Spec: p.Spec,
CreatedAt: p.CreatedAt,
UpdatedAt: p.UpdatedAt,
Extensions: p.Extensions,