168 lines
6.2 KiB
Go
168 lines
6.2 KiB
Go
/*
|
|
Copyright 2017 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 selfhosting
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"time"
|
|
|
|
"k8s.io/api/core/v1"
|
|
extensions "k8s.io/api/extensions/v1beta1"
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
kuberuntime "k8s.io/apimachinery/pkg/runtime"
|
|
clientset "k8s.io/client-go/kubernetes"
|
|
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
|
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
|
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
|
"k8s.io/kubernetes/pkg/api"
|
|
)
|
|
|
|
const (
|
|
kubeAPIServer = "kube-apiserver"
|
|
kubeControllerManager = "kube-controller-manager"
|
|
kubeScheduler = "kube-scheduler"
|
|
|
|
selfHostingPrefix = "self-hosted-"
|
|
)
|
|
|
|
// CreateSelfHostedControlPlane is responsible for turning a Static Pod-hosted control plane to a self-hosted one
|
|
// It achieves that task this way:
|
|
// 1. Load the Static Pod specification from disk (from /etc/kubernetes/manifests)
|
|
// 2. Extract the PodSpec from that Static Pod specification
|
|
// 3. Mutate the PodSpec to be compatible with self-hosting (add the right labels, taints, etc. so it can schedule correctly)
|
|
// 4. Build a new DaemonSet object for the self-hosted component in question. Use the above mentioned PodSpec
|
|
// 5. Create the DaemonSet resource. Wait until the Pods are running.
|
|
// 6. Remove the Static Pod manifest file. The kubelet will stop the original Static Pod-hosted component that was running.
|
|
// 7. The self-hosted containers should now step up and take over.
|
|
// 8. In order to avoid race conditions, we're still making sure the API /healthz endpoint is healthy
|
|
// 9. Do that for the kube-apiserver, kube-controller-manager and kube-scheduler in a loop
|
|
func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error {
|
|
|
|
if err := createTLSSecrets(cfg, client); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := createOpaqueSecrets(cfg, client); err != nil {
|
|
return err
|
|
}
|
|
|
|
// The sequence here isn't set in stone, but seems to work well to start with the API server
|
|
components := []string{kubeAPIServer, kubeControllerManager, kubeScheduler}
|
|
|
|
for _, componentName := range components {
|
|
start := time.Now()
|
|
manifestPath := buildStaticManifestFilepath(componentName)
|
|
|
|
// Load the Static Pod file in order to be able to create a self-hosted variant of that file
|
|
podSpec, err := loadPodSpecFromFile(manifestPath)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Build a DaemonSet object from the loaded PodSpec
|
|
ds := buildDaemonSet(cfg, componentName, podSpec)
|
|
|
|
// Create the DaemonSet in the API Server
|
|
if _, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Create(ds); err != nil {
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
return fmt.Errorf("failed to create self-hosted %q daemonset [%v]", componentName, err)
|
|
}
|
|
|
|
if _, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Update(ds); err != nil {
|
|
// TODO: We should retry on 409 responses
|
|
return fmt.Errorf("failed to update self-hosted %q daemonset [%v]", componentName, err)
|
|
}
|
|
}
|
|
|
|
// Wait for the self-hosted component to come up
|
|
kubeadmutil.WaitForPodsWithLabel(client, buildSelfHostedWorkloadLabelQuery(componentName))
|
|
|
|
// Remove the old Static Pod manifest
|
|
if err := os.RemoveAll(manifestPath); err != nil {
|
|
return fmt.Errorf("unable to delete static pod manifest for %s [%v]", componentName, err)
|
|
}
|
|
|
|
// Make sure the API is responsive at /healthz
|
|
kubeadmutil.WaitForAPI(client)
|
|
|
|
fmt.Printf("[self-hosted] self-hosted %s ready after %f seconds\n", componentName, time.Since(start).Seconds())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// buildDaemonSet is responsible for mutating the PodSpec and return a DaemonSet which is suitable for the self-hosting purporse
|
|
func buildDaemonSet(cfg *kubeadmapi.MasterConfiguration, name string, podSpec *v1.PodSpec) *extensions.DaemonSet {
|
|
// Mutate the PodSpec so it's suitable for self-hosting
|
|
mutatePodSpec(cfg, name, podSpec)
|
|
|
|
// Return a DaemonSet based on that Spec
|
|
return &extensions.DaemonSet{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: addSelfHostedPrefix(name),
|
|
Namespace: metav1.NamespaceSystem,
|
|
Labels: map[string]string{
|
|
"k8s-app": addSelfHostedPrefix(name),
|
|
},
|
|
},
|
|
Spec: extensions.DaemonSetSpec{
|
|
Template: v1.PodTemplateSpec{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Labels: map[string]string{
|
|
"k8s-app": addSelfHostedPrefix(name),
|
|
},
|
|
},
|
|
Spec: *podSpec,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// loadPodSpecFromFile reads and decodes a file containing a specification of a Pod
|
|
// TODO: Consider using "k8s.io/kubernetes/pkg/volume/util".LoadPodFromFile(filename string) in the future instead.
|
|
func loadPodSpecFromFile(manifestPath string) (*v1.PodSpec, error) {
|
|
podBytes, err := ioutil.ReadFile(manifestPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
staticPod := &v1.Pod{}
|
|
if err := kuberuntime.DecodeInto(api.Codecs.UniversalDecoder(), podBytes, staticPod); err != nil {
|
|
return nil, fmt.Errorf("unable to decode static pod %v", err)
|
|
}
|
|
|
|
return &staticPod.Spec, nil
|
|
}
|
|
|
|
// buildStaticManifestFilepath returns the location on the disk where the Static Pod should be present
|
|
func buildStaticManifestFilepath(componentName string) string {
|
|
return filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName, componentName+".yaml")
|
|
}
|
|
|
|
// buildSelfHostedWorkloadLabelQuery creates the right query for matching a self-hosted Pod
|
|
func buildSelfHostedWorkloadLabelQuery(componentName string) string {
|
|
return fmt.Sprintf("k8s-app=%s", addSelfHostedPrefix(componentName))
|
|
}
|
|
|
|
// addSelfHostedPrefix adds the self-hosted- prefix to the component name
|
|
func addSelfHostedPrefix(componentName string) string {
|
|
return fmt.Sprintf("%s%s", selfHostingPrefix, componentName)
|
|
}
|