Change WithSpec to take SpecOpts

Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
This commit is contained in:
Michael Crosby 2017-08-23 15:05:42 -04:00
parent 5c7f67186a
commit 2052b76fa7
7 changed files with 170 additions and 104 deletions

View File

@ -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
View 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()
}

View File

@ -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),
)
```

View File

@ -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

View File

@ -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
}
}

View File

@ -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)

View File

@ -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