Repository: kubeclipper/kubeclipper (go)

Issue:
support kubeadm ignore-preflight-errors 
<!--
You don't need to remove this comment section, it's invisible on the issues page.

## General remarks

* Attention, please fill out this issues form using English!
* 注意！GitHub Issue 请使用英文
-->

**Describe the Bug**

```bash
W0606 01:20:32.508266   24487 initconfiguration.go:120] Usage of CRI endpoints without URL scheme is deprecated and can cause kubelet errors in the future. Automatically prepending scheme "unix" to the "criSocket" with value "/run/containerd/containerd.sock". Please update your configuration!
[init] Using Kubernetes version: v1.27.4
[preflight] Running pre-flight checks
	[WARNING FileExisting-socat]: socat not found in system path
error execution phase preflight: [preflight] Some fatal errors occurred:
	[ERROR NumCPU]: the number of available CPUs 1 is less than the required 2
[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`
To see the stack trace of this error execute with --v=5 or higher
```
use --ignore-preflight-errors=NumCPU to ignore
```bash
/usr/bin/kubeadm init --config /tmp/.k8s/kubeadm.yaml --upload-certs --ignore-preflight-errors=NumCPU
```

**Versions Used**
Kubernetes: v1.27.4
Kubeclipper: 1.4.0


**Environment**
How many nodes and their hardware configuration:

For example: CentOS 7.5 / 3 masters:  8cpu/8g; 3 nodes: 8cpu/16g
(and other info are welcomed to help us debugging)

**How To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error

**Expected behavior**
A clear and concise description of what you expected to happen.

Pull request:
feat: support ignore-preflight-errors flag
<!-- Thanks for sending a pull request! Here are some tips for you:

1. If you want **faster** PR reviews, read how: https://github.com/kubeclipper/community/blob/master/developer-guide/development/the-pr-author-guide-to-getting-through-code-review.md
2. In case you want to know how your PR got reviewed, read: https://github.com/kubeclipper/community/blob/master/developer-guide/development/code-review-guide.md
3. Here are some coding convetions followed by Kubeclipper community: https://github.com/kubeclipper/community/blob/master/developer-guide/development/coding-conventions.md
-->

### What type of PR is this?
/kind feature
<!--
Add one of the following kinds:
/kind bug
/kind cleanup
/kind documentation
/kind feature
/kind design

Optionally add one or more of the following kinds if applicable:
/kind api-change
/kind deprecation
/kind failing-test
/kind flake
/kind regression
-->


### What this PR does / why we need it:
add ignore-preflight-errors to support some test case
### Which issue(s) this PR fixes:
<!--
Usage: `Fixes #<issue number>`, or `Fixes (paste link of issue)`.
_If PR is about `failing-tests or flakes`, please post the related issues/tests in a comment and do not use `Fixes`_*
-->
Fixes #700

### Special notes for reviewers:
```
None
```

### Does this PR introduced a user-facing change?
<!--
If no, just write "None" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".

For more information on release notes see: https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md
-->
```release-note
None
```

### Additional documentation, usage docs, etc.:
<!--
This section can be blank if this pull request does not require a release note.
Please use the following format for linking documentation or pass the
section below:
- [KEP]: <link>
- [Usage]: <link>
- [Other doc]: <link>
-->
```docs

```

Patch:
diff --git a/pkg/cli/create/create_cluster.go b/pkg/cli/create/create_cluster.go
index 6e41183..711381e 100644
--- a/pkg/cli/create/create_cluster.go
+++ b/pkg/cli/create/create_cluster.go
@@ -137,30 +137,31 @@ IP6_AUTODETECTION_METHOD=can-reach=www.google.com`
 
 type CreateClusterOptions struct {
 	BaseOptions
-	Masters                   []string
-	Workers                   []string
-	UntaintMaster             bool
-	Offline                   bool
-	LocalRegistry             string
-	InsecureRegistries        []string
-	CRI                       string
-	CRIVersion                string
-	K8sVersion                string
-	CNI                       string
-	CNIVersion                string
-	Name                      string
-	createdByIP               bool
-	CertSans                  []string
-	CaCertFile                string
-	CaKeyFile                 string
-	DNSDomain                 string
-	CalicoNetMode             string
-	IPv4AutoDetection         string
-	ServiceSubnet             string
-	PodSubnet                 string
-	OnlyInstallKubernetesComp bool
-	FeatureGatesString        []string
-	FeatureGates              map[string]bool
+	Masters                          []string
+	Workers                          []string
+	UntaintMaster                    bool
+	Offline                          bool
+	LocalRegistry                    string
+	InsecureRegistries               []string
+	CRI                              string
+	CRIVersion                       string
+	K8sVersion                       string
+	CNI                              string
+	CNIVersion                       string
+	Name                             string
+	createdByIP                      bool
+	CertSans                         []string
+	CaCertFile                       string
+	CaKeyFile                        string
+	DNSDomain                        string
+	CalicoNetMode                    string
+	IPv4AutoDetection                string
+	ServiceSubnet                    string
+	PodSubnet                        string
+	KubeadmInitIgnorePreflightErrors string // kubeadm init --ignore-preflight-errors
+	OnlyInstallKubernetesComp        bool
+	FeatureGatesString               []string
+	FeatureGates                     map[string]bool
 }
 
 var (
@@ -225,6 +226,7 @@ func NewCmdCreateCluster(streams options.IOStreams) *cobra.Command {
 	cmd.Flags().StringVar(&o.CalicoNetMode, "calico.net-mode", o.CalicoNetMode, "calico network mode, support [BGP|Overlay-IPIP-All|Overlay-IPIP-Cross-Subnet|Overlay-Vxlan-All|Overlay-Vxlan-Cross-Subnet] now. \n"+CalicoNetModeDescription)
 	cmd.Flags().StringVar(&o.ServiceSubnet, "service-subnet", o.ServiceSubnet, "serviceSubnet is the subnet used by Kubernetes Services. Defaults to '10.96.0.0/12'")
 	cmd.Flags().StringVar(&o.PodSubnet, "pod-subnet", o.PodSubnet, "podSubnet is the subnet used by Pods. Defaults to '172.25.0.0/16'")
+	cmd.Flags().StringVar(&o.KubeadmInitIgnorePreflightErrors, "kubeadm-init-ignore-preflight-errors", o.KubeadmInitIgnorePreflightErrors, "A list of checks whose errors will be shown as warnings. Example: 'IsPrivilegedUser,Swap'. Value 'all' ignores errors from all checks.,kubeadm init --ignore-preflight-errors=xxx")
 	cmd.Flags().BoolVar(&o.OnlyInstallKubernetesComp, "only-install-kubernetes-component", o.OnlyInstallKubernetesComp, "only install kubernetes component, not install cni")
 	cmd.Flags().StringSliceVar(&o.FeatureGatesString, "feature-gates", o.FeatureGatesString, "k8s feature gates, format as: --feature-gates=xxx=true|false")
 	o.CliOpts.AddFlags(cmd.Flags())
@@ -442,6 +444,11 @@ func (l *CreateClusterOptions) newCluster() *v1.Cluster {
 	if l.OnlyInstallKubernetesComp {
 		annotations[common.AnnotationOnlyInstallKubernetesComp] = "true"
 	}
+
+	if l.KubeadmInitIgnorePreflightErrors != "" {
+		annotations[common.AnnotationOnlyIgnorePreflightErrors] = l.KubeadmInitIgnorePreflightErrors
+	}
+
 	if l.ServiceSubnet == "" {
 		l.ServiceSubnet = constatns.ClusterServiceSubnet
 	}
diff --git a/pkg/scheme/common/well_known_labels.go b/pkg/scheme/common/well_known_labels.go
index 96ca617..8d5bc1a 100644
--- a/pkg/scheme/common/well_known_labels.go
+++ b/pkg/scheme/common/well_known_labels.go
@@ -71,6 +71,8 @@ const (
 
 	// AnnotationOnlyInstallKubernetesComp mean not install cni when create cluster
 	AnnotationOnlyInstallKubernetesComp = "kubeclipper.io/only-install-kubernetes-component"
+	// AnnotationOnlyIgnorePreflightErrors specify kubeadm init --ignore-preflight-errors
+	AnnotationOnlyIgnorePreflightErrors = "kubeclipper.io/ignore-preflight-errors"
 )
 
 type NodeRole string // master/worker/ingress(worker)
diff --git a/pkg/scheme/core/v1/k8s/cluster.go b/pkg/scheme/core/v1/k8s/cluster.go
index 97afffe..333b99b 100644
--- a/pkg/scheme/core/v1/k8s/cluster.go
+++ b/pkg/scheme/core/v1/k8s/cluster.go
@@ -29,6 +29,8 @@ import (
 	"strings"
 	"time"
 
+	"github.com/kubeclipper/kubeclipper/pkg/scheme/common"
+
 	"github.com/kubeclipper/kubeclipper/pkg/agent/config"
 	"github.com/kubeclipper/kubeclipper/pkg/component"
 	"github.com/kubeclipper/kubeclipper/pkg/component/utils"
@@ -150,6 +152,7 @@ func (stepper *Upgrade) InitStepper(metadata *component.ExtraMetadata, c *v1.Clu
 		CertSANs:                c.GetAllCertSANs(),
 		LocalRegistry:           c.LocalRegistry,
 		FeatureGates:            c.FeatureGates,
+		IgnorePreflightErrors:   parseIgnorePreflightErrors(c.Annotations[common.AnnotationOnlyIgnorePreflightErrors]),
 	}
 	stepper.Offline = metadata.Offline
 	stepper.Version = metadata.KubeVersion
diff --git a/pkg/scheme/core/v1/k8s/kubeadm.go b/pkg/scheme/core/v1/k8s/kubeadm.go
index 6713920..44db596 100644
--- a/pkg/scheme/core/v1/k8s/kubeadm.go
+++ b/pkg/scheme/core/v1/k8s/kubeadm.go
@@ -105,32 +105,34 @@ type KubeadmConfig struct {
 	ClusterConfigAPIVersion string `json:"clusterConfigAPIVersion"`
 	// If both Docker and containerd are detected, Docker takes precedence,so we must specify cri.
 	// https://v1-20.docs.kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/#installing-runtime
-	ContainerRuntime     string          `json:"containerRuntime"`
-	Etcd                 v1.Etcd         `json:"etcd"`
-	Networking           v1.Networking   `json:"networking"`
-	KubeProxy            v1.KubeProxy    `json:"kubeProxy"`
-	Kubelet              v1.Kubelet      `json:"kubelet"`
-	ClusterName          string          `json:"clusterName"`
-	KubernetesVersion    string          `json:"kubernetesVersion"`
-	ControlPlaneEndpoint string          `json:"controlPlaneEndpoint"`
-	CertSANs             []string        `json:"certSANs"`
-	LocalRegistry        string          `json:"localRegistry"`
-	Offline              bool            `json:"offline"`
-	IsControlPlane       bool            `json:"isControlPlane,omitempty"`
-	CACertHashes         string          `json:"caCertHashes,omitempty"`
-	BootstrapToken       string          `json:"bootstrapToken,omitempty"`
-	CertificateKey       string          `json:"certificateKey,omitempty"`
-	AdvertiseAddress     string          `json:"advertiseAddress,omitempty"`
-	FeatureGates         map[string]bool `json:"featureGates,omitempty"`
+	ContainerRuntime      string          `json:"containerRuntime"`
+	Etcd                  v1.Etcd         `json:"etcd"`
+	Networking            v1.Networking   `json:"networking"`
+	KubeProxy             v1.KubeProxy    `json:"kubeProxy"`
+	Kubelet               v1.Kubelet      `json:"kubelet"`
+	ClusterName           string          `json:"clusterName"`
+	KubernetesVersion     string          `json:"kubernetesVersion"`
+	ControlPlaneEndpoint  string          `json:"controlPlaneEndpoint"`
+	CertSANs              []string        `json:"certSANs"`
+	LocalRegistry         string          `json:"localRegistry"`
+	Offline               bool            `json:"offline"`
+	IsControlPlane        bool            `json:"isControlPlane,omitempty"`
+	CACertHashes          string          `json:"caCertHashes,omitempty"`
+	BootstrapToken        string          `json:"bootstrapToken,omitempty"`
+	CertificateKey        string          `json:"certificateKey,omitempty"`
+	AdvertiseAddress      string          `json:"advertiseAddress,omitempty"`
+	FeatureGates          map[string]bool `json:"featureGates,omitempty"`
+	IgnorePreflightErrors []string        `json:"ignorePreflightErrors,omitempty"`
 }
 
 type ControlPlane struct {
 	// KubeConfig file
-	APIServerDomainName string
-	EtcdDataPath        string
-	ContainerRuntime    string
-	ExternalCaCert      string
-	ExternalCaKey       string
+	APIServerDomainName   string
+	EtcdDataPath          string
+	ContainerRuntime      string
+	ExternalCaCert        string
+	ExternalCaKey         string
+	IgnorePreflightErrors string
 }
 
 type ClusterNode struct {
diff --git a/pkg/scheme/core/v1/k8s/kubeadm_step.go b/pkg/scheme/core/v1/k8s/kubeadm_step.go
index 4318472..a6a4eee 100644
--- a/pkg/scheme/core/v1/k8s/kubeadm_step.go
+++ b/pkg/scheme/core/v1/k8s/kubeadm_step.go
@@ -25,6 +25,7 @@ import (
 	"fmt"
 	"os"
 	"path/filepath"
+	"strings"
 	"time"
 
 	"k8s.io/apimachinery/pkg/util/sets"
@@ -433,10 +434,18 @@ func (stepper *KubeadmConfig) InitStepper(c *v1.Cluster, metadata *component.Ext
 	stepper.FeatureGates = c.FeatureGates
 	// TODO: No vip is currently introduced as controlPlaneEndpoint
 	stepper.AdvertiseAddress = metadata.Masters[0].NodeIPv4
+	stepper.IgnorePreflightErrors = parseIgnorePreflightErrors(c.Annotations[common.AnnotationOnlyIgnorePreflightErrors])
 
 	return stepper
 }
 
+func parseIgnorePreflightErrors(errStr string) []string {
+	if errStr == "" {
+		return nil
+	}
+	return strings.Split(errStr, ",")
+}
+
 func (stepper *KubeadmConfig) InstallSteps(nodes []v1.StepNode) ([]v1.Step, error) {
 	kubeadmBytes, err := json.Marshal(stepper)
 	if err != nil {
@@ -506,6 +515,7 @@ func (stepper *ControlPlane) InitStepper(c *v1.Cluster) *ControlPlane {
 	stepper.ContainerRuntime = c.ContainerRuntime.Type
 	stepper.ExternalCaCert = c.ExternalCaCert
 	stepper.ExternalCaKey = c.ExternalCaKey
+	stepper.IgnorePreflightErrors = c.Annotations[common.AnnotationOnlyIgnorePreflightErrors]
 
 	return stepper
 }
diff --git a/pkg/scheme/core/v1/k8s/template.go b/pkg/scheme/core/v1/k8s/template.go
index c77bff3..445c431 100644
--- a/pkg/scheme/core/v1/k8s/template.go
+++ b/pkg/scheme/core/v1/k8s/template.go
@@ -100,6 +100,10 @@ nodeRegistration:
 {{- if .Kubelet.IPAsName }}
   name: "{{.Kubelet.NodeIP}}"
 {{- end}}
+{{if gt (len .IgnorePreflightErrors) 0}}
+  ignorePreflightErrors:{{range .IgnorePreflightErrors}}
+    - {{.}}{{end}}
+{{- end}}
 {{- if eq .ContainerRuntime  "containerd"}}
   criSocket: /run/containerd/containerd.sock
 {{end}}


Test patch:
diff --git a/pkg/scheme/core/v1/k8s/kubeadm_test.go b/pkg/scheme/core/v1/k8s/kubeadm_test.go
index 94562cf..a29e20e 100644
--- a/pkg/scheme/core/v1/k8s/kubeadm_test.go
+++ b/pkg/scheme/core/v1/k8s/kubeadm_test.go
@@ -53,6 +53,7 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 		CertSANs                []string
 		LocalRegistry           string
 		FeatureGates            map[string]bool
+		IgnorePreflightErrors   string
 	}
 	tests := []struct {
 		name   string
@@ -73,13 +74,14 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 					ProxyMode:     "ipvs",
 					WorkerNodeVip: "8.8.8.8",
 				},
-				KubeProxy:            v1.KubeProxy{},
-				Kubelet:              v1.Kubelet{RootDir: "/var/lib/kubelet"},
-				ClusterName:          "test-cluster",
-				KubernetesVersion:    "v1.23.6",
-				ControlPlaneEndpoint: "apiserver.cluster.local:6443",
-				CertSANs:             []string{"127.0.0.1"},
-				LocalRegistry:        "127.0.0.1:5000",
+				KubeProxy:             v1.KubeProxy{},
+				Kubelet:               v1.Kubelet{RootDir: "/var/lib/kubelet"},
+				ClusterName:           "test-cluster",
+				KubernetesVersion:     "v1.23.6",
+				ControlPlaneEndpoint:  "apiserver.cluster.local:6443",
+				CertSANs:              []string{"127.0.0.1"},
+				LocalRegistry:         "127.0.0.1:5000",
+				IgnorePreflightErrors: "",
 			},
 		},
 		{
@@ -96,13 +98,14 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 					ProxyMode:     "ipvs",
 					WorkerNodeVip: "8.8.8.8",
 				},
-				KubeProxy:            v1.KubeProxy{},
-				Kubelet:              v1.Kubelet{RootDir: "/var/lib/kubelet", NodeIP: "127.0.0.1", IPAsName: true},
-				ClusterName:          "test-cluster",
-				KubernetesVersion:    "v1.23.6",
-				ControlPlaneEndpoint: "apiserver.cluster.local:6443",
-				CertSANs:             []string{"127.0.0.1"},
-				LocalRegistry:        "127.0.0.1:5000",
+				KubeProxy:             v1.KubeProxy{},
+				Kubelet:               v1.Kubelet{RootDir: "/var/lib/kubelet", NodeIP: "127.0.0.1", IPAsName: true},
+				ClusterName:           "test-cluster",
+				KubernetesVersion:     "v1.23.6",
+				ControlPlaneEndpoint:  "apiserver.cluster.local:6443",
+				CertSANs:              []string{"127.0.0.1"},
+				LocalRegistry:         "127.0.0.1:5000",
+				IgnorePreflightErrors: "NumCPU",
 			},
 		},
 		{
@@ -130,6 +133,7 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 					"AdmissionWebhookMatchConditions": true,
 					"AggregatedDiscoveryEndpoint":     true,
 				},
+				IgnorePreflightErrors: "NumCPU,Swap",
 			},
 		},
 	}
@@ -148,7 +152,9 @@ func TestKubeadmConfig_renderTo(t *testing.T) {
 				CertSANs:                tt.fields.CertSANs,
 				LocalRegistry:           tt.fields.LocalRegistry,
 				FeatureGates:            tt.fields.FeatureGates,
+				IgnorePreflightErrors:   parseIgnorePreflightErrors(tt.fields.IgnorePreflightErrors),
 			}
+			t.Logf("IgnorePreflightErrors %v,len %v", stepper.IgnorePreflightErrors, len(stepper.IgnorePreflightErrors))
 			w := &bytes.Buffer{}
 			err := stepper.renderTo(w)
 			if err != nil {