diff --git a/pkg/operator/logging/logging.go b/pkg/operator/logging/logging.go index 4bf5ad9619..36edad94b5 100644 --- a/pkg/operator/logging/logging.go +++ b/pkg/operator/logging/logging.go @@ -18,10 +18,7 @@ package logging import ( "context" - "encoding/json" - "fmt" - "log" - "os" + "strings" "github.com/go-logr/logr" "github.com/go-logr/zapr" @@ -35,11 +32,6 @@ import ( "sigs.k8s.io/karpenter/pkg/operator/options" ) -const ( - loggerCfgDir = "/etc/karpenter/logging" - loggerCfgFilePath = loggerCfgDir + "/zap-logger-config" -) - // NopLogger is used to throw away logs when we don't actually want to log in // certain portions of the code since logging would be too noisy var NopLogger = zapr.NewLogger(zap.NewNop()) @@ -75,18 +67,14 @@ func DefaultZapConfig(ctx context.Context, component string) zap.Config { EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, }, - OutputPaths: []string{"stdout"}, - ErrorOutputPaths: []string{"stderr"}, + OutputPaths: strings.Split(options.FromContext(ctx).LogOutputPaths, ","), + ErrorOutputPaths: strings.Split(options.FromContext(ctx).LogErrorOutputPaths, ","), } } // NewLogger returns a configured *zap.SugaredLogger func NewLogger(ctx context.Context, component string) *zap.Logger { - if logger := loggerFromFile(ctx, component); logger != nil { - logger.Debug(fmt.Sprintf("loaded log configuration from file %q", loggerCfgFilePath)) - return logger - } - return defaultLogger(ctx, component) + return WithCommit(lo.Must(DefaultZapConfig(ctx, component).Build())).Named(component) } func WithCommit(logger *zap.Logger) *zap.Logger { @@ -99,31 +87,6 @@ func WithCommit(logger *zap.Logger) *zap.Logger { return logger.With(zap.String(logkey.Commit, revision)) } -func defaultLogger(ctx context.Context, component string) *zap.Logger { - return WithCommit(lo.Must(DefaultZapConfig(ctx, component).Build())).Named(component) -} - -func loggerFromFile(ctx context.Context, component string) *zap.Logger { - raw, err := os.ReadFile(loggerCfgFilePath) - if err != nil { - if os.IsNotExist(err) { - return nil - } - log.Fatalf("retrieving logging configuration file from %q", loggerCfgFilePath) - } - cfg := DefaultZapConfig(ctx, component) - lo.Must0(json.Unmarshal(raw, &cfg)) - - raw, err = os.ReadFile(loggerCfgDir + fmt.Sprintf("/loglevel.%s", component)) - if err != nil && !os.IsNotExist(err) { - log.Fatalf("retrieving logging controller log level file from %q", loggerCfgDir+fmt.Sprintf("/loglevel.%s", component)) - } - if raw != nil { - cfg.Level = lo.Must(zap.ParseAtomicLevel(string(raw))) - } - return WithCommit(lo.Must(cfg.Build())).Named(component) -} - type ignoreDebugEventsSink struct { name string sink logr.LogSink diff --git a/pkg/operator/options/options.go b/pkg/operator/options/options.go index 7785dfe42b..262733b439 100644 --- a/pkg/operator/options/options.go +++ b/pkg/operator/options/options.go @@ -58,6 +58,8 @@ type Options struct { DisableLeaderElection bool MemoryLimit int64 LogLevel string + LogOutputPaths string + LogErrorOutputPaths string BatchMaxDuration time.Duration BatchIdleDuration time.Duration FeatureGates FeatureGates @@ -93,6 +95,8 @@ func (o *Options) AddFlags(fs *FlagSet) { fs.BoolVarWithEnv(&o.DisableLeaderElection, "disable-leader-election", "DISABLE_LEADER_ELECTION", false, "Disable the leader election client before executing the main loop. Disable when running replicated components for high availability is not desired.") fs.Int64Var(&o.MemoryLimit, "memory-limit", env.WithDefaultInt64("MEMORY_LIMIT", -1), "Memory limit on the container running the controller. The GC soft memory limit is set to 90% of this value.") fs.StringVar(&o.LogLevel, "log-level", env.WithDefaultString("LOG_LEVEL", "info"), "Log verbosity level. Can be one of 'debug', 'info', or 'error'") + fs.StringVar(&o.LogOutputPaths, "log-output-paths", env.WithDefaultString("LOG_OUTPUT_PATHS", "stdout"), "Optional comma separated paths for directing log output") + fs.StringVar(&o.LogErrorOutputPaths, "log-error-output-paths", env.WithDefaultString("LOG_ERROR_OUTPUT_PATHS", "stderr"), "Optional comma separated paths for logging error output") fs.DurationVar(&o.BatchMaxDuration, "batch-max-duration", env.WithDefaultDuration("BATCH_MAX_DURATION", 10*time.Second), "The maximum length of a batch window. The longer this is, the more pods we can consider for provisioning at one time which usually results in fewer but larger nodes.") fs.DurationVar(&o.BatchIdleDuration, "batch-idle-duration", env.WithDefaultDuration("BATCH_IDLE_DURATION", time.Second), "The maximum amount of time with no new pending pods that if exceeded ends the current batching window. If pods arrive faster than this time, the batching window will be extended up to the maxDuration. If they arrive slower, the pods will be batched separately.") fs.StringVar(&o.FeatureGates.inputStr, "feature-gates", env.WithDefaultString("FEATURE_GATES", "SpotToSpotConsolidation=false"), "Optional features can be enabled / disabled using feature gates. Current options are: SpotToSpotConsolidation") diff --git a/pkg/operator/options/suite_test.go b/pkg/operator/options/suite_test.go index f5afb78962..5dca0ba426 100644 --- a/pkg/operator/options/suite_test.go +++ b/pkg/operator/options/suite_test.go @@ -57,6 +57,8 @@ var _ = Describe("Options", func() { "DISABLE_LEADER_ELECTION", "MEMORY_LIMIT", "LOG_LEVEL", + "LOG_OUTPUT_PATHS", + "LOG_ERROR_OUTPUT_PATHS", "BATCH_MAX_DURATION", "BATCH_IDLE_DURATION", "FEATURE_GATES", @@ -108,6 +110,8 @@ var _ = Describe("Options", func() { DisableLeaderElection: lo.ToPtr(false), MemoryLimit: lo.ToPtr[int64](-1), LogLevel: lo.ToPtr("info"), + LogOutputPaths: lo.ToPtr("stdout"), + LogErrorOutputPaths: lo.ToPtr("stderr"), BatchMaxDuration: lo.ToPtr(10 * time.Second), BatchIdleDuration: lo.ToPtr(time.Second), FeatureGates: test.FeatureGates{ @@ -117,6 +121,8 @@ var _ = Describe("Options", func() { }) It("shouldn't overwrite CLI flags with environment variables", func() { + os.Setenv("LOG_OUTPUT_PATHS", "stdout") + os.Setenv("LOG_ERROR_OUTPUT_PATHS", "stderr") err := opts.Parse( fs, "--karpenter-service", "cli", @@ -131,6 +137,8 @@ var _ = Describe("Options", func() { "--disable-leader-election=true", "--memory-limit", "0", "--log-level", "debug", + "--log-output-paths", "/etc/k8s/test", + "--log-error-output-paths", "/etc/k8s/testerror", "--batch-max-duration", "5s", "--batch-idle-duration", "5s", "--feature-gates", "SpotToSpotConsolidation=true", @@ -149,6 +157,8 @@ var _ = Describe("Options", func() { DisableLeaderElection: lo.ToPtr(true), MemoryLimit: lo.ToPtr[int64](0), LogLevel: lo.ToPtr("debug"), + LogOutputPaths: lo.ToPtr("/etc/k8s/test"), + LogErrorOutputPaths: lo.ToPtr("/etc/k8s/testerror"), BatchMaxDuration: lo.ToPtr(5 * time.Second), BatchIdleDuration: lo.ToPtr(5 * time.Second), FeatureGates: test.FeatureGates{ @@ -170,6 +180,8 @@ var _ = Describe("Options", func() { os.Setenv("DISABLE_LEADER_ELECTION", "true") os.Setenv("MEMORY_LIMIT", "0") os.Setenv("LOG_LEVEL", "debug") + os.Setenv("LOG_OUTPUT_PATHS", "/etc/k8s/test") + os.Setenv("LOG_ERROR_OUTPUT_PATHS", "/etc/k8s/testerror") os.Setenv("BATCH_MAX_DURATION", "5s") os.Setenv("BATCH_IDLE_DURATION", "5s") os.Setenv("FEATURE_GATES", "SpotToSpotConsolidation=true") @@ -192,6 +204,8 @@ var _ = Describe("Options", func() { DisableLeaderElection: lo.ToPtr(true), MemoryLimit: lo.ToPtr[int64](0), LogLevel: lo.ToPtr("debug"), + LogOutputPaths: lo.ToPtr("/etc/k8s/test"), + LogErrorOutputPaths: lo.ToPtr("/etc/k8s/testerror"), BatchMaxDuration: lo.ToPtr(5 * time.Second), BatchIdleDuration: lo.ToPtr(5 * time.Second), FeatureGates: test.FeatureGates{ @@ -221,12 +235,13 @@ var _ = Describe("Options", func() { err := opts.Parse( fs, "--karpenter-service", "cli", - "--disable-webhook", + "--log-output-paths", "/etc/k8s/test", + "--log-error-output-paths", "/etc/k8s/testerror", ) Expect(err).To(BeNil()) expectOptionsMatch(opts, test.Options(test.OptionsFields{ ServiceName: lo.ToPtr("cli"), - DisableWebhook: lo.ToPtr(true), + DisableWebhook: lo.ToPtr(false), WebhookPort: lo.ToPtr(0), MetricsPort: lo.ToPtr(0), WebhookMetricsPort: lo.ToPtr(0), @@ -237,6 +252,8 @@ var _ = Describe("Options", func() { DisableLeaderElection: lo.ToPtr(true), MemoryLimit: lo.ToPtr[int64](0), LogLevel: lo.ToPtr("debug"), + LogOutputPaths: lo.ToPtr("/etc/k8s/test"), + LogErrorOutputPaths: lo.ToPtr("/etc/k8s/testerror"), BatchMaxDuration: lo.ToPtr(5 * time.Second), BatchIdleDuration: lo.ToPtr(5 * time.Second), FeatureGates: test.FeatureGates{ @@ -297,6 +314,8 @@ func expectOptionsMatch(optsA, optsB *options.Options) { Expect(optsA.DisableLeaderElection).To(Equal(optsB.DisableLeaderElection)) Expect(optsA.MemoryLimit).To(Equal(optsB.MemoryLimit)) Expect(optsA.LogLevel).To(Equal(optsB.LogLevel)) + Expect(optsA.LogOutputPaths).To(Equal(optsB.LogOutputPaths)) + Expect(optsA.LogErrorOutputPaths).To(Equal(optsB.LogErrorOutputPaths)) Expect(optsA.BatchMaxDuration).To(Equal(optsB.BatchMaxDuration)) Expect(optsA.BatchIdleDuration).To(Equal(optsB.BatchIdleDuration)) Expect(optsA.FeatureGates.SpotToSpotConsolidation).To(Equal(optsB.FeatureGates.SpotToSpotConsolidation)) diff --git a/pkg/test/options.go b/pkg/test/options.go index 6f63eb87d5..0c0a14f8f9 100644 --- a/pkg/test/options.go +++ b/pkg/test/options.go @@ -40,6 +40,8 @@ type OptionsFields struct { DisableLeaderElection *bool MemoryLimit *int64 LogLevel *string + LogOutputPaths *string + LogErrorOutputPaths *string BatchMaxDuration *time.Duration BatchIdleDuration *time.Duration FeatureGates FeatureGates @@ -70,6 +72,8 @@ func Options(overrides ...OptionsFields) *options.Options { DisableLeaderElection: lo.FromPtrOr(opts.DisableLeaderElection, false), MemoryLimit: lo.FromPtrOr(opts.MemoryLimit, -1), LogLevel: lo.FromPtrOr(opts.LogLevel, ""), + LogOutputPaths: lo.FromPtrOr(opts.LogOutputPaths, "stdout"), + LogErrorOutputPaths: lo.FromPtrOr(opts.LogErrorOutputPaths, "stderr"), BatchMaxDuration: lo.FromPtrOr(opts.BatchMaxDuration, 10*time.Second), BatchIdleDuration: lo.FromPtrOr(opts.BatchIdleDuration, time.Second), FeatureGates: options.FeatureGates{