AB#2074: Choosable K8S Version (#277)

AB#2074: Add configurable k8s version

Configurable version flow:
* cli config holds/validates k8sVersion
* InitCluster receive a k8sVersion arg
* InitCluster creates CM "k8s-version"
* kubeadm's InitConfiguration receives k8sVersion
* joinservice spec mounts/reads k8s-version CM
* joinservice supplies k8sVersion via JoinTicketResponse
Other changes:
* Remove unused test code (FakeK8SClient)
* move VersionConfig map to /internal/versions
* installk8sComponents is now a function instead of a method
This commit is contained in:
Otto Bittner 2022-07-18 12:28:02 +02:00 committed by GitHub
parent d3466da393
commit a68ee817ff
31 changed files with 360 additions and 191 deletions

View file

@ -16,8 +16,11 @@ import (
attestationtypes "github.com/edgelesssys/constellation/internal/attestation/types"
"github.com/edgelesssys/constellation/internal/cloud/metadata"
"github.com/edgelesssys/constellation/internal/logger"
"github.com/edgelesssys/constellation/internal/versions"
"github.com/spf13/afero"
"go.uber.org/zap"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeadm "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
)
@ -28,7 +31,7 @@ type configReader interface {
// configurationProvider provides kubeadm init and join configuration.
type configurationProvider interface {
InitConfiguration(externalCloudProvider bool) k8sapi.KubeadmInitYAML
InitConfiguration(externalCloudProvider bool, k8sVersion string) k8sapi.KubeadmInitYAML
JoinConfiguration(externalCloudProvider bool) k8sapi.KubeadmJoinYAML
}
@ -79,7 +82,6 @@ func (k *KubeWrapper) InitCluster(
ctx context.Context, autoscalingNodeGroups []string, cloudServiceAccountURI, k8sVersion string,
id attestationtypes.ID, kmsConfig KMSConfig, sshUsers map[string]string, log *logger.Logger,
) ([]byte, error) {
// TODO: k8s version should be user input
log.With(zap.String("version", k8sVersion)).Infof("Installing Kubernetes components")
if err := k.clusterUtil.InstallComponents(ctx, k8sVersion); err != nil {
return nil, err
@ -149,7 +151,7 @@ func (k *KubeWrapper) InitCluster(
).Infof("Setting information for node")
// Step 2: configure kubeadm init config
initConfig := k.configProvider.InitConfiguration(k.cloudControllerManager.Supported())
initConfig := k.configProvider.InitConfiguration(k.cloudControllerManager.Supported(), k8sVersion)
initConfig.SetNodeIP(nodeIP)
initConfig.SetCertSANs([]string{publicIP, nodeIP})
initConfig.SetNodeName(nodeName)
@ -219,16 +221,21 @@ func (k *KubeWrapper) InitCluster(
}
}
// Store the received k8sVersion in a ConfigMap, overwriting exisiting values (there shouldn't be any).
// Joining nodes determine the kubernetes version they will install based on this ConfigMap.
if err := k.setupK8sVersionConfigMap(ctx, k8sVersion, true); err != nil {
return nil, fmt.Errorf("failed to setup k8s version ConfigMap: %v", err)
}
k.clusterUtil.FixCilium(nodeName)
return k.GetKubeconfig()
}
// JoinCluster joins existing Kubernetes cluster.
func (k *KubeWrapper) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, peerRole role.Role, log *logger.Logger) error {
// TODO: k8s version should be user input
log.With(zap.String("version", "1.23.6")).Infof("Installing Kubernetes components")
if err := k.clusterUtil.InstallComponents(ctx, "1.23.6"); err != nil {
func (k *KubeWrapper) JoinCluster(ctx context.Context, args *kubeadm.BootstrapTokenDiscovery, peerRole role.Role, k8sVersion string, log *logger.Logger) error {
log.With(zap.String("version", k8sVersion)).Infof("Installing Kubernetes components")
if err := k.clusterUtil.InstallComponents(ctx, k8sVersion); err != nil {
return err
}
@ -357,6 +364,35 @@ func (k *KubeWrapper) setupClusterAutoscaler(instance metadata.InstanceMetadata,
return nil
}
// setupK8sVersionConfigMap applies a ConfigMap (cf. server-side apply) to consistently store the installed k8s version.
func (k *KubeWrapper) setupK8sVersionConfigMap(ctx context.Context, k8sVersion string, forceConflicts bool) error {
if !versions.IsSupportedK8sVersion(k8sVersion) {
return fmt.Errorf("supplied k8s version is not supported: %v", k8sVersion)
}
config := corev1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "k8s-version",
Namespace: "kube-system",
},
Data: map[string]string{
"K8sVersion": k8sVersion,
},
}
// We do not use the client's Apply method here since we are handling a kubernetes-native type.
// These types don't implement our custom Marshaler interface.
if err := k.client.CreateConfigMap(ctx, config); err != nil {
return fmt.Errorf("Apply in KubeWrapper.setupK8sVersionConfigMap(..) failed with: %v", err)
}
return nil
}
// manuallySetLoadbalancerIP sets the loadbalancer IP of the first control plane during init.
// The GCP guest agent does this usually, but is deployed in the cluster that doesn't exist
// at this point. This is a workaround to set the loadbalancer IP manually, so kubeadm and kubelet
@ -391,22 +427,3 @@ func k8sCompliantHostname(in string) string {
func (k *KubeWrapper) StartKubelet() error {
return k.clusterUtil.StartKubelet()
}
type fakeK8SClient struct {
kubeconfig []byte
}
// NewFakeK8SClient creates a new, fake k8s client where apply always works.
func NewFakeK8SClient([]byte) (k8sapi.Client, error) {
return &fakeK8SClient{}, nil
}
// Apply fakes applying Kubernetes resources.
func (f *fakeK8SClient) Apply(resources resources.Marshaler, forceConflicts bool) error {
return nil
}
// SetKubeconfig stores the kubeconfig given to it.
func (f *fakeK8SClient) SetKubeconfig(kubeconfig []byte) {
f.kubeconfig = kubeconfig
}