[sandbox] Add ctr support
Signed-off-by: Maksym Pavlenko <pavlenko.maksym@gmail.com>
This commit is contained in:
parent
982de8a5d5
commit
17a2aaded3
@ -31,6 +31,7 @@ import (
|
|||||||
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
|
"github.com/containerd/containerd/cmd/ctr/commands/plugins"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands/pprof"
|
"github.com/containerd/containerd/cmd/ctr/commands/pprof"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands/run"
|
"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/snapshots"
|
||||||
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
|
"github.com/containerd/containerd/cmd/ctr/commands/tasks"
|
||||||
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
|
versionCmd "github.com/containerd/containerd/cmd/ctr/commands/version"
|
||||||
@ -114,6 +115,7 @@ containerd CLI
|
|||||||
tasks.Command,
|
tasks.Command,
|
||||||
install.Command,
|
install.Command,
|
||||||
ociCmd.Command,
|
ociCmd.Command,
|
||||||
|
sandboxes.Command,
|
||||||
}, extraCmds...)
|
}, extraCmds...)
|
||||||
app.Before = func(context *cli.Context) error {
|
app.Before = func(context *cli.Context) error {
|
||||||
if context.GlobalBool("debug") {
|
if context.GlobalBool("debug") {
|
||||||
|
196
cmd/ctr/commands/sandboxes/sandboxes.go
Normal file
196
cmd/ctr/commands/sandboxes/sandboxes.go
Normal 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
|
||||||
|
},
|
||||||
|
}
|
@ -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 {
|
if err := view(ctx, s.db, func(tx *bbolt.Tx) error {
|
||||||
bucket := getSandboxBucket(tx, ns)
|
bucket := getSandboxBucket(tx, ns)
|
||||||
if bucket == nil {
|
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 {
|
if err := bucket.ForEach(func(k, v []byte) error {
|
||||||
|
@ -25,6 +25,8 @@ import (
|
|||||||
"github.com/containerd/containerd/identifiers"
|
"github.com/containerd/containerd/identifiers"
|
||||||
"github.com/containerd/containerd/mount"
|
"github.com/containerd/containerd/mount"
|
||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const configFilename = "config.json"
|
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
|
// 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 {
|
if err := identifiers.Validate(id); err != nil {
|
||||||
return nil, fmt.Errorf("invalid task id %s: %w", id, err)
|
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 {
|
if err := os.Mkdir(b.Path, 0700); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if err := prepareBundleDirectoryPermissions(b.Path, spec); err != nil {
|
if typeurl.Is(spec, &specs.Spec{}) {
|
||||||
return nil, err
|
if err := prepareBundleDirectoryPermissions(b.Path, spec.GetValue()); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
paths = append(paths, b.Path)
|
paths = append(paths, b.Path)
|
||||||
// create working directory for the bundle
|
// 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 {
|
if err := os.Symlink(work, filepath.Join(b.Path, "work")); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// write the spec to the bundle
|
if spec := spec.GetValue(); spec != nil {
|
||||||
err = os.WriteFile(filepath.Join(b.Path, configFilename), spec, 0666)
|
// write the spec to the bundle
|
||||||
return b, err
|
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
|
// Bundle represents an OCI bundle
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
"github.com/containerd/containerd/namespaces"
|
"github.com/containerd/containerd/namespaces"
|
||||||
"github.com/containerd/containerd/oci"
|
"github.com/containerd/containerd/oci"
|
||||||
"github.com/containerd/containerd/pkg/testutil"
|
"github.com/containerd/containerd/pkg/testutil"
|
||||||
|
"github.com/containerd/typeurl"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
"github.com/opencontainers/runtime-spec/specs-go"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
@ -57,11 +58,11 @@ func TestNewBundle(t *testing.T) {
|
|||||||
GIDMappings: []specs.LinuxIDMapping{{ContainerID: 0, HostID: usernsGID}},
|
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")
|
require.NoError(t, err, "failed to marshal spec")
|
||||||
|
|
||||||
ctx := namespaces.WithNamespace(context.TODO(), namespaces.Default)
|
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.NoError(t, err, "NewBundle should succeed")
|
||||||
require.NotNil(t, b, "bundle should not be nil")
|
require.NotNil(t, b, "bundle should not be nil")
|
||||||
|
|
||||||
|
@ -158,7 +158,7 @@ func (m *ShimManager) ID() string {
|
|||||||
|
|
||||||
// Start launches a new shim instance
|
// Start launches a new shim instance
|
||||||
func (m *ShimManager) Start(ctx context.Context, id string, opts runtime.CreateOpts) (_ ShimProcess, retErr error) {
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
33
sandbox.go
33
sandbox.go
@ -18,6 +18,7 @@ package containerd
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
api "github.com/containerd/containerd/sandbox"
|
api "github.com/containerd/containerd/sandbox"
|
||||||
@ -36,8 +37,9 @@ type Sandbox interface {
|
|||||||
Labels(ctx context.Context) (map[string]string, error)
|
Labels(ctx context.Context) (map[string]string, error)
|
||||||
// Start starts new sandbox instance
|
// Start starts new sandbox instance
|
||||||
Start(ctx context.Context) error
|
Start(ctx context.Context) error
|
||||||
// Shutdown will turn down existing sandbox instance
|
// Shutdown will turn down existing sandbox instance.
|
||||||
Shutdown(ctx context.Context) error
|
// If using force, the client will ignore shutdown errors.
|
||||||
|
Shutdown(ctx context.Context, force bool) error
|
||||||
// Pause will freeze running sandbox instance
|
// Pause will freeze running sandbox instance
|
||||||
Pause(ctx context.Context) error
|
Pause(ctx context.Context) error
|
||||||
// Resume will unfreeze previously paused sandbox instance
|
// 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())
|
return s.client.SandboxController().Start(ctx, s.ID())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sandboxClient) Shutdown(ctx context.Context) error {
|
func (s *sandboxClient) Shutdown(ctx context.Context, force bool) error {
|
||||||
return s.client.SandboxController().Shutdown(ctx, s.ID())
|
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 {
|
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
|
// NewSandbox creates new sandbox client
|
||||||
func (c *Client) NewSandbox(ctx context.Context, sandboxID string, opts ...NewSandboxOpts) (Sandbox, error) {
|
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{
|
newSandbox := api.Sandbox{
|
||||||
ID: sandboxID,
|
ID: sandboxID,
|
||||||
CreatedAt: time.Now().UTC(),
|
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
|
// WithSandboxRuntime allows a user to specify the runtime to be used to run a sandbox
|
||||||
func WithSandboxRuntime(name string, options interface{}) NewSandboxOpts {
|
func WithSandboxRuntime(name string, options interface{}) NewSandboxOpts {
|
||||||
return func(ctx context.Context, client *Client, s *api.Sandbox) error {
|
return func(ctx context.Context, client *Client, s *api.Sandbox) error {
|
||||||
|
if options == nil {
|
||||||
|
options = &types.Empty{}
|
||||||
|
}
|
||||||
|
|
||||||
opts, err := typeurl.MarshalAny(options)
|
opts, err := typeurl.MarshalAny(options)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to marshal sandbox runtime options")
|
return errors.Wrap(err, "failed to marshal sandbox runtime options")
|
||||||
|
@ -47,6 +47,7 @@ func FromProto(p *types.Sandbox) Sandbox {
|
|||||||
ID: p.SandboxID,
|
ID: p.SandboxID,
|
||||||
Labels: p.Labels,
|
Labels: p.Labels,
|
||||||
Runtime: runtime,
|
Runtime: runtime,
|
||||||
|
Spec: p.Spec,
|
||||||
CreatedAt: p.CreatedAt,
|
CreatedAt: p.CreatedAt,
|
||||||
UpdatedAt: p.UpdatedAt,
|
UpdatedAt: p.UpdatedAt,
|
||||||
Extensions: p.Extensions,
|
Extensions: p.Extensions,
|
||||||
|
Loading…
Reference in New Issue
Block a user