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