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  }