github.com/lalkh/containerd@v1.4.3/cmd/containerd-shim/main_unix.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package main 20 21 import ( 22 "bytes" 23 "context" 24 "flag" 25 "fmt" 26 "io" 27 "net" 28 "os" 29 "os/exec" 30 "os/signal" 31 "runtime" 32 "runtime/debug" 33 "strings" 34 "sync" 35 "syscall" 36 "time" 37 38 "github.com/containerd/containerd/events" 39 "github.com/containerd/containerd/namespaces" 40 "github.com/containerd/containerd/pkg/process" 41 shimlog "github.com/containerd/containerd/runtime/v1" 42 "github.com/containerd/containerd/runtime/v1/shim" 43 shimapi "github.com/containerd/containerd/runtime/v1/shim/v1" 44 "github.com/containerd/containerd/sys/reaper" 45 "github.com/containerd/ttrpc" 46 "github.com/containerd/typeurl" 47 ptypes "github.com/gogo/protobuf/types" 48 "github.com/pkg/errors" 49 "github.com/sirupsen/logrus" 50 "golang.org/x/sys/unix" 51 ) 52 53 var ( 54 debugFlag bool 55 namespaceFlag string 56 socketFlag string 57 addressFlag string 58 workdirFlag string 59 runtimeRootFlag string 60 criuFlag string 61 systemdCgroupFlag bool 62 containerdBinaryFlag string 63 64 bufPool = sync.Pool{ 65 New: func() interface{} { 66 return bytes.NewBuffer(nil) 67 }, 68 } 69 ) 70 71 func init() { 72 flag.BoolVar(&debugFlag, "debug", false, "enable debug output in logs") 73 flag.StringVar(&namespaceFlag, "namespace", "", "namespace that owns the shim") 74 flag.StringVar(&socketFlag, "socket", "", "socket path to serve") 75 flag.StringVar(&addressFlag, "address", "", "grpc address back to main containerd") 76 flag.StringVar(&workdirFlag, "workdir", "", "path used to storge large temporary data") 77 flag.StringVar(&runtimeRootFlag, "runtime-root", process.RuncRoot, "root directory for the runtime") 78 flag.StringVar(&criuFlag, "criu", "", "path to criu binary") 79 flag.BoolVar(&systemdCgroupFlag, "systemd-cgroup", false, "set runtime to use systemd-cgroup") 80 // currently, the `containerd publish` utility is embedded in the daemon binary. 81 // The daemon invokes `containerd-shim -containerd-binary ...` with its own os.Executable() path. 82 flag.StringVar(&containerdBinaryFlag, "containerd-binary", "containerd", "path to containerd binary (used for `containerd publish`)") 83 flag.Parse() 84 } 85 86 func main() { 87 debug.SetGCPercent(40) 88 go func() { 89 for range time.Tick(30 * time.Second) { 90 debug.FreeOSMemory() 91 } 92 }() 93 94 if debugFlag { 95 logrus.SetLevel(logrus.DebugLevel) 96 } 97 98 if os.Getenv("GOMAXPROCS") == "" { 99 // If GOMAXPROCS hasn't been set, we default to a value of 2 to reduce 100 // the number of Go stacks present in the shim. 101 runtime.GOMAXPROCS(2) 102 } 103 104 stdout, stderr, err := openStdioKeepAlivePipes(workdirFlag) 105 if err != nil { 106 fmt.Fprintf(os.Stderr, "containerd-shim: %s\n", err) 107 os.Exit(1) 108 } 109 defer func() { 110 stdout.Close() 111 stderr.Close() 112 }() 113 114 // redirect the following output into fifo to make sure that containerd 115 // still can read the log after restart 116 logrus.SetOutput(stdout) 117 118 if err := executeShim(); err != nil { 119 fmt.Fprintf(os.Stderr, "containerd-shim: %s\n", err) 120 os.Exit(1) 121 } 122 } 123 124 // If containerd server process dies, we need the shim to keep stdout/err reader 125 // FDs so that Linux does not SIGPIPE the shim process if it tries to use its end of 126 // these pipes. 127 func openStdioKeepAlivePipes(dir string) (io.ReadWriteCloser, io.ReadWriteCloser, error) { 128 background := context.Background() 129 keepStdoutAlive, err := shimlog.OpenShimStdoutLog(background, dir) 130 if err != nil { 131 return nil, nil, err 132 } 133 keepStderrAlive, err := shimlog.OpenShimStderrLog(background, dir) 134 if err != nil { 135 return nil, nil, err 136 } 137 return keepStdoutAlive, keepStderrAlive, nil 138 } 139 140 func executeShim() error { 141 // start handling signals as soon as possible so that things are properly reaped 142 // or if runtime exits before we hit the handler 143 signals, err := setupSignals() 144 if err != nil { 145 return err 146 } 147 dump := make(chan os.Signal, 32) 148 signal.Notify(dump, syscall.SIGUSR1) 149 150 path, err := os.Getwd() 151 if err != nil { 152 return err 153 } 154 server, err := newServer() 155 if err != nil { 156 return errors.Wrap(err, "failed creating server") 157 } 158 sv, err := shim.NewService( 159 shim.Config{ 160 Path: path, 161 Namespace: namespaceFlag, 162 WorkDir: workdirFlag, 163 Criu: criuFlag, 164 SystemdCgroup: systemdCgroupFlag, 165 RuntimeRoot: runtimeRootFlag, 166 }, 167 &remoteEventsPublisher{address: addressFlag}, 168 ) 169 if err != nil { 170 return err 171 } 172 logrus.Debug("registering ttrpc server") 173 shimapi.RegisterShimService(server, sv) 174 175 socket := socketFlag 176 if err := serve(context.Background(), server, socket); err != nil { 177 return err 178 } 179 logger := logrus.WithFields(logrus.Fields{ 180 "pid": os.Getpid(), 181 "path": path, 182 "namespace": namespaceFlag, 183 }) 184 go func() { 185 for range dump { 186 dumpStacks(logger) 187 } 188 }() 189 return handleSignals(logger, signals, server, sv) 190 } 191 192 // serve serves the ttrpc API over a unix socket at the provided path 193 // this function does not block 194 func serve(ctx context.Context, server *ttrpc.Server, path string) error { 195 var ( 196 l net.Listener 197 err error 198 ) 199 if path == "" { 200 f := os.NewFile(3, "socket") 201 l, err = net.FileListener(f) 202 f.Close() 203 path = "[inherited from parent]" 204 } else { 205 const ( 206 abstractSocketPrefix = "\x00" 207 socketPathLimit = 106 208 ) 209 p := strings.TrimPrefix(path, "unix://") 210 if len(p) == len(path) { 211 p = abstractSocketPrefix + p 212 } 213 if len(p) > socketPathLimit { 214 return errors.Errorf("%q: unix socket path too long (> %d)", p, socketPathLimit) 215 } 216 l, err = net.Listen("unix", p) 217 } 218 if err != nil { 219 return err 220 } 221 logrus.WithField("socket", path).Debug("serving api on unix socket") 222 go func() { 223 defer l.Close() 224 if err := server.Serve(ctx, l); err != nil && 225 !strings.Contains(err.Error(), "use of closed network connection") { 226 logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure") 227 } 228 }() 229 return nil 230 } 231 232 func handleSignals(logger *logrus.Entry, signals chan os.Signal, server *ttrpc.Server, sv *shim.Service) error { 233 var ( 234 termOnce sync.Once 235 done = make(chan struct{}) 236 ) 237 238 for { 239 select { 240 case <-done: 241 return nil 242 case s := <-signals: 243 switch s { 244 case unix.SIGCHLD: 245 if err := reaper.Reap(); err != nil { 246 logger.WithError(err).Error("reap exit status") 247 } 248 case unix.SIGTERM, unix.SIGINT: 249 go termOnce.Do(func() { 250 ctx := context.TODO() 251 if err := server.Shutdown(ctx); err != nil { 252 logger.WithError(err).Error("failed to shutdown server") 253 } 254 // Ensure our child is dead if any 255 sv.Kill(ctx, &shimapi.KillRequest{ 256 Signal: uint32(syscall.SIGKILL), 257 All: true, 258 }) 259 sv.Delete(context.Background(), &ptypes.Empty{}) 260 close(done) 261 }) 262 case unix.SIGPIPE: 263 } 264 } 265 } 266 } 267 268 func dumpStacks(logger *logrus.Entry) { 269 var ( 270 buf []byte 271 stackSize int 272 ) 273 bufferLen := 16384 274 for stackSize == len(buf) { 275 buf = make([]byte, bufferLen) 276 stackSize = runtime.Stack(buf, true) 277 bufferLen *= 2 278 } 279 buf = buf[:stackSize] 280 logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) 281 } 282 283 type remoteEventsPublisher struct { 284 address string 285 } 286 287 func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error { 288 ns, _ := namespaces.Namespace(ctx) 289 encoded, err := typeurl.MarshalAny(event) 290 if err != nil { 291 return err 292 } 293 data, err := encoded.Marshal() 294 if err != nil { 295 return err 296 } 297 cmd := exec.CommandContext(ctx, containerdBinaryFlag, "--address", l.address, "publish", "--topic", topic, "--namespace", ns) 298 cmd.Stdin = bytes.NewReader(data) 299 b := bufPool.Get().(*bytes.Buffer) 300 defer bufPool.Put(b) 301 cmd.Stdout = b 302 cmd.Stderr = b 303 c, err := reaper.Default.Start(cmd) 304 if err != nil { 305 return err 306 } 307 status, err := reaper.Default.Wait(cmd, c) 308 if err != nil { 309 return errors.Wrapf(err, "failed to publish event: %s", b.String()) 310 } 311 if status != 0 { 312 return errors.Errorf("failed to publish event: %s", b.String()) 313 } 314 return nil 315 }