mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-21 02:41:25 +00:00
Merge pull request #63450 from chuckha/images
Automatic merge from submit-queue (batch tested with PRs 62665, 62194, 63616, 63672, 63450). 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>. Adds kubeadm images command Closes kubernetes/kubeadm#388 Signed-off-by: Chuck Ha <ha.chuck@gmail.com> **What this PR does / why we need it**: This PR adds a `list-images` subcommand to `kubeadm config`. We need this to make installing kubernetes on air-gapped environments a little easier. This command will print out a list of images it expects to use for the master node. **Which issue(s) this PR fixes** *(optional, in `fixes #<issue number>(, fixes #<issue_number>, ...)` format, will close the issue(s) when PR gets merged)*: Fixes kubernetes/kubeadm#388 **Special notes for your reviewer**: **Release note**: ```release-note Adds a list-images subcommand to kubeadm that lists required images for a kubeadm install. ```
This commit is contained in:
commit
828ffd5a4e
@ -126,3 +126,14 @@ filegroup(
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["config_test.go"],
|
||||
deps = [
|
||||
":go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/features:go_default_library",
|
||||
"//vendor/github.com/renstrom/dedent:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -19,17 +19,21 @@ package cmd
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
flag "github.com/spf13/pflag"
|
||||
|
||||
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"
|
||||
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/uploadconfig"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
@ -62,7 +66,7 @@ func NewCmdConfig(out io.Writer) *cobra.Command {
|
||||
|
||||
cmd.AddCommand(NewCmdConfigUpload(out, &kubeConfigFile))
|
||||
cmd.AddCommand(NewCmdConfigView(out, &kubeConfigFile))
|
||||
|
||||
cmd.AddCommand(NewCmdConfigListImages(out))
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -201,3 +205,70 @@ func uploadConfiguration(client clientset.Interface, cfgPath string, defaultcfg
|
||||
// Then just call the uploadconfig phase to do the rest of the work
|
||||
return uploadconfig.UploadConfiguration(internalcfg, client)
|
||||
}
|
||||
|
||||
// NewCmdConfigListImages returns the "kubeadm images" command
|
||||
func NewCmdConfigListImages(out io.Writer) *cobra.Command {
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
kubeadmapiext.SetDefaults_MasterConfiguration(cfg)
|
||||
var cfgPath, featureGatesString string
|
||||
var err error
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list-images",
|
||||
Short: "Print a list of images kubeadm will use. The configuration file is used in case any images or image repositories are customized.",
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
if cfg.FeatureGates, err = features.NewFeatureGate(&features.InitFeatureGates, featureGatesString); err != nil {
|
||||
kubeadmutil.CheckErr(err)
|
||||
}
|
||||
listImages, err := NewListImages(cfgPath, cfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
kubeadmutil.CheckErr(listImages.Run(out))
|
||||
},
|
||||
}
|
||||
AddListImagesConfigFlag(cmd.PersistentFlags(), cfg, &featureGatesString)
|
||||
AddListImagesFlags(cmd.PersistentFlags(), &cfgPath)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// NewListImages returns a "kubeadm images" command
|
||||
func NewListImages(cfgPath string, cfg *kubeadmapiext.MasterConfiguration) (*ListImages, error) {
|
||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(cfgPath, cfg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not convert cfg to an internal cfg: %v", err)
|
||||
}
|
||||
|
||||
return &ListImages{
|
||||
cfg: internalcfg,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// ListImages defines the struct used for "kubeadm images"
|
||||
type ListImages struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
}
|
||||
|
||||
// Run runs the images command and writes the result to the io.Writer passed in
|
||||
func (i *ListImages) Run(out io.Writer) error {
|
||||
imgs := images.GetAllImages(i.cfg)
|
||||
for _, img := range imgs {
|
||||
fmt.Fprintln(out, img)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddListImagesConfigFlag adds the flags that configure kubeadm
|
||||
func AddListImagesConfigFlag(flagSet *flag.FlagSet, cfg *kubeadmapiext.MasterConfiguration, featureGatesString *string) {
|
||||
flagSet.StringVar(
|
||||
&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion,
|
||||
`Choose a specific Kubernetes version for the control plane.`,
|
||||
)
|
||||
flagSet.StringVar(featureGatesString, "feature-gates", *featureGatesString, "A set of key=value pairs that describe feature gates for various features. "+
|
||||
"Options are:\n"+strings.Join(features.KnownFeatures(&features.InitFeatureGates), "\n"))
|
||||
}
|
||||
|
||||
// AddListImagesFlags adds the flag that defines the location of the config file
|
||||
func AddListImagesFlags(flagSet *flag.FlagSet, cfgPath *string) {
|
||||
flagSet.StringVar(cfgPath, "config", *cfgPath, "Path to kubeadm config file.")
|
||||
}
|
||||
|
172
cmd/kubeadm/app/cmd/config_test.go
Normal file
172
cmd/kubeadm/app/cmd/config_test.go
Normal file
@ -0,0 +1,172 @@
|
||||
/*
|
||||
Copyright 2018 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 cmd_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultNumberOfImages = 8
|
||||
)
|
||||
|
||||
func TestNewCmdConfigListImages(t *testing.T) {
|
||||
var output bytes.Buffer
|
||||
images := cmd.NewCmdConfigListImages(&output)
|
||||
images.Run(nil, nil)
|
||||
actual := strings.Split(output.String(), "\n")
|
||||
if len(actual) != defaultNumberOfImages {
|
||||
t.Fatalf("Expected %v but found %v images", defaultNumberOfImages, len(actual))
|
||||
}
|
||||
}
|
||||
|
||||
func TestListImagesRunWithCustomConfigPath(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
expectedImageCount int
|
||||
// each string provided here must appear in at least one image returned by Run
|
||||
expectedImageSubstrings []string
|
||||
configContents []byte
|
||||
}{
|
||||
{
|
||||
name: "empty config contents",
|
||||
expectedImageCount: defaultNumberOfImages,
|
||||
configContents: []byte{},
|
||||
},
|
||||
{
|
||||
name: "set k8s version",
|
||||
expectedImageCount: defaultNumberOfImages,
|
||||
expectedImageSubstrings: []string{
|
||||
":v1.9.1",
|
||||
},
|
||||
configContents: []byte(dedent.Dedent(`
|
||||
apiVersion: kubeadm.k8s.io/v1alpha1
|
||||
kind: MasterConfiguration
|
||||
kubernetesVersion: 1.9.1
|
||||
`)),
|
||||
},
|
||||
{
|
||||
name: "use coredns",
|
||||
expectedImageCount: defaultNumberOfImages,
|
||||
expectedImageSubstrings: []string{
|
||||
"coredns",
|
||||
},
|
||||
configContents: []byte(dedent.Dedent(`
|
||||
apiVersion: kubeadm.k8s.io/v1alpha1
|
||||
kind: MasterConfiguration
|
||||
featureGates:
|
||||
CoreDNS: True
|
||||
`)),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
tmpDir, err := ioutil.TempDir("", "kubeadm-images-test")
|
||||
if err != nil {
|
||||
t.Fatalf("Unable to create temporary directory: %v", err)
|
||||
}
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
configFilePath := filepath.Join(tmpDir, "test-config-file")
|
||||
err = ioutil.WriteFile(configFilePath, tc.configContents, 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed writing a config file: %v", err)
|
||||
}
|
||||
|
||||
i, err := cmd.NewListImages(configFilePath, &kubeadmapiext.MasterConfiguration{})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed getting the kubeadm images command: %v", err)
|
||||
}
|
||||
var output bytes.Buffer
|
||||
if i.Run(&output) != nil {
|
||||
t.Fatalf("Error from running the images command: %v", err)
|
||||
}
|
||||
actual := strings.Split(output.String(), "\n")
|
||||
if len(actual) != tc.expectedImageCount {
|
||||
t.Fatalf("did not get the same number of images: actual: %v expected: %v. Actual value: %v", len(actual), tc.expectedImageCount, actual)
|
||||
}
|
||||
|
||||
for _, substring := range tc.expectedImageSubstrings {
|
||||
if !strings.Contains(output.String(), substring) {
|
||||
t.Errorf("Expected to find %v but did not in this list of images: %v", substring, actual)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigListImagesRunWithoutPath(t *testing.T) {
|
||||
testcases := []struct {
|
||||
name string
|
||||
cfg kubeadmapiext.MasterConfiguration
|
||||
expectedImages int
|
||||
}{
|
||||
{
|
||||
name: "empty config",
|
||||
expectedImages: defaultNumberOfImages,
|
||||
},
|
||||
{
|
||||
name: "external etcd configuration",
|
||||
cfg: kubeadmapiext.MasterConfiguration{
|
||||
Etcd: kubeadmapiext.Etcd{
|
||||
Endpoints: []string{"hi"},
|
||||
},
|
||||
},
|
||||
expectedImages: defaultNumberOfImages - 1,
|
||||
},
|
||||
{
|
||||
name: "coredns enabled",
|
||||
cfg: kubeadmapiext.MasterConfiguration{
|
||||
FeatureGates: map[string]bool{
|
||||
features.CoreDNS: true,
|
||||
},
|
||||
},
|
||||
expectedImages: defaultNumberOfImages,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
i, err := cmd.NewListImages("", &tc.cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("did not expect an error while creating the Images command: %v", err)
|
||||
}
|
||||
|
||||
var output bytes.Buffer
|
||||
if i.Run(&output) != nil {
|
||||
t.Fatalf("did not expect an error running the Images command: %v", err)
|
||||
}
|
||||
|
||||
actual := strings.Split(output.String(), "\n")
|
||||
if len(actual) != tc.expectedImages {
|
||||
t.Fatalf("expected %v images but got %v", tc.expectedImages, actual)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -11,7 +11,10 @@ go_library(
|
||||
srcs = ["images.go"],
|
||||
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/images",
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/features:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/addons/dns:go_default_library",
|
||||
"//cmd/kubeadm/app/util:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -20,7 +20,10 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/addons/dns"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
)
|
||||
|
||||
@ -42,3 +45,25 @@ func GetCoreImage(image, repoPrefix, k8sVersion, overrideImage string) string {
|
||||
constants.KubeScheduler: fmt.Sprintf("%s/%s-%s:%s", repoPrefix, "kube-scheduler", runtime.GOARCH, kubernetesImageTag),
|
||||
}[image]
|
||||
}
|
||||
|
||||
// GetAllImages returns a list of container images kubeadm expects to use on a control plane node
|
||||
func GetAllImages(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||
imgs := []string{}
|
||||
imgs = append(imgs, GetCoreImage(constants.KubeAPIServer, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||
imgs = append(imgs, GetCoreImage(constants.KubeControllerManager, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||
imgs = append(imgs, GetCoreImage(constants.KubeScheduler, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage))
|
||||
imgs = append(imgs, fmt.Sprintf("%v/%v-%v:%v", cfg.ImageRepository, constants.KubeProxy, runtime.GOARCH, kubeadmutil.KubernetesVersionToImageTag(cfg.KubernetesVersion)))
|
||||
imgs = append(imgs, fmt.Sprintf("%v/pause-%v:%v", cfg.ImageRepository, runtime.GOARCH, "3.1"))
|
||||
|
||||
// if etcd is not external then add the image as it will be required
|
||||
if len(cfg.Etcd.Endpoints) == 0 {
|
||||
imgs = append(imgs, GetCoreImage(constants.Etcd, cfg.ImageRepository, cfg.KubernetesVersion, cfg.Etcd.Image))
|
||||
}
|
||||
|
||||
dnsImage := fmt.Sprintf("%v/k8s-dns-kube-dns-%v:%v", cfg.ImageRepository, runtime.GOARCH, dns.GetDNSVersion(nil, constants.KubeDNS))
|
||||
if features.Enabled(cfg.FeatureGates, features.CoreDNS) {
|
||||
dnsImage = fmt.Sprintf("coredns/coredns:%v", dns.GetDNSVersion(nil, constants.CoreDNS))
|
||||
}
|
||||
imgs = append(imgs, dnsImage)
|
||||
return imgs
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ docs/admin/kubeadm_alpha_phase_selfhosting_convert-from-staticpods.md
|
||||
docs/admin/kubeadm_alpha_phase_upload-config.md
|
||||
docs/admin/kubeadm_completion.md
|
||||
docs/admin/kubeadm_config.md
|
||||
docs/admin/kubeadm_config_list-images.md
|
||||
docs/admin/kubeadm_config_upload.md
|
||||
docs/admin/kubeadm_config_upload_from-file.md
|
||||
docs/admin/kubeadm_config_upload_from-flags.md
|
||||
@ -132,6 +133,7 @@ docs/man/man1/kubeadm-alpha-phase-upload-config.1
|
||||
docs/man/man1/kubeadm-alpha-phase.1
|
||||
docs/man/man1/kubeadm-alpha.1
|
||||
docs/man/man1/kubeadm-completion.1
|
||||
docs/man/man1/kubeadm-config-list-images.1
|
||||
docs/man/man1/kubeadm-config-upload-from-file.1
|
||||
docs/man/man1/kubeadm-config-upload-from-flags.1
|
||||
docs/man/man1/kubeadm-config-upload.1
|
||||
|
3
docs/admin/kubeadm_config_list-images.md
Normal file
3
docs/admin/kubeadm_config_list-images.md
Normal file
@ -0,0 +1,3 @@
|
||||
This file is autogenerated, but we've stopped checking such files into the
|
||||
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||
populate this file.
|
3
docs/man/man1/kubeadm-config-list-images.1
Normal file
3
docs/man/man1/kubeadm-config-list-images.1
Normal file
@ -0,0 +1,3 @@
|
||||
This file is autogenerated, but we've stopped checking such files into the
|
||||
repository to reduce the need for rebases. Please run hack/generate-docs.sh to
|
||||
populate this file.
|
Loading…
Reference in New Issue
Block a user