diff --git a/README.md b/README.md index 45809ce9..3707be0a 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,14 @@ To enable Kubernetes, start Colima with `--kubernetes` flag. colima start --kubernetes ``` +By default, colima use [k3s](https://github.com/k3s-io/k3s/). + +Use [k0s](https://github.com/k0sproject/k0s). + +``` +colima start --kubernetes --k0s +``` + #### Interacting with Image Registry For Docker runtime, images built or pulled with Docker are accessible to Kubernetes. diff --git a/app/app.go b/app/app.go index c5bf5616..72916636 100644 --- a/app/app.go +++ b/app/app.go @@ -70,7 +70,11 @@ func (c colimaApp) startWithRuntime(conf config.Config) ([]environment.Container { runtime := conf.Runtime if kubernetesEnabled { - runtime += "+k3s" + if conf.Kubernetes.UseK0s { + runtime += "+k0s" + } else { + runtime += "+k3s" + } } log.Println("runtime:", runtime) } diff --git a/cmd/start.go b/cmd/start.go index 89b16145..64d69dcf 100644 --- a/cmd/start.go +++ b/cmd/start.go @@ -45,7 +45,9 @@ Run 'colima template' to set the default configurations or 'colima start --edit' " colima start --arch aarch64\n" + " colima start --dns 1.1.1.1 --dns 8.8.8.8\n" + " colima start --dns-host example.com=1.2.3.4\n" + - " colima start --kubernetes --k3s-arg=--disable=coredns,servicelb,traefik,local-storage,metrics-server", + " colima start --kubernetes --k3s-arg=--disable=coredns,servicelb,traefik,local-storage,metrics-server" + + " colima start --kubernetes --k0s" + + " colima start --kubernetes --k0s --kubernetes-version=v1.28.15+k0s.0", Args: cobra.MaximumNArgs(1), RunE: func(cmd *cobra.Command, args []string) error { app := newApp() @@ -109,10 +111,11 @@ Run 'colima template' to set the default configurations or 'colima start --edit' } const ( - defaultCPU = 2 - defaultMemory = 2 - defaultDisk = 100 - defaultKubernetesVersion = kubernetes.DefaultVersion + defaultCPU = 2 + defaultMemory = 2 + defaultDisk = 100 + defaultK3sVersion = kubernetes.DefaultK3sVersion + defaultK0sVersion = kubernetes.DefaultK0sVersion defaultMountTypeQEMU = "sshfs" defaultMountTypeVZ = "virtiofs" @@ -204,7 +207,8 @@ func init() { // k8s startCmd.Flags().BoolVarP(&startCmdArgs.Kubernetes.Enabled, "kubernetes", "k", false, "start with Kubernetes") startCmd.Flags().BoolVar(&startCmdArgs.Flags.LegacyKubernetes, "with-kubernetes", false, "start with Kubernetes") - startCmd.Flags().StringVar(&startCmdArgs.Kubernetes.Version, "kubernetes-version", defaultKubernetesVersion, "must match a k3s version https://github.com/k3s-io/k3s/releases") + startCmd.Flags().StringVar(&startCmdArgs.Kubernetes.Version, "kubernetes-version", "", "Kubernetes version to use") + startCmd.Flags().BoolVarP(&startCmdArgs.Kubernetes.UseK0s, "k0s", "0", false, "use k0s instead of k3s") startCmd.Flags().StringSliceVar(&startCmdArgs.Flags.LegacyKubernetesDisable, "kubernetes-disable", nil, "components to disable for k3s e.g. traefik,servicelb") startCmd.Flags().StringSliceVar(&startCmdArgs.Kubernetes.K3sArgs, "k3s-arg", defaultK3sArgs, "additional args to pass to k3s") startCmd.Flag("with-kubernetes").Hidden = true @@ -417,6 +421,9 @@ func prepareConfig(cmd *cobra.Command) { if !cmd.Flag("kubernetes-version").Changed { startCmdArgs.Kubernetes.Version = current.Kubernetes.Version } + if !cmd.Flag("k0s").Changed { + startCmdArgs.Kubernetes.UseK0s = current.Kubernetes.UseK0s + } if !cmd.Flag("k3s-arg").Changed { startCmdArgs.Kubernetes.K3sArgs = current.Kubernetes.K3sArgs } diff --git a/config/config.go b/config/config.go index 27ea4c16..4d629fee 100644 --- a/config/config.go +++ b/config/config.go @@ -70,6 +70,7 @@ type Kubernetes struct { Enabled bool `yaml:"enabled"` Version string `yaml:"version"` K3sArgs []string `yaml:"k3sArgs"` + UseK0s bool `yaml:"usek0s"` } // Network is VM network configuration diff --git a/environment/container/kubernetes/k0s.go b/environment/container/kubernetes/k0s.go new file mode 100644 index 00000000..583281a9 --- /dev/null +++ b/environment/container/kubernetes/k0s.go @@ -0,0 +1,42 @@ +package kubernetes + +import ( + "fmt" + + "github.com/abiosoft/colima/cli" + "github.com/abiosoft/colima/environment" +) + +func installK0s( + guest environment.GuestActions, + a *cli.ActiveCommandChain, + k0sVersion string, +) { + installK0sBinary(guest, a, k0sVersion) + installK0sCluster(guest, a) +} + +func installK0sBinary( + guest environment.GuestActions, + a *cli.ActiveCommandChain, + k0sVersion string, +) { + a.Add(func() error { + k0senv := fmt.Sprintf("K0S_VERSION=%s", k0sVersion) + cmd := fmt.Sprintf("curl --tlsv1.2 -sSf https://get.k0s.sh | sudo %s sh", k0senv) + if err := guest.Run("sh", "-c", cmd); err != nil { + return fmt.Errorf("failed to install k0s %w", err) + } + return nil + }) +} + +func installK0sCluster( + guest environment.GuestActions, + a *cli.ActiveCommandChain, +) { + // Initialize k0s with default configuration + a.Add(func() error { + return guest.Run("sudo", "k0s", "install", "controller", "--single") + }) +} diff --git a/environment/container/kubernetes/kubeconfig.go b/environment/container/kubernetes/kubeconfig.go index d72d77ba..9c615986 100644 --- a/environment/container/kubernetes/kubeconfig.go +++ b/environment/container/kubernetes/kubeconfig.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "path/filepath" + "regexp" "strings" "time" @@ -46,16 +47,31 @@ func (c kubernetesRuntime) provisionKubeconfig(ctx context.Context) error { // manipulate in VM and save to host a.Add(func() error { - kubeconfig, err := c.guest.Read("/etc/rancher/k3s/k3s.yaml") - if err != nil { - return fmt.Errorf("error fetching kubeconfig on guest: %w", err) - } - // replace name - kubeconfig = strings.ReplaceAll(kubeconfig, ": default", ": "+profile) + var err error + kubeconfig := "" + if c.config().UseK0s { + kubeconfig, err = c.guest.Read("/var/lib/k0s/pki/admin.conf") + if err != nil { + return fmt.Errorf("error fetching kubeconfig on guest: %w", err) + } + kubeconfig = strings.ReplaceAll(kubeconfig, ": Default", ": "+profile) - // replace IP - if ip != "" && ip != "127.0.0.1" { - kubeconfig = strings.ReplaceAll(kubeconfig, "https://127.0.0.1:", "https://"+ip+":") + if ip != "" && ip != "127.0.0.1" { + re := regexp.MustCompile(`https://[^:]+:`) + kubeconfig = re.ReplaceAllString(kubeconfig, "https://"+ip+":") + } + } else { + kubeconfig, err = c.guest.Read("/etc/rancher/k3s/k3s.yaml") + if err != nil { + return fmt.Errorf("error fetching kubeconfig on guest: %w", err) + } + // replace name + kubeconfig = strings.ReplaceAll(kubeconfig, ": default", ": "+profile) + + // replace IP + if ip != "" && ip != "127.0.0.1" { + kubeconfig = strings.ReplaceAll(kubeconfig, "https://127.0.0.1:", "https://"+ip+":") + } } // save on the host diff --git a/environment/container/kubernetes/kubernetes.go b/environment/container/kubernetes/kubernetes.go index d6b98320..90eee72b 100644 --- a/environment/container/kubernetes/kubernetes.go +++ b/environment/container/kubernetes/kubernetes.go @@ -17,10 +17,10 @@ import ( // Name is container runtime name const ( - Name = "kubernetes" - DefaultVersion = "v1.31.2+k3s1" - - ConfigKey = "kubernetes_config" + Name = "kubernetes" + DefaultK3sVersion = "v1.31.2+k3s1" + DefaultK0sVersion = "v1.31.3+k0s.0" + ConfigKey = "kubernetes_config" ) func newRuntime(host environment.HostActions, guest environment.GuestActions) environment.Container { @@ -47,21 +47,35 @@ func (c kubernetesRuntime) Name() string { return Name } -func (c kubernetesRuntime) isInstalled() bool { +func (c kubernetesRuntime) isInstalled(useK0s bool) bool { + if useK0s { + return c.guest.RunQuiet("command", "-v", "k0s") == nil + } // it is installed if uninstall script is present. return c.guest.RunQuiet("command", "-v", "k3s-uninstall.sh") == nil } -func (c kubernetesRuntime) isVersionInstalled(version string) bool { - // validate version change via cli flag/config. - out, err := c.guest.RunOutput("k3s", "--version") - if err != nil { - return false +func (c kubernetesRuntime) isVersionInstalled(version string, useK0s bool) bool { + if useK0s { + out, err := c.guest.RunOutput("k0s", "version") + if err != nil { + return false + } + return strings.Contains(out, version) + } else { + // validate version change via cli flag/config. + out, err := c.guest.RunOutput("k3s", "--version") + if err != nil { + return false + } + return strings.Contains(out, version) } - return strings.Contains(out, version) } func (c kubernetesRuntime) Running(context.Context) bool { + if c.config().UseK0s { + return c.guest.RunQuiet("sudo", "service", "k0scontroller", "status") == nil + } return c.guest.RunQuiet("sudo", "service", "k3s", "status") == nil } @@ -70,10 +84,20 @@ func (c kubernetesRuntime) runtime() string { } func (c kubernetesRuntime) config() config.Kubernetes { - conf := config.Kubernetes{Version: DefaultVersion} + conf := config.Kubernetes{} if b := c.guest.Get(ConfigKey); b != "" { _ = json.Unmarshal([]byte(b), &conf) } + + // Set default version based on UseK0s flag + if conf.Version == "" { + if conf.UseK0s { + conf.Version = DefaultK0sVersion + } else { + conf.Version = DefaultK3sVersion + } + } + return conf } @@ -104,16 +128,17 @@ func (c *kubernetesRuntime) Provision(ctx context.Context) error { conf = c.config() } - if c.isVersionInstalled(conf.Version) { + if c.isVersionInstalled(conf.Version, conf.UseK0s) { // runtime has changed, ensure the required images are in the registry if currentRuntime := c.runtime(); currentRuntime != "" && currentRuntime != runtime { - a.Stagef("changing runtime to %s", runtime) - installK3sCache(c.host, c.guest, a, log, runtime, conf.Version) + if !conf.UseK0s { + a.Stagef("changing runtime to %s", runtime) + // other settings may have changed e.g. ingress + installK3sCache(c.host, c.guest, a, log, runtime, conf.Version) + } } - // other settings may have changed e.g. ingress - installK3sCluster(c.host, c.guest, a, runtime, conf.Version, conf.K3sArgs) } else { - if c.isInstalled() { + if c.isInstalled(conf.UseK0s) { a.Stagef("version changed to %s, downloading and installing", conf.Version) } else { if ok { @@ -122,13 +147,19 @@ func (c *kubernetesRuntime) Provision(ctx context.Context) error { a.Stage("installing") } } - installK3s(c.host, c.guest, a, log, runtime, conf.Version, conf.K3sArgs) + if conf.UseK0s { + installK0s(c.guest, a, conf.Version) + } else { + installK3s(c.host, c.guest, a, log, runtime, conf.Version, conf.K3sArgs) + } } // this needs to happen on each startup { - // cni is used by both cri-dockerd and containerd - installCniConfig(c.guest, a) + if !conf.UseK0s { + // cni is used by both cri-dockerd and containerd + installCniConfig(c.guest, a) + } } // provision successful, now we can persist the version @@ -145,12 +176,21 @@ func (c kubernetesRuntime) Start(ctx context.Context) error { return nil } - a.Add(func() error { - return c.guest.Run("sudo", "service", "k3s", "start") - }) - a.Retry("", time.Second*2, 10, func(int) error { - return c.guest.RunQuiet("kubectl", "cluster-info") - }) + if c.config().UseK0s { + a.Add(func() error { + return c.guest.Run("sudo", "systemctl", "start", "k0scontroller") + }) + a.Retry("", time.Second*2, 10, func(int) error { + return c.guest.RunQuiet("sudo", "k0s", "kubectl", "cluster-info") + }) + } else { + a.Add(func() error { + return c.guest.Run("sudo", "service", "k3s", "start") + }) + a.Retry("", time.Second*2, 10, func(int) error { + return c.guest.RunQuiet("kubectl", "cluster-info") + }) + } if err := a.Exec(); err != nil { return err @@ -238,15 +278,24 @@ func (c kubernetesRuntime) runningContainerIDs() string { func (c kubernetesRuntime) Teardown(ctx context.Context) error { a := c.Init(ctx) - if c.isInstalled() { + if c.isInstalled(c.config().UseK0s) { a.Add(func() error { - return c.guest.Run("k3s-uninstall.sh") + if c.config().UseK0s { + if err := c.guest.Run("sudo", "systemctl", "stop", "k0scontroller"); err != nil { + return fmt.Errorf("error stopping k0scontroller services: %w", err) + } + return c.guest.Run("sudo", "k0s", "reset") + } else { + return c.guest.Run("k3s-uninstall.sh") + } }) } - // k3s is buggy with external containerd for now - // cleanup is manual - a.Add(c.deleteAllContainers) + if !c.config().UseK0s { + // k3s is buggy with external containerd for now + // cleanup is manual + a.Add(c.deleteAllContainers) + } c.teardownKubeconfig(a)