karmada operator: support install karmada on remote cluster

Signed-off-by: calvin <wen.chen@daocloud.io>
This commit is contained in:
calvin 2023-08-09 19:07:57 +08:00
parent 0fe6f1411e
commit fa0884c333
4 changed files with 142 additions and 38 deletions

View File

@ -4,7 +4,10 @@ import (
"context" "context"
"fmt" "fmt"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
"k8s.io/klog/v2" "k8s.io/klog/v2"
"sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/client"
@ -33,6 +36,7 @@ type Planner struct {
client.Client client.Client
karmada *operatorv1alpha1.Karmada karmada *operatorv1alpha1.Karmada
job *workflow.Job job *workflow.Job
config *rest.Config
} }
// NewPlannerFor creates planner, it will recognize the karmada resource action // NewPlannerFor creates planner, it will recognize the karmada resource action
@ -68,6 +72,7 @@ func NewPlannerFor(karmada *operatorv1alpha1.Karmada, c client.Client, config *r
Client: c, Client: c,
job: job, job: job,
action: action, action: action,
config: config,
}, nil }, nil
} }
@ -124,18 +129,44 @@ func (p *Planner) runJobErr(err error) error {
func (p *Planner) afterRunJob() error { func (p *Planner) afterRunJob() error {
if p.action == InitAction { if p.action == InitAction {
// Update the condition to Ready and set kubeconfig of karmada-apiserver to status. // Update the karmada condition to Ready and set kubeconfig of karmada apiserver to karmada status.
operatorv1alpha1.KarmadaCompleted(p.karmada, operatorv1alpha1.Ready, "karmada init job is completed") operatorv1alpha1.KarmadaCompleted(p.karmada, operatorv1alpha1.Ready, "karmada init job is completed")
if !util.IsInCluster(p.karmada.Spec.HostCluster) {
localClusterClient, err := clientset.NewForConfig(p.config)
if err != nil {
return fmt.Errorf("error when creating local cluster client, err: %w", err)
}
remoteClient, err := util.BuildClientFromSecretRef(localClusterClient, p.karmada.Spec.HostCluster.SecretRef)
if err != nil {
return fmt.Errorf("error when creating cluster client to install karmada, err: %w", err)
}
secret, err := remoteClient.CoreV1().Secrets(p.karmada.GetNamespace()).Get(context.TODO(), util.AdminKubeconfigSecretName(p.karmada.GetName()), metav1.GetOptions{})
if err != nil {
return err
}
_, err = localClusterClient.CoreV1().Secrets(p.karmada.GetNamespace()).Create(context.TODO(), &corev1.Secret{
ObjectMeta: metav1.ObjectMeta{
Namespace: p.karmada.GetNamespace(),
Name: util.AdminKubeconfigSecretName(p.karmada.GetName()),
},
Data: secret.Data,
}, metav1.CreateOptions{})
if err != nil {
return err
}
}
p.karmada.Status.SecretRef = &operatorv1alpha1.LocalSecretReference{ p.karmada.Status.SecretRef = &operatorv1alpha1.LocalSecretReference{
Namespace: p.karmada.GetNamespace(), Namespace: p.karmada.GetNamespace(),
Name: util.AdminKubeconfigSecretName(p.karmada.GetName()), Name: util.AdminKubeconfigSecretName(p.karmada.GetName()),
} }
return p.Client.Status().Update(context.TODO(), p.karmada) return p.Client.Status().Update(context.TODO(), p.karmada)
} }
// if it is deInit workflow, the cr will be deleted with karmada is be deleted, so we need not to // if it is deInit workflow, the cr will be deleted with karmada is be deleted, so we need not to
// update the karmada status. // update the karmada status.
return nil return nil
} }

View File

@ -10,6 +10,7 @@ import (
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1" operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
"github.com/karmada-io/karmada/operator/pkg/constants" "github.com/karmada-io/karmada/operator/pkg/constants"
tasks "github.com/karmada-io/karmada/operator/pkg/tasks/deinit" tasks "github.com/karmada-io/karmada/operator/pkg/tasks/deinit"
"github.com/karmada-io/karmada/operator/pkg/util"
"github.com/karmada-io/karmada/operator/pkg/workflow" "github.com/karmada-io/karmada/operator/pkg/workflow"
) )
@ -44,16 +45,21 @@ func NewDeInitDataJob(opt *DeInitOptions) *workflow.Job {
deInitJob.AppendTask(tasks.NewCleanupKubeconfigTask()) deInitJob.AppendTask(tasks.NewCleanupKubeconfigTask())
deInitJob.SetDataInitializer(func() (workflow.RunData, error) { deInitJob.SetDataInitializer(func() (workflow.RunData, error) {
localClusterClient, err := clientset.NewForConfig(opt.Kubeconfig)
if err != nil {
return nil, fmt.Errorf("error when creating local cluster client, err: %w", err)
}
// if there is no endpoint info, we are consider that the local cluster // if there is no endpoint info, we are consider that the local cluster
// is remote cluster to install karmada. // is remote cluster to install karmada.
var remoteClient clientset.Interface var remoteClient clientset.Interface
if opt.HostCluster.SecretRef == nil && len(opt.HostCluster.APIEndpoint) == 0 { if util.IsInCluster(opt.HostCluster) {
client, err := clientset.NewForConfig(opt.Kubeconfig) remoteClient = localClusterClient
} else {
remoteClient, err = util.BuildClientFromSecretRef(localClusterClient, opt.HostCluster.SecretRef)
if err != nil { if err != nil {
return nil, fmt.Errorf("error when create cluster client to install karmada, err: %w", err) return nil, fmt.Errorf("error when creating cluster client to install karmada, err: %w", err)
} }
remoteClient = client
} }
if len(opt.Name) == 0 || len(opt.Namespace) == 0 { if len(opt.Name) == 0 || len(opt.Namespace) == 0 {

View File

@ -7,6 +7,7 @@ import (
"sync" "sync"
corev1 "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
utilversion "k8s.io/apimachinery/pkg/util/version" utilversion "k8s.io/apimachinery/pkg/util/version"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
@ -31,11 +32,42 @@ type InitOptions struct {
Namespace string Namespace string
Kubeconfig *rest.Config Kubeconfig *rest.Config
KarmadaVersion string KarmadaVersion string
CrdRemoteURL string CRDRemoteURL string
KarmadaDataDir string KarmadaDataDir string
Karmada *operatorv1alpha1.Karmada Karmada *operatorv1alpha1.Karmada
} }
// Validate is used to validate the initOptions before creating initJob.
func (opt *InitOptions) Validate() error {
var errs []error
if len(opt.Name) == 0 || len(opt.Namespace) == 0 {
return errors.New("unexpected empty name or namespace")
}
if len(opt.CRDRemoteURL) > 0 {
if _, err := url.Parse(opt.CRDRemoteURL); err != nil {
return fmt.Errorf("unexpected invalid crds remote url %s", opt.CRDRemoteURL)
}
}
if !util.IsInCluster(opt.Karmada.Spec.HostCluster) && opt.Karmada.Spec.Components.KarmadaAPIServer.ServiceType == corev1.ServiceTypeClusterIP {
return fmt.Errorf("if karmada is installed in a remote cluster, the service type of karmada-apiserver must be either NodePort or LoadBalancer")
}
_, err := utilversion.ParseGeneric(opt.KarmadaVersion)
if err != nil {
return fmt.Errorf("unexpected karmada invalid version %s", opt.KarmadaVersion)
}
if opt.Karmada.Spec.Components.Etcd.Local != nil && opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas != nil {
replicas := *opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas
if (replicas % 2) == 0 {
klog.Warningf("invalid etcd replicas %d, expected an odd number", replicas)
}
}
return utilerrors.NewAggregate(errs)
}
// InitOpt defines a type of function to set InitOptions values. // InitOpt defines a type of function to set InitOptions values.
type InitOpt func(o *InitOptions) type InitOpt func(o *InitOptions)
@ -54,7 +86,7 @@ type initData struct {
remoteClient clientset.Interface remoteClient clientset.Interface
karmadaClient clientset.Interface karmadaClient clientset.Interface
dnsDomain string dnsDomain string
crdRemoteURL string CRDRemoteURL string
karmadaDataDir string karmadaDataDir string
privateRegistry string privateRegistry string
featureGates map[string]bool featureGates map[string]bool
@ -89,16 +121,25 @@ func NewInitJob(opt *InitOptions) *workflow.Job {
} }
func newRunData(opt *InitOptions) (*initData, error) { func newRunData(opt *InitOptions) (*initData, error) {
// if there is no endpoint info, we are consider that the local cluster if err := opt.Validate(); err != nil {
return nil, err
}
localClusterClient, err := clientset.NewForConfig(opt.Kubeconfig)
if err != nil {
return nil, fmt.Errorf("error when creating local cluster client, err: %w", err)
}
// if there is no endpoint message, we are consider that the local cluster
// is remote cluster to install karmada. // is remote cluster to install karmada.
var remoteClient clientset.Interface var remoteClient clientset.Interface
if opt.Karmada.Spec.HostCluster.SecretRef == nil && len(opt.Karmada.Spec.HostCluster.APIEndpoint) == 0 { if util.IsInCluster(opt.Karmada.Spec.HostCluster) {
client, err := clientset.NewForConfig(opt.Kubeconfig) remoteClient = localClusterClient
} else {
remoteClient, err = util.BuildClientFromSecretRef(localClusterClient, opt.Karmada.Spec.HostCluster.SecretRef)
if err != nil { if err != nil {
return nil, fmt.Errorf("error when create cluster client to install karmada, err: %w", err) return nil, fmt.Errorf("error when creating cluster client to install karmada, err: %w", err)
} }
remoteClient = client
} }
var privateRegistry string var privateRegistry string
@ -106,29 +147,11 @@ func newRunData(opt *InitOptions) (*initData, error) {
privateRegistry = opt.Karmada.Spec.PrivateRegistry.Registry privateRegistry = opt.Karmada.Spec.PrivateRegistry.Registry
} }
if len(opt.Name) == 0 || len(opt.Namespace) == 0 {
return nil, errors.New("unexpected empty name or namespace")
}
version, err := utilversion.ParseGeneric(opt.KarmadaVersion) version, err := utilversion.ParseGeneric(opt.KarmadaVersion)
if err != nil { if err != nil {
return nil, fmt.Errorf("unexpected karmada invalid version %s", opt.KarmadaVersion) return nil, fmt.Errorf("unexpected karmada invalid version %s", opt.KarmadaVersion)
} }
if len(opt.CrdRemoteURL) > 0 {
if _, err := url.Parse(opt.CrdRemoteURL); err != nil {
return nil, fmt.Errorf("unexpected invalid crds remote url %s", opt.CrdRemoteURL)
}
}
if opt.Karmada.Spec.Components.Etcd.Local != nil && opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas != nil {
replicas := *opt.Karmada.Spec.Components.Etcd.Local.CommonSettings.Replicas
if (replicas % 2) == 0 {
klog.Warningf("invalid etcd replicas %d, expected an odd number", replicas)
}
}
// TODO: Verify whether important values of initData is valid // TODO: Verify whether important values of initData is valid
var address string var address string
if opt.Karmada.Spec.Components.KarmadaAPIServer.ServiceType == corev1.ServiceTypeNodePort { if opt.Karmada.Spec.Components.KarmadaAPIServer.ServiceType == corev1.ServiceTypeNodePort {
@ -144,7 +167,7 @@ func newRunData(opt *InitOptions) (*initData, error) {
karmadaVersion: version, karmadaVersion: version,
controlplaneAddress: address, controlplaneAddress: address,
remoteClient: remoteClient, remoteClient: remoteClient,
crdRemoteURL: opt.CrdRemoteURL, CRDRemoteURL: opt.CRDRemoteURL,
karmadaDataDir: opt.KarmadaDataDir, karmadaDataDir: opt.KarmadaDataDir,
privateRegistry: privateRegistry, privateRegistry: privateRegistry,
components: opt.Karmada.Spec.Components, components: opt.Karmada.Spec.Components,
@ -197,7 +220,7 @@ func (data *initData) DataDir() string {
} }
func (data *initData) CrdsRemoteURL() string { func (data *initData) CrdsRemoteURL() string {
return data.crdRemoteURL return data.CRDRemoteURL
} }
func (data *initData) KarmadaVersion() string { func (data *initData) KarmadaVersion() string {
@ -230,7 +253,7 @@ func defaultJobInitOptions() *InitOptions {
operatorscheme.Scheme.Default(karmada) operatorscheme.Scheme.Default(karmada)
return &InitOptions{ return &InitOptions{
CrdRemoteURL: fmt.Sprintf(defaultCrdURL, operatorv1alpha1.DefaultKarmadaImageVersion), CRDRemoteURL: fmt.Sprintf(defaultCrdURL, operatorv1alpha1.DefaultKarmadaImageVersion),
KarmadaVersion: operatorv1alpha1.DefaultKarmadaImageVersion, KarmadaVersion: operatorv1alpha1.DefaultKarmadaImageVersion,
KarmadaDataDir: constants.KarmadaDataDir, KarmadaDataDir: constants.KarmadaDataDir,
Karmada: karmada, Karmada: karmada,

View File

@ -1,9 +1,15 @@
package util package util
import ( import (
"context"
"fmt" "fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api" clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
operatorv1alpha1 "github.com/karmada-io/karmada/operator/pkg/apis/operator/v1alpha1"
) )
// CreateWithCerts creates a KubeConfig object with access to the API server with client certificates // CreateWithCerts creates a KubeConfig object with access to the API server with client certificates
@ -38,3 +44,41 @@ func CreateBasic(serverURL, clusterName, userName string, caCert []byte) *client
CurrentContext: contextName, CurrentContext: contextName,
} }
} }
// IsInCluster returns a bool represents whether the remote cluster is the local or not.
func IsInCluster(hostCluster *operatorv1alpha1.HostCluster) bool {
return hostCluster == nil || hostCluster.SecretRef == nil || len(hostCluster.SecretRef.Name) == 0
}
func BuildClientFromSecretRef(client *clientset.Clientset, ref *operatorv1alpha1.LocalSecretReference) (*clientset.Clientset, error) {
secret, err := client.CoreV1().Secrets(ref.Namespace).Get(context.TODO(), ref.Name, metav1.GetOptions{})
if err != nil {
return nil, err
}
kubeconfigBytes, ok := secret.Data["kubeconfig"]
if !ok {
return nil, fmt.Errorf("the kubeconfig or data key 'kubeconfig' is not found, please check the secret %s/%s", secret.Namespace, secret.Name)
}
return newClientSetForConfig(kubeconfigBytes)
}
func newClientSetForConfig(kubeconfig []byte) (*clientset.Clientset, error) {
clientConfig, err := clientcmd.NewClientConfigFromBytes(kubeconfig)
if err != nil {
return nil, err
}
config, err := clientConfig.ClientConfig()
if err != nil {
return nil, err
}
client, err := clientset.NewForConfig(config)
if err != nil {
return nil, err
}
return client, nil
}