Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

karmadactl init with external etcd #3898

Merged
merged 5 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion pkg/karmadactl/addons/search/manifests.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,8 @@ spec:
- --audit-log-path=-
- --feature-gates=APIPriorityAndFairness=false
- --audit-log-maxage=0
- --audit-log-maxbackup=0
- --audit-log-maxbackup=0{{- if .KeyPrefix }}
- --etcd-prefix={{ .KeyPrefix }}{{- end }}
livenessProbe:
httpGet:
path: /livez
Expand Down Expand Up @@ -126,6 +127,7 @@ type DeploymentReplace struct {
Replicas *int32
Image string
ETCDSevers string
KeyPrefix string
}

// ServiceReplace is a struct to help to concrete
Expand Down
55 changes: 50 additions & 5 deletions pkg/karmadactl/addons/search/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package search
import (
"context"
"fmt"
"k8s.io/client-go/kubernetes"
"strings"
"time"

Expand All @@ -28,6 +29,8 @@ const (

// etcdStatefulSetAndServiceName define etcd statefulSet and serviceName installed by init command
etcdStatefulSetAndServiceName = "etcd"
// karmadaAPIServerDeploymentAndServiceName defines the name of karmada-apiserver deployment and service installed by init command
karmadaAPIServerDeploymentAndServiceName = "karmada-apiserver"

// etcdContainerClientPort define etcd pod installed by init command
etcdContainerClientPort = 2379
Expand Down Expand Up @@ -134,7 +137,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
return fmt.Errorf("create karmada search service error: %v", err)
}

etcdServers, err := etcdServers(opts)
etcdServers, keyPrefix, err := etcdServers(opts)
if err != nil {
return err
}
Expand All @@ -146,6 +149,7 @@ func installComponentsOnHostCluster(opts *addoninit.CommandAddonsEnableOption) e
Namespace: opts.Namespace,
Replicas: &opts.KarmadaSearchReplicas,
ETCDSevers: etcdServers,
KeyPrefix: keyPrefix,
Image: addoninit.KarmadaSearchImage(opts),
})
if err != nil {
Expand Down Expand Up @@ -212,10 +216,51 @@ func installComponentsOnKarmadaControlPlane(opts *addoninit.CommandAddonsEnableO
return nil
}

func etcdServers(opts *addoninit.CommandAddonsEnableOption) (string, error) {
sts, err := opts.KubeClientSet.AppsV1().StatefulSets(opts.Namespace).Get(context.TODO(), etcdStatefulSetAndServiceName, metav1.GetOptions{})
const (
etcdServerArgPrefix = "--etcd-servers="
etcdServerArgPrefixLength = len(etcdServerArgPrefix)
etcdKeyPrefixArgPrefix = "--etcd-prefix="
etcdKeyPrefixArgPrefixLength = len(etcdKeyPrefixArgPrefix)
)

func getExternalEtcdServerConfig(ctx context.Context, host kubernetes.Interface, namespace string) (servers, prefix string, err error) {
var apiserver *appsv1.Deployment
if apiserver, err = host.AppsV1().Deployments(namespace).Get(
ctx, karmadaAPIServerDeploymentAndServiceName, metav1.GetOptions{}); err != nil {
return
}
// should be only one container, but it may be injected others by mutating webhook of host cluster,
// anyway, a for can handle all cases.
for _, container := range apiserver.Spec.Template.Spec.Containers {
if container.Name == karmadaAPIServerDeploymentAndServiceName {
tedli marked this conversation as resolved.
Show resolved Hide resolved
for _, cmd := range container.Command {
if strings.HasPrefix(etcdServerArgPrefix, cmd) {
servers = cmd[etcdServerArgPrefixLength:]
} else if strings.HasPrefix(etcdKeyPrefixArgPrefix, cmd) {
prefix = cmd[etcdKeyPrefixArgPrefixLength:]
}
if servers != "" && prefix != "" {
break
}
}
return
}
}
return
}

func etcdServers(opts *addoninit.CommandAddonsEnableOption) (string, string, error) {
ctx := context.TODO()
sts, err := opts.KubeClientSet.AppsV1().StatefulSets(opts.Namespace).Get(ctx, etcdStatefulSetAndServiceName, metav1.GetOptions{})
if err != nil {
return "", err
if apierrors.IsNotFound(err) {
if servers, prefix, cfgErr := getExternalEtcdServerConfig(ctx, opts.KubeClientSet, opts.Namespace); cfgErr != nil {
return "", "", cfgErr
} else if servers != "" {
return servers, prefix, nil
}
}
return "", "", err
}

etcdReplicas := *sts.Spec.Replicas
Expand All @@ -225,5 +270,5 @@ func etcdServers(opts *addoninit.CommandAddonsEnableOption) (string, error) {
etcdServers += fmt.Sprintf("https://%s-%v.%s.%s.svc.%s:%v", etcdStatefulSetAndServiceName, v, etcdStatefulSetAndServiceName, opts.Namespace, opts.HostClusterDomain, etcdContainerClientPort) + ","
}

return strings.TrimRight(etcdServers, ","), nil
return strings.TrimRight(etcdServers, ","), "", nil
}
4 changes: 4 additions & 0 deletions pkg/karmadactl/cmdinit/cert/cert.go
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,10 @@ func GenCerts(pkiPath string, etcdServerCertCfg, etcdClientCertCfg, karmadaCertC
return err
}

if etcdServerCertCfg == nil && etcdClientCertCfg == nil {
// use external etcd
return nil
}
return genEtcdCerts(pkiPath, etcdServerCertCfg, etcdClientCertCfg)
}

Expand Down
5 changes: 5 additions & 0 deletions pkg/karmadactl/cmdinit/cmdinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ func NewCmdInit(parentCommand string) *cobra.Command {
flags.StringVarP(&opts.EtcdHostDataPath, "etcd-data", "", "/var/lib/karmada-etcd", "etcd data path,valid in hostPath mode.")
flags.StringVarP(&opts.EtcdNodeSelectorLabels, "etcd-node-selector-labels", "", "", "etcd pod select the labels of the node. valid in hostPath mode ( e.g. --etcd-node-selector-labels karmada.io/etcd=true)")
flags.StringVarP(&opts.EtcdPersistentVolumeSize, "etcd-pvc-size", "", "5Gi", "etcd data path,valid in pvc mode.")
flags.StringVar(&opts.ExternalEtcdCACertPath, "external-etcd-ca-cert-path", "", "The path of CA certificate of the external etcd cluster in pem format.")
flags.StringVar(&opts.ExternalEtcdClientCertPath, "external-etcd-client-cert-path", "", "The path of client side certificate to the external etcd cluster in pem format.")
flags.StringVar(&opts.ExternalEtcdClientKeyPath, "external-etcd-client-key-path", "", "The path of client side private key to the external etcd cluster in pem format.")
flags.StringVar(&opts.ExternalEtcdServers, "external-etcd-servers", "", "The server urls of external etcd cluster, to be used by kube-apiserver through --etcd-servers.")
flags.StringVar(&opts.ExternalEtcdKeyPrefix, "external-etcd-key-prefix", "", "The key prefix to be configured to kube-apiserver through --etcd-prefix.")
tedli marked this conversation as resolved.
Show resolved Hide resolved
// karmada
flags.StringVar(&opts.CRDs, "crds", kubernetes.DefaultCrdURL, "Karmada crds resource.(local file e.g. --crds /root/crds.tar.gz)")
flags.StringVarP(&opts.KarmadaAPIServerAdvertiseAddress, "karmada-apiserver-advertise-address", "", "", "The IP address the Karmada API Server will advertise it's listening on. If not set, the address on the master node will be used.")
Expand Down
131 changes: 96 additions & 35 deletions pkg/karmadactl/cmdinit/kubernetes/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,25 @@
options.FrontProxyClientCertAndKeyName,
}

emptyByteSlice = make([]byte, 0)
externalEtcdCertSpecialization = map[string]func(*CommandInitOption) ([]byte, []byte, error){
options.EtcdCaCertAndKeyName: func(option *CommandInitOption) (cert, key []byte, err error) {
cert, err = os.ReadFile(option.ExternalEtcdCACertPath)
RainbowMango marked this conversation as resolved.
Show resolved Hide resolved
key = emptyByteSlice
return
},
options.EtcdServerCertAndKeyName: func(_ *CommandInitOption) ([]byte, []byte, error) {
return emptyByteSlice, emptyByteSlice, nil
},
options.EtcdClientCertAndKeyName: func(option *CommandInitOption) (cert, key []byte, err error) {
if cert, err = os.ReadFile(option.ExternalEtcdClientCertPath); err != nil {
return
}
key, err = os.ReadFile(option.ExternalEtcdClientKeyPath)
return
},
}

karmadaRelease string

defaultEtcdImage = "etcd:3.5.9-0"
Expand Down Expand Up @@ -98,6 +117,11 @@
EtcdHostDataPath string
EtcdNodeSelectorLabels string
EtcdPersistentVolumeSize string
ExternalEtcdCACertPath string
ExternalEtcdClientCertPath string
ExternalEtcdClientKeyPath string
ExternalEtcdServers string
ExternalEtcdKeyPrefix string
KarmadaAPIServerImage string
KarmadaAPIServerReplicas int32
KarmadaAPIServerAdvertiseAddress string
Expand Down Expand Up @@ -131,16 +155,7 @@
WaitComponentReadyTimeout int
}

// Validate Check that there are enough flags to run the command.
//
//nolint:gocyclo
func (i *CommandInitOption) Validate(parentCommand string) error {
if i.KarmadaAPIServerAdvertiseAddress != "" {
if netutils.ParseIPSloppy(i.KarmadaAPIServerAdvertiseAddress) == nil {
return fmt.Errorf("karmada apiserver advertise address is not valid")
}
}

func (i *CommandInitOption) validateBundledEtcd(parentCommand string) error {
if i.EtcdStorageMode == etcdStorageModeHostPath && i.EtcdHostDataPath == "" {
return fmt.Errorf("when etcd storage mode is hostPath, dataPath is not empty. See '%s init --help'", parentCommand)
}
Expand Down Expand Up @@ -173,6 +188,36 @@
return nil
}

func (i *CommandInitOption) validateExternalEtcd(_ string) error {
if i.ExternalEtcdCACertPath == "" {
return fmt.Errorf("etcd ca cert path should be specified")
}
if i.ExternalEtcdClientCertPath == "" {
return fmt.Errorf("etcd client cert path should be specified")
}
if i.ExternalEtcdClientKeyPath == "" {
return fmt.Errorf("etcd client cert private key path should be specified")
}
tedli marked this conversation as resolved.
Show resolved Hide resolved
return nil
}

// Validate Check that there are enough flags to run the command.
//
//nolint:gocyclo
func (i *CommandInitOption) Validate(parentCommand string) error {
if i.KarmadaAPIServerAdvertiseAddress != "" {
if netutils.ParseIPSloppy(i.KarmadaAPIServerAdvertiseAddress) == nil {
return fmt.Errorf("karmada apiserver advertise address is not valid")
}
}

if i.ExternalEtcdServers != "" {
return i.validateExternalEtcd(parentCommand)
} else {
return i.validateBundledEtcd(parentCommand)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure whether Bundled is the most appropriate adjective.
External looks easy to associate with Internal.
Let us for others opinion.
Otherwise LGTM.
@chaosi-zju @RainbowMango

Copy link
Contributor

@liangyuanpeng liangyuanpeng Aug 28, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since karmadactl does essentially the similar thing as kubeadm, I usually compare karmadactl with kubeadm, which is divided into etcd.local and etcd.external in kubeadm.

etcd.local and etcd.external also using by Karmada operator, so i would recommend using Local instead of Bundled.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree with @liangyuanpeng.

So, we just need to change the function name, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bundled -> local
done

}
}

// Complete Initialize k8s client
func (i *CommandInitOption) Complete() error {
restConfig, err := apiclient.RestConfig(i.Context, i.KubeConfig)
Expand All @@ -192,7 +237,7 @@
return fmt.Errorf("nodePort of karmada apiserver %v already exist", i.KarmadaAPIServerNodePort)
}

if i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels == "" {
if i.ExternalEtcdServers == "" && i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels == "" {
if err := i.AddNodeSelectorLabels(); err != nil {
return err
}
Expand All @@ -203,7 +248,7 @@
}
klog.Infof("karmada apiserver ip: %s", i.KarmadaAPIServerIP)

if i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels != "" {
if i.ExternalEtcdServers == "" && i.EtcdStorageMode == "hostPath" && i.EtcdNodeSelectorLabels != "" {
if !i.isNodeExist(i.EtcdNodeSelectorLabels) {
return fmt.Errorf("no node found by label %s", i.EtcdNodeSelectorLabels)
}
Expand All @@ -230,19 +275,22 @@
func (i *CommandInitOption) genCerts() error {
notAfter := time.Now().Add(i.CertValidity).UTC()

etcdServerCertDNS := []string{
"localhost",
}
for number := int32(0); number < i.EtcdReplicas; number++ {
etcdServerCertDNS = append(etcdServerCertDNS, fmt.Sprintf("%s-%v.%s.%s.svc.%s",
etcdStatefulSetAndServiceName, number, etcdStatefulSetAndServiceName, i.Namespace, i.HostClusterDomain))
}
etcdServerAltNames := certutil.AltNames{
DNSNames: etcdServerCertDNS,
IPs: []net.IP{utils.StringToNetIP("127.0.0.1")},
var etcdServerCertConfig, etcdClientCertCfg *cert.CertsConfig
if i.ExternalEtcdServers == "" {
etcdServerCertDNS := []string{
"localhost",
}
for number := int32(0); number < i.EtcdReplicas; number++ {
etcdServerCertDNS = append(etcdServerCertDNS, fmt.Sprintf("%s-%v.%s.%s.svc.%s",
etcdStatefulSetAndServiceName, number, etcdStatefulSetAndServiceName, i.Namespace, i.HostClusterDomain))
}
etcdServerAltNames := certutil.AltNames{
DNSNames: etcdServerCertDNS,
IPs: []net.IP{utils.StringToNetIP("127.0.0.1")},
}
etcdServerCertConfig = cert.NewCertConfig("karmada-etcd-server", []string{}, etcdServerAltNames, &notAfter)
etcdClientCertCfg = cert.NewCertConfig("karmada-etcd-client", []string{}, certutil.AltNames{}, &notAfter)
}
etcdServerCertConfig := cert.NewCertConfig("karmada-etcd-server", []string{}, etcdServerAltNames, &notAfter)
etcdClientCertCfg := cert.NewCertConfig("karmada-etcd-client", []string{}, certutil.AltNames{}, &notAfter)

karmadaDNS := []string{
"localhost",
Expand Down Expand Up @@ -357,16 +405,18 @@
}

func (i *CommandInitOption) initKarmadaAPIServer() error {
if err := util.CreateOrUpdateService(i.KubeClientSet, i.makeEtcdService(etcdStatefulSetAndServiceName)); err != nil {
return err
}
klog.Info("Create etcd StatefulSets")
etcdStatefulSet := i.makeETCDStatefulSet()
if _, err := i.KubeClientSet.AppsV1().StatefulSets(i.Namespace).Create(context.TODO(), etcdStatefulSet, metav1.CreateOptions{}); err != nil {
klog.Warning(err)
}
if err := util.WaitForStatefulSetRollout(i.KubeClientSet, etcdStatefulSet, i.WaitComponentReadyTimeout); err != nil {
klog.Warning(err)
if i.ExternalEtcdServers == "" {
tedli marked this conversation as resolved.
Show resolved Hide resolved
if err := util.CreateOrUpdateService(i.KubeClientSet, i.makeEtcdService(etcdStatefulSetAndServiceName)); err != nil {
return err
}
klog.Info("Create etcd StatefulSets")
etcdStatefulSet := i.makeETCDStatefulSet()
if _, err := i.KubeClientSet.AppsV1().StatefulSets(i.Namespace).Create(context.TODO(), etcdStatefulSet, metav1.CreateOptions{}); err != nil {
klog.Warning(err)
}
if err := util.WaitForStatefulSetRollout(i.KubeClientSet, etcdStatefulSet, i.WaitComponentReadyTimeout); err != nil {
klog.Warning(err)
}
}
klog.Info("Create karmada ApiServer Deployment")
if err := util.CreateOrUpdateService(i.KubeClientSet, i.makeKarmadaAPIServerService()); err != nil {
Expand All @@ -388,7 +438,7 @@
klog.Exitln(err)
}
karmadaAggregatedAPIServerDeployment := i.makeKarmadaAggregatedAPIServerDeployment()
if _, err := i.KubeClientSet.AppsV1().Deployments(i.Namespace).Create(context.TODO(), i.makeKarmadaAggregatedAPIServerDeployment(), metav1.CreateOptions{}); err != nil {
if _, err := i.KubeClientSet.AppsV1().Deployments(i.Namespace).Create(context.TODO(), karmadaAggregatedAPIServerDeployment, metav1.CreateOptions{}); err != nil {
klog.Warning(err)
}
if err := util.WaitForDeploymentRollout(i.KubeClientSet, karmadaAggregatedAPIServerDeployment, i.WaitComponentReadyTimeout); err != nil {
Expand Down Expand Up @@ -452,7 +502,7 @@
}

// RunInit Deploy karmada in kubernetes
func (i *CommandInitOption) RunInit(parentCommand string) error {

Check failure on line 505 in pkg/karmadactl/cmdinit/kubernetes/deploy.go

View workflow job for this annotation

GitHub Actions / lint

cyclomatic complexity 16 of func `(*CommandInitOption).RunInit` is high (> 15) (gocyclo)
// generate certificate
if err := i.genCerts(); err != nil {
return fmt.Errorf("certificate generation failed.%v", err)
Expand All @@ -461,6 +511,17 @@
i.CertAndKeyFileData = map[string][]byte{}

for _, v := range certList {
if i.ExternalEtcdServers != "" {
if getCertAndKey, needSpecialization := externalEtcdCertSpecialization[v]; needSpecialization {
if certs, key, err := getCertAndKey(i); err != nil {
return fmt.Errorf("read external etcd certificate failed, %s. %v", v, err)
} else {
i.CertAndKeyFileData[fmt.Sprintf("%s.crt", v)] = certs
i.CertAndKeyFileData[fmt.Sprintf("%s.key", v)] = key
}
continue
}
}
certs, err := utils.FileToBytes(i.KarmadaPkiPath, fmt.Sprintf("%s.crt", v))
if err != nil {
return fmt.Errorf("'%s.crt' conversion failed. %v", v, err)
Expand Down
Loading
Loading