github.com/containerd/containerd@v22.0.0-20200918172823-438c87b8e050+incompatible/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", "", "abstract 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  
   199  	service, err := initFunc(ctx, idFlag, publisher, cancel)
   200  	if err != nil {
   201  		return err
   202  	}
   203  	switch action {
   204  	case "delete":
   205  		logger := logrus.WithFields(logrus.Fields{
   206  			"pid":       os.Getpid(),
   207  			"namespace": namespaceFlag,
   208  		})
   209  		go handleSignals(ctx, logger, signals)
   210  		response, err := service.Cleanup(ctx)
   211  		if err != nil {
   212  			return err
   213  		}
   214  		data, err := proto.Marshal(response)
   215  		if err != nil {
   216  			return err
   217  		}
   218  		if _, err := os.Stdout.Write(data); err != nil {
   219  			return err
   220  		}
   221  		return nil
   222  	case "start":
   223  		address, err := service.StartShim(ctx, idFlag, containerdBinaryFlag, addressFlag, ttrpcAddress)
   224  		if err != nil {
   225  			return err
   226  		}
   227  		if _, err := os.Stdout.WriteString(address); err != nil {
   228  			return err
   229  		}
   230  		return nil
   231  	default:
   232  		if !config.NoSetupLogger {
   233  			if err := setLogger(ctx, idFlag); err != nil {
   234  				return err
   235  			}
   236  		}
   237  		client := NewShimClient(ctx, service, signals)
   238  		if err := client.Serve(); err != nil {
   239  			if err != context.Canceled {
   240  				return err
   241  			}
   242  		}
   243  		select {
   244  		case <-publisher.Done():
   245  			return nil
   246  		case <-time.After(5 * time.Second):
   247  			return errors.New("publisher not closed")
   248  		}
   249  	}
   250  }
   251  
   252  // NewShimClient creates a new shim server client
   253  func NewShimClient(ctx context.Context, svc shimapi.TaskService, signals chan os.Signal) *Client {
   254  	s := &Client{
   255  		service: svc,
   256  		context: ctx,
   257  		signals: signals,
   258  	}
   259  	return s
   260  }
   261  
   262  // Serve the shim server
   263  func (s *Client) Serve() error {
   264  	dump := make(chan os.Signal, 32)
   265  	setupDumpStacks(dump)
   266  
   267  	path, err := os.Getwd()
   268  	if err != nil {
   269  		return err
   270  	}
   271  	server, err := newServer()
   272  	if err != nil {
   273  		return errors.Wrap(err, "failed creating server")
   274  	}
   275  
   276  	logrus.Debug("registering ttrpc server")
   277  	shimapi.RegisterTaskService(server, s.service)
   278  
   279  	if err := serve(s.context, server, socketFlag); err != nil {
   280  		return err
   281  	}
   282  	logger := logrus.WithFields(logrus.Fields{
   283  		"pid":       os.Getpid(),
   284  		"path":      path,
   285  		"namespace": namespaceFlag,
   286  	})
   287  	go func() {
   288  		for range dump {
   289  			dumpStacks(logger)
   290  		}
   291  	}()
   292  	return handleSignals(s.context, logger, s.signals)
   293  }
   294  
   295  // serve serves the ttrpc API over a unix socket at the provided path
   296  // this function does not block
   297  func serve(ctx context.Context, server *ttrpc.Server, path string) error {
   298  	l, err := serveListener(path)
   299  	if err != nil {
   300  		return err
   301  	}
   302  	go func() {
   303  		defer l.Close()
   304  		if err := server.Serve(ctx, l); err != nil &&
   305  			!strings.Contains(err.Error(), "use of closed network connection") {
   306  			logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure")
   307  		}
   308  	}()
   309  	return nil
   310  }
   311  
   312  func dumpStacks(logger *logrus.Entry) {
   313  	var (
   314  		buf       []byte
   315  		stackSize int
   316  	)
   317  	bufferLen := 16384
   318  	for stackSize == len(buf) {
   319  		buf = make([]byte, bufferLen)
   320  		stackSize = runtime.Stack(buf, true)
   321  		bufferLen *= 2
   322  	}
   323  	buf = buf[:stackSize]
   324  	logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
   325  }