github.com/containerd/Containerd@v1.4.13/runtime/v2/manager.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package v2 18 19 import ( 20 "context" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 26 "github.com/containerd/containerd/containers" 27 "github.com/containerd/containerd/events/exchange" 28 "github.com/containerd/containerd/log" 29 "github.com/containerd/containerd/metadata" 30 "github.com/containerd/containerd/mount" 31 "github.com/containerd/containerd/namespaces" 32 "github.com/containerd/containerd/pkg/timeout" 33 "github.com/containerd/containerd/platforms" 34 "github.com/containerd/containerd/plugin" 35 "github.com/containerd/containerd/runtime" 36 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 37 ) 38 39 // Config for the v2 runtime 40 type Config struct { 41 // Supported platforms 42 Platforms []string `toml:"platforms"` 43 } 44 45 func init() { 46 plugin.Register(&plugin.Registration{ 47 Type: plugin.RuntimePluginV2, 48 ID: "task", 49 Requires: []plugin.Type{ 50 plugin.MetadataPlugin, 51 }, 52 Config: &Config{ 53 Platforms: defaultPlatforms(), 54 }, 55 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 56 supportedPlatforms, err := parsePlatforms(ic.Config.(*Config).Platforms) 57 if err != nil { 58 return nil, err 59 } 60 61 ic.Meta.Platforms = supportedPlatforms 62 if err := os.MkdirAll(ic.Root, 0711); err != nil { 63 return nil, err 64 } 65 if err := os.MkdirAll(ic.State, 0711); err != nil { 66 return nil, err 67 } 68 m, err := ic.Get(plugin.MetadataPlugin) 69 if err != nil { 70 return nil, err 71 } 72 cs := metadata.NewContainerStore(m.(*metadata.DB)) 73 74 return New(ic.Context, ic.Root, ic.State, ic.Address, ic.TTRPCAddress, ic.Events, cs) 75 }, 76 }) 77 } 78 79 // New task manager for v2 shims 80 func New(ctx context.Context, root, state, containerdAddress, containerdTTRPCAddress string, events *exchange.Exchange, cs containers.Store) (*TaskManager, error) { 81 for _, d := range []string{root, state} { 82 if err := os.MkdirAll(d, 0711); err != nil { 83 return nil, err 84 } 85 } 86 m := &TaskManager{ 87 root: root, 88 state: state, 89 containerdAddress: containerdAddress, 90 containerdTTRPCAddress: containerdTTRPCAddress, 91 tasks: runtime.NewTaskList(), 92 events: events, 93 containers: cs, 94 } 95 if err := m.loadExistingTasks(ctx); err != nil { 96 return nil, err 97 } 98 return m, nil 99 } 100 101 // TaskManager manages v2 shim's and their tasks 102 type TaskManager struct { 103 root string 104 state string 105 containerdAddress string 106 containerdTTRPCAddress string 107 108 tasks *runtime.TaskList 109 events *exchange.Exchange 110 containers containers.Store 111 } 112 113 // ID of the task manager 114 func (m *TaskManager) ID() string { 115 return fmt.Sprintf("%s.%s", plugin.RuntimePluginV2, "task") 116 } 117 118 // Create a new task 119 func (m *TaskManager) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) { 120 ns, err := namespaces.NamespaceRequired(ctx) 121 if err != nil { 122 return nil, err 123 } 124 bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec.Value) 125 if err != nil { 126 return nil, err 127 } 128 defer func() { 129 if err != nil { 130 bundle.Delete() 131 } 132 }() 133 topts := opts.TaskOptions 134 if topts == nil { 135 topts = opts.RuntimeOptions 136 } 137 138 b := shimBinary(ctx, bundle, opts.Runtime, m.containerdAddress, m.containerdTTRPCAddress, m.events, m.tasks) 139 shim, err := b.Start(ctx, topts, func() { 140 log.G(ctx).WithField("id", id).Info("shim disconnected") 141 _, err := m.tasks.Get(ctx, id) 142 if err != nil { 143 // Task was never started or was already successfully deleted 144 return 145 } 146 cleanupAfterDeadShim(context.Background(), id, ns, m.events, b) 147 // Remove self from the runtime task list. Even though the cleanupAfterDeadShim() 148 // would publish taskExit event, but the shim.Delete() would always failed with ttrpc 149 // disconnect and there is no chance to remove this dead task from runtime task lists. 150 // Thus it's better to delete it here. 151 m.tasks.Delete(ctx, id) 152 }) 153 if err != nil { 154 return nil, err 155 } 156 defer func() { 157 if err != nil { 158 dctx, cancel := timeout.WithContext(context.Background(), cleanupTimeout) 159 defer cancel() 160 _, errShim := shim.Delete(dctx) 161 if errShim != nil { 162 shim.Shutdown(dctx) 163 shim.Close() 164 } 165 } 166 }() 167 t, err := shim.Create(ctx, opts) 168 if err != nil { 169 return nil, err 170 } 171 m.tasks.Add(ctx, t) 172 return t, nil 173 } 174 175 // Get a specific task 176 func (m *TaskManager) Get(ctx context.Context, id string) (runtime.Task, error) { 177 return m.tasks.Get(ctx, id) 178 } 179 180 // Add a runtime task 181 func (m *TaskManager) Add(ctx context.Context, task runtime.Task) error { 182 return m.tasks.Add(ctx, task) 183 } 184 185 // Delete a runtime task 186 func (m *TaskManager) Delete(ctx context.Context, id string) { 187 m.tasks.Delete(ctx, id) 188 } 189 190 // Tasks lists all tasks 191 func (m *TaskManager) Tasks(ctx context.Context, all bool) ([]runtime.Task, error) { 192 return m.tasks.GetAll(ctx, all) 193 } 194 195 func (m *TaskManager) loadExistingTasks(ctx context.Context) error { 196 nsDirs, err := ioutil.ReadDir(m.state) 197 if err != nil { 198 return err 199 } 200 for _, nsd := range nsDirs { 201 if !nsd.IsDir() { 202 continue 203 } 204 ns := nsd.Name() 205 // skip hidden directories 206 if len(ns) > 0 && ns[0] == '.' { 207 continue 208 } 209 log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace") 210 if err := m.loadTasks(namespaces.WithNamespace(ctx, ns)); err != nil { 211 log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace") 212 continue 213 } 214 if err := m.cleanupWorkDirs(namespaces.WithNamespace(ctx, ns)); err != nil { 215 log.G(ctx).WithField("namespace", ns).WithError(err).Error("cleanup working directory in namespace") 216 continue 217 } 218 } 219 return nil 220 } 221 222 func (m *TaskManager) loadTasks(ctx context.Context) error { 223 ns, err := namespaces.NamespaceRequired(ctx) 224 if err != nil { 225 return err 226 } 227 shimDirs, err := ioutil.ReadDir(filepath.Join(m.state, ns)) 228 if err != nil { 229 return err 230 } 231 for _, sd := range shimDirs { 232 if !sd.IsDir() { 233 continue 234 } 235 id := sd.Name() 236 // skip hidden directories 237 if len(id) > 0 && id[0] == '.' { 238 continue 239 } 240 bundle, err := LoadBundle(ctx, m.state, id) 241 if err != nil { 242 // fine to return error here, it is a programmer error if the context 243 // does not have a namespace 244 return err 245 } 246 // fast path 247 bf, err := ioutil.ReadDir(bundle.Path) 248 if err != nil { 249 bundle.Delete() 250 log.G(ctx).WithError(err).Errorf("fast path read bundle path for %s", bundle.Path) 251 continue 252 } 253 if len(bf) == 0 { 254 bundle.Delete() 255 continue 256 } 257 container, err := m.container(ctx, id) 258 if err != nil { 259 log.G(ctx).WithError(err).Errorf("loading container %s", id) 260 if err := mount.UnmountAll(filepath.Join(bundle.Path, "rootfs"), 0); err != nil { 261 log.G(ctx).WithError(err).Errorf("forceful unmount of rootfs %s", id) 262 } 263 bundle.Delete() 264 continue 265 } 266 binaryCall := shimBinary(ctx, bundle, container.Runtime.Name, m.containerdAddress, m.containerdTTRPCAddress, m.events, m.tasks) 267 shim, err := loadShim(ctx, bundle, m.events, m.tasks, func() { 268 log.G(ctx).WithField("id", id).Info("shim disconnected") 269 _, err := m.tasks.Get(ctx, id) 270 if err != nil { 271 // Task was never started or was already successfully deleted 272 return 273 } 274 cleanupAfterDeadShim(context.Background(), id, ns, m.events, binaryCall) 275 // Remove self from the runtime task list. 276 m.tasks.Delete(ctx, id) 277 }) 278 if err != nil { 279 cleanupAfterDeadShim(ctx, id, ns, m.events, binaryCall) 280 continue 281 } 282 m.tasks.Add(ctx, shim) 283 } 284 return nil 285 } 286 287 func (m *TaskManager) container(ctx context.Context, id string) (*containers.Container, error) { 288 container, err := m.containers.Get(ctx, id) 289 if err != nil { 290 return nil, err 291 } 292 return &container, nil 293 } 294 295 func (m *TaskManager) cleanupWorkDirs(ctx context.Context) error { 296 ns, err := namespaces.NamespaceRequired(ctx) 297 if err != nil { 298 return err 299 } 300 dirs, err := ioutil.ReadDir(filepath.Join(m.root, ns)) 301 if err != nil { 302 return err 303 } 304 for _, d := range dirs { 305 // if the task was not loaded, cleanup and empty working directory 306 // this can happen on a reboot where /run for the bundle state is cleaned up 307 // but that persistent working dir is left 308 if _, err := m.tasks.Get(ctx, d.Name()); err != nil { 309 path := filepath.Join(m.root, ns, d.Name()) 310 if err := os.RemoveAll(path); err != nil { 311 log.G(ctx).WithError(err).Errorf("cleanup working dir %s", path) 312 } 313 } 314 } 315 return nil 316 } 317 318 func parsePlatforms(platformStr []string) ([]ocispec.Platform, error) { 319 p := make([]ocispec.Platform, len(platformStr)) 320 for i, v := range platformStr { 321 parsed, err := platforms.Parse(v) 322 if err != nil { 323 return nil, err 324 } 325 p[i] = parsed 326 } 327 return p, nil 328 }