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 }