diff --git a/.github/workflows/k8s.yml b/.github/workflows/k8s.yml index ff305831..c7d8963b 100644 --- a/.github/workflows/k8s.yml +++ b/.github/workflows/k8s.yml @@ -63,7 +63,7 @@ jobs: - name: Run cloud-provider-kind run: | make - nohup bin/cloud-provider-kind > ./_artifacts/ccm-kind.log 2>&1 & + nohup bin/cloud-provider-kind -v 2 --log-dir /_artifacts/loadbalancers > ./_artifacts/ccm-kind.log 2>&1 & - name: Create multi node cluster run: | @@ -175,6 +175,7 @@ jobs: run: | /usr/local/bin/kind export logs --name ${{ env.KIND_CLUSTER_NAME}} --loglevel=debug ./_artifacts/logs cp ./_artifacts/ccm-kind.log ./_artifacts/logs + cp ./_artifacts/loadbalancers/* ./_artifacts/logs - name: Upload logs if: always() @@ -187,4 +188,4 @@ jobs: uses: mikepenz/action-junit-report@v2 if: always() with: - report_paths: './_artifacts/*.xml' \ No newline at end of file + report_paths: './_artifacts/*.xml' diff --git a/cmd/app.go b/cmd/app.go index f9fe5fa8..635680b1 100644 --- a/cmd/app.go +++ b/cmd/app.go @@ -4,24 +4,29 @@ import ( "context" "flag" "fmt" - "log" "os" "os/signal" "strconv" "syscall" "k8s.io/component-base/logs" + "k8s.io/klog/v2" + "sigs.k8s.io/cloud-provider-kind/pkg/config" "sigs.k8s.io/cloud-provider-kind/pkg/controller" kindcmd "sigs.k8s.io/kind/pkg/cmd" ) var ( - flagV int + flagV int + enableLogDump bool + logDumpDir string ) func init() { flag.IntVar(&flagV, "v", 2, "Verbosity level") + flag.BoolVar(&enableLogDump, "log-dump", false, "store logs toa temporal directory or to the directory specified with log-dir flag") + flag.StringVar(&logDumpDir, "log-dir", "", "store logs to the specified directory") flag.Usage = func() { fmt.Fprint(os.Stderr, "Usage: cloud-provider-kind [options]\n\n") @@ -33,7 +38,7 @@ func Main() { // Parse command line flags and arguments flag.Parse() flag.VisitAll(func(flag *flag.Flag) { - log.Printf("FLAG: --%s=%q", flag.Name, flag.Value) + klog.Infof("FLAG: --%s=%q", flag.Name, flag.Value) }) // trap Ctrl+C and call cancel on the context @@ -51,14 +56,14 @@ func Main() { go func() { select { case <-signalCh: - log.Printf("Exiting: received signal") + klog.Infof("Exiting: received signal") cancel() case <-ctx.Done(): // cleanup } }() - // initializet loggers, kind logger and klog + // initialize loggers, kind logger and klog logger := kindcmd.NewLogger() type verboser interface { SetVerbosity(int) @@ -72,5 +77,26 @@ func Main() { if err != nil { logger.Errorf("error setting klog verbosity to %d : %v", flagV, err) } + + // initialize log directory + if enableLogDump { + if logDumpDir == "" { + dir, err := os.MkdirTemp(os.TempDir(), "kind-provider-") + if err != nil { + klog.Fatal(err) + } + logDumpDir = dir + } + + if _, err := os.Stat(logDumpDir); os.IsNotExist(err) { + if err := os.MkdirAll(logDumpDir, 0755); err != nil { + klog.Fatalf("directory %s does not exist", logDumpDir) + } + } + config.DefaultConfig.EnableLogDump = true + config.DefaultConfig.LogDir = logDumpDir + klog.Infof("**** Dumping load balancers logs to: %s", logDumpDir) + } + controller.New(logger).Run(ctx) } diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 00000000..ce45b1c2 --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,10 @@ +package config + +// DefaultConfig is a global variable that is initialized at startup with the flags options. +// It can not be modified after that. +var DefaultConfig = &Config{} + +type Config struct { + EnableLogDump bool + LogDir string +} diff --git a/pkg/container/container.go b/pkg/container/container.go index 9a5e8e9b..49c694a2 100644 --- a/pkg/container/container.go +++ b/pkg/container/container.go @@ -4,7 +4,9 @@ import ( "encoding/json" "fmt" "io" + "os" "os/exec" + "path" "strings" "k8s.io/klog/v2" @@ -43,6 +45,31 @@ func init() { } } +func Logs(name string, w io.Writer) error { + cmd := exec.Command(containerRuntime, []string{"logs", name}...) + cmd.Stderr = w + cmd.Stdout = w + err := cmd.Run() + if err != nil { + return fmt.Errorf("failed to get container logs: %w", err) + } + return nil +} + +func LogDump(containerName string, dir string) error { + f, err := os.Create(path.Join(dir, containerName+".log")) + if err != nil { + return err + } + defer f.Close() + + err = Logs(containerName, f) + if err != nil { + return err + } + return nil +} + func Create(name string, args []string) error { if err := exec.Command(containerRuntime, append([]string{"run", "--name", name}, args...)...).Run(); err != nil { return err diff --git a/pkg/loadbalancer/server.go b/pkg/loadbalancer/server.go index 6c4abbbb..89d5fb41 100644 --- a/pkg/loadbalancer/server.go +++ b/pkg/loadbalancer/server.go @@ -15,6 +15,7 @@ import ( cloudprovider "k8s.io/cloud-provider" "k8s.io/klog/v2" "k8s.io/utils/ptr" + "sigs.k8s.io/cloud-provider-kind/pkg/config" "sigs.k8s.io/cloud-provider-kind/pkg/constants" "sigs.k8s.io/cloud-provider-kind/pkg/container" ) @@ -90,6 +91,7 @@ func (s *Server) GetLoadBalancerName(ctx context.Context, clusterName string, se func (s *Server) EnsureLoadBalancer(ctx context.Context, clusterName string, service *v1.Service, nodes []*v1.Node) (*v1.LoadBalancerStatus, error) { name := loadBalancerName(clusterName, service) if !container.IsRunning(name) { + klog.Infof("container %s for loadbalancer is not running", name) if container.Exist(name) { err := container.Delete(name) if err != nil { @@ -143,6 +145,13 @@ func (s *Server) EnsureLoadBalancerDeleted(ctx context.Context, clusterName stri if s.tunnelManager != nil { err1 = s.tunnelManager.removeTunnels(containerName) } + // Before deleting the load balancer store the logs if required + if config.DefaultConfig.EnableLogDump { + klog.V(2).Infof("storing logs for loadbalancer %s", containerName) + if err := container.LogDump(containerName, config.DefaultConfig.LogDir); err != nil { + klog.Infof("error trying to store logs for load balancer %s : %v", containerName, err) + } + } err2 = container.Delete(containerName) return errors.Join(err1, err2) }