github.com/lalkh/containerd@v1.4.3/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", "", "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  		const (
   206  			abstractSocketPrefix = "\x00"
   207  			socketPathLimit      = 106
   208  		)
   209  		p := strings.TrimPrefix(path, "unix://")
   210  		if len(p) == len(path) {
   211  			p = abstractSocketPrefix + p
   212  		}
   213  		if len(p) > socketPathLimit {
   214  			return errors.Errorf("%q: unix socket path too long (> %d)", p, socketPathLimit)
   215  		}
   216  		l, err = net.Listen("unix", p)
   217  	}
   218  	if err != nil {
   219  		return err
   220  	}
   221  	logrus.WithField("socket", path).Debug("serving api on unix socket")
   222  	go func() {
   223  		defer l.Close()
   224  		if err := server.Serve(ctx, l); err != nil &&
   225  			!strings.Contains(err.Error(), "use of closed network connection") {
   226  			logrus.WithError(err).Fatal("containerd-shim: ttrpc server failure")
   227  		}
   228  	}()
   229  	return nil
   230  }
   231  
   232  func handleSignals(logger *logrus.Entry, signals chan os.Signal, server *ttrpc.Server, sv *shim.Service) error {
   233  	var (
   234  		termOnce sync.Once
   235  		done     = make(chan struct{})
   236  	)
   237  
   238  	for {
   239  		select {
   240  		case <-done:
   241  			return nil
   242  		case s := <-signals:
   243  			switch s {
   244  			case unix.SIGCHLD:
   245  				if err := reaper.Reap(); err != nil {
   246  					logger.WithError(err).Error("reap exit status")
   247  				}
   248  			case unix.SIGTERM, unix.SIGINT:
   249  				go termOnce.Do(func() {
   250  					ctx := context.TODO()
   251  					if err := server.Shutdown(ctx); err != nil {
   252  						logger.WithError(err).Error("failed to shutdown server")
   253  					}
   254  					// Ensure our child is dead if any
   255  					sv.Kill(ctx, &shimapi.KillRequest{
   256  						Signal: uint32(syscall.SIGKILL),
   257  						All:    true,
   258  					})
   259  					sv.Delete(context.Background(), &ptypes.Empty{})
   260  					close(done)
   261  				})
   262  			case unix.SIGPIPE:
   263  			}
   264  		}
   265  	}
   266  }
   267  
   268  func dumpStacks(logger *logrus.Entry) {
   269  	var (
   270  		buf       []byte
   271  		stackSize int
   272  	)
   273  	bufferLen := 16384
   274  	for stackSize == len(buf) {
   275  		buf = make([]byte, bufferLen)
   276  		stackSize = runtime.Stack(buf, true)
   277  		bufferLen *= 2
   278  	}
   279  	buf = buf[:stackSize]
   280  	logger.Infof("=== BEGIN goroutine stack dump ===\n%s\n=== END goroutine stack dump ===", buf)
   281  }
   282  
   283  type remoteEventsPublisher struct {
   284  	address string
   285  }
   286  
   287  func (l *remoteEventsPublisher) Publish(ctx context.Context, topic string, event events.Event) error {
   288  	ns, _ := namespaces.Namespace(ctx)
   289  	encoded, err := typeurl.MarshalAny(event)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	data, err := encoded.Marshal()
   294  	if err != nil {
   295  		return err
   296  	}
   297  	cmd := exec.CommandContext(ctx, containerdBinaryFlag, "--address", l.address, "publish", "--topic", topic, "--namespace", ns)
   298  	cmd.Stdin = bytes.NewReader(data)
   299  	b := bufPool.Get().(*bytes.Buffer)
   300  	defer bufPool.Put(b)
   301  	cmd.Stdout = b
   302  	cmd.Stderr = b
   303  	c, err := reaper.Default.Start(cmd)
   304  	if err != nil {
   305  		return err
   306  	}
   307  	status, err := reaper.Default.Wait(cmd, c)
   308  	if err != nil {
   309  		return errors.Wrapf(err, "failed to publish event: %s", b.String())
   310  	}
   311  	if status != 0 {
   312  		return errors.Errorf("failed to publish event: %s", b.String())
   313  	}
   314  	return nil
   315  }