Change WithSpec to take SpecOpts
Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
parent
5c7f67186a
commit
2052b76fa7
27
README.md
27
README.md
@ -60,25 +60,25 @@ image, err := client.Pull(context, "docker.io/library/redis:latest")
|
||||
err := client.Push(context, "docker.io/library/redis:latest", image.Target())
|
||||
```
|
||||
|
||||
### OCI Runtime Specification
|
||||
|
||||
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
||||
|
||||
```go
|
||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(context, image))
|
||||
```
|
||||
|
||||
### Containers
|
||||
|
||||
In containerd, a container is a metadata object. Resources such as an OCI runtime specification, image, root filesystem, and other metadata can be attached to a container.
|
||||
|
||||
```go
|
||||
redis, err := client.NewContainer(context, "redis-master",
|
||||
containerd.WithNewSpec(spec),
|
||||
)
|
||||
redis, err := client.NewContainer(context, "redis-master")
|
||||
defer redis.Delete(context)
|
||||
```
|
||||
|
||||
### OCI Runtime Specification
|
||||
|
||||
containerd fully supports the OCI runtime specification for running containers. We have built in functions to help you generate runtime specifications based on images as well as custom parameters.
|
||||
|
||||
You can specify options when creating a container about how to modify the specification.
|
||||
|
||||
```go
|
||||
redis, err := client.NewContainer(context, "redis-master", containerd.WithNewSpec(containerd.WithImageConfig(image)))
|
||||
```
|
||||
|
||||
## Root Filesystems
|
||||
|
||||
containerd allows you to use overlay or snapshot filesystems with your containers. It comes with builtin support for overlayfs and btrfs.
|
||||
@ -89,16 +89,17 @@ image, err := client.Pull(context, "docker.io/library/redis:latest", containerd.
|
||||
|
||||
// allocate a new RW root filesystem for a container based on the image
|
||||
redis, err := client.NewContainer(context, "redis-master",
|
||||
containerd.WithNewSpec(spec),
|
||||
containerd.WithNewSnapshot("redis-rootfs", image),
|
||||
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||
|
||||
)
|
||||
|
||||
// use a readonly filesystem with multiple containers
|
||||
for i := 0; i < 10; i++ {
|
||||
id := fmt.Sprintf("id-%s", i)
|
||||
container, err := client.NewContainer(ctx, id,
|
||||
containerd.WithNewSpec(spec),
|
||||
containerd.WithNewSnapshotView(id, image),
|
||||
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||
)
|
||||
}
|
||||
```
|
||||
|
105
benchmark_test.go
Normal file
105
benchmark_test.go
Normal file
@ -0,0 +1,105 @@
|
||||
package containerd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func BenchmarkContainerCreate(b *testing.B) {
|
||||
client, err := newClient(b, address)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext()
|
||||
defer cancel()
|
||||
|
||||
image, err := client.GetImage(ctx, testImage)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
spec, err := GenerateSpec(ctx, client, nil, WithImageConfig(image), withTrue())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
var containers []Container
|
||||
defer func() {
|
||||
for _, c := range containers {
|
||||
if err := c.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// reset the timer before creating containers
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := fmt.Sprintf("%s-%d", b.Name(), i)
|
||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
containers = append(containers, container)
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
||||
|
||||
func BenchmarkContainerStart(b *testing.B) {
|
||||
client, err := newClient(b, address)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
ctx, cancel := testContext()
|
||||
defer cancel()
|
||||
|
||||
image, err := client.GetImage(ctx, testImage)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
spec, err := GenerateSpec(ctx, client, nil, WithImageConfig(image), withTrue())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
var containers []Container
|
||||
defer func() {
|
||||
for _, c := range containers {
|
||||
if err := c.Delete(ctx, WithSnapshotCleanup); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := fmt.Sprintf("%s-%d", b.Name(), i)
|
||||
container, err := client.NewContainer(ctx, id, WithSpec(spec), WithNewSnapshot(id, image))
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
containers = append(containers, container)
|
||||
|
||||
}
|
||||
// reset the timer before starting tasks
|
||||
b.ResetTimer()
|
||||
for _, c := range containers {
|
||||
task, err := c.NewTask(ctx, empty())
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
defer task.Delete(ctx)
|
||||
if err := task.Start(ctx); err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
}
|
||||
}
|
||||
b.StopTimer()
|
||||
}
|
@ -10,7 +10,7 @@ For many functions and methods within the client package you will generally see
|
||||
|
||||
If we look at the `NewContainer` method on the client we can see that it has a required argument of `id` and then additional `NewContainerOpts`.
|
||||
|
||||
There are a few built in options that allow the container to be created with an existing spec, `WithNewSpec`, and snapshot opts for creating or using an existing snapshot.
|
||||
There are a few built in options that allow the container to be created with an existing spec, `WithSpec`, and snapshot opts for creating or using an existing snapshot.
|
||||
|
||||
```go
|
||||
func (c *Client) NewContainer(ctx context.Context, id string, opts ...NewContainerOpts) (Container, error) {
|
||||
@ -90,9 +90,8 @@ Adding your new option to spec generation is as easy as importing your new packa
|
||||
```go
|
||||
import "github.com/crosbymichael/monitor"
|
||||
|
||||
spec, err := containerd.GenerateSpec(
|
||||
containerd.WithImageConfig(ctx, image),
|
||||
monitor.WithHtop,
|
||||
container, err := client.NewContainer(ctx, id,
|
||||
containerd.WithNewSpec(containerd.WithImageConfig(image), monitor.WithHtop),
|
||||
)
|
||||
```
|
||||
|
||||
|
@ -90,28 +90,19 @@ We use the `containerd.WithPullUnpack` so that we not only fetch and download th
|
||||
|
||||
## Creating an OCI Spec and Container
|
||||
|
||||
Now that we have an image to base our container off of, we need to generate an OCI runtime specification that the container can be based off of.
|
||||
Now that we have an image to base our container off of, we need to generate an OCI runtime specification that the container can be based off of as well as the new container.
|
||||
|
||||
containerd provides reasonable defaults for generating OCI runtime specs.
|
||||
There is also an `Opt` for modifying the default config based on the image that we pulled.
|
||||
|
||||
```go
|
||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(ctx, image))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
```
|
||||
|
||||
After we have a spec generated we need to create a container.
|
||||
The container will be based off of the image, use the runtime information in the spec that was just created, and we will allocate a new read-write snapshot so the container can store any persistent information.
|
||||
|
||||
```go
|
||||
container, err := client.NewContainer(
|
||||
ctx,
|
||||
"redis-server",
|
||||
containerd.WithNewSpec(spec),
|
||||
containerd.WithImage(image),
|
||||
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
||||
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -119,6 +110,8 @@ The container will be based off of the image, use the runtime information in the
|
||||
defer container.Delete(ctx, containerd.WithSnapshotCleanup)
|
||||
```
|
||||
|
||||
If you have an existing OCI specification created you can use `containerd.WithSpec(spec)` to set it on the container.
|
||||
|
||||
When creating a new snapshot for the container we need to provide a snapshot ID as well as the Image that the container will be based on.
|
||||
By providing a separate snapshot ID than the container ID we can easily reuse, existing snapshots across different containers.
|
||||
|
||||
@ -244,19 +237,13 @@ func redisExample() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// generate an OCI runtime spec using the Args, Env, etc from the redis image that we pulled
|
||||
spec, err := containerd.GenerateSpec(containerd.WithImageConfig(ctx, image))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create a container
|
||||
container, err := client.NewContainer(
|
||||
ctx,
|
||||
"redis-server",
|
||||
containerd.WithNewSpec(spec),
|
||||
containerd.WithImage(image),
|
||||
containerd.WithNewSnapshot("redis-server-snapshot", image),
|
||||
containerd.WithNewSpec(containerd.WithImageConfig(image)),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
39
spec_opts.go
39
spec_opts.go
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/typeurl"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
|
||||
@ -25,3 +26,41 @@ func WithHostname(name string) SpecOpts {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithNewSpec generates a new spec for a new container
|
||||
func WithNewSpec(opts ...SpecOpts) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
s, err := createDefaultSpec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(ctx, client, c, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
any, err := typeurl.MarshalAny(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpec sets the provided spec on the container
|
||||
func WithSpec(s *specs.Spec, opts ...SpecOpts) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
for _, o := range opts {
|
||||
if err := o(ctx, client, c, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
any, err := typeurl.MarshalAny(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -18,11 +18,11 @@ import (
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/namespaces"
|
||||
"github.com/containerd/containerd/typeurl"
|
||||
"github.com/opencontainers/image-spec/identity"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/opencontainers/runc/libcontainer/user"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// WithTTY sets the information on the spec as well as the environment variables for
|
||||
@ -148,39 +148,6 @@ func WithRootFSPath(path string, readonly bool) SpecOpts {
|
||||
}
|
||||
}
|
||||
|
||||
// WithNewSpec generates a new spec for a new container
|
||||
func WithNewSpec(opts ...SpecOpts) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
s, err := createDefaultSpec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(ctx, client, c, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
any, err := typeurl.MarshalAny(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpec sets the provided spec on the container
|
||||
func WithSpec(s *specs.Spec) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
any, err := typeurl.MarshalAny(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithResources sets the provided resources on the spec for task updates
|
||||
func WithResources(resources *specs.LinuxResources) UpdateTaskOpts {
|
||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||
@ -335,10 +302,10 @@ func WithUserIDs(uid, gid uint32) SpecOpts {
|
||||
func WithUsername(username string) SpecOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container, s *specs.Spec) error {
|
||||
if c.Snapshotter == "" {
|
||||
return fmt.Errorf("no snapshotter set for container")
|
||||
return errors.Errorf("no snapshotter set for container")
|
||||
}
|
||||
if c.RootFS == "" {
|
||||
return fmt.Errorf("rootfs not created for container")
|
||||
return errors.Errorf("rootfs not created for container")
|
||||
}
|
||||
snapshotter := client.SnapshotService(c.Snapshotter)
|
||||
mounts, err := snapshotter.Mounts(ctx, c.RootFS)
|
||||
@ -368,7 +335,7 @@ func WithUsername(username string) SpecOpts {
|
||||
return err
|
||||
}
|
||||
if len(users) == 0 {
|
||||
return fmt.Errorf("no users found for %s", username)
|
||||
return errors.Errorf("no users found for %s", username)
|
||||
}
|
||||
u := users[0]
|
||||
s.Process.User.UID, s.Process.User.GID = uint32(u.Uid), uint32(u.Gid)
|
||||
|
@ -10,7 +10,6 @@ import (
|
||||
"github.com/containerd/containerd/containers"
|
||||
"github.com/containerd/containerd/content"
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/containerd/containerd/typeurl"
|
||||
"github.com/opencontainers/image-spec/specs-go/v1"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
)
|
||||
@ -60,37 +59,6 @@ func WithTTY(width, height int) SpecOpts {
|
||||
}
|
||||
}
|
||||
|
||||
func WithNewSpec(opts ...SpecOpts) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
s, err := createDefaultSpec()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(ctx, client, c, s); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
any, err := typeurl.MarshalAny(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithSpec(spec *specs.Spec) NewContainerOpts {
|
||||
return func(ctx context.Context, client *Client, c *containers.Container) error {
|
||||
any, err := typeurl.MarshalAny(spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Spec = any
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithResources(resources *specs.WindowsResources) UpdateTaskOpts {
|
||||
return func(ctx context.Context, client *Client, r *UpdateTaskInfo) error {
|
||||
r.Resources = resources
|
||||
|
Loading…
Reference in New Issue
Block a user