github.com/containerd/Containerd@v1.4.13/runtime/v2/shim/shim.go (about) 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 17 package shim 18 19 import ( 20 "context" 21 "flag" 22 "fmt" 23 "io" 24 "os" 25 "runtime" 26 "runtime/debug" 27 "strings" 28 "time" 29 30 "github.com/containerd/containerd/events" 31 "github.com/containerd/containerd/log" 32 "github.com/containerd/containerd/namespaces" 33 shimapi "github.com/containerd/containerd/runtime/v2/task" 34 "github.com/containerd/containerd/version" 35 "github.com/containerd/ttrpc" 36 "github.com/gogo/protobuf/proto" 37 "github.com/pkg/errors" 38 "github.com/sirupsen/logrus" 39 ) 40 41 // Client for a shim server 42 type Client struct { 43 service shimapi.TaskService 44 context context.Context 45 signals chan os.Signal 46 } 47 48 // Publisher for events 49 type Publisher interface { 50 events.Publisher 51 io.Closer 52 } 53 54 // Init func for the creation of a shim server 55 type Init func(context.Context, string, Publisher, func()) (Shim, error) 56 57 // Shim server interface 58 type Shim interface { 59 shimapi.TaskService 60 Cleanup(ctx context.Context) (*shimapi.DeleteResponse, error) 61 StartShim(ctx context.Context, id, containerdBinary, containerdAddress, containerdTTRPCAddress string) (string, error) 62 } 63 64 // OptsKey is the context key for the Opts value. 65 type OptsKey struct{} 66 67 // Opts are context options associated with the shim invocation. 68 type Opts struct { 69 BundlePath string 70 Debug bool 71 } 72 73 // BinaryOpts allows the configuration of a shims binary setup 74 type BinaryOpts func(*Config) 75 76 // Config of shim binary options provided by shim implementations 77 type Config struct { 78 // NoSubreaper disables setting the shim as a child subreaper 79 NoSubreaper bool 80 // NoReaper disables the shim binary from reaping any child process implicitly 81 NoReaper bool 82 // NoSetupLogger disables automatic configuration of logrus to use the shim FIFO 83 NoSetupLogger bool 84 } 85 86 var ( 87 debugFlag bool 88 versionFlag bool 89 idFlag string 90 namespaceFlag string 91 socketFlag string 92 bundlePath string 93 addressFlag string 94 containerdBinaryFlag string 95 action string 96 ) 97 98 const ( 99 ttrpcAddressEnv = "TTRPC_ADDRESS" 100 ) 101 102 func parseFlags() { 103 flag.BoolVar(&debugFlag, "debug", false, "enable debug output in logs") 104 flag.BoolVar(&versionFlag, "v", false, "show the shim version and exit") 105 flag.StringVar(&namespaceFlag, "namespace", "", "namespace that owns the shim") 106 flag.StringVar(&idFlag, "id", "", "id of the task") 107 flag.StringVar(&socketFlag, "socket", "", "socket path to serve") 108 flag.StringVar(&bundlePath, "bundle", "", "path to the bundle if not workdir") 109 110 flag.StringVar(&addressFlag, "address", "", "grpc address back to main containerd") 111 flag.StringVar(&containerdBinaryFlag, "publish-binary", "containerd", "path to publish binary (used for publishing events)") 112 113 flag.Parse() 114 action = flag.Arg(0) 115 } 116 117 func setRuntime() { 118 debug.SetGCPercent(40) 119 go func() { 120 for range time.Tick(30 * time.Second) { 121 debug.FreeOSMemory() 122 } 123 }() 124 if os.Getenv("GOMAXPROCS") == "" { 125 // If GOMAXPROCS hasn't been set, we default to a value of 2 to reduce 126 // the number of Go stacks present in the shim. 127 runtime.GOMAXPROCS(2) 128 } 129 } 130 131 func setLogger(ctx context.Context, id string) error { 132 logrus.SetFormatter(&logrus.TextFormatter{ 133 TimestampFormat: log.RFC3339NanoFixed, 134 FullTimestamp: true, 135 }) 136 if debugFlag { 137 logrus.SetLevel(logrus.DebugLevel) 138 } 139 f, err := openLog(ctx, id) 140 if err != nil { 141 return err 142 } 143 logrus.SetOutput(f) 144 return nil 145 } 146 147 // Run initializes and runs a shim server 148 func Run(id string, initFunc Init, opts ...BinaryOpts) { 149 var config Config 150 for _, o := range opts { 151 o(&config) 152 } 153 if err := run(id, initFunc, config); err != nil { 154 fmt.Fprintf(os.Stderr, "%s: %s\n", id, err) 155 os.Exit(1) 156 } 157 } 158 159 func run(id string, initFunc Init, config Config) error { 160 parseFlags() 161 if versionFlag { 162 fmt.Printf("%s:\n", os.Args[0]) 163 fmt.Println(" Version: ", version.Version) 164 fmt.Println(" Revision:", version.Revision) 165 fmt.Println(" Go version:", version.GoVersion) 166 fmt.Println("") 167 return nil 168 } 169 170 setRuntime() 171 172 signals, err := setupSignals(config) 173 if err != nil { 174 return err 175 } 176 if !config.NoSubreaper { 177 if err := subreaper(); err != nil { 178 return err 179 } 180 } 181 182 ttrpcAddress := os.Getenv(ttrpcAddressEnv) 183 184 publisher, err := NewPublisher(ttrpcAddress) 185 if err != nil { 186 return err 187 } 188 189 defer publisher.Close() 190 191 if namespaceFlag == "" { 192 return fmt.Errorf("shim namespace cannot be empty") 193 } 194 ctx := namespaces.WithNamespace(context.Background(), namespaceFlag) 195 ctx = context.WithValue(ctx, OptsKey{}, Opts{BundlePath: bundlePath, Debug: debugFlag}) 196 ctx = log.WithLogger(ctx, log.G(ctx).WithField("runtime", id)) 197 ctx, cancel := context.WithCancel(ctx) 198 service, err := initFunc(ctx, idFlag, publisher, cancel) 199 if err != nil { 200 return err 201 } 202 switch action { 203 case "delete": 204 logger := logrus.WithFields(logrus.Fields{ 205 "pid": os.Getpid(), 206 "namespace": namespaceFlag, 207 }) 208 go handleSignals(ctx, logger, signals) 209 response, err := service.Cleanup(ctx) 210 if err != nil { 211 return err 212 } 213 data, err := proto.Marshal(response) 214 if err != nil { 215 return err 216 } 217 if _, err := os.Stdout.Write(data); err != nil { 218 return err 219 } 220 return nil 221 case "start": 222 address, err := service.StartShim(ctx, idFlag, containerdBinaryFlag, addressFlag, ttrpcAddress) 223 if err != nil { 224 return err 225 } 226 if _, err := os.Stdout.WriteString(address); err != nil { 227 return err 228 } 229 return nil 230 default: 231 if !config.NoSetupLogger { 232 if err := setLogger(ctx, idFlag); err != nil { 233 return err 234 } 235 } 236 client := NewShimClient(ctx, service, signals) 237 if err := client.Serve(); err != nil { 238 if err != context.Canceled { 239 return err 240 } 241 } 242 243 // NOTE: If the shim server is down(like oom killer), the address 244 // socket might be leaking. 245 if address, err := ReadAddress("address"); err == nil { 246 _ = RemoveSocket(address) 247 } 248 249 select { 250 case <-publisher.Done(): 251 return nil 252 case <-time.After(5 * time.Second): 253 return errors.New("publisher not closed") 254 } 255 } 256 } 257 258 // NewShimClient creates a new shim server client 259 func NewShimClient(ctx context.Context, svc shimapi.TaskService, signals chan os.Signal) *Client { 260 s := &Client{ 261 service: svc, 262 context: ctx, 263 signals: signals, 264 } 265 return s 266 } 267 268 // Serve the shim server 269 func (s *Client) Serve() error { 270 dump := make(chan os.Signal, 32) 271 setupDumpStacks(dump) 272 273 path, err := os.Getwd() 274 if err != nil { 275 return err 276 } 277 server, err := newServer() 278 if err != nil { 279 return errors.Wrap(err, "failed creating server") 280 } 281 282 logrus.Debug("registering ttrpc server") 283 shimapi.RegisterTaskService(server, s.service) 284 285 if err := serve(s.context, server, socketFlag); err != nil { 286 return err 287 } 288 logger := logrus.WithFields(logrus.Fields{ 289 "pid": os.Getpid(), 290 "path": path, 291 "namespace": namespaceFlag, 292 }) 293 go func() { 294 for range dump { 295 dumpStacks(logger) 296 } 297 }() 298 return handleSignals(s.context, logger, s.signals) 299 } 300 301 // serve serves the ttrpc API over a unix socket at the provided path 302 // this function does not block 303 func serve(ctx context.Context, server *ttrpc.Server, path string) error { 304 l, err := serveListener(path) 305 if err != nil { 306 return err 307 } 308 go func() { 309 defer l.Close() 310 if err := server.Serve(ctx, l); err != nil && 311 !strings.Contains(err.Error(), "use of closed network connection") { 312 logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure") 313 } 314 }() 315 return nil 316 } 317 318 func dumpStacks(logger *logrus.Entry) { 319 var ( 320 buf []byte 321 stackSize int 322 ) 323 bufferLen := 16384 324 for stackSize == len(buf) { 325 buf = make([]byte, bufferLen) 326 stackSize = runtime.Stack(buf, true) 327 bufferLen *= 2 328 } 329 buf = buf[:stackSize] 330 logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf) 331 }