* Copyright (c) 2025 Bocloud Technologies Co., Ltd.
* installer is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain n copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
******************************************************************/
package k3s
import (
"bytes"
"context"
_ "embed"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"time"
"github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/mount"
"github.com/docker/go-connections/nat"
bkecommon "gopkg.openfuyao.cn/cluster-api-provider-bke/common"
configinit "gopkg.openfuyao.cn/cluster-api-provider-bke/common/cluster/initialize"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
econd "gopkg.openfuyao.cn/bkeadm/pkg/executor/containerd"
"gopkg.openfuyao.cn/bkeadm/pkg/executor/docker"
"gopkg.openfuyao.cn/bkeadm/pkg/executor/exec"
"gopkg.openfuyao.cn/bkeadm/pkg/executor/k8s"
"gopkg.openfuyao.cn/bkeadm/pkg/global"
"gopkg.openfuyao.cn/bkeadm/utils"
"gopkg.openfuyao.cn/bkeadm/utils/log"
)
var (
registries string
k3sCertScript string
k3sCoreScript string
k3sImage = utils.DefaultLocalK3sRegistry
k3sPause = utils.DefaultK3sPause
k3sCoredns string
)
const (
DefaultK3sDataDir = "/var/lib/rancher/k3s"
waitConfigInterval = 2
waitInterval = 5
waitTimeout = 300
k3sKubeconfigPath = "/etc/rancher/k3s/k3s.yaml"
kubeconfigReadRetry = 5
kubeconfigReadDelay = 2
k8sClientRetryCount = 10
k8sClientRetryDelay = 6
nodeReadyRetryCount = 10
nodeReadyRetryDelay = 3
)
type Config struct {
OnlineImage string
OtherRepo string
OtherRepoIP string
HostIP string
ImageRepo string
ImageRepoPort string
KubernetesPort string
}
func EnsureDirExists(dir string) error {
if !utils.Exists(dir) {
err := os.MkdirAll(dir, utils.DefaultDirPermission)
if err != nil {
return err
}
}
return nil
}
func readKubeconfig() ([]byte, error) {
var result []byte
var err error
for i := 0; i < kubeconfigReadRetry; i++ {
result, err = os.ReadFile(k3sKubeconfigPath)
if err != nil {
time.Sleep(kubeconfigReadDelay * time.Second)
log.Warn("Failed to get kubeconfig, retrying...")
continue
}
break
}
if len(result) == 0 {
return nil, errors.New("failed to get k3s kubeconfig file")
}
return result, nil
}
func processKubeconfig(hostIP, kubernetesPort string, kubeconfigData []byte) (string, error) {
home, err := os.UserHomeDir()
if err != nil {
return "", fmt.Errorf("failed to get user home directory: %w", err)
}
kubeDir := fmt.Sprintf("%s/.kube", home)
if err := os.MkdirAll(kubeDir, utils.DefaultDirPermission); err != nil {
return "", fmt.Errorf("failed to create kube directory: %w", err)
}
kubeconfigPath := fmt.Sprintf("%s/config", kubeDir)
kubeconfigContent := strings.Replace(
string(kubeconfigData), "127.0.0.1:36443", fmt.Sprintf("%s:%s", hostIP, kubernetesPort), 1)
if err := os.WriteFile(kubeconfigPath, []byte(kubeconfigContent), utils.SecureFilePermission); err != nil {
return "", err
}
if err := os.Remove(k3sKubeconfigPath); err != nil {
log.Warnf("failed to remove original k3s.yaml: %v", err)
}
if err := os.WriteFile(k3sKubeconfigPath, []byte(kubeconfigContent), utils.DefaultFilePermission); err != nil {
log.Warnf("failed to write k3s.yaml, please run export KUBECONFIG=%s", kubeconfigPath)
}
return kubeconfigContent, nil
}
func waitForK8sClient(kubeconfigPath string) error {
log.Info("Waiting for the cluster to start...")
var err error
for i := 0; i < k8sClientRetryCount; i++ {
global.K8s, err = k8s.NewKubernetesClient(kubeconfigPath)
if err != nil {
time.Sleep(k8sClientRetryDelay * time.Second)
continue
}
break
}
if global.K8s == nil {
return errors.New("failed to connect to kubernetes")
}
return nil
}
func waitForNodeReady() error {
log.Info("Waiting for cluster Ready...")
clientset := global.K8s.GetClient()
for i := 0; i < nodeReadyRetryCount; i++ {
node, err := clientset.CoreV1().Nodes().Get(context.Background(), utils.LocalKubernetesName, metav1.GetOptions{})
if err != nil {
time.Sleep(nodeReadyRetryDelay * time.Second)
continue
}
if len(node.Spec.Taints) > 1 {
time.Sleep(nodeReadyRetryDelay * time.Second)
continue
}
_, err = clientset.CoreV1().Namespaces().Get(context.Background(), "kube-system", metav1.GetOptions{})
if err != nil {
time.Sleep(nodeReadyRetryDelay * time.Second)
continue
}
break
}
return nil
}
func createKubeconfigSecret(kubeconfigContent string) error {
clientset := global.K8s.GetClient()
_, err := clientset.CoreV1().Secrets(metav1.NamespaceSystem).Create(context.Background(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Name: "localkubeconfig",
Namespace: metav1.NamespaceSystem,
},
StringData: map[string]string{
"config": kubeconfigContent,
},
}, metav1.CreateOptions{})
return err
}
func setupKubeconfigAndWaitCluster(hostIP, kubernetesPort string) error {
kubeconfigData, err := readKubeconfig()
if err != nil {
return err
}
kubeconfigContent, err := processKubeconfig(hostIP, kubernetesPort, kubeconfigData)
if err != nil {
return err
}
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("failed to get user home directory: %w", err)
}
kubeconfigPath := fmt.Sprintf("%s/.kube/config", home)
if err := waitForK8sClient(kubeconfigPath); err != nil {
return err
}
if err := waitForNodeReady(); err != nil {
return err
}
if err := createKubeconfigSecret(kubeconfigContent); err != nil {
return err
}
log.Info("The local Kubernetes startup succeeded")
return nil
}
func prepareK3sImages(onlineImage, otherRepo, imageRepoPort, imageRepo, localImage string) {
localK3sImagePath := fmt.Sprintf("127.0.0.1:%s/%s/%s", imageRepoPort, bkecommon.ImageRegistryKubernetes, utils.DefaultLocalK3sRegistry)
localK3sPausePath := fmt.Sprintf("%s:443/%s/%s", imageRepo, bkecommon.ImageRegistryKubernetes, utils.DefaultK3sPause)
if localImage != "" {
k3sImage = localK3sImagePath
k3sPause = localK3sPausePath
} else if otherRepo != "" {
k3sImage = fmt.Sprintf("%s%s", otherRepo, utils.DefaultLocalK3sRegistry)
k3sPause = fmt.Sprintf("%s%s", otherRepo, utils.DefaultK3sPause)
} else if onlineImage == "" {
k3sImage = localK3sImagePath
k3sPause = localK3sPausePath
} else {
k3sImage = fmt.Sprintf("%s/%s", utils.DefaultThirdMirror, utils.DefaultLocalK3sRegistry)
k3sPause = fmt.Sprintf("%s/%s", utils.DefaultThirdMirror, utils.DefaultK3sPause)
}
}
func getImageRepoIP(otherRepo, otherRepoIp, hostIP, imageRepo, localImagePath string) (string, string) {
repo := fmt.Sprintf("%s:%s", imageRepo, "443")
imageRepoIP := hostIP
if otherRepo != "" && strings.Contains(otherRepo, imageRepo) && localImagePath == "" {
imageRepoIP = otherRepoIp
repo = strings.Split(otherRepo, "/")[0]
} else {
imageRepoInfo, err := econd.ContainerInspect(utils.LocalImageRegistryName)
if err == nil && len(imageRepoInfo.Id) > 0 {
imageRepoIP = imageRepoInfo.NetworkSettings.IPAddress
}
}
return repo, imageRepoIP
}
func getImageRepoIPWithDocker(otherRepo, otherRepoIp, hostIP, imageRepo string) (string, string) {
repo := fmt.Sprintf("%s:%s", imageRepo, "443")
imageRepoIP := hostIP
if otherRepo != "" && strings.Contains(otherRepo, configinit.DefaultImageRepo) {
imageRepoIP = otherRepoIp
repo = strings.Split(otherRepo, "/")[0]
} else {
client := global.Docker.GetClient()
imageRepoInfo, err := client.ContainerInspect(context.Background(), utils.LocalImageRegistryName)
if err == nil {
imageRepoIP = imageRepoInfo.NetworkSettings.IPAddress
}
}
return repo, imageRepoIP
}
func generateRegistriesConfig(repo, k3sConfig string) error {
tmpl0, err := template.New("registries").Parse(registries)
if err != nil {
return err
}
buf0 := new(bytes.Buffer)
if err = tmpl0.Execute(buf0, map[string]string{"repo": repo}); err != nil {
return err
}
err = os.Remove(k3sConfig + "/registries.yaml")
if err != nil && !os.IsNotExist(err) {
log.Warnf("failed to remove original registries.yaml: %v", err)
}
return os.WriteFile(k3sConfig+"/registries.yaml", buf0.Bytes(), utils.DefaultFilePermission)
}
func StartK3sWithContainerd(cfg Config, localImage string) error {
if isKubernetesAvailable() {
log.Info("A kubernetes cluster already exists.")
return nil
}
prepareK3sImages(cfg.OnlineImage, cfg.OtherRepo, cfg.ImageRepoPort, cfg.ImageRepo, localImage)
if err := econd.EnsureImageExists(k3sImage); err != nil {
return err
}
_ = econd.ContainerRemove(utils.LocalKubernetesName)
k3sConfigPath := "/etc/rancher/k3s"
if !utils.Exists(k3sConfigPath) {
if err := os.MkdirAll(k3sConfigPath, utils.DefaultDirPermission); err != nil {
return err
}
}
if err := customCA(); err != nil {
return err
}
repo, imageRepoIP := getImageRepoIP(cfg.OtherRepo, cfg.OtherRepoIP, cfg.HostIP, cfg.ImageRepo, localImage)
log.Infof("params: onlineImage=%s otherRepo=%s, otherRepoIp=%s, hostIP=%s, imageRepo=%s, imageRepoPort=%s, kubernetesPort=%s",
cfg.OnlineImage, cfg.OtherRepo, cfg.OtherRepoIP, cfg.HostIP, cfg.ImageRepo, cfg.ImageRepoPort, cfg.KubernetesPort)
if err := generateRegistriesConfig(repo, k3sConfigPath); err != nil {
return err
}
log.Info("Start the local Kubernetes cluster...")
k3sStartScript := []string{
"run", "-d", fmt.Sprintf("--name=%s", utils.LocalKubernetesName),
"-p", fmt.Sprintf("%s:36443", cfg.KubernetesPort), "--privileged", "--restart=always", "-p", "30010:30010",
"--add-host", fmt.Sprintf("%s:%s", cfg.ImageRepo, imageRepoIP),
"-v", "/etc/rancher/k3s:/etc/rancher/k3s", "-v", "/etc/timezone:/etc/timezone", "-v", "/etc/docker:/etc/docker",
"-v", "/etc/localtime:/etc/localtime", "-v", "/var/lib/rancher/k3s:/var/lib/rancher/k3s", "-v", "/bke:/bke",
"-v", "/etc/openFuyao:/etc/openFuyao", "-v", fmt.Sprintf("%s:%s", utils.DefaultExtendManifestsDir, utils.DefaultExtendManifestsDir),
k3sImage, "server", "--snapshotter=native", fmt.Sprintf("--https-listen-port=%s", cfg.KubernetesPort),
"--service-cidr=100.10.0.0/16", "--cluster-cidr=100.20.0.0/16", "--token=e65832d9d955473260d9247e7dd2879c",
fmt.Sprintf("--advertise-address=%s", cfg.HostIP),
fmt.Sprintf("--tls-san=%s", cfg.HostIP), fmt.Sprintf("--node-name=%s", utils.LocalKubernetesName),
fmt.Sprintf("--pause-image=%s", k3sPause),
"--disable=coredns,servicelb,traefik,local-storage,metrics-server"}
if err := econd.Run(k3sStartScript); err != nil {
return err
}
time.Sleep(waitConfigInterval * time.Second)
if err := econd.CP(fmt.Sprintf("%s:/bin/k3s", utils.LocalKubernetesName), "/usr/bin/kubectl"); err != nil {
log.Error("Failed to copy kubectl from the container")
return err
}
return setupKubeconfigAndWaitCluster(cfg.HostIP, cfg.KubernetesPort)
}
func checkCurrentCoreDNSConfig() error {
cmd := exec.CommandExecutor{}
getCmd := []string{"get", "configmap", "coredns", "-n", "kube-system", "-o", "jsonpath={.data.Corefile}"}
output, err := cmd.ExecuteCommandWithCombinedOutput(utils.KubeCtl, getCmd...)
if err != nil {
return fmt.Errorf("failed to get current coredns config: %v", err)
}
log.Info("Current CoreDNS configuration detected")
if strings.Contains(output, "/etc/resolv.conf") {
log.Info("Found /etc/resolv.conf in current config, will be replaced")
return nil
}
if containsFixedDNS(output) {
log.Info("Fixed DNS already configured, no need to patch")
return nil
}
log.Info("Current config does not contain /etc/resolv.conf")
return nil
}
func verifyCoreDNSConfig() error {
cmd := exec.CommandExecutor{}
time.Sleep(time.Duration(waitConfigInterval) * time.Second)
getCmd := []string{"get", "configmap", "coredns", "-n", "kube-system", "-o", "jsonpath={.data.Corefile}"}
output, err := cmd.ExecuteCommandWithCombinedOutput(utils.KubeCtl, getCmd...)
if err != nil {
return fmt.Errorf("failed to verify coredns config: %v", err)
}
if strings.Contains(output, "/etc/resolv.conf") {
return fmt.Errorf("config still contains /etc/resolv.conf after patch")
}
if !containsFixedDNS(output) {
return fmt.Errorf("fixed DNS not found in config after patch")
}
log.Info("CoreDNS config verification passed: fixed DNS configured successfully")
return nil
}
func verifyCoreDNSRunning() error {
cmd := exec.CommandExecutor{}
time.Sleep(time.Duration(waitInterval) * time.Second)
checkCmd := []string{"get", "pods", "-n", "kube-system", "-l", "k8s-app=kube-dns", "-o", "jsonpath={.items[*].status.phase}"}
output, err := cmd.ExecuteCommandWithCombinedOutput(utils.KubeCtl, checkCmd...)
if err != nil {
return fmt.Errorf("failed to check coredns pod status: %v", err)
}
if !strings.Contains(output, "Running") {
return fmt.Errorf("coredns pods not in Running state: %s", output)
}
log.Info("CoreDNS pods are running successfully")
return nil
}
func containsFixedDNS(config string) bool {
fixedDNSPatterns := []string{
"forward . 8.8.8.8",
"forward . 8.8.4.4",
"forward . 1.1.1.1",
"forward . 1.0.0.1",
"forward . 208.67.222.222",
"forward . 208.67.220.220",
}
for _, pattern := range fixedDNSPatterns {
if strings.Contains(config, pattern) {
return true
}
}
return false
}
func FixCoreDnsLoop(otherRepo, imageRepo string) error {
if err := checkCurrentCoreDNSConfig(); err != nil {
return fmt.Errorf("coreDNS config check failed: %v", err)
}
escapedCorefile := strings.ReplaceAll(k3sCoreScript, "\n", "\\n")
escapedCorefile = strings.ReplaceAll(escapedCorefile, "\"", "\\\"")
patchData := fmt.Sprintf(`{"data":{"Corefile":"%s"}}`, escapedCorefile)
cmd := exec.CommandExecutor{}
patchCmd := []string{"patch", "configmap", "coredns", "-n", "kube-system", "--type", "merge", "-p", patchData}
output, err := cmd.ExecuteCommandWithCombinedOutput(utils.KubeCtl, patchCmd...)
if err != nil {
return fmt.Errorf("failed to patch coredns: %v, output: %s", err, output)
}
log.Infof("CoreDNS ConfigMap updated: %s", output)
if err := verifyCoreDNSConfig(); err != nil {
return fmt.Errorf("config verification failed after patch: %v", err)
}
if err := ModK3sCorednsImage(otherRepo, imageRepo); err != nil {
return fmt.Errorf("mod coredns image tag failed: %v", err)
}
deleteCmd := []string{"delete", "pod", "-n", "kube-system", "-l", "k8s-app=kube-dns"}
output, err = cmd.ExecuteCommandWithCombinedOutput(utils.KubeCtl, deleteCmd...)
if err != nil {
return fmt.Errorf("failed to delete coredns pods: %v, output: %s", err, output)
}
log.Infof("CoreDNS pods restarted: %s", output)
if err := verifyCoreDNSRunning(); err != nil {
return fmt.Errorf("coreDNS not running properly after restart: %v", err)
}
return nil
}
func ModCorednsConfigWithRetry(otherRepo, imageRepo string) error {
const maxRetries = 3
var lastError error
for i := 0; i < maxRetries; i++ {
log.Infof("Attempting to fix CoreDNS loop (attempt %d/%d)", i+1, maxRetries)
if err := FixCoreDnsLoop(otherRepo, imageRepo); err != nil {
lastError = err
log.Warnf("Attempt %d failed: %v", i+1, err)
time.Sleep(time.Duration(i+1) * waitConfigInterval * time.Second)
continue
}
log.Infof("CoreDNS loop fix completed successfully on attempt %d", i+1)
return nil
}
return fmt.Errorf("failed to fix CoreDNS loop after %d attempts: %v", maxRetries, lastError)
}
func ModK3sCorednsImage(otherRepo, imageRepo string) error {
log.Infof("Waiting for coredns to be ready (timeout=%ds, interval=%ds)", waitTimeout, waitInterval)
startTime := time.Now()
cmdExecutor := exec.CommandExecutor{}
for {
checkCmd := []string{
"exec", utils.LocalKubernetesName, "kubectl",
"-n", "kube-system",
"get", "deployment/coredns",
"-o", `jsonpath='{.status.conditions[?(@.type=="Available")].status}'`,
"--ignore-not-found",
}
output, err := cmdExecutor.ExecuteCommandWithCombinedOutput(utils.NerdCtl, checkCmd...)
outputStr := strings.Trim(string(output), "'")
if outputStr == "True" {
log.Infof("coredns is ready (elapsed: %v)", time.Since(startTime).Round(time.Second))
break
}
if time.Since(startTime) >= time.Duration(waitTimeout)*time.Second {
errMsg := fmt.Sprintf("Timeout waiting for coredns (%ds): output=%s, error=%v",
waitTimeout, outputStr, err)
log.Error(errMsg)
return fmt.Errorf("%s", errMsg)
}
log.Infof("coredns not ready (current status: %s), waiting...", outputStr)
time.Sleep(time.Duration(waitInterval) * time.Second)
}
if otherRepo != "" {
if imageRepo == configinit.DefaultImageRepo {
k3sCoredns = fmt.Sprintf("%s%s", "cr.openfuyao.cn/openfuyao/", "kubernetes/coredns:v1.10.1")
} else {
log.Infof("coredns image registry: %s", otherRepo)
k3sCoredns = fmt.Sprintf("%s%s", otherRepo, "kubernetes/coredns:v1.10.1")
}
} else {
k3sCoredns = fmt.Sprintf("%s:443/%s/%s", imageRepo, bkecommon.ImageRegistryKubernetes, "kubernetes/coredns:v1.10.1")
}
k3sModCorednsImageScript := []string{"exec", utils.LocalKubernetesName, "kubectl",
"-n", "kube-system", "set", "image", "deployment/coredns", fmt.Sprintf("coredns=%s", k3sCoredns)}
log.Infof("Generated coredns image address: %s", k3sCoredns)
var cmd2 = exec.CommandExecutor{}
output, err := cmd2.ExecuteCommandWithCombinedOutput(utils.NerdCtl, k3sModCorednsImageScript...)
if err != nil {
log.Errorf("Command execution failed: %v, command: %v, output: %s",
err, k3sModCorednsImageScript, string(output))
return err
}
log.Info("mod k3s coredns image tag succeeded")
return nil
}
func isKubernetesAvailable() bool {
k8sClient, err := k8s.NewKubernetesClient("")
if err != nil {
return false
}
nodes, err := k8sClient.GetClient().CoreV1().Nodes().List(context.Background(), metav1.ListOptions{})
if err != nil {
return false
}
for _, node := range nodes.Items {
for _, cond := range node.Status.Conditions {
if cond.Type == corev1.NodeReady && cond.Status == corev1.ConditionTrue {
global.K8s = k8sClient
return true
}
}
}
return false
}
func buildK3sContainerConfig(hostIP string) *container.Config {
return &container.Config{
Hostname: utils.LocalKubernetesName,
AttachStdin: false,
AttachStdout: false,
AttachStderr: false,
ExposedPorts: map[nat.Port]struct{}{"6443/tcp": {}},
Tty: true,
StdinOnce: false,
Env: []string{"KUBECONFIG=/etc/kubernetes/admin.conf"},
Cmd: []string{"server", "--snapshotter=native", "--service-cidr=100.10.0.0/16",
"--cluster-cidr=100.20.0.0/16", "--token=e65832d9d955473260d9247e7dd2879c",
fmt.Sprintf("--tls-san=%s", hostIP), fmt.Sprintf("--node-name=%s", utils.LocalKubernetesName),
fmt.Sprintf("--pause-image=%s", k3sPause), "--disable=coredns,servicelb,traefik,local-storage,metrics-server"},
Image: k3sImage,
Volumes: map[string]struct{}{"/var": {}},
Labels: map[string]string{"bke-local-kubernetes": "cluster-api"},
StopSignal: "SIGRTMIN+3",
}
}
func buildK3sHostConfig(kubernetesPort, imageRepo, imageRepoIP string) *container.HostConfig {
initFlag := false
return &container.HostConfig{
PortBindings: map[nat.Port][]nat.PortBinding{
nat.Port("6443/tcp"): {{HostIP: "0.0.0.0", HostPort: kubernetesPort}},
},
RestartPolicy: container.RestartPolicy{Name: "on-failure", MaximumRetryCount: 10},
ExtraHosts: []string{fmt.Sprintf("%s:%s", imageRepo, imageRepoIP)},
Privileged: true,
SecurityOpt: []string{"seccomp=unconfined", "apparmor=unconfined", "label=disable"},
Tmpfs: map[string]string{"/run": "", "/tmp": ""},
Mounts: []mount.Mount{
{Type: mount.TypeBind, Source: "/etc/rancher/k3s", Target: "/etc/rancher/k3s"},
{Type: mount.TypeBind, Source: "/var/lib/rancher/k3s", Target: "/var/lib/rancher/k3s"},
{Type: mount.TypeBind, Source: "/etc/timezone", Target: "/etc/timezone", ReadOnly: true},
{Type: mount.TypeBind, Source: "/etc/localtime", Target: "/etc/localtime", ReadOnly: true},
},
Init: &initFlag,
}
}
func prepareK3sEnvironment(cfg Config, localImage string) (string, string, error) {
prepareK3sImages(cfg.OnlineImage, cfg.OtherRepo, cfg.ImageRepoPort, cfg.ImageRepo, localImage)
err := global.Docker.EnsureImageExists(docker.ImageRef{Image: k3sImage}, utils.RetryOptions{MaxRetry: 3, Delay: 1})
if err != nil {
return "", "", err
}
containerRunFlag, err := global.Docker.EnsureContainerRun(utils.LocalKubernetesName)
if err != nil {
return "", "", err
}
if containerRunFlag {
if isKubernetesAvailable() {
return "", "", nil
}
_ = global.Docker.ContainerRemove(utils.LocalKubernetesName)
}
k3sConfigPath := "/etc/rancher/k3s"
if !utils.Exists(k3sConfigPath) {
if err = os.MkdirAll(k3sConfigPath, utils.DefaultDirPermission); err != nil {
return "", "", err
}
}
if err = customCA(); err != nil {
return "", "", err
}
repo, imageRepoIP := getImageRepoIPWithDocker(cfg.OtherRepo, cfg.OtherRepoIP, cfg.HostIP, cfg.ImageRepo)
if err = generateRegistriesConfig(repo, k3sConfigPath); err != nil {
return "", "", err
}
return repo, imageRepoIP, nil
}
func StartK3sWithDocker(cfg Config, localImage string) error {
if isKubernetesAvailable() {
log.Info("A kubernetes cluster already exists.")
return nil
}
repo, imageRepoIP, err := prepareK3sEnvironment(cfg, localImage)
if err != nil {
return err
}
if repo == "" && imageRepoIP == "" {
log.Info("The local Kubernetes cluster is already running")
return nil
}
log.Info("Start the local Kubernetes cluster...")
containerConfig := buildK3sContainerConfig(cfg.HostIP)
hostConfig := buildK3sHostConfig(cfg.KubernetesPort, cfg.ImageRepo, imageRepoIP)
err = global.Docker.Run(containerConfig, hostConfig, nil, nil, utils.LocalKubernetesName)
if err != nil {
return err
}
time.Sleep(utils.DefaultSleepSeconds * time.Second)
if err = global.Docker.CopyFromContainer(utils.LocalKubernetesName, "/bin/k3s", "/usr/bin/kubectl"); err != nil {
log.Error("Failed to copy kubectl from the container")
return err
}
return setupKubeconfigAndWaitCluster(cfg.HostIP, cfg.KubernetesPort)
}
func customCA() error {
var (
output string
err error
)
if !utils.Exists(DefaultK3sDataDir) {
err = os.MkdirAll(DefaultK3sDataDir, utils.DefaultDirPermission)
if err != nil {
return fmt.Errorf("create k3s certs dir failed: %w", err)
}
}
genShFile := filepath.Join(DefaultK3sDataDir, "generate-custom-ca-certs.sh")
err = os.WriteFile(genShFile, []byte(k3sCertScript), utils.DefaultFilePermission)
if err != nil {
return fmt.Errorf("write generate-custom-ca-certs.sh failed: %w", err)
}
executor := &exec.CommandExecutor{}
output, err = executor.ExecuteCommandWithCombinedOutput("/bin/bash", "-c",
fmt.Sprintf("cd %s && chmod +x ./generate-custom-ca-certs.sh && ./generate-custom-ca-certs.sh &&"+
"chmod -x ./generate-custom-ca-certs.sh", DefaultK3sDataDir))
if err != nil {
return fmt.Errorf("generate k3s tls cert failed, output: %s, err: %w", output, err)
}
return nil
}