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.