diff options
Diffstat (limited to 'cmd/crio/main.go')
-rw-r--r-- | cmd/crio/main.go | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/cmd/crio/main.go b/cmd/crio/main.go new file mode 100644 index 000000000..e58adb114 --- /dev/null +++ b/cmd/crio/main.go @@ -0,0 +1,532 @@ +package main + +import ( + "context" + "fmt" + "net" + "net/http" + _ "net/http/pprof" + "os" + "os/signal" + "sort" + "strings" + "time" + + "github.com/containers/storage/pkg/reexec" + "github.com/kubernetes-incubator/cri-o/libkpod" + "github.com/kubernetes-incubator/cri-o/server" + "github.com/kubernetes-incubator/cri-o/version" + "github.com/opencontainers/selinux/go-selinux" + "github.com/sirupsen/logrus" + "github.com/soheilhy/cmux" + "github.com/urfave/cli" + "golang.org/x/sys/unix" + "google.golang.org/grpc" + "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +// gitCommit is the commit that the binary is being built from. +// It will be populated by the Makefile. +var gitCommit = "" + +func validateConfig(config *server.Config) error { + switch config.ImageVolumes { + case libkpod.ImageVolumesMkdir: + case libkpod.ImageVolumesIgnore: + case libkpod.ImageVolumesBind: + default: + return fmt.Errorf("Unrecognized image volume type specified") + + } + + // This needs to match the read buffer size in conmon + if config.LogSizeMax >= 0 && config.LogSizeMax < 8192 { + return fmt.Errorf("log size max should be negative or >= 8192") + } + return nil +} + +func mergeConfig(config *server.Config, ctx *cli.Context) error { + // Don't parse the config if the user explicitly set it to "". + if path := ctx.GlobalString("config"); path != "" { + if err := config.UpdateFromFile(path); err != nil { + if ctx.GlobalIsSet("config") || !os.IsNotExist(err) { + return err + } + + // We don't error out if --config wasn't explicitly set and the + // default doesn't exist. But we will log a warning about it, so + // the user doesn't miss it. + logrus.Warnf("default configuration file does not exist: %s", server.CrioConfigPath) + } + } + + // Override options set with the CLI. + if ctx.GlobalIsSet("conmon") { + config.Conmon = ctx.GlobalString("conmon") + } + if ctx.GlobalIsSet("pause-command") { + config.PauseCommand = ctx.GlobalString("pause-command") + } + if ctx.GlobalIsSet("pause-image") { + config.PauseImage = ctx.GlobalString("pause-image") + } + if ctx.GlobalIsSet("signature-policy") { + config.SignaturePolicyPath = ctx.GlobalString("signature-policy") + } + if ctx.GlobalIsSet("root") { + config.Root = ctx.GlobalString("root") + } + if ctx.GlobalIsSet("runroot") { + config.RunRoot = ctx.GlobalString("runroot") + } + if ctx.GlobalIsSet("storage-driver") { + config.Storage = ctx.GlobalString("storage-driver") + } + if ctx.GlobalIsSet("storage-opt") { + config.StorageOptions = ctx.GlobalStringSlice("storage-opt") + } + if ctx.GlobalIsSet("file-locking") { + config.FileLocking = ctx.GlobalBool("file-locking") + } + if ctx.GlobalIsSet("insecure-registry") { + config.InsecureRegistries = ctx.GlobalStringSlice("insecure-registry") + } + if ctx.GlobalIsSet("registry") { + config.Registries = ctx.GlobalStringSlice("registry") + } + if ctx.GlobalIsSet("default-transport") { + config.DefaultTransport = ctx.GlobalString("default-transport") + } + if ctx.GlobalIsSet("listen") { + config.Listen = ctx.GlobalString("listen") + } + if ctx.GlobalIsSet("stream-address") { + config.StreamAddress = ctx.GlobalString("stream-address") + } + if ctx.GlobalIsSet("stream-port") { + config.StreamPort = ctx.GlobalString("stream-port") + } + if ctx.GlobalIsSet("runtime") { + config.Runtime = ctx.GlobalString("runtime") + } + if ctx.GlobalIsSet("selinux") { + config.SELinux = ctx.GlobalBool("selinux") + } + if ctx.GlobalIsSet("seccomp-profile") { + config.SeccompProfile = ctx.GlobalString("seccomp-profile") + } + if ctx.GlobalIsSet("apparmor-profile") { + config.ApparmorProfile = ctx.GlobalString("apparmor-profile") + } + if ctx.GlobalIsSet("cgroup-manager") { + config.CgroupManager = ctx.GlobalString("cgroup-manager") + } + if ctx.GlobalIsSet("hooks-dir-path") { + config.HooksDirPath = ctx.GlobalString("hooks-dir-path") + } + if ctx.GlobalIsSet("default-mounts") { + config.DefaultMounts = ctx.GlobalStringSlice("default-mounts") + } + if ctx.GlobalIsSet("pids-limit") { + config.PidsLimit = ctx.GlobalInt64("pids-limit") + } + if ctx.GlobalIsSet("log-size-max") { + config.LogSizeMax = ctx.GlobalInt64("log-size-max") + } + if ctx.GlobalIsSet("cni-config-dir") { + config.NetworkDir = ctx.GlobalString("cni-config-dir") + } + if ctx.GlobalIsSet("cni-plugin-dir") { + config.PluginDir = ctx.GlobalString("cni-plugin-dir") + } + if ctx.GlobalIsSet("image-volumes") { + config.ImageVolumes = libkpod.ImageVolumesType(ctx.GlobalString("image-volumes")) + } + return nil +} + +func catchShutdown(gserver *grpc.Server, sserver *server.Server, hserver *http.Server, signalled *bool) { + sig := make(chan os.Signal, 10) + signal.Notify(sig, unix.SIGINT, unix.SIGTERM) + go func() { + for s := range sig { + switch s { + case unix.SIGINT: + logrus.Debugf("Caught SIGINT") + case unix.SIGTERM: + logrus.Debugf("Caught SIGTERM") + default: + continue + } + *signalled = true + gserver.GracefulStop() + hserver.Shutdown(context.Background()) + // TODO(runcom): enable this after https://github.com/kubernetes/kubernetes/pull/51377 + //sserver.StopStreamServer() + sserver.StopExitMonitor() + if err := sserver.Shutdown(); err != nil { + logrus.Warnf("error shutting down main service %v", err) + } + return + } + }() +} + +func main() { + if reexec.Init() { + return + } + app := cli.NewApp() + + var v []string + v = append(v, version.Version) + if gitCommit != "" { + v = append(v, fmt.Sprintf("commit: %s", gitCommit)) + } + app.Name = "crio" + app.Usage = "crio server" + app.Version = strings.Join(v, "\n") + app.Metadata = map[string]interface{}{ + "config": server.DefaultConfig(), + } + + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config", + Value: server.CrioConfigPath, + Usage: "path to configuration file", + }, + cli.StringFlag{ + Name: "conmon", + Usage: "path to the conmon executable", + }, + cli.StringFlag{ + Name: "listen", + Usage: "path to crio socket", + }, + cli.StringFlag{ + Name: "stream-address", + Usage: "bind address for streaming socket", + }, + cli.StringFlag{ + Name: "stream-port", + Usage: "bind port for streaming socket (default: \"10010\")", + }, + cli.StringFlag{ + Name: "log", + Value: "", + Usage: "set the log file path where internal debug information is written", + }, + cli.StringFlag{ + Name: "log-format", + Value: "text", + Usage: "set the format used by logs ('text' (default), or 'json')", + }, + cli.StringFlag{ + Name: "log-level", + Usage: "log messages above specified level: debug, info (default), warn, error, fatal or panic", + }, + + cli.StringFlag{ + Name: "pause-command", + Usage: "name of the pause command in the pause image", + }, + cli.StringFlag{ + Name: "pause-image", + Usage: "name of the pause image", + }, + cli.StringFlag{ + Name: "signature-policy", + Usage: "path to signature policy file", + }, + cli.StringFlag{ + Name: "root", + Usage: "crio root dir", + }, + cli.StringFlag{ + Name: "runroot", + Usage: "crio state dir", + }, + cli.StringFlag{ + Name: "storage-driver", + Usage: "storage driver", + }, + cli.StringSliceFlag{ + Name: "storage-opt", + Usage: "storage driver option", + }, + cli.BoolFlag{ + Name: "file-locking", + Usage: "enable or disable file-based locking", + }, + cli.StringSliceFlag{ + Name: "insecure-registry", + Usage: "whether to disable TLS verification for the given registry", + }, + cli.StringSliceFlag{ + Name: "registry", + Usage: "registry to be prepended when pulling unqualified images, can be specified multiple times", + }, + cli.StringFlag{ + Name: "default-transport", + Usage: "default transport", + }, + cli.StringFlag{ + Name: "runtime", + Usage: "OCI runtime path", + }, + cli.StringFlag{ + Name: "seccomp-profile", + Usage: "default seccomp profile path", + }, + cli.StringFlag{ + Name: "apparmor-profile", + Usage: "default apparmor profile name (default: \"crio-default\")", + }, + cli.BoolFlag{ + Name: "selinux", + Usage: "enable selinux support", + }, + cli.StringFlag{ + Name: "cgroup-manager", + Usage: "cgroup manager (cgroupfs or systemd)", + }, + cli.Int64Flag{ + Name: "pids-limit", + Value: libkpod.DefaultPidsLimit, + Usage: "maximum number of processes allowed in a container", + }, + cli.Int64Flag{ + Name: "log-size-max", + Value: libkpod.DefaultLogSizeMax, + Usage: "maximum log size in bytes for a container", + }, + cli.StringFlag{ + Name: "cni-config-dir", + Usage: "CNI configuration files directory", + }, + cli.StringFlag{ + Name: "cni-plugin-dir", + Usage: "CNI plugin binaries directory", + }, + cli.StringFlag{ + Name: "image-volumes", + Value: string(libkpod.ImageVolumesMkdir), + Usage: "image volume handling ('mkdir', 'bind', or 'ignore')", + }, + cli.StringFlag{ + Name: "hooks-dir-path", + Usage: "set the OCI hooks directory path", + Value: libkpod.DefaultHooksDirPath, + Hidden: true, + }, + cli.StringSliceFlag{ + Name: "default-mounts", + Usage: "add one or more default mount paths in the form host:container", + Hidden: true, + }, + cli.BoolFlag{ + Name: "profile", + Usage: "enable pprof remote profiler on localhost:6060", + }, + cli.IntFlag{ + Name: "profile-port", + Value: 6060, + Usage: "port for the pprof profiler", + }, + cli.BoolFlag{ + Name: "enable-metrics", + Usage: "enable metrics endpoint for the servier on localhost:9090", + }, + cli.IntFlag{ + Name: "metrics-port", + Value: 9090, + Usage: "port for the metrics endpoint", + }, + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.FlagsByName(configCommand.Flags)) + + app.Commands = []cli.Command{ + configCommand, + } + + app.Before = func(c *cli.Context) error { + // Load the configuration file. + config := c.App.Metadata["config"].(*server.Config) + if err := mergeConfig(config, c); err != nil { + return err + } + + if err := validateConfig(config); err != nil { + return err + } + + cf := &logrus.TextFormatter{ + TimestampFormat: "2006-01-02 15:04:05.000000000Z07:00", + FullTimestamp: true, + } + + logrus.SetFormatter(cf) + + if loglevel := c.GlobalString("log-level"); loglevel != "" { + level, err := logrus.ParseLevel(loglevel) + if err != nil { + return err + } + + logrus.SetLevel(level) + } + + if path := c.GlobalString("log"); path != "" { + f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666) + if err != nil { + return err + } + logrus.SetOutput(f) + } + + switch c.GlobalString("log-format") { + case "text": + // retain logrus's default. + case "json": + logrus.SetFormatter(new(logrus.JSONFormatter)) + default: + return fmt.Errorf("unknown log-format %q", c.GlobalString("log-format")) + } + + return nil + } + + app.Action = func(c *cli.Context) error { + if c.GlobalBool("profile") { + profilePort := c.GlobalInt("profile-port") + profileEndpoint := fmt.Sprintf("localhost:%v", profilePort) + go func() { + http.ListenAndServe(profileEndpoint, nil) + }() + } + + args := c.Args() + if len(args) > 0 { + for _, command := range app.Commands { + if args[0] == command.Name { + break + } + } + return fmt.Errorf("command %q not supported", args[0]) + } + + config := c.App.Metadata["config"].(*server.Config) + + if !config.SELinux { + selinux.SetDisabled() + } + + if _, err := os.Stat(config.Runtime); os.IsNotExist(err) { + // path to runtime does not exist + return fmt.Errorf("invalid --runtime value %q", err) + } + + // Remove the socket if it already exists + if _, err := os.Stat(config.Listen); err == nil { + if err := os.Remove(config.Listen); err != nil { + logrus.Fatal(err) + } + } + lis, err := net.Listen("unix", config.Listen) + if err != nil { + logrus.Fatalf("failed to listen: %v", err) + } + + s := grpc.NewServer() + + service, err := server.New(config) + if err != nil { + logrus.Fatal(err) + } + + if c.GlobalBool("enable-metrics") { + metricsPort := c.GlobalInt("metrics-port") + me, err := service.CreateMetricsEndpoint() + if err != nil { + logrus.Fatalf("Failed to create metrics endpoint: %v", err) + } + l, err := net.Listen("tcp", fmt.Sprintf(":%v", metricsPort)) + if err != nil { + logrus.Fatalf("Failed to create listener for metrics: %v", err) + } + go func() { + if err := http.Serve(l, me); err != nil { + logrus.Fatalf("Failed to serve metrics endpoint: %v", err) + } + }() + } + + runtime.RegisterRuntimeServiceServer(s, service) + runtime.RegisterImageServiceServer(s, service) + + // after the daemon is done setting up we can notify systemd api + notifySystem() + + go func() { + service.StartExitMonitor() + }() + + m := cmux.New(lis) + grpcL := m.Match(cmux.HTTP2HeaderField("content-type", "application/grpc")) + httpL := m.Match(cmux.HTTP1Fast()) + + infoMux := service.GetInfoMux() + srv := &http.Server{ + Handler: infoMux, + ReadTimeout: 5 * time.Second, + } + + graceful := false + catchShutdown(s, service, srv, &graceful) + + go s.Serve(grpcL) + go srv.Serve(httpL) + + serverCloseCh := make(chan struct{}) + go func() { + defer close(serverCloseCh) + if err := m.Serve(); err != nil { + if graceful && strings.Contains(strings.ToLower(err.Error()), "use of closed network connection") { + err = nil + } else { + logrus.Errorf("Failed to serve grpc grpc request: %v", err) + } + } + }() + + // TODO(runcom): enable this after https://github.com/kubernetes/kubernetes/pull/51377 + //streamServerCloseCh := service.StreamingServerCloseChan() + serverExitMonitorCh := service.ExitMonitorCloseChan() + select { + // TODO(runcom): enable this after https://github.com/kubernetes/kubernetes/pull/51377 + //case <-streamServerCloseCh: + case <-serverExitMonitorCh: + case <-serverCloseCh: + } + + service.Shutdown() + + // TODO(runcom): enable this after https://github.com/kubernetes/kubernetes/pull/51377 + //<-streamServerCloseCh + //logrus.Debug("closed stream server") + <-serverExitMonitorCh + logrus.Debug("closed exit monitor") + <-serverCloseCh + logrus.Debug("closed main server") + + return nil + } + + if err := app.Run(os.Args); err != nil { + logrus.Fatal(err) + } +} |