Unverified Commit 19829a24 authored by Kubernetes Submit Queue's avatar Kubernetes Submit Queue Committed by GitHub

Merge pull request #58960 from rosti/kubeadm-imagepullpolicy

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. kubeadm: imagePullPolicy option in init config **What this PR does / why we need it**: This PR adds `imagePullPolicy` option to the `kubeadm init` configuration file. The new `imagePullPolicy` option is forwarded to the generated kubelet static pods for etcd, kube-apiserver, kube-controller-manager and kube-scheduler. This option allows for precise image pull policy specification for master nodes and thus for more tight control over images. It is useful in CI environments and in environments, where the user has total control over master VM templates (thus, the master VM templates can be preloaded with the required Docker images for the control plane services). **Special notes for your reviewer**: /cc @kubernetes/sig-cluster-lifecycle-pr-reviews /area kubeadm /assign @luxas **Release note**: ```release-note kubeadm: New "imagePullPolicy" option in the init configuration file, that gets forwarded to kubelet static pods to control pull policy for etcd and control plane images. ```
parents cfa67745 a50736f1
...@@ -17,6 +17,7 @@ go_library( ...@@ -17,6 +17,7 @@ go_library(
deps = [ deps = [
"//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", "//pkg/proxy/apis/kubeproxyconfig/v1alpha1: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/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package kubeadm package kubeadm
import ( import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
...@@ -92,6 +93,9 @@ type MasterConfiguration struct { ...@@ -92,6 +93,9 @@ type MasterConfiguration struct {
// CertificatesDir specifies where to store or look for all required certificates. // CertificatesDir specifies where to store or look for all required certificates.
CertificatesDir string CertificatesDir string
// ImagePullPolicy for control plane images. Can be Always, IfNotPresent or Never.
ImagePullPolicy v1.PullPolicy
// ImageRepository is the container registry to pull control plane images from. // ImageRepository is the container registry to pull control plane images from.
ImageRepository string ImageRepository string
......
...@@ -56,6 +56,7 @@ go_library( ...@@ -56,6 +56,7 @@ go_library(
"//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library", "//pkg/kubelet/apis/kubeletconfig/v1alpha1:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library", "//pkg/proxy/apis/kubeproxyconfig/scheme:go_default_library",
"//pkg/proxy/apis/kubeproxyconfig/v1alpha1:go_default_library", "//pkg/proxy/apis/kubeproxyconfig/v1alpha1: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/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
......
...@@ -17,6 +17,7 @@ limitations under the License. ...@@ -17,6 +17,7 @@ limitations under the License.
package v1alpha1 package v1alpha1
import ( import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1" kubeletconfigv1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/kubeletconfig/v1alpha1"
kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1" kubeproxyconfigv1alpha1 "k8s.io/kubernetes/pkg/proxy/apis/kubeproxyconfig/v1alpha1"
...@@ -93,6 +94,8 @@ type MasterConfiguration struct { ...@@ -93,6 +94,8 @@ type MasterConfiguration struct {
// ImageRepository what container registry to pull control plane images from // ImageRepository what container registry to pull control plane images from
ImageRepository string `json:"imageRepository"` ImageRepository string `json:"imageRepository"`
// ImagePullPolicy that control plane images. Can be Always, IfNotPresent or Never.
ImagePullPolicy v1.PullPolicy `json:"imagePullPolicy,omitempty"`
// UnifiedControlPlaneImage specifies if a specific container image should // UnifiedControlPlaneImage specifies if a specific container image should
// be used for all control plane components. // be used for all control plane components.
UnifiedControlPlaneImage string `json:"unifiedControlPlaneImage"` UnifiedControlPlaneImage string `json:"unifiedControlPlaneImage"`
......
...@@ -23,6 +23,7 @@ package v1alpha1 ...@@ -23,6 +23,7 @@ package v1alpha1
import ( import (
unsafe "unsafe" unsafe "unsafe"
core_v1 "k8s.io/api/core/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion" conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
...@@ -214,6 +215,7 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in ...@@ -214,6 +215,7 @@ func autoConvert_v1alpha1_MasterConfiguration_To_kubeadm_MasterConfiguration(in
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs)) out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir out.CertificatesDir = in.CertificatesDir
out.ImageRepository = in.ImageRepository out.ImageRepository = in.ImageRepository
out.ImagePullPolicy = core_v1.PullPolicy(in.ImagePullPolicy)
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates)) out.FeatureGates = *(*map[string]bool)(unsafe.Pointer(&in.FeatureGates))
return nil return nil
...@@ -255,6 +257,7 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in ...@@ -255,6 +257,7 @@ func autoConvert_kubeadm_MasterConfiguration_To_v1alpha1_MasterConfiguration(in
out.SchedulerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes)) out.SchedulerExtraVolumes = *(*[]HostPathMount)(unsafe.Pointer(&in.SchedulerExtraVolumes))
out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs)) out.APIServerCertSANs = *(*[]string)(unsafe.Pointer(&in.APIServerCertSANs))
out.CertificatesDir = in.CertificatesDir out.CertificatesDir = in.CertificatesDir
out.ImagePullPolicy = core_v1.PullPolicy(in.ImagePullPolicy)
out.ImageRepository = in.ImageRepository out.ImageRepository = in.ImageRepository
// INFO: in.CIImageRepository opted out of conversion generation // INFO: in.CIImageRepository opted out of conversion generation
out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage out.UnifiedControlPlaneImage = in.UnifiedControlPlaneImage
......
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"github.com/spf13/cobra" "github.com/spf13/cobra"
flag "github.com/spf13/pflag" flag "github.com/spf13/pflag"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/util/sets"
utilflag "k8s.io/apiserver/pkg/util/flag" utilflag "k8s.io/apiserver/pkg/util/flag"
...@@ -92,10 +93,12 @@ var ( ...@@ -92,10 +93,12 @@ var (
This error is likely caused by: This error is likely caused by:
- The kubelet is not running - The kubelet is not running
- The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled) - The kubelet is unhealthy due to a misconfiguration of the node in some way (required cgroups disabled)
- There is no internet connection, so the kubelet cannot pull the following control plane images: - Either there is no internet connection, or imagePullPolicy is set to "Never",
so the kubelet cannot pull or find the following control plane images:
- {{ .APIServerImage }} - {{ .APIServerImage }}
- {{ .ControllerManagerImage }} - {{ .ControllerManagerImage }}
- {{ .SchedulerImage }} - {{ .SchedulerImage }}
- {{ .EtcdImage }} (only if no external etcd endpoints are configured)
If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands: If you are on a systemd-powered system, you can try to troubleshoot the error with the following commands:
- 'systemctl status kubelet' - 'systemctl status kubelet'
...@@ -357,7 +360,7 @@ func (i *Init) Run(out io.Writer) error { ...@@ -357,7 +360,7 @@ func (i *Init) Run(out io.Writer) error {
} }
// waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled // waiter holds the apiclient.Waiter implementation of choice, responsible for querying the API server in various ways and waiting for conditions to be fulfilled
waiter := getWaiter(i.dryRun, client) waiter := getWaiter(i, client)
if err := waitForAPIAndKubelet(waiter); err != nil { if err := waitForAPIAndKubelet(waiter); err != nil {
ctx := map[string]string{ ctx := map[string]string{
...@@ -365,6 +368,7 @@ func (i *Init) Run(out io.Writer) error { ...@@ -365,6 +368,7 @@ func (i *Init) Run(out io.Writer) error {
"APIServerImage": images.GetCoreImage(kubeadmconstants.KubeAPIServer, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage), "APIServerImage": images.GetCoreImage(kubeadmconstants.KubeAPIServer, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"ControllerManagerImage": images.GetCoreImage(kubeadmconstants.KubeControllerManager, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage), "ControllerManagerImage": images.GetCoreImage(kubeadmconstants.KubeControllerManager, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"SchedulerImage": images.GetCoreImage(kubeadmconstants.KubeScheduler, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage), "SchedulerImage": images.GetCoreImage(kubeadmconstants.KubeScheduler, i.cfg.GetControlPlaneImageRepository(), i.cfg.KubernetesVersion, i.cfg.UnifiedControlPlaneImage),
"EtcdImage": images.GetCoreImage(kubeadmconstants.Etcd, i.cfg.ImageRepository, i.cfg.KubernetesVersion, i.cfg.Etcd.Image),
} }
kubeletFailTempl.Execute(out, ctx) kubeletFailTempl.Execute(out, ctx)
...@@ -522,11 +526,18 @@ func printFilesIfDryRunning(dryRun bool, manifestDir string) error { ...@@ -522,11 +526,18 @@ func printFilesIfDryRunning(dryRun bool, manifestDir string) error {
} }
// getWaiter gets the right waiter implementation for the right occasion // getWaiter gets the right waiter implementation for the right occasion
func getWaiter(dryRun bool, client clientset.Interface) apiclient.Waiter { func getWaiter(i *Init, client clientset.Interface) apiclient.Waiter {
if dryRun { if i.dryRun {
return dryrunutil.NewWaiter() return dryrunutil.NewWaiter()
} }
return apiclient.NewKubeWaiter(client, 30*time.Minute, os.Stdout)
timeout := 30 * time.Minute
// No need for a large timeout if we don't expect downloads
if i.cfg.ImagePullPolicy == v1.PullNever {
timeout = 60 * time.Second
}
return apiclient.NewKubeWaiter(client, timeout, os.Stdout)
} }
// waitForAPIAndKubelet waits primarily for the API server to come up. If that takes a long time, and the kubelet // waitForAPIAndKubelet waits primarily for the API server to come up. If that takes a long time, and the kubelet
......
...@@ -77,31 +77,34 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version. ...@@ -77,31 +77,34 @@ func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.
// Prepare static pod specs // Prepare static pod specs
staticPodSpecs := map[string]v1.Pod{ staticPodSpecs := map[string]v1.Pod{
kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{ kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeAPIServer, Name: kubeadmconstants.KubeAPIServer,
Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getAPIServerCommand(cfg, k8sVersion), ImagePullPolicy: cfg.ImagePullPolicy,
VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)), Command: getAPIServerCommand(cfg, k8sVersion),
LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeAPIServer, int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS), VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)),
Resources: staticpodutil.ComponentResources("250m"), LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeAPIServer, int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
Env: getProxyEnvVars(), Resources: staticpodutil.ComponentResources("250m"),
Env: getProxyEnvVars(),
}, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)), }, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{ kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeControllerManager, Name: kubeadmconstants.KubeControllerManager,
Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getControllerManagerCommand(cfg, k8sVersion), ImagePullPolicy: cfg.ImagePullPolicy,
VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)), Command: getControllerManagerCommand(cfg, k8sVersion),
LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeControllerManager, 10252, "/healthz", v1.URISchemeHTTP), VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)),
Resources: staticpodutil.ComponentResources("200m"), LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeControllerManager, 10252, "/healthz", v1.URISchemeHTTP),
Env: getProxyEnvVars(), Resources: staticpodutil.ComponentResources("200m"),
Env: getProxyEnvVars(),
}, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)), }, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{ kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.KubeScheduler, Name: kubeadmconstants.KubeScheduler,
Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage), Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.GetControlPlaneImageRepository(), cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
Command: getSchedulerCommand(cfg), ImagePullPolicy: cfg.ImagePullPolicy,
VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)), Command: getSchedulerCommand(cfg),
LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeScheduler, 10251, "/healthz", v1.URISchemeHTTP), VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)),
Resources: staticpodutil.ComponentResources("100m"), LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.KubeScheduler, 10251, "/healthz", v1.URISchemeHTTP),
Env: getProxyEnvVars(), Resources: staticpodutil.ComponentResources("100m"),
Env: getProxyEnvVars(),
}, mounts.GetVolumes(kubeadmconstants.KubeScheduler)), }, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
} }
......
...@@ -53,9 +53,10 @@ func GetEtcdPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.Pod { ...@@ -53,9 +53,10 @@ func GetEtcdPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.Pod {
etcdVolumeName: staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir, &pathType), etcdVolumeName: staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir, &pathType),
} }
return staticpodutil.ComponentPod(v1.Container{ return staticpodutil.ComponentPod(v1.Container{
Name: kubeadmconstants.Etcd, Name: kubeadmconstants.Etcd,
Command: getEtcdCommand(cfg), Command: getEtcdCommand(cfg),
Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, cfg.KubernetesVersion, cfg.Etcd.Image), Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, cfg.KubernetesVersion, cfg.Etcd.Image),
ImagePullPolicy: cfg.ImagePullPolicy,
// Mount the etcd datadir path read-write so etcd can store data in a more persistent manner // Mount the etcd datadir path read-write so etcd can store data in a more persistent manner
VolumeMounts: []v1.VolumeMount{staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)}, VolumeMounts: []v1.VolumeMount{staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)},
LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.Etcd, 2379, "/health", v1.URISchemeHTTP), LivenessProbe: staticpodutil.ComponentProbe(cfg, kubeadmconstants.Etcd, 2379, "/health", v1.URISchemeHTTP),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment