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  }