mirror of https://github.com/kubernetes/kops.git
271 lines
7.6 KiB
Go
271 lines
7.6 KiB
Go
/*
|
|
Copyright 2019 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package model
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/base64"
|
|
"fmt"
|
|
"sort"
|
|
|
|
"k8s.io/klog/v2"
|
|
"k8s.io/kops/pkg/apis/kops"
|
|
"k8s.io/kops/pkg/apis/nodeup"
|
|
"k8s.io/kops/pkg/assets"
|
|
"k8s.io/kops/pkg/model/resources"
|
|
"k8s.io/kops/pkg/wellknownservices"
|
|
"k8s.io/kops/upup/pkg/fi"
|
|
"k8s.io/kops/upup/pkg/fi/fitasks"
|
|
"k8s.io/kops/upup/pkg/fi/utils"
|
|
"k8s.io/kops/util/pkg/architectures"
|
|
)
|
|
|
|
type NodeUpConfigBuilder interface {
|
|
BuildConfig(ig *kops.InstanceGroup, wellKnownAddresses WellKnownAddresses, keysets map[string]*fi.Keyset) (*nodeup.Config, *nodeup.BootConfig, error)
|
|
}
|
|
|
|
// WellKnownAddresses holds known addresses for well-known services
|
|
type WellKnownAddresses map[wellknownservices.WellKnownService][]string
|
|
|
|
// BootstrapScriptBuilder creates the bootstrap script
|
|
type BootstrapScriptBuilder struct {
|
|
*KopsModelContext
|
|
Lifecycle fi.Lifecycle
|
|
NodeUpAssets map[architectures.Architecture]*assets.MirroredAsset
|
|
NodeUpConfigBuilder NodeUpConfigBuilder
|
|
}
|
|
|
|
type BootstrapScript struct {
|
|
Name string
|
|
Lifecycle fi.Lifecycle
|
|
cluster *kops.Cluster
|
|
ig *kops.InstanceGroup
|
|
builder *BootstrapScriptBuilder
|
|
resource fi.CloudupTaskDependentResource
|
|
|
|
// hasAddressTasks holds fi.HasAddress tasks, that contribute well-known services.
|
|
hasAddressTasks []fi.HasAddress
|
|
|
|
// caTasks hold the CA tasks, for dependency analysis.
|
|
caTasks map[string]*fitasks.Keypair
|
|
|
|
// nodeupConfig contains the nodeup config.
|
|
nodeupConfig fi.CloudupTaskDependentResource
|
|
}
|
|
|
|
var (
|
|
_ fi.CloudupTask = &BootstrapScript{}
|
|
_ fi.HasName = &BootstrapScript{}
|
|
_ fi.CloudupHasDependencies = &BootstrapScript{}
|
|
)
|
|
|
|
// kubeEnv returns the boot config for the instance group
|
|
func (b *BootstrapScript) kubeEnv(ig *kops.InstanceGroup, c *fi.CloudupContext) (*nodeup.BootConfig, error) {
|
|
wellKnownAddresses := make(WellKnownAddresses)
|
|
|
|
for _, hasAddress := range b.hasAddressTasks {
|
|
addresses, err := hasAddress.FindAddresses(c)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error finding address for %v: %v", hasAddress, err)
|
|
}
|
|
if len(addresses) == 0 {
|
|
// Such tasks won't have an address in dry-run mode, until the resource is created
|
|
klog.V(2).Infof("Task did not have an address: %v", hasAddress)
|
|
continue
|
|
}
|
|
|
|
klog.V(8).Infof("Resolved alternateNames %q for %q", addresses, hasAddress)
|
|
|
|
for _, wellKnownService := range hasAddress.GetWellKnownServices() {
|
|
wellKnownAddresses[wellKnownService] = append(wellKnownAddresses[wellKnownService], addresses...)
|
|
}
|
|
}
|
|
|
|
for k := range wellKnownAddresses {
|
|
sort.Strings(wellKnownAddresses[k])
|
|
}
|
|
|
|
keysets := make(map[string]*fi.Keyset)
|
|
for _, caTask := range b.caTasks {
|
|
name := *caTask.Name
|
|
keyset := caTask.Keyset()
|
|
if keyset == nil {
|
|
return nil, fmt.Errorf("failed to get keyset from %q", name)
|
|
}
|
|
keysets[name] = keyset
|
|
}
|
|
config, bootConfig, err := b.builder.NodeUpConfigBuilder.BuildConfig(ig, wellKnownAddresses, keysets)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
configData, err := utils.YamlMarshal(config)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("error converting nodeup config to yaml: %v", err)
|
|
}
|
|
sum256 := sha256.Sum256(configData)
|
|
bootConfig.NodeupConfigHash = base64.StdEncoding.EncodeToString(sum256[:])
|
|
b.nodeupConfig.Resource = fi.NewBytesResource(configData)
|
|
|
|
return bootConfig, nil
|
|
}
|
|
|
|
func KeypairNamesForInstanceGroup(cluster *kops.Cluster, ig *kops.InstanceGroup) []string {
|
|
keypairs := []string{"kubernetes-ca"}
|
|
|
|
// Add keypairs for default etcd clusters (main and events, not cilium)
|
|
if ig.IsControlPlane() {
|
|
for _, etcdCluster := range cluster.Spec.EtcdClusters {
|
|
k := etcdCluster.Name
|
|
if k != "events" && k != "main" {
|
|
// Likely cilium
|
|
continue
|
|
}
|
|
keypairs = append(keypairs, "etcd-manager-ca-"+k, "etcd-peers-ca-"+k)
|
|
// The client ca certificate is shared between events and main etcd clusters
|
|
keypairs = append(keypairs, "etcd-clients-ca")
|
|
}
|
|
}
|
|
|
|
if ig.HasAPIServer() {
|
|
keypairs = append(keypairs, "apiserver-aggregator-ca", "service-account", "etcd-clients-ca")
|
|
}
|
|
|
|
// Add keypairs for cilium etcd clusters (not the default etcd clusters)
|
|
for _, etcdCluster := range cluster.Spec.EtcdClusters {
|
|
k := etcdCluster.Name
|
|
if k == "events" || k == "main" {
|
|
// Not cilium
|
|
continue
|
|
}
|
|
|
|
keypairs = append(keypairs, "etcd-manager-ca-"+k, "etcd-peers-ca-"+k, "etcd-clients-ca-"+k)
|
|
}
|
|
|
|
if ig.IsBastion() {
|
|
keypairs = nil
|
|
}
|
|
|
|
return keypairs
|
|
}
|
|
|
|
// ResourceNodeUp generates and returns a nodeup (bootstrap) script from a
|
|
// template file, substituting in specific env vars & cluster spec configuration
|
|
func (b *BootstrapScriptBuilder) ResourceNodeUp(c *fi.CloudupModelBuilderContext, ig *kops.InstanceGroup) (fi.Resource, error) {
|
|
keypairNames := KeypairNamesForInstanceGroup(b.Cluster, ig)
|
|
|
|
if ig.IsBastion() {
|
|
// Bastions can have AdditionalUserData, but if there isn't any skip this part
|
|
if len(ig.Spec.AdditionalUserData) == 0 {
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
caTasks := map[string]*fitasks.Keypair{}
|
|
for _, keypair := range keypairNames {
|
|
caTaskObject, found := c.Tasks["Keypair/"+keypair]
|
|
if !found {
|
|
return nil, fmt.Errorf("keypair/%s task not found", keypair)
|
|
}
|
|
caTasks[keypair] = caTaskObject.(*fitasks.Keypair)
|
|
}
|
|
|
|
task := &BootstrapScript{
|
|
Name: ig.Name,
|
|
Lifecycle: b.Lifecycle,
|
|
cluster: b.Cluster,
|
|
ig: ig,
|
|
builder: b,
|
|
caTasks: caTasks,
|
|
}
|
|
task.resource.Task = task
|
|
task.nodeupConfig.Task = task
|
|
c.AddTask(task)
|
|
|
|
c.AddTask(&fitasks.ManagedFile{
|
|
Name: fi.PtrTo("nodeupconfig-" + ig.Name),
|
|
Lifecycle: b.Lifecycle,
|
|
Location: fi.PtrTo("igconfig/" + ig.Spec.Role.ToLowerString() + "/" + ig.Name + "/nodeupconfig.yaml"),
|
|
Contents: &task.nodeupConfig,
|
|
})
|
|
return &task.resource, nil
|
|
}
|
|
|
|
func (b *BootstrapScript) GetName() *string {
|
|
return &b.Name
|
|
}
|
|
|
|
func (b *BootstrapScript) GetDependencies(tasks map[string]fi.CloudupTask) []fi.CloudupTask {
|
|
var deps []fi.CloudupTask
|
|
|
|
for _, task := range tasks {
|
|
if hasAddress, ok := task.(fi.HasAddress); ok && len(hasAddress.GetWellKnownServices()) > 0 {
|
|
deps = append(deps, task)
|
|
b.hasAddressTasks = append(b.hasAddressTasks, hasAddress)
|
|
}
|
|
}
|
|
|
|
for _, task := range b.caTasks {
|
|
deps = append(deps, task)
|
|
}
|
|
|
|
return deps
|
|
}
|
|
|
|
func (b *BootstrapScript) Run(c *fi.CloudupContext) error {
|
|
if b.Lifecycle == fi.LifecycleIgnore {
|
|
return nil
|
|
}
|
|
|
|
bootConfig, err := b.kubeEnv(b.ig, c)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var nodeupScript resources.NodeUpScript
|
|
nodeupScript.NodeUpAssets = b.builder.NodeUpAssets
|
|
nodeupScript.BootConfig = bootConfig
|
|
|
|
nodeupScript.WithEnvironmentVariables(b.cluster, b.ig)
|
|
nodeupScript.WithProxyEnv(b.cluster)
|
|
nodeupScript.WithSysctls()
|
|
|
|
nodeupScript.CompressUserData = fi.ValueOf(b.ig.Spec.CompressUserData)
|
|
|
|
nodeupScript.CloudProvider = string(c.T.Cluster.GetCloudProvider())
|
|
|
|
nodeupScriptResource, err := nodeupScript.Build()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
b.resource.Resource = fi.FunctionToResource(func() ([]byte, error) {
|
|
nodeupScript, err := fi.ResourceAsString(nodeupScriptResource)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
awsUserData, err := resources.AWSMultipartMIME(nodeupScript, b.ig)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return []byte(awsUserData), nil
|
|
})
|
|
return nil
|
|
}
|