github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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", "", "abstract 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 if len(path) > 106 { 206 return errors.Errorf("%q: unix socket path too long (> 106)", path) 207 } 208 l, err = net.Listen("unix", "\x00"+path) 209 } 210 if err != nil { 211 return err 212 } 213 logrus.WithField("socket", path).Debug("serving api on unix socket") 214 go func() { 215 defer l.Close() 216 if err := server.Serve(ctx, l); err != nil && 217 !strings.Contains(err.Error(), "use of closed network connection") { 218 logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure") 219 } 220 }() 221 return nil 222 } 223 224 func handleSignals(logger *logrus.Entry, signals chan os.Signal, server *ttrpc.Server, sv *shim.Service) error { 225 var ( 226 termOnce sync.Once 227 done = make(chan struct{}) 228 ) 229 230 for { 231 select { 232 case <-done: 233 return nil 234 case s := <-signals: 235 switch s { 236 case unix.SIGCHLD: 237 if err := reaper.Reap(); err != nil { 238 logger.WithError(err).Error("reap exit status") 239 } 240 case unix.SIGTERM, unix.SIGINT: 241 go termOnce.Do(func() { 242 ctx := context.TODO() 243 if err := server.Shutdown(ctx); err != nil { 244 logger.WithError(err).Error("failed to shutdown server") 245 } 246 // Ensure our child is dead if any 247 sv.Kill(ctx, &shimapi.KillRequest{ 248 Signal: uint32(syscall.SIGKILL), 249 All: true, 250 }) 251 sv.Delete(context.Background(), &ptypes.Empty{}) 252 close(done) 253 }) 254 case unix.SIGPIPE: 255 } 256 } 257 } 258 } 259 260 func dumpStacks(logger *logrus.Entry) { 261 var ( 262 buf []byte 263 stackSize int 264 ) 265 bufferLen := 16384 266 for stackSize == len(buf) { 267 buf = make([]byte, bufferLen) 268 stackSize = runtime.Stack(buf, true) 269 bufferLen *= 2 270 } 271 buf = buf[:stackSize] 272 logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) 273 } 274 275 type remoteEventsPublisher struct { 276 address string 277 } 278 279 func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error { 280 ns, _ := namespaces.Namespace(ctx) 281 encoded, err := typeurl.MarshalAny(event) 282 if err != nil { 283 return err 284 } 285 data, err := encoded.Marshal() 286 if err != nil { 287 return err 288 } 289 cmd := exec.CommandContext(ctx, containerdBinaryFlag, "--address", l.address, "publish", "--topic", topic, "--namespace", ns) 290 cmd.Stdin = bytes.NewReader(data) 291 b := bufPool.Get().(*bytes.Buffer) 292 defer bufPool.Put(b) 293 cmd.Stdout = b 294 cmd.Stderr = b 295 c, err := reaper.Default.Start(cmd) 296 if err != nil { 297 return err 298 } 299 status, err := reaper.Default.Wait(cmd, c) 300 if err != nil { 301 return errors.Wrapf(err, "failed to publish event: %s", b.String()) 302 } 303 if status != 0 { 304 return errors.Errorf("failed to publish event: %s", b.String()) 305 } 306 return nil 307 }