github.com/containerd/Containerd@v1.4.13/runtime/restart/monitor/monitor.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 monitor 18 19 import ( 20 "context" 21 "fmt" 22 "time" 23 24 "github.com/containerd/containerd" 25 containers "github.com/containerd/containerd/api/services/containers/v1" 26 diff "github.com/containerd/containerd/api/services/diff/v1" 27 images "github.com/containerd/containerd/api/services/images/v1" 28 namespacesapi "github.com/containerd/containerd/api/services/namespaces/v1" 29 tasks "github.com/containerd/containerd/api/services/tasks/v1" 30 "github.com/containerd/containerd/content" 31 "github.com/containerd/containerd/leases" 32 "github.com/containerd/containerd/namespaces" 33 "github.com/containerd/containerd/plugin" 34 "github.com/containerd/containerd/runtime/restart" 35 "github.com/containerd/containerd/services" 36 "github.com/containerd/containerd/snapshots" 37 "github.com/pkg/errors" 38 "github.com/sirupsen/logrus" 39 ) 40 41 type duration struct { 42 time.Duration 43 } 44 45 func (d *duration) UnmarshalText(text []byte) error { 46 var err error 47 d.Duration, err = time.ParseDuration(string(text)) 48 return err 49 } 50 51 func (d duration) MarshalText() ([]byte, error) { 52 return []byte(d.Duration.String()), nil 53 } 54 55 // Config for the restart monitor 56 type Config struct { 57 // Interval for how long to wait to check for state changes 58 Interval duration `toml:"interval"` 59 } 60 61 func init() { 62 plugin.Register(&plugin.Registration{ 63 Type: plugin.InternalPlugin, 64 Requires: []plugin.Type{ 65 plugin.ServicePlugin, 66 }, 67 ID: "restart", 68 Config: &Config{ 69 Interval: duration{ 70 Duration: 10 * time.Second, 71 }, 72 }, 73 InitFn: func(ic *plugin.InitContext) (interface{}, error) { 74 opts, err := getServicesOpts(ic) 75 if err != nil { 76 return nil, err 77 } 78 client, err := containerd.New("", containerd.WithServices(opts...)) 79 if err != nil { 80 return nil, err 81 } 82 m := &monitor{ 83 client: client, 84 } 85 go m.run(ic.Config.(*Config).Interval.Duration) 86 return m, nil 87 }, 88 }) 89 } 90 91 // getServicesOpts get service options from plugin context. 92 func getServicesOpts(ic *plugin.InitContext) ([]containerd.ServicesOpt, error) { 93 plugins, err := ic.GetByType(plugin.ServicePlugin) 94 if err != nil { 95 return nil, errors.Wrap(err, "failed to get service plugin") 96 } 97 opts := []containerd.ServicesOpt{ 98 containerd.WithEventService(ic.Events), 99 } 100 for s, fn := range map[string]func(interface{}) containerd.ServicesOpt{ 101 services.ContentService: func(s interface{}) containerd.ServicesOpt { 102 return containerd.WithContentStore(s.(content.Store)) 103 }, 104 services.ImagesService: func(s interface{}) containerd.ServicesOpt { 105 return containerd.WithImageService(s.(images.ImagesClient)) 106 }, 107 services.SnapshotsService: func(s interface{}) containerd.ServicesOpt { 108 return containerd.WithSnapshotters(s.(map[string]snapshots.Snapshotter)) 109 }, 110 services.ContainersService: func(s interface{}) containerd.ServicesOpt { 111 return containerd.WithContainerService(s.(containers.ContainersClient)) 112 }, 113 services.TasksService: func(s interface{}) containerd.ServicesOpt { 114 return containerd.WithTaskService(s.(tasks.TasksClient)) 115 }, 116 services.DiffService: func(s interface{}) containerd.ServicesOpt { 117 return containerd.WithDiffService(s.(diff.DiffClient)) 118 }, 119 services.NamespacesService: func(s interface{}) containerd.ServicesOpt { 120 return containerd.WithNamespaceService(s.(namespacesapi.NamespacesClient)) 121 }, 122 services.LeasesService: func(s interface{}) containerd.ServicesOpt { 123 return containerd.WithLeasesService(s.(leases.Manager)) 124 }, 125 } { 126 p := plugins[s] 127 if p == nil { 128 return nil, errors.Errorf("service %q not found", s) 129 } 130 i, err := p.Instance() 131 if err != nil { 132 return nil, errors.Wrapf(err, "failed to get instance of service %q", s) 133 } 134 if i == nil { 135 return nil, errors.Errorf("instance of service %q not found", s) 136 } 137 opts = append(opts, fn(i)) 138 } 139 return opts, nil 140 } 141 142 type change interface { 143 apply(context.Context, *containerd.Client) error 144 } 145 146 type monitor struct { 147 client *containerd.Client 148 } 149 150 func (m *monitor) run(interval time.Duration) { 151 if interval == 0 { 152 interval = 10 * time.Second 153 } 154 for { 155 time.Sleep(interval) 156 if err := m.reconcile(context.Background()); err != nil { 157 logrus.WithError(err).Error("reconcile") 158 } 159 } 160 } 161 162 func (m *monitor) reconcile(ctx context.Context) error { 163 ns, err := m.client.NamespaceService().List(ctx) 164 if err != nil { 165 return err 166 } 167 for _, name := range ns { 168 ctx = namespaces.WithNamespace(ctx, name) 169 changes, err := m.monitor(ctx) 170 if err != nil { 171 logrus.WithError(err).Error("monitor for changes") 172 continue 173 } 174 for _, c := range changes { 175 if err := c.apply(ctx, m.client); err != nil { 176 logrus.WithError(err).Error("apply change") 177 } 178 } 179 } 180 return nil 181 } 182 183 func (m *monitor) monitor(ctx context.Context) ([]change, error) { 184 containers, err := m.client.Containers(ctx, fmt.Sprintf("labels.%q", restart.StatusLabel)) 185 if err != nil { 186 return nil, err 187 } 188 var changes []change 189 for _, c := range containers { 190 labels, err := c.Labels(ctx) 191 if err != nil { 192 return nil, err 193 } 194 desiredStatus := containerd.ProcessStatus(labels[restart.StatusLabel]) 195 if m.isSameStatus(ctx, desiredStatus, c) { 196 continue 197 } 198 switch desiredStatus { 199 case containerd.Running: 200 changes = append(changes, &startChange{ 201 container: c, 202 logPath: labels[restart.LogPathLabel], 203 logURI: labels[restart.LogURILabel], 204 }) 205 case containerd.Stopped: 206 changes = append(changes, &stopChange{ 207 container: c, 208 }) 209 } 210 } 211 return changes, nil 212 } 213 214 func (m *monitor) isSameStatus(ctx context.Context, desired containerd.ProcessStatus, container containerd.Container) bool { 215 task, err := container.Task(ctx, nil) 216 if err != nil { 217 return desired == containerd.Stopped 218 } 219 state, err := task.Status(ctx) 220 if err != nil { 221 return desired == containerd.Stopped 222 } 223 return desired == state.Status 224 }