diff --git a/cmd/minikube/cmd/config/configure.go b/cmd/minikube/cmd/config/configure.go index 412fa1204d1f..3f361f3bbe5c 100644 --- a/cmd/minikube/cmd/config/configure.go +++ b/cmd/minikube/cmd/config/configure.go @@ -18,8 +18,10 @@ package config import ( "io/ioutil" + "net" "github.com/spf13/cobra" + "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/exit" "k8s.io/minikube/pkg/minikube/out" "k8s.io/minikube/pkg/minikube/service" @@ -178,6 +180,30 @@ var addonsConfigureCmd = &cobra.Command{ out.WarningT("ERROR creating `registry-creds-acr` secret") } + case "metallb": + profile := ClusterFlagValue() + cfg, err := config.Load(profile) + if err != nil { + out.ErrT(out.FatalType, "Failed to load config {{.profile}}", out.V{"profile": profile}) + } + + validator := func(s string) bool { + return net.ParseIP(s) != nil + } + + if cfg.KubernetesConfig.LoadBalancerStartIP == "" { + cfg.KubernetesConfig.LoadBalancerStartIP = AskForStaticValidatedValue("-- Enter Load Balancer Start IP: ", validator) + } + + if cfg.KubernetesConfig.LoadBalancerEndIP == "" { + cfg.KubernetesConfig.LoadBalancerEndIP = AskForStaticValidatedValue("-- Enter Load Balancer End IP: ", validator) + } + + err = config.SaveProfile(profile, cfg) + if err != nil { + out.ErrT(out.FatalType, "Failed to save config {{.profile}}", out.V{"profile": profile}) + } + default: out.FailureT("{{.name}} has no available configuration options", out.V{"name": addon}) return diff --git a/cmd/minikube/cmd/config/prompt.go b/cmd/minikube/cmd/config/prompt.go index 10c00a7f0f51..8f85d6061c20 100644 --- a/cmd/minikube/cmd/config/prompt.go +++ b/cmd/minikube/cmd/config/prompt.go @@ -153,3 +153,23 @@ func posString(slice []string, element string) int { func containsString(slice []string, element string) bool { return posString(slice, element) != -1 } + +// AskForStaticValidatedValue asks for a single value to enter and check for valid input +func AskForStaticValidatedValue(s string, validator func(s string) bool) string { + reader := bufio.NewReader(os.Stdin) + + for { + response := getStaticValue(reader, s) + + // Can't have zero length + if len(response) == 0 { + out.Err("--Error, please enter a value:") + continue + } + if !validator(response) { + out.Err("--Invalid input, please enter a value:") + continue + } + return response + } +} diff --git a/deploy/addons/metallb/metallb-config.yaml.tmpl b/deploy/addons/metallb/metallb-config.yaml.tmpl new file mode 100644 index 000000000000..d89de8f582da --- /dev/null +++ b/deploy/addons/metallb/metallb-config.yaml.tmpl @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + namespace: metallb-system + name: config +data: + config: | + address-pools: + - name: default + protocol: layer2 + addresses: + - {{ .LoadBalancerStartIP }}-{{ .LoadBalancerEndIP }} diff --git a/deploy/addons/metallb/metallb.yaml b/deploy/addons/metallb/metallb.yaml new file mode 100644 index 000000000000..0384232eab62 --- /dev/null +++ b/deploy/addons/metallb/metallb.yaml @@ -0,0 +1,293 @@ +apiVersion: v1 +kind: Namespace +metadata: + labels: + app: metallb + name: metallb-system +--- +apiVersion: policy/v1beta1 +kind: PodSecurityPolicy +metadata: + labels: + app: metallb + name: speaker + namespace: metallb-system +spec: + allowPrivilegeEscalation: false + allowedCapabilities: + - NET_ADMIN + - NET_RAW + - SYS_ADMIN + fsGroup: + rule: RunAsAny + hostNetwork: true + hostPorts: + - max: 7472 + min: 7472 + privileged: true + runAsUser: + rule: RunAsAny + seLinux: + rule: RunAsAny + supplementalGroups: + rule: RunAsAny + volumes: + - '*' +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: metallb + name: controller + namespace: metallb-system +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + labels: + app: metallb + name: speaker + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app: metallb + name: metallb-system:controller +rules: +- apiGroups: + - '' + resources: + - services + verbs: + - get + - list + - watch + - update +- apiGroups: + - '' + resources: + - services/status + verbs: + - update +- apiGroups: + - '' + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app: metallb + name: metallb-system:speaker +rules: +- apiGroups: + - '' + resources: + - services + - endpoints + - nodes + verbs: + - get + - list + - watch +- apiGroups: + - '' + resources: + - events + verbs: + - create + - patch +- apiGroups: + - extensions + resourceNames: + - speaker + resources: + - podsecuritypolicies + verbs: + - use +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + labels: + app: metallb + name: config-watcher + namespace: metallb-system +rules: +- apiGroups: + - '' + resources: + - configmaps + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app: metallb + name: metallb-system:controller +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metallb-system:controller +subjects: +- kind: ServiceAccount + name: controller + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + labels: + app: metallb + name: metallb-system:speaker +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: metallb-system:speaker +subjects: +- kind: ServiceAccount + name: speaker + namespace: metallb-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + labels: + app: metallb + name: config-watcher + namespace: metallb-system +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: config-watcher +subjects: +- kind: ServiceAccount + name: controller +- kind: ServiceAccount + name: speaker +--- +apiVersion: apps/v1 +kind: DaemonSet +metadata: + labels: + app: metallb + component: speaker + name: speaker + namespace: metallb-system +spec: + selector: + matchLabels: + app: metallb + component: speaker + template: + metadata: + annotations: + prometheus.io/port: '7472' + prometheus.io/scrape: 'true' + labels: + app: metallb + component: speaker + spec: + containers: + - args: + - --port=7472 + - --config=config + env: + - name: METALLB_NODE_NAME + valueFrom: + fieldRef: + fieldPath: spec.nodeName + - name: METALLB_HOST + valueFrom: + fieldRef: + fieldPath: status.hostIP + image: metallb/speaker:v0.8.2 + imagePullPolicy: IfNotPresent + name: speaker + ports: + - containerPort: 7472 + name: monitoring + resources: + limits: + cpu: 100m + memory: 100Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + add: + - NET_ADMIN + - NET_RAW + - SYS_ADMIN + drop: + - ALL + readOnlyRootFilesystem: true + hostNetwork: true + nodeSelector: + beta.kubernetes.io/os: linux + serviceAccountName: speaker + terminationGracePeriodSeconds: 0 + tolerations: + - effect: NoSchedule + key: node-role.kubernetes.io/master +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + labels: + app: metallb + component: controller + name: controller + namespace: metallb-system +spec: + revisionHistoryLimit: 3 + selector: + matchLabels: + app: metallb + component: controller + template: + metadata: + annotations: + prometheus.io/port: '7472' + prometheus.io/scrape: 'true' + labels: + app: metallb + component: controller + spec: + containers: + - args: + - --port=7472 + - --config=config + image: metallb/controller:v0.8.2 + imagePullPolicy: IfNotPresent + name: controller + ports: + - containerPort: 7472 + name: monitoring + resources: + limits: + cpu: 100m + memory: 100Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - all + readOnlyRootFilesystem: true + nodeSelector: + beta.kubernetes.io/os: linux + securityContext: + runAsNonRoot: true + runAsUser: 65534 + serviceAccountName: controller + terminationGracePeriodSeconds: 0 diff --git a/pkg/addons/config.go b/pkg/addons/config.go index 4a39951f0d5b..9faef8e484a9 100644 --- a/pkg/addons/config.go +++ b/pkg/addons/config.go @@ -129,4 +129,9 @@ var Addons = []*Addon{ set: SetBool, callbacks: []setFn{enableOrDisableStorageClasses}, }, + { + name: "metallb", + set: SetBool, + callbacks: []setFn{enableOrDisableAddon}, + }, } diff --git a/pkg/minikube/assets/addons.go b/pkg/minikube/assets/addons.go index 852eb272522a..aed7526d1637 100644 --- a/pkg/minikube/assets/addons.go +++ b/pkg/minikube/assets/addons.go @@ -364,6 +364,20 @@ var Addons = map[string]*Addon{ "0640", false), }, false, "ingress-dns"), + "metallb": NewAddon([]*BinAsset{ + MustBinAsset( + "deploy/addons/metallb/metallb.yaml", + vmpath.GuestAddonsDir, + "metallb.yaml", + "0640", + false), + MustBinAsset( + "deploy/addons/metallb/metallb-config.yaml.tmpl", + vmpath.GuestAddonsDir, + "metallb-config.yaml", + "0640", + true), + }, false, "metallb"), } // GenerateTemplateData generates template data for template assets @@ -377,13 +391,17 @@ func GenerateTemplateData(cfg config.KubernetesConfig) interface{} { ea = "-" + runtime.GOARCH } opts := struct { - Arch string - ExoticArch string - ImageRepository string + Arch string + ExoticArch string + ImageRepository string + LoadBalancerStartIP string + LoadBalancerEndIP string }{ - Arch: a, - ExoticArch: ea, - ImageRepository: cfg.ImageRepository, + Arch: a, + ExoticArch: ea, + ImageRepository: cfg.ImageRepository, + LoadBalancerStartIP: cfg.LoadBalancerStartIP, + LoadBalancerEndIP: cfg.LoadBalancerEndIP, } return opts diff --git a/pkg/minikube/config/types.go b/pkg/minikube/config/types.go index 97c2a1303991..3008f8b2ddd4 100644 --- a/pkg/minikube/config/types.go +++ b/pkg/minikube/config/types.go @@ -69,19 +69,21 @@ type ClusterConfig struct { // KubernetesConfig contains the parameters used to configure the VM Kubernetes. type KubernetesConfig struct { - KubernetesVersion string - ClusterName string - APIServerName string - APIServerNames []string - APIServerIPs []net.IP - DNSDomain string - ContainerRuntime string - CRISocket string - NetworkPlugin string - FeatureGates string // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ - ServiceCIDR string // the subnet which kubernetes services will be deployed to - ImageRepository string - ExtraOptions ExtraOptionSlice + KubernetesVersion string + ClusterName string + APIServerName string + APIServerNames []string + APIServerIPs []net.IP + DNSDomain string + ContainerRuntime string + CRISocket string + NetworkPlugin string + FeatureGates string // https://kubernetes.io/docs/reference/command-line-tools-reference/feature-gates/ + ServiceCIDR string // the subnet which kubernetes services will be deployed to + ImageRepository string + LoadBalancerStartIP string // currently only used by MetalLB addon + LoadBalancerEndIP string // currently only used by MetalLB addon + ExtraOptions ExtraOptionSlice ShouldLoadCachedImages bool EnableDefaultCNI bool