-
Notifications
You must be signed in to change notification settings - Fork 225
Handling configurable machine setup/installation #664
Changes from 17 commits
5cd6e74
6bc01dd
242d193
9b9a8b9
bd5d0ec
a2782a1
dfc5f96
9c5614b
88ceb5d
c79b5a0
7e00e5b
1cc6a4d
078e7e8
5c450a0
e1d2b28
6eea28e
66a419f
e829e94
c124da1
c1c88ef
1cfe66b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,5 +27,5 @@ type GCEProviderConfig struct { | |
Project string `json:"project"` | ||
Zone string `json:"zone"` | ||
MachineType string `json:"machineType"` | ||
Image string `json:"image"` | ||
OS string `json:"os"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this OS image or OS name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should be the name, it gets mapped to the image in the machine setup configs. I added a comment. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -27,5 +27,5 @@ type GCEProviderConfig struct { | |
Project string `json:"project"` | ||
Zone string `json:"zone"` | ||
MachineType string `json:"machineType"` | ||
Image string `json:"image"` | ||
OS string `json:"os"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as above. |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,8 +37,12 @@ import ( | |
|
||
"regexp" | ||
|
||
corev1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes" | ||
gceconfig "k8s.io/kube-deploy/cluster-api/cloud/google/gceproviderconfig" | ||
gceconfigv1 "k8s.io/kube-deploy/cluster-api/cloud/google/gceproviderconfig/v1alpha1" | ||
"k8s.io/kube-deploy/cluster-api/cloud/google/machinesetup" | ||
apierrors "k8s.io/kube-deploy/cluster-api/errors" | ||
clusterv1 "k8s.io/kube-deploy/cluster-api/pkg/apis/cluster/v1alpha1" | ||
client "k8s.io/kube-deploy/cluster-api/pkg/client/clientset_generated/clientset/typed/cluster/v1alpha1" | ||
|
@@ -52,6 +56,8 @@ const ( | |
|
||
UIDLabelKey = "machine-crd-uid" | ||
BootstrapLabelKey = "boostrap" | ||
|
||
MachineSetupConfigsFilename = "machine_setup_configs.yaml" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Please add a comment about what this file contains and in what format. |
||
) | ||
|
||
type SshCreds struct { | ||
|
@@ -66,14 +72,15 @@ type GCEClient struct { | |
kubeadmToken string | ||
sshCreds SshCreds | ||
machineClient client.MachineInterface | ||
configWatch *machinesetup.ConfigWatch | ||
} | ||
|
||
const ( | ||
gceTimeout = time.Minute * 10 | ||
gceWaitSleep = time.Second * 5 | ||
) | ||
|
||
func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterface) (*GCEClient, error) { | ||
func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterface, configListPath string) (*GCEClient, error) { | ||
// The default GCP client expects the environment variable | ||
// GOOGLE_APPLICATION_CREDENTIALS to point to a file with service credentials. | ||
client, err := google.DefaultClient(context.TODO(), compute.ComputeScope) | ||
|
@@ -104,6 +111,15 @@ func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterfa | |
} | ||
} | ||
|
||
// TODO: get rid of empty string check when we switch to the new bootstrapping method. | ||
var configWatch *machinesetup.ConfigWatch | ||
if configListPath != "" { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think this check is necessary. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Create is the only command that has to pass a config list path, and then add and delete pass an empty string to the method. I put this check here to handle the empty string from add and delete, and create has a check to prevent an empty string. NewConfigWatch() checks that the path is valid. I can move the empty string check to NewConfigWatch() so it just returns nil (but no error) if it gets an empty string if that makes more sense. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair enough. Can we add TODOs to remove this when we switch to the new bootstrapping method. In that setup, we will be running a full-blown controller that will always need a config path. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So would add/delete also need to pass the config path when we switch over (and I should add TODOs there too)? |
||
configWatch, err = machinesetup.NewConfigWatch(configListPath) | ||
if err != nil { | ||
glog.Errorf("Error creating config watch: %v", err) | ||
} | ||
} | ||
|
||
return &GCEClient{ | ||
service: service, | ||
scheme: scheme, | ||
|
@@ -114,10 +130,11 @@ func NewMachineActuator(kubeadmToken string, machineClient client.MachineInterfa | |
user: user, | ||
}, | ||
machineClient: machineClient, | ||
configWatch: configWatch, | ||
}, nil | ||
} | ||
|
||
func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine) error { | ||
func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initialMachines []*clusterv1.Machine, clientSet kubernetes.Clientset) error { | ||
if err := gce.CreateMachineControllerServiceAccount(cluster, initialMachines); err != nil { | ||
return err | ||
} | ||
|
@@ -131,6 +148,26 @@ func (gce *GCEClient) CreateMachineController(cluster *clusterv1.Cluster, initia | |
return err | ||
} | ||
|
||
// Create the configmap so the machine setup configs can be mounted into the node. | ||
machineSetupConfigs, err := gce.configWatch.ValidConfigs() | ||
if err != nil { | ||
return err | ||
} | ||
yaml, err := machineSetupConfigs.GetYaml() | ||
if err != nil { | ||
return err | ||
} | ||
configMap := corev1.ConfigMap{ | ||
ObjectMeta: v1.ObjectMeta{Name: "machine-setup"}, | ||
Data: map[string]string{ | ||
MachineSetupConfigsFilename: yaml, | ||
}, | ||
} | ||
configMaps := clientSet.CoreV1().ConfigMaps(corev1.NamespaceDefault) | ||
if _, err := configMaps.Create(&configMap); err != nil { | ||
return err | ||
} | ||
|
||
if err := CreateApiServerAndController(gce.kubeadmToken); err != nil { | ||
return err | ||
} | ||
|
@@ -153,22 +190,32 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach | |
return errors.New("invalid master configuration: missing Machine.Spec.Versions.Kubelet") | ||
} | ||
|
||
image, preloaded := gce.getImage(machine, config) | ||
machineSetupConfigs, err := gce.configWatch.ValidConfigs() | ||
if err != nil { | ||
return err | ||
} | ||
configParams := &machinesetup.ConfigParams{ | ||
OS: config.OS, | ||
Roles: machine.Spec.Roles, | ||
Versions: machine.Spec.Versions, | ||
} | ||
image, err := machineSetupConfigs.GetImage(configParams) | ||
if err != nil { | ||
return err | ||
} | ||
imagePath := gce.getImagePath(image) | ||
|
||
machineSetupMetadata, err := machineSetupConfigs.GetMetadata(configParams) | ||
if err != nil { | ||
return err | ||
} | ||
if util.IsMaster(machine) { | ||
if machine.Spec.Versions.ControlPlane == "" { | ||
return gce.handleMachineError(machine, apierrors.InvalidMachineConfiguration( | ||
"invalid master configuration: missing Machine.Spec.Versions.ControlPlane")) | ||
} | ||
var err error | ||
metadata, err = masterMetadata( | ||
templateParams{ | ||
Token: gce.kubeadmToken, | ||
Cluster: cluster, | ||
Machine: machine, | ||
Preloaded: preloaded, | ||
}, | ||
) | ||
metadata, err = masterMetadata(gce.kubeadmToken, cluster, machine, config.Project, &machineSetupMetadata) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -177,14 +224,7 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach | |
return errors.New("invalid cluster state: cannot create a Kubernetes node without an API endpoint") | ||
} | ||
var err error | ||
metadata, err = nodeMetadata( | ||
templateParams{ | ||
Token: gce.kubeadmToken, | ||
Cluster: cluster, | ||
Machine: machine, | ||
Preloaded: preloaded, | ||
}, | ||
) | ||
metadata, err = nodeMetadata(gce.kubeadmToken, cluster, machine, &machineSetupMetadata) | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -207,13 +247,7 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach | |
name := machine.ObjectMeta.Name | ||
project := config.Project | ||
zone := config.Zone | ||
diskSize := int64(10) | ||
|
||
// Our preloaded image already has a lot stored on it, so increase the | ||
// disk size to have more free working space. | ||
if preloaded { | ||
diskSize = 30 | ||
} | ||
diskSize := int64(30) | ||
|
||
if instance == nil { | ||
labels := map[string]string{ | ||
|
@@ -242,7 +276,7 @@ func (gce *GCEClient) Create(cluster *clusterv1.Cluster, machine *clusterv1.Mach | |
AutoDelete: true, | ||
Boot: true, | ||
InitializeParams: &compute.AttachedDiskInitializeParams{ | ||
SourceImage: image, | ||
SourceImage: imagePath, | ||
DiskSizeGb: diskSize, | ||
}, | ||
}, | ||
|
@@ -653,42 +687,29 @@ func (gce *GCEClient) handleMachineError(machine *clusterv1.Machine, err *apierr | |
return err | ||
} | ||
|
||
func (gce *GCEClient) getImage(machine *clusterv1.Machine, config *gceconfig.GCEProviderConfig) (image string, isPreloaded bool) { | ||
func (gce *GCEClient) getImagePath(img string) (imagePath string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a great method for testing with unit tests. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree, but it makes a call to the GCE API to check that the image is there and I was having trouble figuring out how to mock it (otherwise I would have added the test when I refactored this method in a previous PR). Rob is working on a PR (#688) to set up a fake client so I can write a unit test for this method once that gets merged. |
||
defaultImg := "projects/ubuntu-os-cloud/global/images/family/ubuntu-1710" | ||
project := config.Project | ||
img := config.Image | ||
|
||
// A full image path must match the regex format. If it doesn't, we'll assume it's just the image name and try to get it. | ||
// If that doesn't work, we will fall back to a default base image. | ||
// A full image path must match the regex format. If it doesn't, we will fall back to a default base image. | ||
matches := regexp.MustCompile("projects/(.+)/global/images/(family/)*(.+)").FindStringSubmatch(img) | ||
if matches == nil { | ||
// Only the image name was specified in config, so check if it is preloaded in the project specified in config. | ||
fullPath := fmt.Sprintf("projects/%s/global/images/%s", project, img) | ||
if _, err := gce.service.Images.Get(project, img).Do(); err == nil { | ||
return fullPath, false | ||
if matches != nil { | ||
// Check to see if the image exists in the given path. The presence of "family" in the path dictates which API call we need to make. | ||
project, family, name := matches[1], matches[2], matches[3] | ||
var err error | ||
if family == "" { | ||
_, err = gce.service.Images.Get(project, name).Do() | ||
} else { | ||
_, err = gce.service.Images.GetFromFamily(project, name).Do() | ||
} | ||
|
||
// Otherwise, fall back to the non-preloaded base image. | ||
glog.Infof("Could not find image at %s. Defaulting to %s.", fullPath, defaultImg) | ||
return defaultImg, false | ||
} | ||
|
||
// Check to see if the image exists in the given path. The presence of "family" in the path dictates which API call we need to make. | ||
project, family, name := matches[1], matches[2], matches[3] | ||
var err error | ||
if family == "" { | ||
_, err = gce.service.Images.Get(project, name).Do() | ||
} else { | ||
_, err = gce.service.Images.GetFromFamily(project, name).Do() | ||
} | ||
|
||
if err == nil { | ||
return img, false | ||
if err == nil { | ||
return img | ||
} | ||
} | ||
|
||
// Otherwise, fall back to the non-preloaded base image. | ||
// Otherwise, fall back to the base image. | ||
glog.Infof("Could not find image at %s. Defaulting to %s.", img, defaultImg) | ||
return defaultImg, false | ||
return defaultImg | ||
} | ||
|
||
// Just a temporary hack to grab a single range from the config. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Call it this something other than
config
please. There are way too many things namedconfig
. Be specific here.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I named it machinesetup to be consistent with the flag in create.go.