Merge pull request #1552 from crosbymichael/nri
Add experimental NRI injection points
This commit is contained in:
		| @@ -24,6 +24,8 @@ import ( | ||||
| 	containerdio "github.com/containerd/containerd/cio" | ||||
| 	"github.com/containerd/containerd/errdefs" | ||||
| 	"github.com/containerd/containerd/log" | ||||
| 	"github.com/containerd/nri" | ||||
| 	v1 "github.com/containerd/nri/types/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| 	"golang.org/x/net/context" | ||||
| @@ -107,7 +109,7 @@ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContain | ||||
| 			deferCtx, deferCancel := ctrdutil.DeferContext() | ||||
| 			defer deferCancel() | ||||
| 			// It's possible that task is deleted by event monitor. | ||||
| 			if _, err := task.Delete(deferCtx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) { | ||||
| 			if _, err := task.Delete(deferCtx, WithNRISandboxDelete(sandboxID), containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) { | ||||
| 				log.G(ctx).WithError(err).Errorf("Failed to delete containerd task %q", id) | ||||
| 			} | ||||
| 		} | ||||
| @@ -118,6 +120,18 @@ func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContain | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "failed to wait for containerd task") | ||||
| 	} | ||||
| 	nric, err := nri.New() | ||||
| 	if err != nil { | ||||
| 		log.G(ctx).WithError(err).Error("unable to create nri client") | ||||
| 	} | ||||
| 	if nric != nil { | ||||
| 		nriSB := &nri.Sandbox{ | ||||
| 			ID: sandboxID, | ||||
| 		} | ||||
| 		if _, err := nric.InvokeWithSandbox(ctx, task, v1.Create, nriSB); err != nil { | ||||
| 			return nil, errors.Wrap(err, "nri invoke") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Start containerd task. | ||||
| 	if err := task.Start(ctx); err != nil { | ||||
|   | ||||
| @@ -317,7 +317,7 @@ func handleContainerExit(ctx context.Context, e *eventtypes.TaskExit, cntr conta | ||||
| 		} | ||||
| 	} else { | ||||
| 		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker | ||||
| 		if _, err = task.Delete(ctx, containerd.WithProcessKill); err != nil { | ||||
| 		if _, err = task.Delete(ctx, WithNRISandboxDelete(cntr.SandboxID), containerd.WithProcessKill); err != nil { | ||||
| 			if !errdefs.IsNotFound(err) { | ||||
| 				return errors.Wrap(err, "failed to stop container") | ||||
| 			} | ||||
| @@ -359,7 +359,7 @@ func handleSandboxExit(ctx context.Context, e *eventtypes.TaskExit, sb sandboxst | ||||
| 		} | ||||
| 	} else { | ||||
| 		// TODO(random-liu): [P1] This may block the loop, we may want to spawn a worker | ||||
| 		if _, err = task.Delete(ctx, containerd.WithProcessKill); err != nil { | ||||
| 		if _, err = task.Delete(ctx, WithNRISandboxDelete(sb.ID), containerd.WithProcessKill); err != nil { | ||||
| 			if !errdefs.IsNotFound(err) { | ||||
| 				return errors.Wrap(err, "failed to stop sandbox") | ||||
| 			} | ||||
|   | ||||
							
								
								
									
										51
									
								
								pkg/server/opts.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								pkg/server/opts.go
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| /* | ||||
|    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 server | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
|  | ||||
| 	"github.com/containerd/containerd" | ||||
| 	"github.com/containerd/containerd/log" | ||||
| 	"github.com/containerd/nri" | ||||
| 	v1 "github.com/containerd/nri/types/v1" | ||||
| ) | ||||
|  | ||||
| // WithNRISandboxDelete calls delete for a sandbox'd task | ||||
| func WithNRISandboxDelete(sandboxID string) containerd.ProcessDeleteOpts { | ||||
| 	return func(ctx context.Context, p containerd.Process) error { | ||||
| 		task, ok := p.(containerd.Task) | ||||
| 		if !ok { | ||||
| 			return nil | ||||
| 		} | ||||
| 		nric, err := nri.New() | ||||
| 		if err != nil { | ||||
| 			log.G(ctx).WithError(err).Error("unable to create nri client") | ||||
| 			return nil | ||||
| 		} | ||||
| 		if nric == nil { | ||||
| 			return nil | ||||
| 		} | ||||
| 		sb := &nri.Sandbox{ | ||||
| 			ID: sandboxID, | ||||
| 		} | ||||
| 		if _, err := nric.InvokeWithSandbox(ctx, task, v1.Delete, sb); err != nil { | ||||
| 			log.G(ctx).WithError(err).Errorf("Failed to delete nri for %q", task.ID()) | ||||
| 		} | ||||
| 		return nil | ||||
| 	} | ||||
| } | ||||
| @@ -27,6 +27,8 @@ import ( | ||||
| 	"github.com/containerd/containerd/errdefs" | ||||
| 	"github.com/containerd/containerd/log" | ||||
| 	cni "github.com/containerd/go-cni" | ||||
| 	"github.com/containerd/nri" | ||||
| 	v1 "github.com/containerd/nri/types/v1" | ||||
| 	"github.com/containerd/typeurl" | ||||
| 	"github.com/davecgh/go-spew/spew" | ||||
| 	"github.com/pkg/errors" | ||||
| @@ -269,7 +271,7 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox | ||||
| 			deferCtx, deferCancel := ctrdutil.DeferContext() | ||||
| 			defer deferCancel() | ||||
| 			// Cleanup the sandbox container if an error is returned. | ||||
| 			if _, err := task.Delete(deferCtx, containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) { | ||||
| 			if _, err := task.Delete(deferCtx, WithNRISandboxDelete(id), containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) { | ||||
| 				log.G(ctx).WithError(err).Errorf("Failed to delete sandbox container %q", id) | ||||
| 			} | ||||
| 		} | ||||
| @@ -281,6 +283,20 @@ func (c *criService) RunPodSandbox(ctx context.Context, r *runtime.RunPodSandbox | ||||
| 		return nil, errors.Wrap(err, "failed to wait for sandbox container task") | ||||
| 	} | ||||
|  | ||||
| 	nric, err := nri.New() | ||||
| 	if err != nil { | ||||
| 		return nil, errors.Wrap(err, "unable to create nri client") | ||||
| 	} | ||||
| 	if nric != nil { | ||||
| 		nriSB := &nri.Sandbox{ | ||||
| 			ID:     id, | ||||
| 			Labels: config.Labels, | ||||
| 		} | ||||
| 		if _, err := nric.InvokeWithSandbox(ctx, task, v1.Create, nriSB); err != nil { | ||||
| 			return nil, errors.Wrap(err, "nri invoke") | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if err := task.Start(ctx); err != nil { | ||||
| 		return nil, errors.Wrapf(err, "failed to start sandbox container task %q", id) | ||||
| 	} | ||||
|   | ||||
| @@ -14,6 +14,7 @@ github.com/containerd/containerd                    v1.4.0 | ||||
| github.com/containerd/continuity                    efbc4488d8fe1bdc16bde3b2d2990d9b3a899165 | ||||
| github.com/containerd/fifo                          f15a3290365b9d2627d189e619ab4008e0069caf | ||||
| github.com/containerd/go-runc                       7016d3ce2328dd2cb1192b2076ebd565c4e8df0c | ||||
| github.com/containerd/nri                           0afc7f031eaf9c7d9c1a381b7ab5462e89c998fc | ||||
| github.com/containerd/ttrpc                         v1.0.1 | ||||
| github.com/containerd/typeurl                       v1.0.1 | ||||
| github.com/coreos/go-systemd/v22                    v22.1.0 | ||||
|   | ||||
							
								
								
									
										201
									
								
								vendor/github.com/containerd/nri/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										201
									
								
								vendor/github.com/containerd/nri/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,201 @@ | ||||
|                                  Apache License | ||||
|                            Version 2.0, January 2004 | ||||
|                         http://www.apache.org/licenses/ | ||||
|  | ||||
|    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||||
|  | ||||
|    1. Definitions. | ||||
|  | ||||
|       "License" shall mean the terms and conditions for use, reproduction, | ||||
|       and distribution as defined by Sections 1 through 9 of this document. | ||||
|  | ||||
|       "Licensor" shall mean the copyright owner or entity authorized by | ||||
|       the copyright owner that is granting the License. | ||||
|  | ||||
|       "Legal Entity" shall mean the union of the acting entity and all | ||||
|       other entities that control, are controlled by, or are under common | ||||
|       control with that entity. For the purposes of this definition, | ||||
|       "control" means (i) the power, direct or indirect, to cause the | ||||
|       direction or management of such entity, whether by contract or | ||||
|       otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||||
|       outstanding shares, or (iii) beneficial ownership of such entity. | ||||
|  | ||||
|       "You" (or "Your") shall mean an individual or Legal Entity | ||||
|       exercising permissions granted by this License. | ||||
|  | ||||
|       "Source" form shall mean the preferred form for making modifications, | ||||
|       including but not limited to software source code, documentation | ||||
|       source, and configuration files. | ||||
|  | ||||
|       "Object" form shall mean any form resulting from mechanical | ||||
|       transformation or translation of a Source form, including but | ||||
|       not limited to compiled object code, generated documentation, | ||||
|       and conversions to other media types. | ||||
|  | ||||
|       "Work" shall mean the work of authorship, whether in Source or | ||||
|       Object form, made available under the License, as indicated by a | ||||
|       copyright notice that is included in or attached to the work | ||||
|       (an example is provided in the Appendix below). | ||||
|  | ||||
|       "Derivative Works" shall mean any work, whether in Source or Object | ||||
|       form, that is based on (or derived from) the Work and for which the | ||||
|       editorial revisions, annotations, elaborations, or other modifications | ||||
|       represent, as a whole, an original work of authorship. For the purposes | ||||
|       of this License, Derivative Works shall not include works that remain | ||||
|       separable from, or merely link (or bind by name) to the interfaces of, | ||||
|       the Work and Derivative Works thereof. | ||||
|  | ||||
|       "Contribution" shall mean any work of authorship, including | ||||
|       the original version of the Work and any modifications or additions | ||||
|       to that Work or Derivative Works thereof, that is intentionally | ||||
|       submitted to Licensor for inclusion in the Work by the copyright owner | ||||
|       or by an individual or Legal Entity authorized to submit on behalf of | ||||
|       the copyright owner. For the purposes of this definition, "submitted" | ||||
|       means any form of electronic, verbal, or written communication sent | ||||
|       to the Licensor or its representatives, including but not limited to | ||||
|       communication on electronic mailing lists, source code control systems, | ||||
|       and issue tracking systems that are managed by, or on behalf of, the | ||||
|       Licensor for the purpose of discussing and improving the Work, but | ||||
|       excluding communication that is conspicuously marked or otherwise | ||||
|       designated in writing by the copyright owner as "Not a Contribution." | ||||
|  | ||||
|       "Contributor" shall mean Licensor and any individual or Legal Entity | ||||
|       on behalf of whom a Contribution has been received by Licensor and | ||||
|       subsequently incorporated within the Work. | ||||
|  | ||||
|    2. Grant of Copyright License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       copyright license to reproduce, prepare Derivative Works of, | ||||
|       publicly display, publicly perform, sublicense, and distribute the | ||||
|       Work and such Derivative Works in Source or Object form. | ||||
|  | ||||
|    3. Grant of Patent License. Subject to the terms and conditions of | ||||
|       this License, each Contributor hereby grants to You a perpetual, | ||||
|       worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||||
|       (except as stated in this section) patent license to make, have made, | ||||
|       use, offer to sell, sell, import, and otherwise transfer the Work, | ||||
|       where such license applies only to those patent claims licensable | ||||
|       by such Contributor that are necessarily infringed by their | ||||
|       Contribution(s) alone or by combination of their Contribution(s) | ||||
|       with the Work to which such Contribution(s) was submitted. If You | ||||
|       institute patent litigation against any entity (including a | ||||
|       cross-claim or counterclaim in a lawsuit) alleging that the Work | ||||
|       or a Contribution incorporated within the Work constitutes direct | ||||
|       or contributory patent infringement, then any patent licenses | ||||
|       granted to You under this License for that Work shall terminate | ||||
|       as of the date such litigation is filed. | ||||
|  | ||||
|    4. Redistribution. You may reproduce and distribute copies of the | ||||
|       Work or Derivative Works thereof in any medium, with or without | ||||
|       modifications, and in Source or Object form, provided that You | ||||
|       meet the following conditions: | ||||
|  | ||||
|       (a) You must give any other recipients of the Work or | ||||
|           Derivative Works a copy of this License; and | ||||
|  | ||||
|       (b) You must cause any modified files to carry prominent notices | ||||
|           stating that You changed the files; and | ||||
|  | ||||
|       (c) You must retain, in the Source form of any Derivative Works | ||||
|           that You distribute, all copyright, patent, trademark, and | ||||
|           attribution notices from the Source form of the Work, | ||||
|           excluding those notices that do not pertain to any part of | ||||
|           the Derivative Works; and | ||||
|  | ||||
|       (d) If the Work includes a "NOTICE" text file as part of its | ||||
|           distribution, then any Derivative Works that You distribute must | ||||
|           include a readable copy of the attribution notices contained | ||||
|           within such NOTICE file, excluding those notices that do not | ||||
|           pertain to any part of the Derivative Works, in at least one | ||||
|           of the following places: within a NOTICE text file distributed | ||||
|           as part of the Derivative Works; within the Source form or | ||||
|           documentation, if provided along with the Derivative Works; or, | ||||
|           within a display generated by the Derivative Works, if and | ||||
|           wherever such third-party notices normally appear. The contents | ||||
|           of the NOTICE file are for informational purposes only and | ||||
|           do not modify the License. You may add Your own attribution | ||||
|           notices within Derivative Works that You distribute, alongside | ||||
|           or as an addendum to the NOTICE text from the Work, provided | ||||
|           that such additional attribution notices cannot be construed | ||||
|           as modifying the License. | ||||
|  | ||||
|       You may add Your own copyright statement to Your modifications and | ||||
|       may provide additional or different license terms and conditions | ||||
|       for use, reproduction, or distribution of Your modifications, or | ||||
|       for any such Derivative Works as a whole, provided Your use, | ||||
|       reproduction, and distribution of the Work otherwise complies with | ||||
|       the conditions stated in this License. | ||||
|  | ||||
|    5. Submission of Contributions. Unless You explicitly state otherwise, | ||||
|       any Contribution intentionally submitted for inclusion in the Work | ||||
|       by You to the Licensor shall be under the terms and conditions of | ||||
|       this License, without any additional terms or conditions. | ||||
|       Notwithstanding the above, nothing herein shall supersede or modify | ||||
|       the terms of any separate license agreement you may have executed | ||||
|       with Licensor regarding such Contributions. | ||||
|  | ||||
|    6. Trademarks. This License does not grant permission to use the trade | ||||
|       names, trademarks, service marks, or product names of the Licensor, | ||||
|       except as required for reasonable and customary use in describing the | ||||
|       origin of the Work and reproducing the content of the NOTICE file. | ||||
|  | ||||
|    7. Disclaimer of Warranty. Unless required by applicable law or | ||||
|       agreed to in writing, Licensor provides the Work (and each | ||||
|       Contributor provides its Contributions) on an "AS IS" BASIS, | ||||
|       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||||
|       implied, including, without limitation, any warranties or conditions | ||||
|       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||||
|       PARTICULAR PURPOSE. You are solely responsible for determining the | ||||
|       appropriateness of using or redistributing the Work and assume any | ||||
|       risks associated with Your exercise of permissions under this License. | ||||
|  | ||||
|    8. Limitation of Liability. In no event and under no legal theory, | ||||
|       whether in tort (including negligence), contract, or otherwise, | ||||
|       unless required by applicable law (such as deliberate and grossly | ||||
|       negligent acts) or agreed to in writing, shall any Contributor be | ||||
|       liable to You for damages, including any direct, indirect, special, | ||||
|       incidental, or consequential damages of any character arising as a | ||||
|       result of this License or out of the use or inability to use the | ||||
|       Work (including but not limited to damages for loss of goodwill, | ||||
|       work stoppage, computer failure or malfunction, or any and all | ||||
|       other commercial damages or losses), even if such Contributor | ||||
|       has been advised of the possibility of such damages. | ||||
|  | ||||
|    9. Accepting Warranty or Additional Liability. While redistributing | ||||
|       the Work or Derivative Works thereof, You may choose to offer, | ||||
|       and charge a fee for, acceptance of support, warranty, indemnity, | ||||
|       or other liability obligations and/or rights consistent with this | ||||
|       License. However, in accepting such obligations, You may act only | ||||
|       on Your own behalf and on Your sole responsibility, not on behalf | ||||
|       of any other Contributor, and only if You agree to indemnify, | ||||
|       defend, and hold each Contributor harmless for any liability | ||||
|       incurred by, or claims asserted against, such Contributor by reason | ||||
|       of your accepting any such warranty or additional liability. | ||||
|  | ||||
|    END OF TERMS AND CONDITIONS | ||||
|  | ||||
|    APPENDIX: How to apply the Apache License to your work. | ||||
|  | ||||
|       To apply the Apache License to your work, attach the following | ||||
|       boilerplate notice, with the fields enclosed by brackets "[]" | ||||
|       replaced with your own identifying information. (Don't include | ||||
|       the brackets!)  The text should be enclosed in the appropriate | ||||
|       comment syntax for the file format. We also recommend that a | ||||
|       file or class name and description of purpose be included on the | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright [yyyy] [name of copyright owner] | ||||
|  | ||||
|    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. | ||||
							
								
								
									
										163
									
								
								vendor/github.com/containerd/nri/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										163
									
								
								vendor/github.com/containerd/nri/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,163 @@ | ||||
| # nri - Node Resource Interface | ||||
|  | ||||
| *This project is currently in DRAFT status* | ||||
|  | ||||
| This project is a WIP for a new, CNI like, interface for managing resources on a node for Pods and Containers. | ||||
|  | ||||
| ## Documentation | ||||
|  | ||||
| The basic interface, concepts and plugin design of the Container Network Interface (CNI) is an elegant way to handle multiple implementations of the network stack for containers. | ||||
| This concept can be used for additional interfaces to customize a container's runtime environment. | ||||
| This proposal covers a new interface for resource management on a node with a structured API and plugin design for containers. | ||||
|  | ||||
| ## Lifecycle | ||||
|  | ||||
| The big selling point for CNI is that it has a structured interface for modifying the network namespace for a container. | ||||
| This is different from generic hooks as they lack a type safe API injected into the lifecycle of a container. | ||||
| The lifecycle point that CNI and NRI plugins will be injected into is the point between `Create` and `Start` of the container's init process. | ||||
|  | ||||
| `Create->NRI->Start` | ||||
|  | ||||
| ## Configuration | ||||
|  | ||||
| Configuration is split into two parts.  One is the payload that is specific to a plugin invocation while the second is the host level configuration and options that specify what plugins to run and provide additional configuration to a plugin. | ||||
|  | ||||
| ### Filepath and Binaries | ||||
|  | ||||
| Plugin binary paths can be configured via the consumer but will default to `/opt/nri/bin`. | ||||
| Binaries are named with their type as the binary name, same as the CNI plugin naming scheme. | ||||
|  | ||||
| ### Host Level Config | ||||
|  | ||||
| The config's default location will be `/etc/nri/resource.d/*.conf`. | ||||
|  | ||||
| ```json | ||||
| { | ||||
|         "version": "0.1", | ||||
|         "plugins": [ | ||||
|                 { | ||||
|                         "type": "konfine", | ||||
|                         "conf": { | ||||
|                                 "systemReserved": [0,1] | ||||
|                         } | ||||
|                 }, | ||||
|                 { | ||||
|                         "type": "clearcfs" | ||||
|                 } | ||||
|         ] | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Input | ||||
|  | ||||
| Input to a plugin is provided via `STDIN` as a `json` payload. | ||||
|  | ||||
| ```json | ||||
| { | ||||
| 	"version": "0.1", | ||||
| 	"state": "create", | ||||
| 	"id": "redis", | ||||
| 	"pid": 1234, | ||||
| 	"spec": { | ||||
| 		"resources": { | ||||
| 		}, | ||||
| 		"cgroupsPath": "default/redis", | ||||
| 		"namespaces": { | ||||
| 			"pid": "/proc/44/ns/pid", | ||||
| 			"mount": "/proc/44/ns/mnt", | ||||
| 			"net": "/proc/44/ns/net", | ||||
| 		}, | ||||
| 		"annotations": { | ||||
| 			"qos.class: "ls" | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Output | ||||
|  | ||||
| ```json | ||||
| { | ||||
| 	"version": "0.1", | ||||
| 	"state": "create", | ||||
| 	"id": "redis", | ||||
| 	"pid": 1234, | ||||
|     "cgroupsPath": "qos-ls/default/redis" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Commands | ||||
|  | ||||
| *  Invoke - provides invocations into different lifecycle changes of a container | ||||
| 	- states: `setup|pause|resume|update|delete` | ||||
|  | ||||
| ## Packages | ||||
|  | ||||
| A Go based API and client package will be created for both producers of plugins and consumers, commonly being the container runtime (containerd). | ||||
|  | ||||
| ### Sample Plugin | ||||
|  | ||||
| **clearcfs** | ||||
|  | ||||
| Clear the cfs quotas for `ls` services. | ||||
|  | ||||
|  | ||||
| ```go | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
|  | ||||
| 	"github.com/containerd/containerd/pkg/nri/skel" | ||||
| 	"github.com/containerd/containerd/pkg/nri/types" | ||||
| 	"github.com/sirupsen/logrus" | ||||
| ) | ||||
|  | ||||
| var max = []byte("max") | ||||
|  | ||||
| // clearCFS clears any cfs quotas for the containers | ||||
| type clearCFS struct { | ||||
| } | ||||
|  | ||||
| func (c *clearCFS) Type() string { | ||||
| 	return "clearcfs" | ||||
| } | ||||
|  | ||||
| func (c *clearCFS) Invoke(ctx context.Context, r *types.Request) (*types.Result, error) { | ||||
| 	result := r.NewResult() | ||||
| 	if r.State != types.Create { | ||||
| 		return result, nil | ||||
| 	} | ||||
| 	switch r.Spec.Annotations["qos.class"] { | ||||
| 	case "ls": | ||||
| 		logrus.Debugf("clearing cfs for %s", r.ID) | ||||
| 		group, err := cg.Load(r.Spec.CgroupsPath) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		return result, group.Write(cg.CFSMax) | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func main() { | ||||
| 	ctx := context.Background() | ||||
| 	if err := skel.Run(ctx, &clearCFS{}); err != nil { | ||||
| 		fmt.Fprintf(os.Stderr, "%s", err) | ||||
| 		os.Exit(1) | ||||
| 	} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## Project details | ||||
|  | ||||
| nri is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE). | ||||
| As a containerd sub-project, you will find the: | ||||
|  | ||||
|  * [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md), | ||||
|  * [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS), | ||||
|  * and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md) | ||||
|  | ||||
| information in our [`containerd/project`](https://github.com/containerd/project) repository. | ||||
							
								
								
									
										165
									
								
								vendor/github.com/containerd/nri/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										165
									
								
								vendor/github.com/containerd/nri/client.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,165 @@ | ||||
| package nri | ||||
|  | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"context" | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
|  | ||||
| 	"github.com/containerd/containerd" | ||||
| 	"github.com/containerd/containerd/oci" | ||||
| 	types "github.com/containerd/nri/types/v1" | ||||
| 	"github.com/pkg/errors" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	// DefaultBinaryPath for nri plugins | ||||
| 	DefaultBinaryPath = "/opt/nri/bin" | ||||
| 	// DefaultConfPath for the global nri configuration | ||||
| 	DefaultConfPath = "/etc/nri/conf.json" | ||||
| 	// Version of NRI | ||||
| 	Version = "0.1" | ||||
| ) | ||||
|  | ||||
| // New nri client | ||||
| func New() (*Client, error) { | ||||
| 	conf, err := loadConfig(DefaultConfPath) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	if err := os.Setenv("PATH", fmt.Sprintf("%s:%s", os.Getenv("PATH"), DefaultBinaryPath)); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &Client{ | ||||
| 		conf: conf, | ||||
| 	}, nil | ||||
| } | ||||
|  | ||||
| // Client for calling nri plugins | ||||
| type Client struct { | ||||
| 	conf *types.ConfigList | ||||
| } | ||||
|  | ||||
| // Sandbox information | ||||
| type Sandbox struct { | ||||
| 	// ID of the sandbox | ||||
| 	ID string | ||||
| 	// Labels of the sandbox | ||||
| 	Labels map[string]string | ||||
| } | ||||
|  | ||||
| // Invoke the ConfList of nri plugins | ||||
| func (c *Client) Invoke(ctx context.Context, task containerd.Task, state types.State) ([]*types.Result, error) { | ||||
| 	return c.InvokeWithSandbox(ctx, task, state, nil) | ||||
| } | ||||
|  | ||||
| // Invoke the ConfList of nri plugins | ||||
| func (c *Client) InvokeWithSandbox(ctx context.Context, task containerd.Task, state types.State, sandbox *Sandbox) ([]*types.Result, error) { | ||||
| 	if len(c.conf.Plugins) == 0 { | ||||
| 		return nil, nil | ||||
| 	} | ||||
| 	spec, err := task.Spec(ctx) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	rs, err := createSpec(spec) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	r := &types.Request{ | ||||
| 		Version: c.conf.Version, | ||||
| 		ID:      task.ID(), | ||||
| 		Pid:     int(task.Pid()), | ||||
| 		State:   state, | ||||
| 		Spec:    rs, | ||||
| 	} | ||||
| 	if sandbox != nil { | ||||
| 		r.SandboxID = sandbox.ID | ||||
| 		r.Labels = sandbox.Labels | ||||
| 	} | ||||
| 	for _, p := range c.conf.Plugins { | ||||
| 		r.Conf = p.Conf | ||||
| 		result, err := c.invokePlugin(ctx, p.Type, r) | ||||
| 		if err != nil { | ||||
| 			return nil, errors.Wrapf(err, "plugin: %s", p.Type) | ||||
| 		} | ||||
| 		r.Results = append(r.Results, result) | ||||
| 	} | ||||
| 	return r.Results, nil | ||||
| } | ||||
|  | ||||
| func (c *Client) invokePlugin(ctx context.Context, name string, r *types.Request) (*types.Result, error) { | ||||
| 	payload, err := json.Marshal(r) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	cmd := exec.CommandContext(ctx, name, "invoke") | ||||
| 	cmd.Stdin = bytes.NewBuffer(payload) | ||||
| 	cmd.Stderr = os.Stderr | ||||
|  | ||||
| 	out, err := cmd.Output() | ||||
| 	if err != nil { | ||||
| 		// ExitError is returned when there is a non-zero exit status | ||||
| 		if _, ok := err.(*exec.ExitError); ok { | ||||
| 			var pe types.PluginError | ||||
| 			if err := json.Unmarshal(out, &pe); err != nil { | ||||
| 				return nil, errors.Wrapf(err, "%s: %s", name, out) | ||||
| 			} | ||||
| 			return nil, &pe | ||||
| 		} | ||||
| 		return nil, errors.Wrapf(err, "%s: %s", name, out) | ||||
| 	} | ||||
| 	var result types.Result | ||||
| 	if err := json.Unmarshal(out, &result); err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &result, nil | ||||
| } | ||||
|  | ||||
| func loadConfig(path string) (*types.ConfigList, error) { | ||||
| 	f, err := os.Open(path) | ||||
| 	if err != nil { | ||||
| 		// if we don't have a config list on disk, create a new one for use | ||||
| 		if os.IsNotExist(err) { | ||||
| 			return &types.ConfigList{ | ||||
| 				Version: Version, | ||||
| 			}, nil | ||||
| 		} | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	var c types.ConfigList | ||||
| 	err = json.NewDecoder(f).Decode(&c) | ||||
| 	f.Close() | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	return &c, nil | ||||
| } | ||||
|  | ||||
| func createSpec(spec *oci.Spec) (*types.Spec, error) { | ||||
| 	s := types.Spec{ | ||||
| 		Namespaces:  make(map[string]string), | ||||
| 		Annotations: spec.Annotations, | ||||
| 	} | ||||
| 	switch { | ||||
| 	case spec.Linux != nil: | ||||
| 		s.CgroupsPath = spec.Linux.CgroupsPath | ||||
| 		data, err := json.Marshal(spec.Linux.Resources) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		s.Resources = json.RawMessage(data) | ||||
| 		for _, ns := range spec.Linux.Namespaces { | ||||
| 			s.Namespaces[string(ns.Type)] = ns.Path | ||||
| 		} | ||||
| 	case spec.Windows != nil: | ||||
| 		data, err := json.Marshal(spec.Windows.Resources) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		s.Resources = json.RawMessage(data) | ||||
| 	} | ||||
| 	return &s, nil | ||||
| } | ||||
							
								
								
									
										30
									
								
								vendor/github.com/containerd/nri/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								vendor/github.com/containerd/nri/go.mod
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| module github.com/containerd/nri | ||||
|  | ||||
| go 1.14 | ||||
|  | ||||
| require ( | ||||
| 	github.com/Microsoft/hcsshim v0.8.9 // indirect | ||||
| 	github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340 // indirect | ||||
| 	github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410 | ||||
| 	github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe // indirect | ||||
| 	github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b // indirect | ||||
| 	github.com/containerd/ttrpc v1.0.1 // indirect | ||||
| 	github.com/containerd/typeurl v1.0.1 // indirect | ||||
| 	github.com/docker/distribution v2.7.1+incompatible // indirect | ||||
| 	github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c // indirect | ||||
| 	github.com/gogo/googleapis v1.4.0 // indirect | ||||
| 	github.com/google/go-cmp v0.5.1 // indirect | ||||
| 	github.com/google/uuid v1.1.1 // indirect | ||||
| 	github.com/imdario/mergo v0.3.10 // indirect | ||||
| 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||
| 	github.com/opencontainers/image-spec v1.0.1 // indirect | ||||
| 	github.com/opencontainers/runc v0.1.1 // indirect | ||||
| 	github.com/opencontainers/selinux v1.6.0 // indirect | ||||
| 	github.com/pkg/errors v0.9.1 | ||||
| 	github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2 // indirect | ||||
| 	go.etcd.io/bbolt v1.3.5 // indirect | ||||
| 	golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect | ||||
| 	golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 // indirect | ||||
| 	golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1 // indirect | ||||
| 	google.golang.org/grpc v1.30.0 // indirect | ||||
| ) | ||||
							
								
								
									
										119
									
								
								vendor/github.com/containerd/nri/types/v1/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								vendor/github.com/containerd/nri/types/v1/types.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,119 @@ | ||||
| package v1 | ||||
|  | ||||
| import ( | ||||
| 	"encoding/json" | ||||
| 	"fmt" | ||||
| ) | ||||
|  | ||||
| // Plugin type and configuration | ||||
| type Plugin struct { | ||||
| 	// Type of plugin | ||||
| 	Type string `json:"type"` | ||||
| 	// Conf for the specific plugin | ||||
| 	Conf json.RawMessage `json:"conf,omitempty"` | ||||
| } | ||||
|  | ||||
| // ConfigList for the global configuration of NRI | ||||
| // | ||||
| // Normally located at /etc/nri/conf.json | ||||
| type ConfigList struct { | ||||
| 	// Verion of the list | ||||
| 	Version string `json:"version"` | ||||
| 	// Plugins | ||||
| 	Plugins []*Plugin `json:"plugins"` | ||||
| } | ||||
|  | ||||
| // Spec for the container being processed | ||||
| type Spec struct { | ||||
| 	// Resources struct from the OCI specification | ||||
| 	// | ||||
| 	// Can be WindowsResources or LinuxResources | ||||
| 	Resources json.RawMessage `json:"resources"` | ||||
| 	// Namespaces for the container | ||||
| 	Namespaces map[string]string `json:"namespaces,omitempty"` | ||||
| 	// CgroupsPath for the container | ||||
| 	CgroupsPath string `json:"cgroupsPath,omitempty"` | ||||
| 	// Annotations passed down to the OCI runtime specification | ||||
| 	Annotations map[string]string `json:"annotations,omitempty"` | ||||
| } | ||||
|  | ||||
| // State of the request | ||||
| type State string | ||||
|  | ||||
| const ( | ||||
| 	// Create the initial resource for the container | ||||
| 	Create State = "create" | ||||
| 	// Delete any resources for the container | ||||
| 	Delete State = "delete" | ||||
| 	// Update the resources for the container | ||||
| 	Update State = "update" | ||||
| 	// Pause action of the container | ||||
| 	Pause State = "pause" | ||||
| 	// Resume action for the container | ||||
| 	Resume State = "resume" | ||||
| ) | ||||
|  | ||||
| // Request for a plugin invocation | ||||
| type Request struct { | ||||
| 	// Conf specific for the plugin | ||||
| 	Conf json.RawMessage `json:"conf,omitempty"` | ||||
|  | ||||
| 	// Version of the plugin | ||||
| 	Version string `json:"version"` | ||||
| 	// State action for the request | ||||
| 	State State `json:"state"` | ||||
| 	// ID for the container | ||||
| 	ID string `json:"id"` | ||||
| 	// SandboxID for the sandbox that the request belongs to | ||||
| 	// | ||||
| 	// If ID and SandboxID are the same, this is a request for the sandbox | ||||
| 	// SandboxID is empty for a non sandboxed container | ||||
| 	SandboxID string `json:"sandboxID,omitempty"` | ||||
| 	// Pid of the container | ||||
| 	// | ||||
| 	// -1 if there is no pid | ||||
| 	Pid int `json:"pid,omitempty"` | ||||
| 	// Spec generated from the OCI runtime specification | ||||
| 	Spec *Spec `json:"spec"` | ||||
| 	// Labels of a sandbox | ||||
| 	Labels map[string]string `json:"labels,omitempty"` | ||||
| 	// Results from previous plugins in the chain | ||||
| 	Results []*Result `json:"results,omitempty"` | ||||
| } | ||||
|  | ||||
| // PluginError for specific plugin execution | ||||
| type PluginError struct { | ||||
| 	// Plugin name | ||||
| 	Plugin string `json:"plugin"` | ||||
| 	// Message for the error | ||||
| 	Message string `json:"message"` | ||||
| } | ||||
|  | ||||
| // Error as a string | ||||
| func (p *PluginError) Error() string { | ||||
| 	return fmt.Sprintf("%s: %s", p.Plugin, p.Message) | ||||
| } | ||||
|  | ||||
| // IsSandbox returns true if the request is for a sandbox | ||||
| func (r *Request) IsSandbox() bool { | ||||
| 	return r.ID == r.SandboxID | ||||
| } | ||||
|  | ||||
| // NewResult returns a result from the original request | ||||
| func (r *Request) NewResult(plugin string) *Result { | ||||
| 	return &Result{ | ||||
| 		Plugin:   plugin, | ||||
| 		Version:  r.Version, | ||||
| 		Metadata: make(map[string]string), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Result of the plugin invocation | ||||
| type Result struct { | ||||
| 	// Plugin name that populated the result | ||||
| 	Plugin string `json:"plugin"` | ||||
| 	// Version of the plugin | ||||
| 	Version string `json:"version"` | ||||
| 	// Metadata specific to actions taken by the plugin | ||||
| 	Metadata map[string]string `json:"metadata,omitempty"` | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Derek McGowan
					Derek McGowan