github.phpd.cn/cilium/cilium@v1.6.12/pkg/workloads/containerd.go (about)

     1  // Copyright 2018-2019 Authors of Cilium
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  //
    15  package workloads
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"fmt"
    21  	"net/url"
    22  	"time"
    23  
    24  	"github.com/cilium/cilium/api/v1/models"
    25  	"github.com/cilium/cilium/pkg/endpoint"
    26  	endpointid "github.com/cilium/cilium/pkg/endpoint/id"
    27  	"github.com/cilium/cilium/pkg/logging/logfields"
    28  
    29  	"github.com/containerd/containerd"
    30  	apiEvents "github.com/containerd/containerd/api/events"
    31  	"github.com/containerd/containerd/events"
    32  	"github.com/containerd/containerd/namespaces"
    33  	"github.com/containerd/typeurl"
    34  	"github.com/sirupsen/logrus"
    35  )
    36  
    37  const (
    38  	ContainerD WorkloadRuntimeType = "containerd"
    39  
    40  	// criContainerdPrefix is common prefix for cri-containerd
    41  	criContainerdPrefix = "io.cri-containerd"
    42  	// containerKindLabel is a label key indicating container is sandbox container or application container
    43  	containerKindLabel = criContainerdPrefix + ".kind"
    44  	// containerKindSandbox is a label value indicating container is sandbox container
    45  	containerKindSandbox = "sandbox"
    46  
    47  	// containerDEndpoint is the default value for the containerd socket
    48  	containerDEndpoint = "/var/run/containerd/containerd.sock"
    49  
    50  	// k8sContainerdNamespace is the namespace kubernetes uses to connect containerd.
    51  	k8sContainerdNamespace = "k8s.io"
    52  )
    53  
    54  var (
    55  	containerDInstance = &containerDModule{
    56  		opts: workloadRuntimeOpts{
    57  			EpOpt: &workloadRuntimeOpt{
    58  				description: "Address of containerD endpoint",
    59  				value:       containerDEndpoint,
    60  			},
    61  		},
    62  	}
    63  )
    64  
    65  type containerDModule struct {
    66  	opts workloadRuntimeOpts
    67  }
    68  
    69  func init() {
    70  	registerWorkload(ContainerD, containerDInstance)
    71  }
    72  
    73  func (c *containerDModule) getName() string {
    74  	return string(ContainerD)
    75  }
    76  
    77  func (c *containerDModule) setConfigDummy() {
    78  }
    79  
    80  func (c *containerDModule) setConfig(opts map[string]string) error {
    81  	return setOpts(opts, c.opts)
    82  }
    83  
    84  func (c *containerDModule) getConfig() map[string]string {
    85  	return getOpts(c.opts)
    86  }
    87  
    88  func (c *containerDModule) newClient() (WorkloadRuntime, error) {
    89  	return newContainerDClient(c.opts)
    90  }
    91  
    92  type containerDClient struct {
    93  	*containerd.Client
    94  	cri *criClient
    95  }
    96  
    97  func newContainerDClient(opts workloadRuntimeOpts) (WorkloadRuntime, error) {
    98  	ep := string(opts[EpOpt].value)
    99  	c, err := containerd.New(ep)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	p, err := url.Parse(ep)
   104  	if err != nil {
   105  		return nil, err
   106  	}
   107  	if p.Scheme == "" {
   108  		ep = "unix://" + ep
   109  	}
   110  	rsc, err := newCRIClient(context.WithValue(context.Background(), EpOpt, ep))
   111  	return &containerDClient{c, rsc}, err
   112  }
   113  
   114  // IsRunning returns false if the provided endpoint cannot be associated with a
   115  // running workload. The runtime must be reachable to make this decision.
   116  func (c *containerDClient) IsRunning(ep *endpoint.Endpoint) bool {
   117  	return c.cri.IsRunning(ep)
   118  }
   119  
   120  // Status returns the status of the workload runtime
   121  func (c *containerDClient) Status() *models.Status {
   122  	if c == nil {
   123  		return workloadStatusDisabled
   124  	}
   125  
   126  	criStatus := c.cri.Status()
   127  	criStatusMsg := fmt.Sprintf("cri-containerd client: %s - %s", criStatus.State, criStatus.Msg)
   128  
   129  	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
   130  	defer cancel()
   131  	if _, err := c.Client.Version(ctx); err != nil {
   132  		return &models.Status{
   133  			State: models.StatusStateFailure,
   134  			Msg: fmt.Sprintf(
   135  				"containerD events watcher: %s - %s; "+criStatusMsg,
   136  				models.StatusStateFailure, err),
   137  		}
   138  	}
   139  
   140  	return &models.Status{
   141  		State: models.StatusStateOk,
   142  		Msg:   fmt.Sprintf("containerD events watcher: %s; "+criStatusMsg, models.StatusStateOk),
   143  	}
   144  }
   145  
   146  // EnableEventListener watches for containerD events. Performs the plumbing for
   147  // the containers started or dead.
   148  func (c *containerDClient) EnableEventListener() (eventsCh chan<- *EventMessage, err error) {
   149  	if c == nil {
   150  		log.Debug("Not enabling containerD event listener because containerDClient is nil")
   151  		return nil, nil
   152  	}
   153  	log.Info("Enabling containerD event listener")
   154  
   155  	ws := newWatcherState()
   156  
   157  	// Note: We do the sync before the first sleep
   158  	go func(state *watcherState) {
   159  		for {
   160  			eventsCh, errCh := c.Client.Subscribe(context.Background(), `topic~="/containers/create"`, `topic~="/containers/delete"`)
   161  			err := c.listenForContainerDEvents(ws, eventsCh, errCh)
   162  			log.WithError(err).Error("failed to listen events")
   163  			time.Sleep(500 * time.Millisecond)
   164  		}
   165  	}(ws)
   166  	return nil, nil
   167  }
   168  
   169  func (c *containerDClient) listenForContainerDEvents(ws *watcherState, eventsCh <-chan *events.Envelope, errCh <-chan error) error {
   170  	open := true
   171  	for open {
   172  		var e *events.Envelope
   173  		select {
   174  		case e, open = <-eventsCh:
   175  		case err := <-errCh:
   176  			return fmt.Errorf("unable to subscribe for containerd events: %s", err)
   177  		}
   178  
   179  		if e.Event != nil {
   180  			v, err := typeurl.UnmarshalAny(e.Event)
   181  			if err != nil {
   182  				return fmt.Errorf("unable to unmarshal event %v: %s", v, err)
   183  			}
   184  			switch event := v.(type) {
   185  			case *apiEvents.ContainerCreate:
   186  				ws.enqueueByContainerID(event.ID, &EventMessage{WorkloadID: event.ID, EventType: EventTypeStart})
   187  			case *apiEvents.ContainerDelete:
   188  				ws.enqueueByContainerID(event.ID, &EventMessage{WorkloadID: event.ID, EventType: EventTypeDelete})
   189  			default:
   190  				log.Debugf("received unknown containerD event %v", v)
   191  			}
   192  		}
   193  	}
   194  
   195  	return fmt.Errorf("channel closed")
   196  }
   197  
   198  func (c *containerDClient) processEvents(events chan EventMessage) {
   199  	for m := range events {
   200  		if m.WorkloadID != "" {
   201  			log.WithFields(logrus.Fields{
   202  				logfields.ContainerID: shortContainerID(m.WorkloadID),
   203  			}).Debug("Processing event for Container")
   204  			c.processEvent(m)
   205  		}
   206  	}
   207  }
   208  
   209  func (c *containerDClient) processEvent(m EventMessage) {
   210  	switch m.EventType {
   211  	case EventTypeStart:
   212  		ns := namespaces.WithNamespace(context.Background(), k8sContainerdNamespace)
   213  		f, err := c.Client.ContainerService().Get(ns, m.WorkloadID)
   214  		if err != nil {
   215  			log.WithError(err).Debugf("Unable to get more details for workload %s", m.WorkloadID)
   216  			return
   217  		}
   218  		// only handle pod events and ignore all other types
   219  		if f.Labels[containerKindLabel] != containerKindSandbox {
   220  			startIgnoringContainer(m.WorkloadID)
   221  			return
   222  		}
   223  		stopIgnoringContainer(m.WorkloadID)
   224  		c.handleCreateWorkload(m.WorkloadID, true)
   225  	case EventTypeDelete:
   226  		Owner().DeleteEndpoint(endpointid.NewID(endpointid.ContainerIdPrefix, m.WorkloadID))
   227  	}
   228  }
   229  
   230  func (c *containerDClient) handleCreateWorkload(id string, retry bool) {
   231  	c.cri.handleCreateWorkload(id, retry)
   232  }
   233  
   234  // IgnoreRunningWorkloads checks for already running containers and checks
   235  // their IP address, then adds the containers to the list of ignored containers
   236  // and allocates the IPs they are using to prevent future collisions.
   237  func (c *containerDClient) IgnoreRunningWorkloads() {
   238  	c.cri.IgnoreRunningWorkloads()
   239  }
   240  
   241  // workloadIDsList returns a list of running workload IDs.
   242  func (c *containerDClient) workloadIDsList(ctx context.Context) ([]string, error) {
   243  	return c.cri.workloadIDsList(ctx)
   244  }
   245  
   246  // GetAllInfraContainersPID returns a map that maps container IDs to the PID
   247  // of that container.
   248  func (c *containerDClient) GetAllInfraContainersPID() (map[string]int, error) {
   249  	return nil, errors.New("not implemented for containerD")
   250  }