mirror of https://github.com/kubernetes/kops.git
				
				
				
			Merge pull request #14460 from hakman/hetzner_bootstrap_kops-controller
hetzner: Use kops-controller for node bootstrap
This commit is contained in:
		
						commit
						5a52bfd67d
					
				|  | @ -41,6 +41,7 @@ import ( | |||
| 	nodeidentityos "k8s.io/kops/pkg/nodeidentity/openstack" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/awsup" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/gce/tpm/gcetpmverifier" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/hetzner" | ||||
| 	ctrl "sigs.k8s.io/controller-runtime" | ||||
| 	"sigs.k8s.io/controller-runtime/pkg/manager" | ||||
| 	"sigs.k8s.io/yaml" | ||||
|  | @ -119,6 +120,12 @@ func main() { | |||
| 				setupLog.Error(err, "unable to create verifier") | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 		} else if opt.Server.Provider.Hetzner != nil { | ||||
| 			verifier, err = hetzner.NewHetznerVerifier(opt.Server.Provider.Hetzner) | ||||
| 			if err != nil { | ||||
| 				setupLog.Error(err, "unable to create verifier") | ||||
| 				os.Exit(1) | ||||
| 			} | ||||
| 		} else { | ||||
| 			klog.Fatalf("server cloud provider config not provided") | ||||
| 		} | ||||
|  |  | |||
|  | @ -19,6 +19,7 @@ package config | |||
| import ( | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/awsup" | ||||
| 	gcetpm "k8s.io/kops/upup/pkg/fi/cloudup/gce/tpm" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/hetzner" | ||||
| ) | ||||
| 
 | ||||
| type Options struct { | ||||
|  | @ -63,6 +64,7 @@ type ServerOptions struct { | |||
| type ServerProviderOptions struct { | ||||
| 	AWS     *awsup.AWSVerifierOptions       `json:"aws,omitempty"` | ||||
| 	GCE     *gcetpm.TPMVerifierOptions      `json:"gce,omitempty"` | ||||
| 	Hetzner *hetzner.HetznerVerifierOptions `json:"hetzner,omitempty"` | ||||
| } | ||||
| 
 | ||||
| // DiscoveryOptions configures our support for discovery, particularly gossip DNS (i.e. k8s.local)
 | ||||
|  |  | |||
|  | @ -1467,7 +1467,6 @@ func (i *integrationTest) runTestTerraformHetzner(t *testing.T) { | |||
| 		"aws_s3_object_"+i.clusterName+"-addons-kops-controller.addons.k8s.io-k8s-1.16_content", | ||||
| 		"aws_s3_object_"+i.clusterName+"-addons-kubelet-api.rbac.addons.k8s.io-k8s-1.9_content", | ||||
| 		"aws_s3_object_"+i.clusterName+"-addons-limit-range.addons.k8s.io_content", | ||||
| 		"aws_s3_object_"+i.clusterName+"-addons-rbac.addons.k8s.io-k8s-1.8_content", | ||||
| 		"hcloud_server_master-fsn1_user_data", | ||||
| 		"hcloud_server_nodes-fsn1_user_data", | ||||
| 	) | ||||
|  |  | |||
|  | @ -28,6 +28,7 @@ import ( | |||
| 	"k8s.io/kops/upup/pkg/fi" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/awsup" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/gce/tpm/gcetpmsigner" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/hetzner" | ||||
| 	"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" | ||||
| ) | ||||
| 
 | ||||
|  | @ -50,6 +51,8 @@ func (b BootstrapClientBuilder) Build(c *fi.ModelBuilderContext) error { | |||
| 		authenticator, err = gcetpmsigner.NewTPMAuthenticator() | ||||
| 		// We don't use the custom resolver here in gossip mode (though we could);
 | ||||
| 		// instead we use this as a check that protokube has now started.
 | ||||
| 	case kops.CloudProviderHetzner: | ||||
| 		authenticator, err = hetzner.NewHetznerAuthenticator() | ||||
| 
 | ||||
| 	default: | ||||
| 		return fmt.Errorf("unsupported cloud provider for authenticator %q", b.CloudProvider) | ||||
|  |  | |||
|  | @ -27,6 +27,8 @@ func UseKopsControllerForNodeBootstrap(cluster *kops.Cluster) bool { | |||
| 		return true | ||||
| 	case kops.CloudProviderGCE: | ||||
| 		return cluster.IsKubernetesGTE("1.22") | ||||
| 	case kops.CloudProviderHetzner: | ||||
| 		return true | ||||
| 	default: | ||||
| 		return false | ||||
| 	} | ||||
|  |  | |||
|  | @ -6,7 +6,7 @@ spec: | |||
|   addons: | ||||
|   - id: k8s-1.16 | ||||
|     manifest: kops-controller.addons.k8s.io/k8s-1.16.yaml | ||||
|     manifestHash: 70c5a1ff20acaea9d84940b827cae1e35538ac7a8f2b17f8d48f02a105963baa | ||||
|     manifestHash: 7ee4cd99b996adcdee8611a23f5b27b88f50176e64c4639295bad04c3a865553 | ||||
|     name: kops-controller.addons.k8s.io | ||||
|     needsRollingUpdate: control-plane | ||||
|     selector: | ||||
|  | @ -19,13 +19,6 @@ spec: | |||
|     selector: | ||||
|       k8s-addon: coredns.addons.k8s.io | ||||
|     version: 9.99.0 | ||||
|   - id: k8s-1.8 | ||||
|     manifest: rbac.addons.k8s.io/k8s-1.8.yaml | ||||
|     manifestHash: f81bd7c57bc1902ca342635d7ad7d01b82dfeaff01a1192b076e66907d87871e | ||||
|     name: rbac.addons.k8s.io | ||||
|     selector: | ||||
|       k8s-addon: rbac.addons.k8s.io | ||||
|     version: 9.99.0 | ||||
|   - id: k8s-1.9 | ||||
|     manifest: kubelet-api.rbac.addons.k8s.io/k8s-1.9.yaml | ||||
|     manifestHash: 01c120e887bd98d82ef57983ad58a0b22bc85efb48108092a24c4b82e4c9ea81 | ||||
|  |  | |||
|  | @ -1,7 +1,7 @@ | |||
| apiVersion: v1 | ||||
| data: | ||||
|   config.yaml: | | ||||
|     {"cloud":"hetzner","configBase":"memfs://tests/minimal.example.com"} | ||||
|     {"cloud":"hetzner","configBase":"memfs://tests/minimal.example.com","server":{"Listen":":3988","provider":{"hetzner":{}},"serverKeyPath":"/etc/kubernetes/kops-controller/pki/kops-controller.key","serverCertificatePath":"/etc/kubernetes/kops-controller/pki/kops-controller.crt","caBasePath":"/etc/kubernetes/kops-controller/pki","signingCAs":["kubernetes-ca"],"certNames":["kubelet","kubelet-server","kube-proxy"]}} | ||||
| kind: ConfigMap | ||||
| metadata: | ||||
|   creationTimestamp: null | ||||
|  | @ -32,6 +32,8 @@ spec: | |||
|       k8s-app: kops-controller | ||||
|   template: | ||||
|     metadata: | ||||
|       annotations: | ||||
|         dns.alpha.kubernetes.io/internal: kops-controller.internal.minimal.example.com | ||||
|       creationTimestamp: null | ||||
|       labels: | ||||
|         k8s-addon: kops-controller.addons.k8s.io | ||||
|  |  | |||
|  | @ -1,19 +0,0 @@ | |||
| apiVersion: rbac.authorization.k8s.io/v1 | ||||
| kind: ClusterRoleBinding | ||||
| metadata: | ||||
|   creationTimestamp: null | ||||
|   labels: | ||||
|     addon.kops.k8s.io/name: rbac.addons.k8s.io | ||||
|     addonmanager.kubernetes.io/mode: Reconcile | ||||
|     app.kubernetes.io/managed-by: kops | ||||
|     k8s-addon: rbac.addons.k8s.io | ||||
|     kubernetes.io/cluster-service: "true" | ||||
|   name: kubelet-cluster-admin | ||||
| roleRef: | ||||
|   apiGroup: rbac.authorization.k8s.io | ||||
|   kind: ClusterRole | ||||
|   name: system:node | ||||
| subjects: | ||||
| - apiGroup: rbac.authorization.k8s.io | ||||
|   kind: User | ||||
|   name: kubelet | ||||
|  | @ -38,8 +38,6 @@ Hooks: | |||
| - null | ||||
| - null | ||||
| KeypairIDs: | ||||
|   kube-proxy: "6986354184403674830529235586" | ||||
|   kubelet: "6986354184404014133128804066" | ||||
|   kubernetes-ca: "6982820025135291416230495506" | ||||
| KubeletConfig: | ||||
|   anonymousAuth: false | ||||
|  |  | |||
|  | @ -162,7 +162,7 @@ CloudProvider: hetzner | |||
| ConfigBase: memfs://tests/minimal.example.com | ||||
| InstanceGroupName: nodes-fsn1 | ||||
| InstanceGroupRole: Node | ||||
| NodeupConfigHash: PWu9ALIVbND2iz1ciF+yjlTyJJFqCSux2yGwXKoPjUw= | ||||
| NodeupConfigHash: 2sOyCik6Vx/z8LXTIydiop+WbTq7ndCUfhQFvGQ/Ki0= | ||||
| 
 | ||||
| __EOF_KUBE_ENV | ||||
| 
 | ||||
|  |  | |||
|  | @ -131,14 +131,6 @@ resource "aws_s3_object" "minimal-example-com-addons-limit-range-addons-k8s-io" | |||
|   server_side_encryption = "AES256" | ||||
| } | ||||
| 
 | ||||
| resource "aws_s3_object" "minimal-example-com-addons-rbac-addons-k8s-io-k8s-1-8" { | ||||
|   bucket                 = "testingBucket" | ||||
|   content                = file("${path.module}/data/aws_s3_object_minimal.example.com-addons-rbac.addons.k8s.io-k8s-1.8_content") | ||||
|   key                    = "tests/minimal.example.com/addons/rbac.addons.k8s.io/k8s-1.8.yaml" | ||||
|   provider               = aws.files | ||||
|   server_side_encryption = "AES256" | ||||
| } | ||||
| 
 | ||||
| resource "aws_s3_object" "nodeupconfig-master-fsn1" { | ||||
|   bucket                 = "testingBucket" | ||||
|   content                = file("${path.module}/data/aws_s3_object_nodeupconfig-master-fsn1_content") | ||||
|  |  | |||
|  | @ -0,0 +1,45 @@ | |||
| /* | ||||
| Copyright 2022 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 hetzner | ||||
| 
 | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
| 
 | ||||
| 	"github.com/hetznercloud/hcloud-go/hcloud/metadata" | ||||
| 	"k8s.io/kops/pkg/bootstrap" | ||||
| ) | ||||
| 
 | ||||
| const HetznerAuthenticationTokenPrefix = "x-hetzner-id " | ||||
| 
 | ||||
| type hetznerAuthenticator struct { | ||||
| } | ||||
| 
 | ||||
| var _ bootstrap.Authenticator = &hetznerAuthenticator{} | ||||
| 
 | ||||
| func NewHetznerAuthenticator() (bootstrap.Authenticator, error) { | ||||
| 	return &hetznerAuthenticator{}, nil | ||||
| } | ||||
| 
 | ||||
| func (h hetznerAuthenticator) CreateToken(body []byte) (string, error) { | ||||
| 	serverID, err := metadata.NewClient().InstanceID() | ||||
| 	if err != nil { | ||||
| 		return "", fmt.Errorf("failed to retrieve server ID: %w", err) | ||||
| 	} | ||||
| 
 | ||||
| 	return HetznerAuthenticationTokenPrefix + strconv.Itoa(serverID), nil | ||||
| } | ||||
|  | @ -0,0 +1,94 @@ | |||
| /* | ||||
| Copyright 2020 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 hetzner | ||||
| 
 | ||||
| import ( | ||||
| 	"context" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
| 
 | ||||
| 	"github.com/hetznercloud/hcloud-go/hcloud" | ||||
| 	"k8s.io/kops/pkg/bootstrap" | ||||
| ) | ||||
| 
 | ||||
| type HetznerVerifierOptions struct { | ||||
| } | ||||
| 
 | ||||
| type hetznerVerifier struct { | ||||
| 	opt    HetznerVerifierOptions | ||||
| 	client *hcloud.Client | ||||
| } | ||||
| 
 | ||||
| var _ bootstrap.Verifier = &hetznerVerifier{} | ||||
| 
 | ||||
| func NewHetznerVerifier(opt *HetznerVerifierOptions) (bootstrap.Verifier, error) { | ||||
| 	hcloudToken := os.Getenv("HCLOUD_TOKEN") | ||||
| 	if hcloudToken == "" { | ||||
| 		return nil, fmt.Errorf("%s is required", "HCLOUD_TOKEN") | ||||
| 	} | ||||
| 
 | ||||
| 	opts := []hcloud.ClientOption{ | ||||
| 		hcloud.WithToken(hcloudToken), | ||||
| 	} | ||||
| 	hcloudClient := hcloud.NewClient(opts...) | ||||
| 
 | ||||
| 	return &hetznerVerifier{ | ||||
| 		opt:    *opt, | ||||
| 		client: hcloudClient, | ||||
| 	}, nil | ||||
| } | ||||
| 
 | ||||
| func (h hetznerVerifier) VerifyToken(ctx context.Context, token string, body []byte, useInstanceIDForNodeName bool) (*bootstrap.VerifyResult, error) { | ||||
| 	if !strings.HasPrefix(token, HetznerAuthenticationTokenPrefix) { | ||||
| 		return nil, fmt.Errorf("incorrect authorization type") | ||||
| 	} | ||||
| 	token = strings.TrimPrefix(token, HetznerAuthenticationTokenPrefix) | ||||
| 
 | ||||
| 	serverID, err := strconv.Atoi(token) | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("failed to convert server ID %q to int: %w", token, err) | ||||
| 	} | ||||
| 	server, _, err := h.client.Server.GetByID(ctx, serverID) | ||||
| 	if err != nil || server == nil { | ||||
| 		return nil, fmt.Errorf("failed to get info for server %q: %w", token, err) | ||||
| 	} | ||||
| 
 | ||||
| 	var addrs []string | ||||
| 	if server.PublicNet.IPv4.IP != nil { | ||||
| 		addrs = append(addrs, server.PublicNet.IPv4.IP.String()) | ||||
| 	} | ||||
| 	for _, network := range server.PrivateNet { | ||||
| 		if network.IP != nil { | ||||
| 			addrs = append(addrs, network.IP.String()) | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	result := &bootstrap.VerifyResult{ | ||||
| 		NodeName:         server.Name, | ||||
| 		CertificateNames: addrs, | ||||
| 	} | ||||
| 
 | ||||
| 	for key, value := range server.Labels { | ||||
| 		if key == TagKubernetesInstanceGroup { | ||||
| 			result.InstanceGroupName = value | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	return result, nil | ||||
| } | ||||
|  | @ -64,6 +64,7 @@ import ( | |||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/awsup" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/gce" | ||||
| 	gcetpm "k8s.io/kops/upup/pkg/fi/cloudup/gce/tpm" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/hetzner" | ||||
| 	"k8s.io/kops/util/pkg/env" | ||||
| ) | ||||
| 
 | ||||
|  | @ -655,6 +656,10 @@ func (tf *TemplateFunctions) KopsControllerConfig() (string, error) { | |||
| 				Region:      tf.Region, | ||||
| 				MaxTimeSkew: 300, | ||||
| 			} | ||||
| 
 | ||||
| 		case kops.CloudProviderHetzner: | ||||
| 			config.Server.Provider.Hetzner = &hetzner.HetznerVerifierOptions{} | ||||
| 
 | ||||
| 		default: | ||||
| 			return "", fmt.Errorf("unsupported cloud provider %s", cluster.Spec.GetCloudProvider()) | ||||
| 		} | ||||
|  |  | |||
|  | @ -52,6 +52,7 @@ import ( | |||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/awsup" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/gce/gcediscovery" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/gce/tpm/gcetpmsigner" | ||||
| 	"k8s.io/kops/upup/pkg/fi/cloudup/hetzner" | ||||
| 	"k8s.io/kops/upup/pkg/fi/nodeup/cloudinit" | ||||
| 	"k8s.io/kops/upup/pkg/fi/nodeup/local" | ||||
| 	"k8s.io/kops/upup/pkg/fi/nodeup/nodetasks" | ||||
|  | @ -757,6 +758,12 @@ func getNodeConfigFromServer(ctx context.Context, bootConfig *nodeup.BootConfig, | |||
| 			return nil, err | ||||
| 		} | ||||
| 		resolver = discovery | ||||
| 	case api.CloudProviderHetzner: | ||||
| 		a, err := hetzner.NewHetznerAuthenticator() | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
| 		authenticator = a | ||||
| 	default: | ||||
| 		return nil, fmt.Errorf("unsupported cloud provider for node configuration %s", bootConfig.CloudProvider) | ||||
| 	} | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue