diff --git a/cmd/kubeadm/app/cmd/BUILD b/cmd/kubeadm/app/cmd/BUILD index 5fe98f53ddf..b1e20ca86c0 100644 --- a/cmd/kubeadm/app/cmd/BUILD +++ b/cmd/kubeadm/app/cmd/BUILD @@ -39,6 +39,7 @@ go_library( "//cmd/kubeadm/app/phases/uploadconfig:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/apiclient:go_default_library", "//cmd/kubeadm/app/util/config:go_default_library", "//cmd/kubeadm/app/util/kubeconfig:go_default_library", "//cmd/kubeadm/app/util/pubkeypin:go_default_library", diff --git a/cmd/kubeadm/app/cmd/init.go b/cmd/kubeadm/app/cmd/init.go index 44a48815651..de419e14f0a 100644 --- a/cmd/kubeadm/app/cmd/init.go +++ b/cmd/kubeadm/app/cmd/init.go @@ -47,7 +47,9 @@ import ( uploadconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig" "k8s.io/kubernetes/cmd/kubeadm/app/preflight" kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config" + kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" "k8s.io/kubernetes/cmd/kubeadm/app/util/pubkeypin" "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/util/version" @@ -253,11 +255,15 @@ func (i *Init) Run(out io.Writer) error { } } - client, err := kubeadmutil.CreateClientAndWaitForAPI(kubeadmconstants.GetAdminKubeConfigPath()) + client, err := kubeconfigutil.ClientSetFromFile(kubeadmconstants.GetAdminKubeConfigPath()) if err != nil { return err } + fmt.Printf("[init] Waiting for the kubelet to boot up the control plane as Static Pods from directory %q\n", kubeadmconstants.GetStaticPodDirectory()) + // TODO: Don't wait forever here + apiclient.WaitForAPI(client) + // PHASE 4: Mark the master with the right label/taint if err := markmasterphase.MarkMaster(client, i.cfg.NodeName); err != nil { return err diff --git a/cmd/kubeadm/app/constants/constants.go b/cmd/kubeadm/app/constants/constants.go index 20105623390..8a5a98c0ab7 100644 --- a/cmd/kubeadm/app/constants/constants.go +++ b/cmd/kubeadm/app/constants/constants.go @@ -73,10 +73,6 @@ const ( NodesGroup = "system:nodes" NodesClusterRoleBinding = "system:node" - // Constants for what we name our ServiceAccounts with limited access to the cluster in case of RBAC - KubeDNSServiceAccountName = "kube-dns" - KubeProxyServiceAccountName = "kube-proxy" - // APICallRetryInterval defines how long kubeadm should wait before retrying a failed API operation APICallRetryInterval = 500 * time.Millisecond // DiscoveryRetryInterval specifies how long kubeadm should wait before retrying to connect to the master when doing discovery diff --git a/cmd/kubeadm/app/phases/addons/dns/BUILD b/cmd/kubeadm/app/phases/addons/dns/BUILD index 666de9a341e..1d5c808cfab 100644 --- a/cmd/kubeadm/app/phases/addons/dns/BUILD +++ b/cmd/kubeadm/app/phases/addons/dns/BUILD @@ -34,6 +34,7 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/dns/dns.go b/cmd/kubeadm/app/phases/addons/dns/dns.go index de9da84c272..51e4dae815f 100644 --- a/cmd/kubeadm/app/phases/addons/dns/dns.go +++ b/cmd/kubeadm/app/phases/addons/dns/dns.go @@ -30,9 +30,15 @@ import ( 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/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api" ) +const ( + // KubeDNSServiceAccountName describes the name of the ServiceAccount for the kube-dns addon + KubeDNSServiceAccountName = "kube-dns" +) + // EnsureDNSAddon creates the kube-dns addon func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { if err := CreateServiceAccount(client); err != nil { @@ -62,7 +68,7 @@ func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interf return fmt.Errorf("error when parsing kube-proxy configmap template: %v", err) } - if err = createKubeDNSAddon(dnsDeploymentBytes, dnsServiceBytes, client); err != nil { + if err := createKubeDNSAddon(dnsDeploymentBytes, dnsServiceBytes, client); err != nil { return err } fmt.Println("[addons] Applied essential addon: kube-dns") @@ -71,18 +77,13 @@ func EnsureDNSAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interf // CreateServiceAccount creates the necessary serviceaccounts that kubeadm uses/might use, if they don't already exist. func CreateServiceAccount(client clientset.Interface) error { - sa := v1.ServiceAccount{ + + return apiclient.CreateOrUpdateServiceAccount(client, &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: kubeadmconstants.KubeDNSServiceAccountName, + Name: KubeDNSServiceAccountName, Namespace: metav1.NamespaceSystem, }, - } - if _, err := client.CoreV1().ServiceAccounts(metav1.NamespaceSystem).Create(&sa); err != nil { - if !apierrors.IsAlreadyExists(err) { - return err - } - } - return nil + }) } func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.Interface) error { @@ -91,14 +92,9 @@ func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.I return fmt.Errorf("unable to decode kube-dns deployment %v", err) } - if _, err := client.ExtensionsV1beta1().Deployments(metav1.NamespaceSystem).Create(kubednsDeployment); err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create a new kube-dns deployment: %v", err) - } - - if _, err := client.ExtensionsV1beta1().Deployments(metav1.NamespaceSystem).Update(kubednsDeployment); err != nil { - return fmt.Errorf("unable to update the kube-dns deployment: %v", err) - } + // Create the Deployment for kube-dns or update it in case it already exists + if err := apiclient.CreateOrUpdateDeployment(client, kubednsDeployment); err != nil { + return err } kubednsService := &v1.Service{} @@ -106,6 +102,7 @@ func createKubeDNSAddon(deploymentBytes, serviceBytes []byte, client clientset.I return fmt.Errorf("unable to decode kube-dns service %v", err) } + // Can't use a generic apiclient helper func here as we have to tolerate more than AlreadyExists. if _, err := client.CoreV1().Services(metav1.NamespaceSystem).Create(kubednsService); err != nil { // Ignore if the Service is invalid with this error message: // Service "kube-dns" is invalid: spec.clusterIP: Invalid value: "10.96.0.10": provided IP is already allocated diff --git a/cmd/kubeadm/app/phases/addons/proxy/BUILD b/cmd/kubeadm/app/phases/addons/proxy/BUILD index 9b974d00ea8..95e6ecc4937 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/BUILD +++ b/cmd/kubeadm/app/phases/addons/proxy/BUILD @@ -33,12 +33,10 @@ go_library( "//cmd/kubeadm/app/util:go_default_library", "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api:go_default_library", - "//pkg/util/version:go_default_library", "//plugin/pkg/scheduler/algorithm:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", diff --git a/cmd/kubeadm/app/phases/addons/proxy/proxy.go b/cmd/kubeadm/app/phases/addons/proxy/proxy.go index 25a232e5521..51d53a37d4d 100644 --- a/cmd/kubeadm/app/phases/addons/proxy/proxy.go +++ b/cmd/kubeadm/app/phases/addons/proxy/proxy.go @@ -23,17 +23,14 @@ import ( "k8s.io/api/core/v1" extensions "k8s.io/api/extensions/v1beta1" rbac "k8s.io/api/rbac/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" - apiclientutil "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api" - "k8s.io/kubernetes/pkg/util/version" - k8sversion "k8s.io/kubernetes/pkg/util/version" "k8s.io/kubernetes/plugin/pkg/scheduler/algorithm" ) @@ -41,9 +38,12 @@ const ( // KubeProxyClusterRoleName sets the name for the kube-proxy ClusterRole // TODO: This k8s-generic, well-known constant should be fetchable from another source, not be in this package KubeProxyClusterRoleName = "system:node-proxier" + + // KubeProxyServiceAccountName describes the name of the ServiceAccount for the kube-proxy addon + KubeProxyServiceAccountName = "kube-proxy" ) -// EnsureProxyAddon creates the kube-proxy and kube-dns addons +// EnsureProxyAddon creates the kube-proxy addons func EnsureProxyAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Interface) error { if err := CreateServiceAccount(client); err != nil { return fmt.Errorf("error when creating kube-proxy service account: %v", err) @@ -70,41 +70,30 @@ func EnsureProxyAddon(cfg *kubeadmapi.MasterConfiguration, client clientset.Inte return fmt.Errorf("error when parsing kube-proxy daemonset template: %v", err) } - if err = createKubeProxyAddon(proxyConfigMapBytes, proxyDaemonSetBytes, client); err != nil { + if err := createKubeProxyAddon(proxyConfigMapBytes, proxyDaemonSetBytes, client); err != nil { return err } - fmt.Println("[addons] Applied essential addon: kube-proxy") - - k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion) - if err != nil { - return fmt.Errorf("couldn't parse kubernetes version %q: %v", cfg.KubernetesVersion, err) - } - if err = CreateRBACRules(client, k8sVersion); err != nil { + if err := CreateRBACRules(client); err != nil { return fmt.Errorf("error when creating kube-proxy RBAC rules: %v", err) } + fmt.Println("[addons] Applied essential addon: kube-proxy") return nil } // CreateServiceAccount creates the necessary serviceaccounts that kubeadm uses/might use, if they don't already exist. func CreateServiceAccount(client clientset.Interface) error { - sa := v1.ServiceAccount{ + + return apiclient.CreateOrUpdateServiceAccount(client, &v1.ServiceAccount{ ObjectMeta: metav1.ObjectMeta{ - Name: kubeadmconstants.KubeProxyServiceAccountName, + Name: KubeProxyServiceAccountName, Namespace: metav1.NamespaceSystem, }, - } - - if _, err := client.CoreV1().ServiceAccounts(metav1.NamespaceSystem).Create(&sa); err != nil { - if !apierrors.IsAlreadyExists(err) { - return err - } - } - return nil + }) } // CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster -func CreateRBACRules(client clientset.Interface, k8sVersion *k8sversion.Version) error { +func CreateRBACRules(client clientset.Interface) error { if err := createClusterRoleBindings(client); err != nil { return err } @@ -117,14 +106,9 @@ func createKubeProxyAddon(configMapBytes, daemonSetbytes []byte, client clientse return fmt.Errorf("unable to decode kube-proxy configmap %v", err) } - if _, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Create(kubeproxyConfigMap); err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create a new kube-proxy configmap: %v", err) - } - - if _, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Update(kubeproxyConfigMap); err != nil { - return fmt.Errorf("unable to update the kube-proxy configmap: %v", err) - } + // Create the ConfigMap for kube-proxy or update it in case it already exists + if err := apiclient.CreateOrUpdateConfigMap(client, kubeproxyConfigMap); err != nil { + return err } kubeproxyDaemonSet := &extensions.DaemonSet{} @@ -132,20 +116,15 @@ func createKubeProxyAddon(configMapBytes, daemonSetbytes []byte, client clientse return fmt.Errorf("unable to decode kube-proxy daemonset %v", err) } - if _, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Create(kubeproxyDaemonSet); err != nil { - if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create a new kube-proxy daemonset: %v", err) - } - - if _, err := client.ExtensionsV1beta1().DaemonSets(metav1.NamespaceSystem).Update(kubeproxyDaemonSet); err != nil { - return fmt.Errorf("unable to update the kube-proxy daemonset: %v", err) - } + // Create the DaemonSet for kube-proxy or update it in case it already exists + if err := apiclient.CreateOrUpdateDaemonSet(client, kubeproxyDaemonSet); err != nil { + return err } return nil } func createClusterRoleBindings(client clientset.Interface) error { - return apiclientutil.CreateClusterRoleBindingIfNotExists(client, &rbac.ClusterRoleBinding{ + return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: "kubeadm:node-proxier", }, @@ -157,7 +136,7 @@ func createClusterRoleBindings(client clientset.Interface) error { Subjects: []rbac.Subject{ { Kind: rbac.ServiceAccountKind, - Name: kubeadmconstants.KubeProxyServiceAccountName, + Name: KubeProxyServiceAccountName, Namespace: metav1.NamespaceSystem, }, }, diff --git a/cmd/kubeadm/app/phases/apiconfig/clusterroles.go b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go index a20c9225e42..daad591faaa 100644 --- a/cmd/kubeadm/app/phases/apiconfig/clusterroles.go +++ b/cmd/kubeadm/app/phases/apiconfig/clusterroles.go @@ -28,12 +28,12 @@ import ( ) // CreateRBACRules creates the essential RBAC rules for a minimally set-up cluster +// TODO: This function and phase package is DEPRECATED. +// When the v1.9 cycle starts and deletePermissiveNodesBindingWhenUsingNodeAuthorization can be removed, this package will be removed with it. func CreateRBACRules(client clientset.Interface, k8sVersion *version.Version) error { if err := deletePermissiveNodesBindingWhenUsingNodeAuthorization(client, k8sVersion); err != nil { return fmt.Errorf("failed to remove the permissive 'system:nodes' Group Subject in the 'system:node' ClusterRoleBinding: %v", err) } - - fmt.Println("[apiconfig] Created RBAC rules") return nil } diff --git a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go index fc7824094bb..ea5c2c7c087 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/clusterinfo/clusterinfo.go @@ -26,7 +26,7 @@ import ( clientset "k8s.io/client-go/kubernetes" "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" - apiclientutil "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1beta1" bootstrapapi "k8s.io/kubernetes/pkg/bootstrap/api" ) @@ -59,7 +59,7 @@ func CreateBootstrapConfigMapIfNotExists(client clientset.Interface, file string } // Create or update the ConfigMap in the kube-public namespace - return apiclientutil.CreateConfigMapIfNotExists(client, &v1.ConfigMap{ + return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: bootstrapapi.ConfigMapClusterInfo, Namespace: metav1.NamespacePublic, @@ -72,7 +72,7 @@ func CreateBootstrapConfigMapIfNotExists(client clientset.Interface, file string // CreateClusterInfoRBACRules creates the RBAC rules for exposing the cluster-info ConfigMap in the kube-public namespace to unauthenticated users func CreateClusterInfoRBACRules(client clientset.Interface) error { - err := apiclientutil.CreateRoleIfNotExists(client, &rbac.Role{ + err := apiclient.CreateOrUpdateRole(client, &rbac.Role{ ObjectMeta: metav1.ObjectMeta{ Name: BootstrapSignerClusterRoleName, Namespace: metav1.NamespacePublic, @@ -85,7 +85,7 @@ func CreateClusterInfoRBACRules(client clientset.Interface) error { return err } - return apiclientutil.CreateRoleBindingIfNotExists(client, &rbac.RoleBinding{ + return apiclient.CreateOrUpdateRoleBinding(client, &rbac.RoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: BootstrapSignerClusterRoleName, Namespace: metav1.NamespacePublic, diff --git a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go index cb53271029b..079204a0158 100644 --- a/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go +++ b/cmd/kubeadm/app/phases/bootstraptoken/node/tlsbootstrap.go @@ -23,7 +23,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/cmd/kubeadm/app/constants" - apiclientutil "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" rbachelper "k8s.io/kubernetes/pkg/apis/rbac/v1beta1" "k8s.io/kubernetes/pkg/util/version" ) @@ -47,7 +47,7 @@ func AllowBootstrapTokensToPostCSRs(client clientset.Interface) error { fmt.Println("[bootstraptoken] Configured RBAC rules to allow Node Bootstrap tokens to post CSRs in order for nodes to get long term certificate credentials") - return apiclientutil.CreateClusterRoleBindingIfNotExists(client, &rbac.ClusterRoleBinding{ + return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: NodeKubeletBootstrap, }, @@ -73,7 +73,7 @@ func AutoApproveNodeBootstrapTokens(client clientset.Interface, k8sVersion *vers // TODO: When the v1.9 cycle starts (targeting v1.9 at HEAD) and v1.8.0 is the minimum supported version, we can remove this function as the ClusterRole will always exist if k8sVersion.LessThan(constants.MinimumCSRAutoApprovalClusterRolesVersion) { - err := apiclientutil.CreateClusterRoleIfNotExists(client, &rbac.ClusterRole{ + err := apiclient.CreateOrUpdateClusterRole(client, &rbac.ClusterRole{ ObjectMeta: metav1.ObjectMeta{ Name: CSRAutoApprovalClusterRoleName, }, @@ -87,7 +87,7 @@ func AutoApproveNodeBootstrapTokens(client clientset.Interface, k8sVersion *vers } // Always create this kubeadm-specific binding though - return apiclientutil.CreateClusterRoleBindingIfNotExists(client, &rbac.ClusterRoleBinding{ + return apiclient.CreateOrUpdateClusterRoleBinding(client, &rbac.ClusterRoleBinding{ ObjectMeta: metav1.ObjectMeta{ Name: NodeAutoApproveBootstrap, }, diff --git a/cmd/kubeadm/app/phases/selfhosting/BUILD b/cmd/kubeadm/app/phases/selfhosting/BUILD index 3818bfbcb12..564a1862295 100644 --- a/cmd/kubeadm/app/phases/selfhosting/BUILD +++ b/cmd/kubeadm/app/phases/selfhosting/BUILD @@ -32,11 +32,10 @@ go_library( deps = [ "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", - "//cmd/kubeadm/app/util:go_default_library", + "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", diff --git a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go index c6ad27eff7c..ec3338b9ffa 100644 --- a/cmd/kubeadm/app/phases/selfhosting/selfhosting.go +++ b/cmd/kubeadm/app/phases/selfhosting/selfhosting.go @@ -24,13 +24,12 @@ import ( "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/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api" ) @@ -69,19 +68,13 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl 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) - } + if err := apiclient.CreateOrUpdateDaemonSet(client, ds); err != nil { + return err } // Wait for the self-hosted component to come up - kubeadmutil.WaitForPodsWithLabel(client, buildSelfHostedWorkloadLabelQuery(componentName)) + // TODO: Enforce a timeout + apiclient.WaitForPodsWithLabel(client, buildSelfHostedWorkloadLabelQuery(componentName)) // Remove the old Static Pod manifest if err := os.RemoveAll(manifestPath); err != nil { @@ -89,7 +82,8 @@ func CreateSelfHostedControlPlane(cfg *kubeadmapi.MasterConfiguration, client cl } // Make sure the API is responsive at /healthz - kubeadmutil.WaitForAPI(client) + // TODO: Follow-up on fixing the race condition here and respect the timeout error that can be returned + apiclient.WaitForAPI(client) fmt.Printf("[self-hosted] self-hosted %s ready after %f seconds\n", componentName, time.Since(start).Seconds()) } diff --git a/cmd/kubeadm/app/phases/uploadconfig/BUILD b/cmd/kubeadm/app/phases/uploadconfig/BUILD index d6803872e9e..558d97232b0 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/BUILD +++ b/cmd/kubeadm/app/phases/uploadconfig/BUILD @@ -12,10 +12,10 @@ go_library( "//cmd/kubeadm/app/apis/kubeadm:go_default_library", "//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library", "//cmd/kubeadm/app/constants:go_default_library", + "//cmd/kubeadm/app/util/apiclient:go_default_library", "//pkg/api:go_default_library", "//vendor/github.com/ghodss/yaml:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], diff --git a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go index c6f0e39936d..9d9e3c5fb7f 100644 --- a/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go +++ b/cmd/kubeadm/app/phases/uploadconfig/uploadconfig.go @@ -22,12 +22,12 @@ import ( "github.com/ghodss/yaml" "k8s.io/api/core/v1" - apierrs "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" clientset "k8s.io/client-go/kubernetes" kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" + "k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient" "k8s.io/kubernetes/pkg/api" ) @@ -45,7 +45,7 @@ func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.I return err } - cm := &v1.ConfigMap{ + return apiclient.CreateOrUpdateConfigMap(client, &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: kubeadmconstants.MasterConfigurationConfigMap, Namespace: metav1.NamespaceSystem, @@ -53,15 +53,5 @@ func UploadConfiguration(cfg *kubeadmapi.MasterConfiguration, client clientset.I Data: map[string]string{ kubeadmconstants.MasterConfigurationConfigMapKey: string(cfgYaml), }, - } - - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil { - if !apierrs.IsAlreadyExists(err) { - return err - } - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(cm); err != nil { - return err - } - } - return nil + }) } diff --git a/cmd/kubeadm/app/util/BUILD b/cmd/kubeadm/app/util/BUILD index ba1dd61d259..e06553c7893 100644 --- a/cmd/kubeadm/app/util/BUILD +++ b/cmd/kubeadm/app/util/BUILD @@ -9,20 +9,13 @@ load( go_library( name = "go_default_library", srcs = [ - "apiclient.go", "error.go", "template.go", "version.go", ], deps = [ - "//cmd/kubeadm/app/constants:go_default_library", "//cmd/kubeadm/app/preflight:go_default_library", - "//cmd/kubeadm/app/util/kubeconfig:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", - "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/cmd/kubeadm/app/util/apiclient/BUILD b/cmd/kubeadm/app/util/apiclient/BUILD index ef48337e99b..02286772b76 100644 --- a/cmd/kubeadm/app/util/apiclient/BUILD +++ b/cmd/kubeadm/app/util/apiclient/BUILD @@ -7,11 +7,18 @@ load( go_library( name = "go_default_library", - srcs = ["idempotency.go"], + srcs = [ + "idempotency.go", + "wait.go", + ], deps = [ + "//cmd/kubeadm/app/constants:go_default_library", "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", "//vendor/k8s.io/api/rbac/v1beta1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", ], ) diff --git a/cmd/kubeadm/app/util/apiclient/idempotency.go b/cmd/kubeadm/app/util/apiclient/idempotency.go index 4db6f98795b..ecae7d87c95 100644 --- a/cmd/kubeadm/app/util/apiclient/idempotency.go +++ b/cmd/kubeadm/app/util/apiclient/idempotency.go @@ -14,49 +14,77 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package apiclient import ( "fmt" "k8s.io/api/core/v1" + extensions "k8s.io/api/extensions/v1beta1" rbac "k8s.io/api/rbac/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" clientset "k8s.io/client-go/kubernetes" ) // TODO: We should invent a dynamic mechanism for this using the dynamic client instead of hard-coding these functions per-type +// TODO: We may want to retry if .Update() fails on 409 Conflict -// CreateClusterRoleIfNotExists creates a ClusterRole if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateClusterRoleIfNotExists(client clientset.Interface, clusterRole *rbac.ClusterRole) error { - if _, err := client.RbacV1beta1().ClusterRoles().Create(clusterRole); err != nil { +// CreateOrUpdateConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error { + if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil { if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create RBAC clusterrole: %v", err) + return fmt.Errorf("unable to create configmap: %v", err) } - if _, err := client.RbacV1beta1().ClusterRoles().Update(clusterRole); err != nil { - return fmt.Errorf("unable to update RBAC clusterrole: %v", err) + if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(cm); err != nil { + return fmt.Errorf("unable to update configmap: %v", err) } } return nil } -// CreateClusterRoleBindingIfNotExists creates a ClusterRoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateClusterRoleBindingIfNotExists(client clientset.Interface, clusterRoleBinding *rbac.ClusterRoleBinding) error { - if _, err := client.RbacV1beta1().ClusterRoleBindings().Create(clusterRoleBinding); err != nil { +// CreateOrUpdateServiceAccount creates a ServiceAccount if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateServiceAccount(client clientset.Interface, sa *v1.ServiceAccount) error { + if _, err := client.CoreV1().ServiceAccounts(sa.ObjectMeta.Namespace).Create(sa); err != nil { + // Note: We don't run .Update here afterwards as that's probably not required + // Only thing that could be updated is annotations/labels in .metadata, but we don't use that currently if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create RBAC clusterrolebinding: %v", err) - } - - if _, err := client.RbacV1beta1().ClusterRoleBindings().Update(clusterRoleBinding); err != nil { - return fmt.Errorf("unable to update RBAC clusterrolebinding: %v", err) + return fmt.Errorf("unable to create serviceaccount: %v", err) } } return nil } -// CreateRoleIfNotExists creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateRoleIfNotExists(client clientset.Interface, role *rbac.Role) error { +// CreateOrUpdateDeployment creates a Deployment if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateDeployment(client clientset.Interface, deploy *extensions.Deployment) error { + if _, err := client.ExtensionsV1beta1().Deployments(deploy.ObjectMeta.Namespace).Create(deploy); err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create deployment: %v", err) + } + + if _, err := client.ExtensionsV1beta1().Deployments(deploy.ObjectMeta.Namespace).Update(deploy); err != nil { + return fmt.Errorf("unable to update deployment: %v", err) + } + } + return nil +} + +// CreateOrUpdateDaemonSet creates a DaemonSet if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateDaemonSet(client clientset.Interface, ds *extensions.DaemonSet) error { + if _, err := client.ExtensionsV1beta1().DaemonSets(ds.ObjectMeta.Namespace).Create(ds); err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create daemonset: %v", err) + } + + if _, err := client.ExtensionsV1beta1().DaemonSets(ds.ObjectMeta.Namespace).Update(ds); err != nil { + return fmt.Errorf("unable to update daemonset: %v", err) + } + } + return nil +} + +// CreateOrUpdateRole creates a Role if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateRole(client clientset.Interface, role *rbac.Role) error { if _, err := client.RbacV1beta1().Roles(role.ObjectMeta.Namespace).Create(role); err != nil { if !apierrors.IsAlreadyExists(err) { return fmt.Errorf("unable to create RBAC role: %v", err) @@ -69,8 +97,8 @@ func CreateRoleIfNotExists(client clientset.Interface, role *rbac.Role) error { return nil } -// CreateRoleBindingIfNotExists creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateRoleBindingIfNotExists(client clientset.Interface, roleBinding *rbac.RoleBinding) error { +// CreateOrUpdateRoleBinding creates a RoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateRoleBinding(client clientset.Interface, roleBinding *rbac.RoleBinding) error { if _, err := client.RbacV1beta1().RoleBindings(roleBinding.ObjectMeta.Namespace).Create(roleBinding); err != nil { if !apierrors.IsAlreadyExists(err) { return fmt.Errorf("unable to create RBAC rolebinding: %v", err) @@ -83,15 +111,29 @@ func CreateRoleBindingIfNotExists(client clientset.Interface, roleBinding *rbac. return nil } -// CreateConfigMapIfNotExists creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. -func CreateConfigMapIfNotExists(client clientset.Interface, cm *v1.ConfigMap) error { - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil { +// CreateOrUpdateClusterRole creates a ClusterRole if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateClusterRole(client clientset.Interface, clusterRole *rbac.ClusterRole) error { + if _, err := client.RbacV1beta1().ClusterRoles().Create(clusterRole); err != nil { if !apierrors.IsAlreadyExists(err) { - return fmt.Errorf("unable to create configmap: %v", err) + return fmt.Errorf("unable to create RBAC clusterrole: %v", err) } - if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Update(cm); err != nil { - return fmt.Errorf("unable to update configmap: %v", err) + if _, err := client.RbacV1beta1().ClusterRoles().Update(clusterRole); err != nil { + return fmt.Errorf("unable to update RBAC clusterrole: %v", err) + } + } + return nil +} + +// CreateOrUpdateClusterRoleBinding creates a ClusterRoleBinding if the target resource doesn't exist. If the resource exists already, this function will update the resource instead. +func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterRoleBinding *rbac.ClusterRoleBinding) error { + if _, err := client.RbacV1beta1().ClusterRoleBindings().Create(clusterRoleBinding); err != nil { + if !apierrors.IsAlreadyExists(err) { + return fmt.Errorf("unable to create RBAC clusterrolebinding: %v", err) + } + + if _, err := client.RbacV1beta1().ClusterRoleBindings().Update(clusterRoleBinding); err != nil { + return fmt.Errorf("unable to update RBAC clusterrolebinding: %v", err) } } return nil diff --git a/cmd/kubeadm/app/util/apiclient.go b/cmd/kubeadm/app/util/apiclient/wait.go similarity index 81% rename from cmd/kubeadm/app/util/apiclient.go rename to cmd/kubeadm/app/util/apiclient/wait.go index 7a773262924..c1efabd0e44 100644 --- a/cmd/kubeadm/app/util/apiclient.go +++ b/cmd/kubeadm/app/util/apiclient/wait.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package apiclient import ( "fmt" @@ -26,22 +26,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" clientset "k8s.io/client-go/kubernetes" kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" - kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig" ) -// CreateClientAndWaitForAPI takes a path to a kubeconfig file, makes a client of it and waits for the API to be healthy -func CreateClientAndWaitForAPI(file string) (*clientset.Clientset, error) { - client, err := kubeconfigutil.ClientSetFromFile(file) - if err != nil { - return nil, err - } - - fmt.Println("[apiclient] Created API client, waiting for the control plane to become ready") - WaitForAPI(client) - - return client, nil -} - // WaitForAPI waits for the API Server's /healthz endpoint to report "ok" func WaitForAPI(client clientset.Interface) { start := time.Now()