github.com/demonoid81/containerd@v1.3.4/docs/getting-started.md (about)

     1  # Getting started with containerd
     2  
     3  There are many different ways to use containerd.
     4  If you are a developer working on containerd you can use the `ctr` tool to quickly test features and functionality without writing extra code.
     5  However, if you want to integrate containerd into your project we have an easy to use client package that allows you to work with containerd.
     6  
     7  In this guide we will pull and run a redis server with containerd using the client package.
     8  We will assume that you are running a modern linux host for this example with a compatible build of `runc`.
     9  Please refer to [RUNC.md](/RUNC.md) for the currently supported version of `runc`.
    10  This project requires Go 1.9.x or above.
    11  If you need to install Go or update your currently installed one, please refer to Go install page at https://golang.org/doc/install.
    12  
    13  ## Starting containerd
    14  
    15  You can download one of the latest builds for containerd on the [github releases](https://github.com/containerd/containerd/releases) page and then use your favorite process supervisor to get the daemon started.
    16  If you are using systemd, we have a `containerd.service` file at the root of the repository that you can use.
    17  
    18  The daemon also uses a configuration file located in `/etc/containerd/config.toml` for specifying daemon level options.
    19  A sample configuration file looks like this:
    20  
    21  ```toml
    22  subreaper = true
    23  oom_score = -999
    24  
    25  [debug]
    26          level = "debug"
    27  
    28  [metrics]
    29          address = "127.0.0.1:1338"
    30  
    31  [plugins.linux]
    32          runtime = "runc"
    33          shim_debug = true
    34  ```
    35  
    36  The default configuration can be generated via `containerd config default > /etc/containerd/config.toml`.
    37  
    38  ## Connecting to containerd
    39  
    40  We will start a new `main.go` file and import the containerd root package that contains the client.
    41  
    42  
    43  ```go
    44  package main
    45  
    46  import (
    47  	"log"
    48  
    49  	"github.com/containerd/containerd"
    50  )
    51  
    52  func main() {
    53  	if err := redisExample(); err != nil {
    54  		log.Fatal(err)
    55  	}
    56  }
    57  
    58  func redisExample() error {
    59  	client, err := containerd.New("/run/containerd/containerd.sock")
    60  	if err != nil {
    61  		return err
    62  	}
    63  	defer client.Close()
    64  	return nil
    65  }
    66  ```
    67  
    68  This will create a new client with the default containerd socket path.
    69  Because we are working with a daemon over GRPC we need to create a `context` for use with calls to client methods.
    70  containerd is also namespaced for callers of the API.
    71  We should also set a namespace for our guide after creating the context.
    72  
    73  ```go
    74  	ctx := namespaces.WithNamespace(context.Background(), "example")
    75  ```
    76  
    77  Having a namespace for our usage ensures that containers, images, and other resources without containerd do not conflict with other users of a single daemon.
    78  
    79  ## Pulling the redis image
    80  
    81  Now that we have a client to work with we need to pull an image.
    82  We can use the redis image based on Alpine Linux from the DockerHub.
    83  
    84  ```go
    85  	image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
    86  	if err != nil {
    87  		return err
    88  	}
    89  ```
    90  
    91  The containerd client uses the `Opts` pattern for many of the method calls.
    92  We use the `containerd.WithPullUnpack` so that we not only fetch and download the content into containerd's content store but also unpack it into a snapshotter for use as a root filesystem.
    93  
    94  Let's put the code together that will pull the redis image based on alpine linux from Dockerhub and then print the name of the image on the console's output.
    95  
    96  ```go
    97  package main
    98  
    99  import (
   100          "context"
   101          "log"
   102  
   103          "github.com/containerd/containerd"
   104          "github.com/containerd/containerd/namespaces"
   105  )
   106  
   107  func main() {
   108          if err := redisExample(); err != nil {
   109                  log.Fatal(err)
   110          }
   111  }
   112  
   113  func redisExample() error {
   114          client, err := containerd.New("/run/containerd/containerd.sock")
   115          if err != nil {
   116                  return err
   117          }
   118          defer client.Close()
   119  
   120          ctx := namespaces.WithNamespace(context.Background(), "example")
   121          image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
   122          if err != nil {
   123                  return err
   124          }
   125          log.Printf("Successfully pulled %s image\n", image.Name())
   126  
   127          return nil
   128  }
   129  ```
   130  
   131  ```bash
   132  > go build main.go
   133  > sudo ./main
   134  
   135  2017/08/13 17:43:21 Successfully pulled docker.io/library/redis:alpine image
   136  ```
   137  
   138  ## Creating an OCI Spec and Container
   139  
   140  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.
   141  
   142  containerd provides reasonable defaults for generating OCI runtime specs.
   143  There is also an `Opt` for modifying the default config based on the image that we pulled.
   144  
   145  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.
   146  
   147  ```go
   148  	container, err := client.NewContainer(
   149  		ctx,
   150  		"redis-server",
   151  		containerd.WithNewSnapshot("redis-server-snapshot", image),
   152  		containerd.WithNewSpec(oci.WithImageConfig(image)),
   153  	)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	defer container.Delete(ctx, containerd.WithSnapshotCleanup)
   158  ```
   159  
   160  If you have an existing OCI specification created you can use `containerd.WithSpec(spec)` to set it on the container.
   161  
   162  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.
   163  By providing a separate snapshot ID than the container ID we can easily reuse, existing snapshots across different containers.
   164  
   165  We also add a line to delete the container along with its snapshot after we are done with this example.
   166  
   167  Here is example code to pull the redis image based on alpine linux from Dockerhub, create an OCI spec, create a container based on the spec and finally delete the container.
   168  ```go
   169  package main
   170  
   171  import (
   172          "context"
   173          "log"
   174  
   175          "github.com/containerd/containerd"
   176          "github.com/containerd/containerd/oci"
   177          "github.com/containerd/containerd/namespaces"
   178  )
   179  
   180  func main() {
   181          if err := redisExample(); err != nil {
   182                  log.Fatal(err)
   183          }
   184  }
   185  
   186  func redisExample() error {
   187          client, err := containerd.New("/run/containerd/containerd.sock")
   188          if err != nil {
   189                  return err
   190          }
   191          defer client.Close()
   192  
   193          ctx := namespaces.WithNamespace(context.Background(), "example")
   194          image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
   195          if err != nil {
   196                  return err
   197          }
   198          log.Printf("Successfully pulled %s image\n", image.Name())
   199  
   200          container, err := client.NewContainer(
   201                  ctx,
   202                  "redis-server",
   203                  containerd.WithNewSnapshot("redis-server-snapshot", image),
   204                  containerd.WithNewSpec(oci.WithImageConfig(image)),
   205          )
   206          if err != nil {
   207                  return err
   208          }
   209          defer container.Delete(ctx, containerd.WithSnapshotCleanup)
   210          log.Printf("Successfully created container with ID %s and snapshot with ID redis-server-snapshot", container.ID())
   211  
   212          return nil
   213  }
   214  ```
   215  
   216  Let's see it in action.
   217  
   218  ```bash
   219  > go build main.go
   220  > sudo ./main
   221  
   222  2017/08/13 18:01:35 Successfully pulled docker.io/library/redis:alpine image
   223  2017/08/13 18:01:35 Successfully created container with ID redis-server and snapshot with ID redis-server-snapshot
   224  ```
   225  
   226  ## Creating a running Task
   227  
   228  One thing that may be confusing at first for new containerd users is the separation between a `Container` and a `Task`.
   229  A container is a metadata object that resources are allocated and attached to.
   230  A task is a live, running process on the system.
   231  Tasks should be deleted after each run while a container can be used, updated, and queried multiple times.
   232  
   233  ```go
   234  	task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
   235  	if err != nil {
   236  		return err
   237  	}
   238  	defer task.Delete(ctx)
   239  ```
   240  
   241  The new task that we just created is actually a running process on your system.
   242  We use `cio.WithStdio` so that all IO from the container is sent to our `main.go` process.
   243  This is a `cio.Opt` that configures the `Streams` used by `NewCreator` to return a `cio.IO`
   244  for the new task.
   245  
   246  If you are familiar with the OCI runtime actions, the task is currently in the "created" state.
   247  This means that the namespaces, root filesystem, and various container level settings have been initialized but the user defined process, in this example "redis-server", has not been started.
   248  This gives users a chance to setup network interfaces or attach different tools to monitor the container.
   249  containerd also takes this opportunity to monitor your container as well.
   250  Waiting on things like the container's exit status and cgroup metrics are setup at this point.
   251  
   252  If you are familiar with prometheus you can curl the containerd metrics endpoint (in the `config.toml` that we created) to see your container's metrics:
   253  
   254  ```bash
   255  > curl 127.0.0.1:1338/v1/metrics
   256  ```
   257  
   258  Pretty cool right?
   259  
   260  ## Task Wait and Start
   261  
   262  Now that we have a task in the created state we need to make sure that we wait on the task to exit.
   263  It is essential to wait for the task to finish so that we can close our example and cleanup the resources that we created.
   264  You always want to make sure you `Wait` before calling `Start` on a task.
   265  This makes sure that you do not encounter any races if the task has a simple program like `/bin/true` that exits promptly after calling start.
   266  
   267  ```go
   268  	exitStatusC, err := task.Wait(ctx)
   269  	if err != nil {
   270  		return err
   271  	}
   272  
   273  	if err := task.Start(ctx); err != nil {
   274  		return err
   275  	}
   276  ```
   277  
   278  Now we should see the `redis-server` logs in our terminal when we run the `main.go` file.
   279  
   280  ## Killing the task
   281  
   282  Since we are running a long running server we will need to kill the task in order to exit out of our example.
   283  To do this we will simply call `Kill` on the task after waiting a couple of seconds so we have a chance to see the redis-server logs.
   284  
   285  ```go
   286  	time.Sleep(3 * time.Second)
   287  
   288  	if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
   289  		return err
   290  	}
   291  
   292  	status := <-exitStatusC
   293  	code, exitedAt, err := status.Result()
   294  	if err != nil {
   295  		return err
   296  	}
   297  	fmt.Printf("redis-server exited with status: %d\n", code)
   298  ```
   299  
   300  We wait on our exit status channel that we setup to ensure the task has fully exited and we get the exit status.
   301  If you have to reload containers or miss waiting on a task, `Delete` will also return the exit status when you finally delete the task.
   302  We got you covered.
   303  
   304  ```go
   305  status, err := task.Delete(ctx)
   306  ```
   307  
   308  ## Full Example
   309  
   310  Here is the full example that we just put together.
   311  
   312  ```go
   313  package main
   314  
   315  import (
   316  	"context"
   317  	"fmt"
   318  	"log"
   319  	"syscall"
   320  	"time"
   321  
   322  	"github.com/containerd/containerd"
   323  	"github.com/containerd/containerd/cio"
   324  	"github.com/containerd/containerd/oci"
   325  	"github.com/containerd/containerd/namespaces"
   326  )
   327  
   328  func main() {
   329  	if err := redisExample(); err != nil {
   330  		log.Fatal(err)
   331  	}
   332  }
   333  
   334  func redisExample() error {
   335  	// create a new client connected to the default socket path for containerd
   336  	client, err := containerd.New("/run/containerd/containerd.sock")
   337  	if err != nil {
   338  		return err
   339  	}
   340  	defer client.Close()
   341  
   342  	// create a new context with an "example" namespace
   343  	ctx := namespaces.WithNamespace(context.Background(), "example")
   344  
   345  	// pull the redis image from DockerHub
   346  	image, err := client.Pull(ctx, "docker.io/library/redis:alpine", containerd.WithPullUnpack)
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	// create a container
   352  	container, err := client.NewContainer(
   353  		ctx,
   354  		"redis-server",
   355  		containerd.WithImage(image),
   356  		containerd.WithNewSnapshot("redis-server-snapshot", image),
   357  		containerd.WithNewSpec(oci.WithImageConfig(image)),
   358  	)
   359  	if err != nil {
   360  		return err
   361  	}
   362  	defer container.Delete(ctx, containerd.WithSnapshotCleanup)
   363  
   364  	// create a task from the container
   365  	task, err := container.NewTask(ctx, cio.NewCreator(cio.WithStdio))
   366  	if err != nil {
   367  		return err
   368  	}
   369  	defer task.Delete(ctx)
   370  
   371  	// make sure we wait before calling start
   372  	exitStatusC, err := task.Wait(ctx)
   373  	if err != nil {
   374  		fmt.Println(err)
   375  	}
   376  
   377  	// call start on the task to execute the redis server
   378  	if err := task.Start(ctx); err != nil {
   379  		return err
   380  	}
   381  
   382  	// sleep for a lil bit to see the logs
   383  	time.Sleep(3 * time.Second)
   384  
   385  	// kill the process and get the exit status
   386  	if err := task.Kill(ctx, syscall.SIGTERM); err != nil {
   387  		return err
   388  	}
   389  
   390  	// wait for the process to fully exit and print out the exit status
   391  
   392  	status := <-exitStatusC
   393  	code, _, err := status.Result()
   394  	if err != nil {
   395  		return err
   396  	}
   397  	fmt.Printf("redis-server exited with status: %d\n", code)
   398  
   399  	return nil
   400  }
   401  ```
   402  
   403  We can build this example and run it as follows to see our hard work come together.
   404  
   405  ```bash
   406  > go build main.go
   407  > sudo ./main
   408  
   409  1:C 04 Aug 20:41:37.682 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo
   410  1:C 04 Aug 20:41:37.682 # Redis version=4.0.1, bits=64, commit=00000000, modified=0, pid=1, just started
   411  1:C 04 Aug 20:41:37.682 # Warning: no config file specified, using the default config. In order to specify a config file use redis-server /path/to/redis.conf
   412  1:M 04 Aug 20:41:37.682 # You requested maxclients of 10000 requiring at least 10032 max file descriptors.
   413  1:M 04 Aug 20:41:37.682 # Server can't set maximum open files to 10032 because of OS error: Operation not permitted.
   414  1:M 04 Aug 20:41:37.682 # Current maximum open files is 1024. maxclients has been reduced to 992 to compensate for low ulimit. If you need higher maxclients increase 'ulimit -n'.
   415  1:M 04 Aug 20:41:37.683 * Running mode=standalone, port=6379.
   416  1:M 04 Aug 20:41:37.683 # WARNING: The TCP backlog setting of 511 cannot be enforced because /proc/sys/net/core/somaxconn is set to the lower value of 128.
   417  1:M 04 Aug 20:41:37.684 # Server initialized
   418  1:M 04 Aug 20:41:37.684 # WARNING overcommit_memory is set to 0! Background save may fail under low memory condition. To fix this issue add 'vm.overcommit_memory = 1' to /etc/sysctl.conf and then reboot or run the command 'sysctl vm.overcommit_memory=1' for this to take effect.
   419  1:M 04 Aug 20:41:37.684 # WARNING you have Transparent Huge Pages (THP) support enabled in your kernel. This will create latency and memory usage issues with Redis. To fix this issue run the command 'echo never > /sys/kernel/mm/transparent_hugepage/enabled' as root, and add it to your /etc/rc.local in order to retain the setting after a reboot. Redis must be restarted after THP is disabled.
   420  1:M 04 Aug 20:41:37.684 * Ready to accept connections
   421  1:signal-handler (1501879300) Received SIGTERM scheduling shutdown...
   422  1:M 04 Aug 20:41:40.791 # User requested shutdown...
   423  1:M 04 Aug 20:41:40.791 * Saving the final RDB snapshot before exiting.
   424  1:M 04 Aug 20:41:40.794 * DB saved on disk
   425  1:M 04 Aug 20:41:40.794 # Redis is now ready to exit, bye bye...
   426  redis-server exited with status: 0
   427  ```
   428  
   429  In the end, we really did not write that much code when you use the client package.
   430  
   431  I hope this guide helped to get you up and running with containerd.
   432  Feel free to join the [slack channel](https://dockr.ly/community) if you have any questions and like all things, if you want to help contribute to containerd or this guide, submit a pull request.