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  }