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 17package server 18 19import ( 20 "io" 21 "time" 22 23 "github.com/containerd/containerd" 24 containerdio "github.com/containerd/containerd/cio" 25 "github.com/containerd/containerd/errdefs" 26 "github.com/containerd/containerd/log" 27 "github.com/containerd/nri" 28 v1 "github.com/containerd/nri/types/v1" 29 "github.com/pkg/errors" 30 "github.com/sirupsen/logrus" 31 "golang.org/x/net/context" 32 runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" 33 34 cio "github.com/containerd/containerd/pkg/cri/io" 35 containerstore "github.com/containerd/containerd/pkg/cri/store/container" 36 sandboxstore "github.com/containerd/containerd/pkg/cri/store/sandbox" 37 ctrdutil "github.com/containerd/containerd/pkg/cri/util" 38 cioutil "github.com/containerd/containerd/pkg/ioutil" 39) 40 41// StartContainer starts the container. 42func (c *criService) StartContainer(ctx context.Context, r *runtime.StartContainerRequest) (retRes *runtime.StartContainerResponse, retErr error) { 43 cntr, err := c.containerStore.Get(r.GetContainerId()) 44 if err != nil { 45 return nil, errors.Wrapf(err, "an error occurred when try to find container %q", r.GetContainerId()) 46 } 47 48 id := cntr.ID 49 meta := cntr.Metadata 50 container := cntr.Container 51 config := meta.Config 52 53 // Set starting state to prevent other start/remove operations against this container 54 // while it's being started. 55 if err := setContainerStarting(cntr); err != nil { 56 return nil, errors.Wrapf(err, "failed to set starting state for container %q", id) 57 } 58 defer func() { 59 if retErr != nil { 60 // Set container to exited if fail to start. 61 if err := cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) { 62 status.Pid = 0 63 status.FinishedAt = time.Now().UnixNano() 64 status.ExitCode = errorStartExitCode 65 status.Reason = errorStartReason 66 status.Message = retErr.Error() 67 return status, nil 68 }); err != nil { 69 log.G(ctx).WithError(err).Errorf("failed to set start failure state for container %q", id) 70 } 71 } 72 if err := resetContainerStarting(cntr); err != nil { 73 log.G(ctx).WithError(err).Errorf("failed to reset starting state for container %q", id) 74 } 75 }() 76 77 // Get sandbox config from sandbox store. 78 sandbox, err := c.sandboxStore.Get(meta.SandboxID) 79 if err != nil { 80 return nil, errors.Wrapf(err, "sandbox %q not found", meta.SandboxID) 81 } 82 sandboxID := meta.SandboxID 83 if sandbox.Status.Get().State != sandboxstore.StateReady { 84 return nil, errors.Errorf("sandbox container %q is not running", sandboxID) 85 } 86 87 // Recheck target container validity in Linux namespace options. 88 if linux := config.GetLinux(); linux != nil { 89 nsOpts := linux.GetSecurityContext().GetNamespaceOptions() 90 if nsOpts.GetPid() == runtime.NamespaceMode_TARGET { 91 _, err := c.validateTargetContainer(sandboxID, nsOpts.TargetId) 92 if err != nil { 93 return nil, errors.Wrap(err, "invalid target container") 94 } 95 } 96 } 97 98 ioCreation := func(id string) (_ containerdio.IO, err error) { 99 stdoutWC, stderrWC, err := c.createContainerLoggers(meta.LogPath, config.GetTty()) 100 if err != nil { 101 return nil, errors.Wrap(err, "failed to create container loggers") 102 } 103 cntr.IO.AddOutput("log", stdoutWC, stderrWC) 104 cntr.IO.Pipe() 105 return cntr.IO, nil 106 } 107 108 ctrInfo, err := container.Info(ctx) 109 if err != nil { 110 return nil, errors.Wrap(err, "failed to get container info") 111 } 112 113 taskOpts := c.taskOpts(ctrInfo.Runtime.Name) 114 task, err := container.NewTask(ctx, ioCreation, taskOpts...) 115 if err != nil { 116 return nil, errors.Wrap(err, "failed to create containerd task") 117 } 118 defer func() { 119 if retErr != nil { 120 deferCtx, deferCancel := ctrdutil.DeferContext() 121 defer deferCancel() 122 // It's possible that task is deleted by event monitor. 123 if _, err := task.Delete(deferCtx, WithNRISandboxDelete(sandboxID), containerd.WithProcessKill); err != nil && !errdefs.IsNotFound(err) { 124 log.G(ctx).WithError(err).Errorf("Failed to delete containerd task %q", id) 125 } 126 } 127 }() 128 129 // wait is a long running background request, no timeout needed. 130 exitCh, err := task.Wait(ctrdutil.NamespacedContext()) 131 if err != nil { 132 return nil, errors.Wrap(err, "failed to wait for containerd task") 133 } 134 nric, err := nri.New() 135 if err != nil { 136 log.G(ctx).WithError(err).Error("unable to create nri client") 137 } 138 if nric != nil { 139 nriSB := &nri.Sandbox{ 140 ID: sandboxID, 141 Labels: sandbox.Config.Labels, 142 } 143 if _, err := nric.InvokeWithSandbox(ctx, task, v1.Create, nriSB); err != nil { 144 return nil, errors.Wrap(err, "nri invoke") 145 } 146 } 147 148 // Start containerd task. 149 if err := task.Start(ctx); err != nil { 150 return nil, errors.Wrapf(err, "failed to start containerd task %q", id) 151 } 152 153 // Update container start timestamp. 154 if err := cntr.Status.UpdateSync(func(status containerstore.Status) (containerstore.Status, error) { 155 status.Pid = task.Pid() 156 status.StartedAt = time.Now().UnixNano() 157 return status, nil 158 }); err != nil { 159 return nil, errors.Wrapf(err, "failed to update container %q state", id) 160 } 161 162 // It handles the TaskExit event and update container state after this. 163 c.eventMonitor.startContainerExitMonitor(context.Background(), id, task.Pid(), exitCh) 164 165 return &runtime.StartContainerResponse{}, nil 166} 167 168// setContainerStarting sets the container into starting state. In starting state, the 169// container will not be removed or started again. 170func setContainerStarting(container containerstore.Container) error { 171 return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) { 172 // Return error if container is not in created state. 173 if status.State() != runtime.ContainerState_CONTAINER_CREATED { 174 return status, errors.Errorf("container is in %s state", criContainerStateToString(status.State())) 175 } 176 // Do not start the container when there is a removal in progress. 177 if status.Removing { 178 return status, errors.New("container is in removing state, can't be started") 179 } 180 if status.Starting { 181 return status, errors.New("container is already in starting state") 182 } 183 status.Starting = true 184 return status, nil 185 }) 186} 187 188// resetContainerStarting resets the container starting state on start failure. So 189// that we could remove the container later. 190func resetContainerStarting(container containerstore.Container) error { 191 return container.Status.Update(func(status containerstore.Status) (containerstore.Status, error) { 192 status.Starting = false 193 return status, nil 194 }) 195} 196 197// createContainerLoggers creates container loggers and return write closer for stdout and stderr. 198func (c *criService) createContainerLoggers(logPath string, tty bool) (stdout io.WriteCloser, stderr io.WriteCloser, err error) { 199 if logPath != "" { 200 // Only generate container log when log path is specified. 201 f, err := openLogFile(logPath) 202 if err != nil { 203 return nil, nil, errors.Wrap(err, "failed to create and open log file") 204 } 205 defer func() { 206 if err != nil { 207 f.Close() 208 } 209 }() 210 var stdoutCh, stderrCh <-chan struct{} 211 wc := cioutil.NewSerialWriteCloser(f) 212 stdout, stdoutCh = cio.NewCRILogger(logPath, wc, cio.Stdout, c.config.MaxContainerLogLineSize) 213 // Only redirect stderr when there is no tty. 214 if !tty { 215 stderr, stderrCh = cio.NewCRILogger(logPath, wc, cio.Stderr, c.config.MaxContainerLogLineSize) 216 } 217 go func() { 218 if stdoutCh != nil { 219 <-stdoutCh 220 } 221 if stderrCh != nil { 222 <-stderrCh 223 } 224 logrus.Debugf("Finish redirecting log file %q, closing it", logPath) 225 f.Close() 226 }() 227 } else { 228 stdout = cio.NewDiscardLogger() 229 stderr = cio.NewDiscardLogger() 230 } 231 return 232} 233