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:
Kubernetes Submit Queue 2018-05-10 12:56:26 -07:00 committed by GitHub
commit 828ffd5a4e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 291 additions and 1 deletions

View File

@ -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",
],
)

View File

@ -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.")
}

View 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)
}
})
}
}

View File

@ -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",
],
)

View File

@ -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
}

View File

@ -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

View 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.

View 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.