mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-26 19:35:10 +00:00
Merge pull request #33262 from errordeveloper/kubeadm
Automatic merge from submit-queue kubeadm **What this PR does / why we need it**: This PR add alpha version of `kubeadm` tool, which allows user to boostrap a cluster rather quite easily. This is the initial contribution from @kubernetes/sig-cluster-lifecycle members, who's aim is to build easy-to-use tools that help to operate a cluster throughout its lifetime. **Which issue this PR fixes**: a leap towards kubernetes/features#11 **Special notes for your reviewer**: previously seen by many folks in #31221 **Release note**: ```release-note `kubeadm` (alpha) provides an easy way to securely bootstrap Kubernetes on Linux, see http://kubernetes.io/docs/kubeadm/ ```
This commit is contained in:
105
cmd/kubeadm/app/api/types.go
Normal file
105
cmd/kubeadm/app/api/types.go
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
Copyright 2016 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 api
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// KubeadmConfig TODO add description
|
||||
// TODO(phase1+) @krousey: Please don't embed structs. It obfuscates the source of the fields and doesn't really buy you anything.
|
||||
type KubeadmConfig struct {
|
||||
InitFlags
|
||||
JoinFlags
|
||||
Secrets struct {
|
||||
GivenToken string // dot-separated `<TokenID>.<Token>` set by the user
|
||||
TokenID string // optional on master side, will be generated if not specified
|
||||
Token []byte // optional on master side, will be generated if not specified
|
||||
BearerToken string // set based on Token
|
||||
}
|
||||
EnvParams map[string]string // TODO(phase2) this is likely to be come componentconfig
|
||||
}
|
||||
|
||||
// TODO(phase2) should we add validation functions for these structs?
|
||||
|
||||
// TODO(phase1+) refactor token handling
|
||||
// - https://github.com/kubernetes/kubernetes/pull/33262/files#r80333662
|
||||
// - https://github.com/kubernetes/kubernetes/pull/33262/files#r80336374
|
||||
// - https://github.com/kubernetes/kubernetes/pull/33262/files#r80333982
|
||||
|
||||
// InitFlags holds values for "kubeadm init" command flags.
|
||||
type InitFlags struct {
|
||||
API struct {
|
||||
AdvertiseAddrs []net.IP
|
||||
ExternalDNSNames []string
|
||||
Etcd struct {
|
||||
ExternalEndpoints []string
|
||||
ExternalCAFile string
|
||||
ExternalCertFile string
|
||||
ExternalKeyFile string
|
||||
}
|
||||
}
|
||||
Services struct {
|
||||
CIDR net.IPNet
|
||||
DNSDomain string
|
||||
}
|
||||
PodNetwork struct {
|
||||
CIDR net.IPNet
|
||||
}
|
||||
Versions struct {
|
||||
Kubernetes string
|
||||
}
|
||||
CloudProvider string
|
||||
}
|
||||
|
||||
const (
|
||||
DefaultServiceDNSDomain = "cluster.local"
|
||||
DefaultServicesCIDRString = "100.64.0.0/12" // Carrier-grade NAT range (RFC 6598)
|
||||
DefaultKubernetesVersion = "v1.4.0"
|
||||
)
|
||||
|
||||
var (
|
||||
DefaultServicesCIDR *net.IPNet
|
||||
ListOfCloudProviders = []string{
|
||||
"aws",
|
||||
"azure",
|
||||
"cloudstack",
|
||||
"gce",
|
||||
"mesos",
|
||||
"openstack",
|
||||
"ovirt",
|
||||
"rackspace",
|
||||
"vsphere",
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
_, DefaultServicesCIDR, _ = net.ParseCIDR(DefaultServicesCIDRString)
|
||||
}
|
||||
|
||||
// JoinFlags holds values for "kubeadm join" command flags.
|
||||
type JoinFlags struct {
|
||||
MasterAddrs []net.IP
|
||||
// TODO(phase1+) add manual mode flags here, e.g. RootCACertPath
|
||||
}
|
||||
|
||||
// ClusterInfo TODO add description
|
||||
type ClusterInfo struct {
|
||||
// TODO(phase1+) this may become simply `api.Config`
|
||||
CertificateAuthorities []string `json:"certificateAuthorities"`
|
||||
Endpoints []string `json:"endpoints"`
|
||||
}
|
||||
90
cmd/kubeadm/app/cmd/cmd.go
Normal file
90
cmd/kubeadm/app/cmd/cmd.go
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
Copyright 2016 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
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/util/flag"
|
||||
)
|
||||
|
||||
func NewKubeadmCommand(f *cmdutil.Factory, in io.Reader, out, err io.Writer, envParams map[string]string) *cobra.Command {
|
||||
cmds := &cobra.Command{
|
||||
Use: "kubeadm",
|
||||
Short: "kubeadm: easily bootstrap a secure Kubernetes cluster.",
|
||||
Long: dedent.Dedent(`
|
||||
kubeadm: easily bootstrap a secure Kubernetes cluster.
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ KUBEADM IS ALPHA, DO NOT USE IT FOR PRODUCTION CLUSTERS! │
|
||||
│ │
|
||||
│ But, please try it out! Give us feedback at: │
|
||||
│ https://github.com/kubernetes/kubernetes/issues │
|
||||
│ and at-mention @kubernetes/sig-cluster-lifecycle │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
Example usage:
|
||||
|
||||
Create a two-machine cluster with one master (which controls the cluster),
|
||||
and one node (where workloads, like pods and replica sets run).
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ On the first machine │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ master# kubeadm init │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
┌──────────────────────────────────────────────────────────┐
|
||||
│ On the second machine │
|
||||
├──────────────────────────────────────────────────────────┤
|
||||
│ node# kubeadm join --token=<token> <ip-of-master> │
|
||||
└──────────────────────────────────────────────────────────┘
|
||||
|
||||
You can then repeat the second step on as many other machines as you like.
|
||||
|
||||
`),
|
||||
}
|
||||
// TODO(phase2+) figure out how to avoid running as root
|
||||
//
|
||||
// TODO(phase2) detect interactive vs non-interactive use and adjust output accordingly
|
||||
// i.e. make it automation friendly
|
||||
//
|
||||
// TODO(phase2) create an abstraction that defines files and the content that needs to
|
||||
// be written to disc and write it all in one go at the end as we have a lot of
|
||||
// crapy little files written from different parts of this code; this could also
|
||||
// be useful for testing
|
||||
// by having this model we can allow users to create some files before `kubeadm init` runs, e.g. PKI assets, we
|
||||
// would then be able to look at files users has given an diff or validate if those are sane, we could also warn
|
||||
// if any of the files had been deprecated
|
||||
|
||||
s := new(kubeadmapi.KubeadmConfig)
|
||||
s.EnvParams = envParams
|
||||
|
||||
cmds.ResetFlags()
|
||||
cmds.SetGlobalNormalizationFunc(flag.WarnWordSepNormalizeFunc)
|
||||
|
||||
cmds.AddCommand(NewCmdInit(out, s))
|
||||
cmds.AddCommand(NewCmdJoin(out, s))
|
||||
cmds.AddCommand(NewCmdVersion(out))
|
||||
|
||||
return cmds
|
||||
}
|
||||
208
cmd/kubeadm/app/cmd/init.go
Normal file
208
cmd/kubeadm/app/cmd/init.go
Normal file
@@ -0,0 +1,208 @@
|
||||
/*
|
||||
Copyright 2016 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
kubemaster "k8s.io/kubernetes/cmd/kubeadm/app/master"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
netutil "k8s.io/kubernetes/pkg/util/net"
|
||||
)
|
||||
|
||||
var (
|
||||
initDoneMsgf = dedent.Dedent(`
|
||||
Kubernetes master initialised successfully!
|
||||
|
||||
You can now join any number of machines by running the following on each node:
|
||||
|
||||
kubeadm join --token %s %s
|
||||
`)
|
||||
)
|
||||
|
||||
// NewCmdInit returns "kubeadm init" command.
|
||||
func NewCmdInit(out io.Writer, s *kubeadmapi.KubeadmConfig) *cobra.Command {
|
||||
advertiseAddrs := &[]string{} // TODO(pahse1+) make it work somehow else, custom flag or whatever
|
||||
cmd := &cobra.Command{
|
||||
Use: "init",
|
||||
Short: "Run this in order to set up the Kubernetes master.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := RunInit(out, cmd, args, s, advertiseAddrs)
|
||||
cmdutil.CheckErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.Secrets.GivenToken, "token", "",
|
||||
"Shared secret used to secure cluster bootstrap; if none is provided, one will be generated for you",
|
||||
)
|
||||
cmd.PersistentFlags().StringSliceVar(
|
||||
advertiseAddrs, "api-advertise-addresses", []string{},
|
||||
"The IP addresses to advertise, in case autodetection fails",
|
||||
)
|
||||
cmd.PersistentFlags().StringSliceVar(
|
||||
&s.InitFlags.API.ExternalDNSNames, "api-external-dns-names", []string{},
|
||||
"The DNS names to advertise, in case you have configured them yourself",
|
||||
)
|
||||
cmd.PersistentFlags().IPNetVar(
|
||||
&s.InitFlags.Services.CIDR, "service-cidr", *kubeadmapi.DefaultServicesCIDR,
|
||||
`Use alterantive range of IP address for service VIPs, defaults to `+
|
||||
kubeadmapi.DefaultServicesCIDRString,
|
||||
)
|
||||
cmd.PersistentFlags().IPNetVar(
|
||||
&s.InitFlags.PodNetwork.CIDR, "pod-network-cidr", net.IPNet{},
|
||||
"Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node",
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.Services.DNSDomain, "service-dns-domain", kubeadmapi.DefaultServiceDNSDomain,
|
||||
`Use alternative domain for services, e.g. "myorg.internal"`,
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.CloudProvider, "cloud-provider", "",
|
||||
`Enable cloud provider features (external load-balancers, storage, etc), e.g. "gce"`,
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.Versions.Kubernetes, "use-kubernetes-version", kubeadmapi.DefaultKubernetesVersion,
|
||||
`Choose a specific Kubernetes version for the control plane`,
|
||||
)
|
||||
|
||||
// TODO (phase1+) @errordeveloper make the flags below not show up in --help but rather on --advanced-help
|
||||
cmd.PersistentFlags().StringSliceVar(
|
||||
&s.InitFlags.API.Etcd.ExternalEndpoints, "external-etcd-endpoints", []string{},
|
||||
"etcd endpoints to use, in case you have an external cluster",
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.API.Etcd.ExternalCAFile, "external-etcd-cafile", "",
|
||||
"etcd certificate authority certificate file. Note: The path must be in /etc/ssl/certs",
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.API.Etcd.ExternalCertFile, "external-etcd-certfile", "",
|
||||
"etcd client certificate file. Note: The path must be in /etc/ssl/certs",
|
||||
)
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.InitFlags.API.Etcd.ExternalKeyFile, "external-etcd-keyfile", "",
|
||||
"etcd client key file. Note: The path must be in /etc/ssl/certs",
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunInit executes master node provisioning, including certificates, needed static pod manifests, etc.
|
||||
func RunInit(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.KubeadmConfig, advertiseAddrs *[]string) error {
|
||||
// Auto-detect the IP
|
||||
if len(*advertiseAddrs) == 0 {
|
||||
// TODO(phase1+) perhaps we could actually grab eth0 and eth1
|
||||
ip, err := netutil.ChooseHostInterface()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.InitFlags.API.AdvertiseAddrs = []net.IP{ip}
|
||||
} else {
|
||||
for _, i := range *advertiseAddrs {
|
||||
addr := net.ParseIP(i)
|
||||
if addr == nil {
|
||||
// TODO(phase1+) custom flag will help to get this error message into a better place
|
||||
return fmt.Errorf("<cmd/init> failed to parse %q (in %q) as an IP address", i, "--api-advertise-addresses="+strings.Join(*advertiseAddrs, ","))
|
||||
}
|
||||
s.InitFlags.API.AdvertiseAddrs = append(s.InitFlags.API.AdvertiseAddrs, addr)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(phase1+) create a custom flag
|
||||
if s.InitFlags.CloudProvider != "" {
|
||||
found := false
|
||||
for _, provider := range kubeadmapi.ListOfCloudProviders {
|
||||
if provider == s.InitFlags.CloudProvider {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
fmt.Printf("<cmd/init> cloud provider %q initialized for the control plane. Remember to set the same cloud provider flag on the kubelet.\n", s.InitFlags.CloudProvider)
|
||||
} else {
|
||||
return fmt.Errorf("<cmd/init> cloud provider %q is not supported, you can use any of %v, or leave it unset.\n", s.InitFlags.CloudProvider, kubeadmapi.ListOfCloudProviders)
|
||||
}
|
||||
}
|
||||
|
||||
if err := kubemaster.CreateTokenAuthFile(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := kubemaster.WriteStaticPodManifests(s); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
caKey, caCert, err := kubemaster.CreatePKIAssets(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kubeconfigs, err := kubemaster.CreateCertsAndConfigForClients(s, []string{"kubelet", "admin"}, caKey, caCert)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// kubeadm is responsible for writing the following kubeconfig file, which
|
||||
// kubelet should be waiting for. Help user avoid foot-shooting by refusing to
|
||||
// write a file that has already been written (the kubelet will be up and
|
||||
// running in that case - they'd need to stop the kubelet, remove the file, and
|
||||
// start it again in that case).
|
||||
// TODO(phase1+) this is no longer the right place to guard agains foo-shooting,
|
||||
// we need to decide how to handle existing files (it may be handy to support
|
||||
// importing existing files, may be we could even make our command idempotant,
|
||||
// or at least allow for external PKI and stuff)
|
||||
for name, kubeconfig := range kubeconfigs {
|
||||
if err := kubeadmutil.WriteKubeconfigIfNotExists(s, name, kubeconfig); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
client, err := kubemaster.CreateClientAndWaitForAPI(kubeconfigs["admin"])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schedulePodsOnMaster := false
|
||||
if err := kubemaster.UpdateMasterRoleLabelsAndTaints(client, schedulePodsOnMaster); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := kubemaster.CreateDiscoveryDeploymentAndSecret(s, client, caCert); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := kubemaster.CreateEssentialAddons(s, client); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(phase1+) use templates to reference struct fields directly as order of args is fragile
|
||||
fmt.Fprintf(out, initDoneMsgf,
|
||||
s.Secrets.GivenToken,
|
||||
s.InitFlags.API.AdvertiseAddrs[0].String(),
|
||||
)
|
||||
|
||||
return nil
|
||||
}
|
||||
97
cmd/kubeadm/app/cmd/join.go
Normal file
97
cmd/kubeadm/app/cmd/join.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2016 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
kubenode "k8s.io/kubernetes/cmd/kubeadm/app/node"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
)
|
||||
|
||||
var (
|
||||
joinDoneMsgf = dedent.Dedent(`
|
||||
Node join complete:
|
||||
* Certificate signing request sent to master and response
|
||||
received.
|
||||
* Kubelet informed of new secure connection details.
|
||||
|
||||
Run 'kubectl get nodes' on the master to see this machine join.
|
||||
`)
|
||||
)
|
||||
|
||||
// NewCmdJoin returns "kubeadm join" command.
|
||||
func NewCmdJoin(out io.Writer, s *kubeadmapi.KubeadmConfig) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "join",
|
||||
Short: "Run this on any machine you wish to join an existing cluster.",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := RunJoin(out, cmd, args, s)
|
||||
cmdutil.CheckErr(err)
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVar(
|
||||
&s.Secrets.GivenToken, "token", "",
|
||||
"(required) Shared secret used to secure bootstrap. Must match the output of 'kubeadm init'",
|
||||
)
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// RunJoin executes worked node provisioning and tries to join an existing cluster.
|
||||
func RunJoin(out io.Writer, cmd *cobra.Command, args []string, s *kubeadmapi.KubeadmConfig) error {
|
||||
// TODO(phase1+) this we are missing args from the help text, there should be a way to tell cobra about it
|
||||
if len(args) == 0 {
|
||||
return fmt.Errorf("<cmd/join> must specify master IP address (see --help)")
|
||||
}
|
||||
for _, i := range args {
|
||||
addr := net.ParseIP(i) // TODO(phase1+) should allow resolvable names too
|
||||
if addr == nil {
|
||||
return fmt.Errorf("<cmd/join> failed to parse argument (%q) as an IP address", i)
|
||||
}
|
||||
s.JoinFlags.MasterAddrs = append(s.JoinFlags.MasterAddrs, addr)
|
||||
}
|
||||
|
||||
ok, err := kubeadmutil.UseGivenTokenIfValid(s)
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return fmt.Errorf("<cmd/join> %v (see --help)\n", err)
|
||||
}
|
||||
return fmt.Errorf("Must specify --token (see --help)\n")
|
||||
}
|
||||
|
||||
kubeconfig, err := kubenode.RetrieveTrustedClusterInfo(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kubeadmutil.WriteKubeconfigIfNotExists(s, "kubelet", kubeconfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fmt.Fprintf(out, joinDoneMsgf)
|
||||
return nil
|
||||
}
|
||||
44
cmd/kubeadm/app/cmd/version.go
Normal file
44
cmd/kubeadm/app/cmd/version.go
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2014 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
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
func NewCmdVersion(out io.Writer) *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version of kubeadm",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
err := RunVersion(out, cmd)
|
||||
cmdutil.CheckErr(err)
|
||||
},
|
||||
}
|
||||
return cmd
|
||||
}
|
||||
|
||||
func RunVersion(out io.Writer, cmd *cobra.Command) error {
|
||||
fmt.Fprintf(out, "kubeadm version: %#v\n", version.Get())
|
||||
return nil
|
||||
}
|
||||
66
cmd/kubeadm/app/images/images.go
Normal file
66
cmd/kubeadm/app/images/images.go
Normal file
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2016 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 images
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
)
|
||||
|
||||
const (
|
||||
KubeEtcdImage = "etcd"
|
||||
|
||||
KubeAPIServerImage = "apiserver"
|
||||
KubeControllerManagerImage = "controller-manager"
|
||||
KubeSchedulerImage = "scheduler"
|
||||
KubeProxyImage = "proxy"
|
||||
|
||||
KubeDNSImage = "kube-dns"
|
||||
KubeDNSmasqImage = "dnsmasq"
|
||||
KubeExechealthzImage = "exechealthz"
|
||||
|
||||
gcrPrefix = "gcr.io/google_containers"
|
||||
etcdVersion = "2.2.5"
|
||||
|
||||
kubeDNSVersion = "1.7"
|
||||
dnsmasqVersion = "1.3"
|
||||
exechealthzVersion = "1.1"
|
||||
)
|
||||
|
||||
func GetCoreImage(image string, cfg *kubeadmapi.KubeadmConfig, overrideImage string) string {
|
||||
if overrideImage != "" {
|
||||
return overrideImage
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
KubeEtcdImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "etcd", runtime.GOARCH, etcdVersion),
|
||||
KubeAPIServerImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kube-apiserver", runtime.GOARCH, cfg.Versions.Kubernetes),
|
||||
KubeControllerManagerImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kube-controller-manager", runtime.GOARCH, cfg.Versions.Kubernetes),
|
||||
KubeSchedulerImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kube-scheduler", runtime.GOARCH, cfg.Versions.Kubernetes),
|
||||
KubeProxyImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kube-proxy", runtime.GOARCH, cfg.Versions.Kubernetes),
|
||||
}[image]
|
||||
}
|
||||
|
||||
func GetAddonImage(image string) string {
|
||||
return map[string]string{
|
||||
KubeDNSImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kubedns", runtime.GOARCH, kubeDNSVersion),
|
||||
KubeDNSmasqImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "kube-dnsmasq", runtime.GOARCH, dnsmasqVersion),
|
||||
KubeExechealthzImage: fmt.Sprintf("%s/%s-%s:%s", gcrPrefix, "exechealthz", runtime.GOARCH, exechealthzVersion),
|
||||
}[image]
|
||||
}
|
||||
74
cmd/kubeadm/app/kubeadm.go
Normal file
74
cmd/kubeadm/app/kubeadm.go
Normal file
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
Copyright 2016 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 app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/renstrom/dedent"
|
||||
"github.com/spf13/pflag"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/util/logs"
|
||||
)
|
||||
|
||||
var AlphaWarningOnExit = dedent.Dedent(`
|
||||
kubeadm: I am an alpha version, my authors welcome your feedback and bug reports
|
||||
kubeadm: please create issue an using https://github.com/kubernetes/kubernetes/issues/new
|
||||
kubeadm: and make sure to mention @kubernetes/sig-cluster-lifecycle. Thank you!
|
||||
`)
|
||||
|
||||
// TODO(phase2) use componentconfig
|
||||
// we need some params for testing etc, let's keep these hidden for now
|
||||
func getEnvParams() map[string]string {
|
||||
|
||||
envParams := map[string]string{
|
||||
// TODO(phase1+): Mode prefix and host_pki_path to another place as constants, and use them everywhere
|
||||
// Right now they're used here and there, but not consequently
|
||||
"kubernetes_dir": "/etc/kubernetes",
|
||||
"host_pki_path": "/etc/kubernetes/pki",
|
||||
"host_etcd_path": "/var/lib/etcd",
|
||||
"hyperkube_image": "",
|
||||
"discovery_image": fmt.Sprintf("gcr.io/google_containers/kube-discovery-%s:%s", runtime.GOARCH, "1.0"),
|
||||
"etcd_image": "",
|
||||
"component_loglevel": "--v=4",
|
||||
}
|
||||
|
||||
for k := range envParams {
|
||||
if v := os.Getenv(fmt.Sprintf("KUBE_%s", strings.ToUpper(k))); v != "" {
|
||||
envParams[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return envParams
|
||||
}
|
||||
|
||||
func Run() error {
|
||||
logs.InitLogs()
|
||||
defer logs.FlushLogs()
|
||||
|
||||
// We do not want these flags to show up in --help
|
||||
pflag.CommandLine.MarkHidden("google-json-key")
|
||||
pflag.CommandLine.MarkHidden("log-flush-frequency")
|
||||
|
||||
cmd := cmd.NewKubeadmCommand(cmdutil.NewFactory(nil), os.Stdin, os.Stdout, os.Stderr, getEnvParams())
|
||||
return cmd.Execute()
|
||||
}
|
||||
267
cmd/kubeadm/app/master/addons.go
Normal file
267
cmd/kubeadm/app/master/addons.go
Normal file
@@ -0,0 +1,267 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"runtime"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// TODO(phase1+): kube-proxy should be a daemonset, three different daemonsets should not be here
|
||||
func createKubeProxyPodSpec(s *kubeadmapi.KubeadmConfig, architecture string) api.PodSpec {
|
||||
privilegedTrue := true
|
||||
return api.PodSpec{
|
||||
SecurityContext: &api.PodSecurityContext{HostNetwork: true},
|
||||
NodeSelector: map[string]string{
|
||||
"beta.kubernetes.io/arch": architecture,
|
||||
},
|
||||
Containers: []api.Container{{
|
||||
Name: kubeProxy,
|
||||
Image: images.GetCoreImage(images.KubeProxyImage, s, s.EnvParams["hyperkube_image"]),
|
||||
Command: append(getComponentCommand("proxy", s), "--kubeconfig=/run/kubeconfig"),
|
||||
SecurityContext: &api.SecurityContext{Privileged: &privilegedTrue},
|
||||
VolumeMounts: []api.VolumeMount{
|
||||
{
|
||||
Name: "dbus",
|
||||
MountPath: "/var/run/dbus",
|
||||
ReadOnly: false,
|
||||
},
|
||||
{
|
||||
// TODO there are handful of clever options to get around this, but it's
|
||||
// easier to just mount kubelet's config here; we should probably just
|
||||
// make sure that proxy reads the token and CA cert from /run/secrets
|
||||
// and accepts `--master` at the same time
|
||||
//
|
||||
// clever options include:
|
||||
// - do CSR dance and create kubeconfig and mount it as a secret
|
||||
// - create a service account with a second secret encoding kubeconfig
|
||||
// - use init container to convert known information to kubeconfig
|
||||
// - ...whatever
|
||||
Name: "kubeconfig",
|
||||
MountPath: "/run/kubeconfig",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
}},
|
||||
Volumes: []api.Volume{
|
||||
{
|
||||
Name: "kubeconfig",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{Path: path.Join(s.EnvParams["kubernetes_dir"], "kubelet.conf")},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "dbus",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{Path: "/var/run/dbus"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func createKubeDNSPodSpec(s *kubeadmapi.KubeadmConfig) api.PodSpec {
|
||||
|
||||
dnsPodResources := api.ResourceList{
|
||||
api.ResourceName(api.ResourceCPU): resource.MustParse("100m"),
|
||||
api.ResourceName(api.ResourceMemory): resource.MustParse("170Mi"),
|
||||
}
|
||||
|
||||
healthzPodResources := api.ResourceList{
|
||||
api.ResourceName(api.ResourceCPU): resource.MustParse("10m"),
|
||||
api.ResourceName(api.ResourceMemory): resource.MustParse("50Mi"),
|
||||
}
|
||||
|
||||
kubeDNSPort := int32(10053)
|
||||
dnsmasqPort := int32(53)
|
||||
|
||||
nslookup := fmt.Sprintf("nslookup kubernetes.default.svc.%s 127.0.0.1", s.InitFlags.Services.DNSDomain)
|
||||
|
||||
nslookup = fmt.Sprintf("-cmd=%s:%d >/dev/null && %s:%d >/dev/null",
|
||||
nslookup, dnsmasqPort,
|
||||
nslookup, kubeDNSPort,
|
||||
)
|
||||
|
||||
return api.PodSpec{
|
||||
NodeSelector: map[string]string{
|
||||
"beta.kubernetes.io/arch": runtime.GOARCH,
|
||||
},
|
||||
Containers: []api.Container{
|
||||
// DNS server
|
||||
{
|
||||
Name: "kube-dns",
|
||||
Image: images.GetAddonImage(images.KubeDNSImage),
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: dnsPodResources,
|
||||
Requests: dnsPodResources,
|
||||
},
|
||||
Args: []string{
|
||||
fmt.Sprintf("--domain=%s", s.InitFlags.Services.DNSDomain),
|
||||
fmt.Sprintf("--dns-port=%d", kubeDNSPort),
|
||||
// TODO __PILLAR__FEDERATIONS__DOMAIN__MAP__
|
||||
},
|
||||
LivenessProbe: &api.Probe{
|
||||
Handler: api.Handler{
|
||||
HTTPGet: &api.HTTPGetAction{
|
||||
Path: "/healthz",
|
||||
Port: intstr.FromInt(8080),
|
||||
Scheme: api.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 60,
|
||||
TimeoutSeconds: 5,
|
||||
SuccessThreshold: 1,
|
||||
FailureThreshold: 1,
|
||||
},
|
||||
// # we poll on pod startup for the Kubernetes master service and
|
||||
// # only setup the /readiness HTTP server once that's available.
|
||||
ReadinessProbe: &api.Probe{
|
||||
Handler: api.Handler{
|
||||
HTTPGet: &api.HTTPGetAction{
|
||||
Path: "/readiness",
|
||||
Port: intstr.FromInt(8081),
|
||||
Scheme: api.URISchemeHTTP,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 30,
|
||||
TimeoutSeconds: 5,
|
||||
},
|
||||
Ports: []api.ContainerPort{
|
||||
{
|
||||
ContainerPort: kubeDNSPort,
|
||||
Name: "dns-local",
|
||||
Protocol: api.ProtocolUDP,
|
||||
},
|
||||
{
|
||||
ContainerPort: kubeDNSPort,
|
||||
Name: "dns-tcp-local",
|
||||
Protocol: api.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
// dnsmasq
|
||||
{
|
||||
Name: "dnsmasq",
|
||||
Image: images.GetAddonImage(images.KubeDNSmasqImage),
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: dnsPodResources,
|
||||
Requests: dnsPodResources,
|
||||
},
|
||||
Args: []string{
|
||||
"--cache-size=1000",
|
||||
"--no-resolv",
|
||||
fmt.Sprintf("--server=127.0.0.1#%d", kubeDNSPort),
|
||||
},
|
||||
Ports: []api.ContainerPort{
|
||||
{
|
||||
ContainerPort: dnsmasqPort,
|
||||
Name: "dns",
|
||||
Protocol: api.ProtocolUDP,
|
||||
},
|
||||
{
|
||||
ContainerPort: dnsmasqPort,
|
||||
Name: "dns-tcp",
|
||||
Protocol: api.ProtocolTCP,
|
||||
},
|
||||
},
|
||||
},
|
||||
// healthz
|
||||
{
|
||||
Name: "healthz",
|
||||
Image: images.GetAddonImage(images.KubeExechealthzImage),
|
||||
Resources: api.ResourceRequirements{
|
||||
Limits: healthzPodResources,
|
||||
Requests: healthzPodResources,
|
||||
},
|
||||
Args: []string{
|
||||
nslookup,
|
||||
"-port=8080",
|
||||
"-quiet",
|
||||
},
|
||||
Ports: []api.ContainerPort{{
|
||||
ContainerPort: 8080,
|
||||
Protocol: api.ProtocolTCP,
|
||||
}},
|
||||
},
|
||||
},
|
||||
DNSPolicy: api.DNSDefault,
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func createKubeDNSServiceSpec(s *kubeadmapi.KubeadmConfig) (*api.ServiceSpec, error) {
|
||||
ip, err := ipallocator.GetIndexedIP(&s.InitFlags.Services.CIDR, 10)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to allocate IP address for kube-dns addon from the given CIDR (%q) [%v]", s.InitFlags.Services.CIDR, err)
|
||||
}
|
||||
|
||||
svc := &api.ServiceSpec{
|
||||
Selector: map[string]string{"name": "kube-dns"},
|
||||
Ports: []api.ServicePort{
|
||||
{Name: "dns", Port: 53, Protocol: api.ProtocolUDP},
|
||||
{Name: "dns-tcp", Port: 53, Protocol: api.ProtocolTCP},
|
||||
},
|
||||
ClusterIP: ip.String(),
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
}
|
||||
|
||||
func CreateEssentialAddons(s *kubeadmapi.KubeadmConfig, client *clientset.Clientset) error {
|
||||
arches := [3]string{"amd64", "arm", "arm64"}
|
||||
|
||||
for _, arch := range arches {
|
||||
kubeProxyDaemonSet := NewDaemonSet(kubeProxy+"-"+arch, createKubeProxyPodSpec(s, arch))
|
||||
SetMasterTaintTolerations(&kubeProxyDaemonSet.Spec.Template.ObjectMeta)
|
||||
|
||||
if _, err := client.Extensions().DaemonSets(api.NamespaceSystem).Create(kubeProxyDaemonSet); err != nil {
|
||||
return fmt.Errorf("<master/addons> failed creating essential kube-proxy addon [%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("<master/addons> created essential addon: kube-proxy")
|
||||
|
||||
kubeDNSDeployment := NewDeployment("kube-dns", 1, createKubeDNSPodSpec(s))
|
||||
SetMasterTaintTolerations(&kubeDNSDeployment.Spec.Template.ObjectMeta)
|
||||
|
||||
if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kubeDNSDeployment); err != nil {
|
||||
return fmt.Errorf("<master/addons> failed creating essential kube-dns addon [%v]", err)
|
||||
}
|
||||
|
||||
kubeDNSServiceSpec, err := createKubeDNSServiceSpec(s)
|
||||
if err != nil {
|
||||
return fmt.Errorf("<master/addons> failed creating essential kube-dns addon - %v", err)
|
||||
}
|
||||
|
||||
kubeDNSService := NewService("kube-dns", *kubeDNSServiceSpec)
|
||||
if _, err := client.Services(api.NamespaceSystem).Create(kubeDNSService); err != nil {
|
||||
return fmt.Errorf("<master/addons> failed creating essential kube-dns addon [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("<master/addons> created essential addon: kube-dns")
|
||||
|
||||
return nil
|
||||
}
|
||||
221
cmd/kubeadm/app/master/apiclient.go
Normal file
221
cmd/kubeadm/app/master/apiclient.go
Normal file
@@ -0,0 +1,221 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apierrs "k8s.io/kubernetes/pkg/api/errors"
|
||||
unversionedapi "k8s.io/kubernetes/pkg/api/unversioned"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
const apiCallRetryInterval = 500 * time.Millisecond
|
||||
|
||||
func CreateClientAndWaitForAPI(adminConfig *clientcmdapi.Config) (*clientset.Clientset, error) {
|
||||
adminClientConfig, err := clientcmd.NewDefaultClientConfig(
|
||||
*adminConfig,
|
||||
&clientcmd.ConfigOverrides{},
|
||||
).ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<master/apiclient> failed to create API client configuration [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("<master/apiclient> created API client configuration")
|
||||
|
||||
client, err := clientset.NewForConfig(adminClientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<master/apiclient> failed to create API client [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("<master/apiclient> created API client, waiting for the control plane to become ready")
|
||||
|
||||
start := time.Now()
|
||||
wait.PollInfinite(apiCallRetryInterval, func() (bool, error) {
|
||||
cs, err := client.ComponentStatuses().List(api.ListOptions{})
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
// TODO(phase2) must revisit this when we implement HA
|
||||
if len(cs.Items) < 3 {
|
||||
fmt.Println("<master/apiclient> not all control plane components are ready yet")
|
||||
return false, nil
|
||||
}
|
||||
for _, item := range cs.Items {
|
||||
for _, condition := range item.Conditions {
|
||||
if condition.Type != api.ComponentHealthy {
|
||||
fmt.Printf("<master/apiclient> control plane component %q is still unhealthy: %#v\n", item.ObjectMeta.Name, item.Conditions)
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Printf("<master/apiclient> all control plane components are healthy after %f seconds\n", time.Since(start).Seconds())
|
||||
return true, nil
|
||||
})
|
||||
|
||||
fmt.Println("<master/apiclient> waiting for at least one node to register and become ready")
|
||||
start = time.Now()
|
||||
wait.PollInfinite(apiCallRetryInterval, func() (bool, error) {
|
||||
nodeList, err := client.Nodes().List(api.ListOptions{})
|
||||
if err != nil {
|
||||
fmt.Println("<master/apiclient> temporarily unable to list nodes (will retry)")
|
||||
return false, nil
|
||||
}
|
||||
if len(nodeList.Items) < 1 {
|
||||
return false, nil
|
||||
}
|
||||
n := &nodeList.Items[0]
|
||||
if !api.IsNodeReady(n) {
|
||||
fmt.Println("<master/apiclient> first node has registered, but is not ready yet")
|
||||
return false, nil
|
||||
}
|
||||
|
||||
fmt.Printf("<master/apiclient> first node is ready after %f seconds\n", time.Since(start).Seconds())
|
||||
return true, nil
|
||||
})
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func standardLabels(n string) map[string]string {
|
||||
return map[string]string{
|
||||
"component": n, "name": n, "k8s-app": n,
|
||||
"kubernetes.io/cluster-service": "true", "tier": "node",
|
||||
}
|
||||
}
|
||||
|
||||
func NewDaemonSet(daemonName string, podSpec api.PodSpec) *extensions.DaemonSet {
|
||||
l := standardLabels(daemonName)
|
||||
return &extensions.DaemonSet{
|
||||
ObjectMeta: api.ObjectMeta{Name: daemonName},
|
||||
Spec: extensions.DaemonSetSpec{
|
||||
Selector: &unversionedapi.LabelSelector{MatchLabels: l},
|
||||
Template: api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{Labels: l},
|
||||
Spec: podSpec,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func NewService(serviceName string, spec api.ServiceSpec) *api.Service {
|
||||
l := standardLabels(serviceName)
|
||||
return &api.Service{
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: serviceName,
|
||||
Labels: l,
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDeployment(deploymentName string, replicas int32, podSpec api.PodSpec) *extensions.Deployment {
|
||||
l := standardLabels(deploymentName)
|
||||
return &extensions.Deployment{
|
||||
ObjectMeta: api.ObjectMeta{Name: deploymentName},
|
||||
Spec: extensions.DeploymentSpec{
|
||||
Replicas: replicas,
|
||||
Selector: &unversionedapi.LabelSelector{MatchLabels: l},
|
||||
Template: api.PodTemplateSpec{
|
||||
ObjectMeta: api.ObjectMeta{Labels: l},
|
||||
Spec: podSpec,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// It's safe to do this for alpha, as we don't have HA and there is no way we can get
|
||||
// more then one node here (TODO(phase1+) use os.Hostname)
|
||||
func findMyself(client *clientset.Clientset) (*api.Node, error) {
|
||||
nodeList, err := client.Nodes().List(api.ListOptions{})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to list nodes [%v]", err)
|
||||
}
|
||||
if len(nodeList.Items) < 1 {
|
||||
return nil, fmt.Errorf("no nodes found")
|
||||
}
|
||||
node := &nodeList.Items[0]
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func attemptToUpdateMasterRoleLabelsAndTaints(client *clientset.Clientset, schedulable bool) error {
|
||||
n, err := findMyself(client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n.ObjectMeta.Labels["kubeadm.alpha.kubernetes.io/role"] = "master"
|
||||
|
||||
if !schedulable {
|
||||
taintsAnnotation, _ := json.Marshal([]api.Taint{{Key: "dedicated", Value: "master", Effect: "NoSchedule"}})
|
||||
n.ObjectMeta.Annotations[api.TaintsAnnotationKey] = string(taintsAnnotation)
|
||||
}
|
||||
|
||||
if _, err := client.Nodes().Update(n); err != nil {
|
||||
if apierrs.IsConflict(err) {
|
||||
fmt.Println("<master/apiclient> temporarily unable to update master node metadata due to conflict (will retry)")
|
||||
time.Sleep(apiCallRetryInterval)
|
||||
attemptToUpdateMasterRoleLabelsAndTaints(client, schedulable)
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func UpdateMasterRoleLabelsAndTaints(client *clientset.Clientset, schedulable bool) error {
|
||||
// TODO(phase1+) use iterate instead of recursion
|
||||
err := attemptToUpdateMasterRoleLabelsAndTaints(client, schedulable)
|
||||
if err != nil {
|
||||
return fmt.Errorf("<master/apiclient> failed to update master node - %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func SetMasterTaintTolerations(meta *api.ObjectMeta) {
|
||||
tolerationsAnnotation, _ := json.Marshal([]api.Toleration{{Key: "dedicated", Value: "master", Effect: "NoSchedule"}})
|
||||
if meta.Annotations == nil {
|
||||
meta.Annotations = map[string]string{}
|
||||
}
|
||||
meta.Annotations[api.TolerationsAnnotationKey] = string(tolerationsAnnotation)
|
||||
}
|
||||
|
||||
func SetMasterNodeAffinity(meta *api.ObjectMeta) {
|
||||
nodeAffinity := &api.NodeAffinity{
|
||||
RequiredDuringSchedulingIgnoredDuringExecution: &api.NodeSelector{
|
||||
NodeSelectorTerms: []api.NodeSelectorTerm{{
|
||||
MatchExpressions: []api.NodeSelectorRequirement{{
|
||||
Key: "kubeadm.alpha.kubernetes.io/role", Operator: api.NodeSelectorOpIn, Values: []string{"master"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
affinityAnnotation, _ := json.Marshal(api.Affinity{NodeAffinity: nodeAffinity})
|
||||
if meta.Annotations == nil {
|
||||
meta.Annotations = map[string]string{}
|
||||
}
|
||||
meta.Annotations[api.AffinityAnnotationKey] = string(affinityAnnotation)
|
||||
}
|
||||
136
cmd/kubeadm/app/master/discovery.go
Normal file
136
cmd/kubeadm/app/master/discovery.go
Normal file
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
type kubeDiscovery struct {
|
||||
Deployment *extensions.Deployment
|
||||
Secret *api.Secret
|
||||
}
|
||||
|
||||
const (
|
||||
kubeDiscoveryName = "kube-discovery"
|
||||
kubeDiscoverySecretName = "clusterinfo"
|
||||
)
|
||||
|
||||
func encodeKubeDiscoverySecretData(s *kubeadmapi.KubeadmConfig, caCert *x509.Certificate) map[string][]byte {
|
||||
var (
|
||||
data = map[string][]byte{}
|
||||
endpointList = []string{}
|
||||
tokenMap = map[string]string{}
|
||||
)
|
||||
|
||||
for _, addr := range s.InitFlags.API.AdvertiseAddrs {
|
||||
endpointList = append(endpointList, fmt.Sprintf("https://%s:443", addr.String()))
|
||||
}
|
||||
|
||||
tokenMap[s.Secrets.TokenID] = s.Secrets.BearerToken
|
||||
|
||||
data["endpoint-list.json"], _ = json.Marshal(endpointList)
|
||||
data["token-map.json"], _ = json.Marshal(tokenMap)
|
||||
data["ca.pem"] = certutil.EncodeCertPEM(caCert)
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
func newKubeDiscoveryPodSpec(s *kubeadmapi.KubeadmConfig) api.PodSpec {
|
||||
return api.PodSpec{
|
||||
// We have to use host network namespace, as `HostPort`/`HostIP` are Docker's
|
||||
// buisness and CNI support isn't quite there yet (except for kubenet)
|
||||
// (see https://github.com/kubernetes/kubernetes/issues/31307)
|
||||
// TODO update this when #31307 is resolved
|
||||
SecurityContext: &api.PodSecurityContext{HostNetwork: true},
|
||||
Containers: []api.Container{{
|
||||
Name: kubeDiscoveryName,
|
||||
Image: s.EnvParams["discovery_image"],
|
||||
Command: []string{"/usr/local/bin/kube-discovery"},
|
||||
VolumeMounts: []api.VolumeMount{{
|
||||
Name: kubeDiscoverySecretName,
|
||||
MountPath: "/tmp/secret", // TODO use a shared constant
|
||||
ReadOnly: true,
|
||||
}},
|
||||
Ports: []api.ContainerPort{
|
||||
// TODO when CNI issue (#31307) is resolved, we should consider adding
|
||||
// `HostIP: s.API.AdvertiseAddrs[0]`, if there is only one address`
|
||||
{Name: "http", ContainerPort: 9898, HostPort: 9898},
|
||||
},
|
||||
}},
|
||||
Volumes: []api.Volume{{
|
||||
Name: kubeDiscoverySecretName,
|
||||
VolumeSource: api.VolumeSource{
|
||||
Secret: &api.SecretVolumeSource{SecretName: kubeDiscoverySecretName},
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newKubeDiscovery(s *kubeadmapi.KubeadmConfig, caCert *x509.Certificate) kubeDiscovery {
|
||||
kd := kubeDiscovery{
|
||||
Deployment: NewDeployment(kubeDiscoveryName, 1, newKubeDiscoveryPodSpec(s)),
|
||||
Secret: &api.Secret{
|
||||
ObjectMeta: api.ObjectMeta{Name: kubeDiscoverySecretName},
|
||||
Type: api.SecretTypeOpaque,
|
||||
Data: encodeKubeDiscoverySecretData(s, caCert),
|
||||
},
|
||||
}
|
||||
|
||||
SetMasterTaintTolerations(&kd.Deployment.Spec.Template.ObjectMeta)
|
||||
SetMasterNodeAffinity(&kd.Deployment.Spec.Template.ObjectMeta)
|
||||
|
||||
return kd
|
||||
}
|
||||
|
||||
func CreateDiscoveryDeploymentAndSecret(s *kubeadmapi.KubeadmConfig, client *clientset.Clientset, caCert *x509.Certificate) error {
|
||||
kd := newKubeDiscovery(s, caCert)
|
||||
|
||||
if _, err := client.Extensions().Deployments(api.NamespaceSystem).Create(kd.Deployment); err != nil {
|
||||
return fmt.Errorf("<master/discovery> failed to create %q deployment [%v]", kubeDiscoveryName, err)
|
||||
}
|
||||
if _, err := client.Secrets(api.NamespaceSystem).Create(kd.Secret); err != nil {
|
||||
return fmt.Errorf("<master/discovery> failed to create %q secret [%v]", kubeDiscoverySecretName, err)
|
||||
}
|
||||
|
||||
fmt.Println("<master/discovery> created essential addon: kube-discovery, waiting for it to become ready")
|
||||
|
||||
start := time.Now()
|
||||
wait.PollInfinite(apiCallRetryInterval, func() (bool, error) {
|
||||
d, err := client.Extensions().Deployments(api.NamespaceSystem).Get(kubeDiscoveryName)
|
||||
if err != nil {
|
||||
return false, nil
|
||||
}
|
||||
if d.Status.AvailableReplicas < 1 {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
})
|
||||
fmt.Printf("<master/discovery> kube-discovery is ready after %f seconds\n", time.Since(start).Seconds())
|
||||
|
||||
return nil
|
||||
}
|
||||
60
cmd/kubeadm/app/master/kubeconfig.go
Normal file
60
cmd/kubeadm/app/master/kubeconfig.go
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
|
||||
// TODO: "k8s.io/client-go/client/tools/clientcmd/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
)
|
||||
|
||||
func CreateCertsAndConfigForClients(s *kubeadmapi.KubeadmConfig, clientNames []string, caKey *rsa.PrivateKey, caCert *x509.Certificate) (map[string]*clientcmdapi.Config, error) {
|
||||
|
||||
basicClientConfig := kubeadmutil.CreateBasicClientConfig(
|
||||
"kubernetes",
|
||||
// TODO this is not great, but there is only one address we can use here
|
||||
// so we'll pick the first one, there is much of chance to have an empty
|
||||
// slice by the time this gets called
|
||||
fmt.Sprintf("https://%s:443", s.InitFlags.API.AdvertiseAddrs[0]),
|
||||
certutil.EncodeCertPEM(caCert),
|
||||
)
|
||||
|
||||
configs := map[string]*clientcmdapi.Config{}
|
||||
|
||||
for _, client := range clientNames {
|
||||
key, cert, err := newClientKeyAndCert(caCert, caKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<master/kubeconfig> failure while creating %s client certificate - %v", client, err)
|
||||
}
|
||||
config := kubeadmutil.MakeClientConfigWithCerts(
|
||||
basicClientConfig,
|
||||
"kubernetes",
|
||||
client,
|
||||
certutil.EncodePrivateKeyPEM(key),
|
||||
certutil.EncodeCertPEM(cert),
|
||||
)
|
||||
configs[client] = config
|
||||
}
|
||||
|
||||
return configs, nil
|
||||
}
|
||||
301
cmd/kubeadm/app/master/manifests.go
Normal file
301
cmd/kubeadm/app/master/manifests.go
Normal file
@@ -0,0 +1,301 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
"k8s.io/kubernetes/pkg/api/resource"
|
||||
"k8s.io/kubernetes/pkg/api/unversioned"
|
||||
api "k8s.io/kubernetes/pkg/api/v1"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/util/intstr"
|
||||
)
|
||||
|
||||
// Static pod definitions in golang form are included below so that `kubeadm init` can get going.
|
||||
|
||||
const (
|
||||
DefaultClusterName = "kubernetes"
|
||||
DefaultCloudConfigPath = "/etc/kubernetes/cloud-config.json"
|
||||
|
||||
etcd = "etcd"
|
||||
apiServer = "apiserver"
|
||||
controllerManager = "controller-manager"
|
||||
scheduler = "scheduler"
|
||||
proxy = "proxy"
|
||||
kubeAPIServer = "kube-apiserver"
|
||||
kubeControllerManager = "kube-controller-manager"
|
||||
kubeScheduler = "kube-scheduler"
|
||||
kubeProxy = "kube-proxy"
|
||||
pkiDir = "/etc/kubernetes/pki"
|
||||
)
|
||||
|
||||
// WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk
|
||||
// where kubelet will pick and schedule them.
|
||||
func WriteStaticPodManifests(s *kubeadmapi.KubeadmConfig) error {
|
||||
// Prepare static pod specs
|
||||
staticPodSpecs := map[string]api.Pod{
|
||||
kubeAPIServer: componentPod(api.Container{
|
||||
Name: kubeAPIServer,
|
||||
Image: images.GetCoreImage(images.KubeAPIServerImage, s, s.EnvParams["hyperkube_image"]),
|
||||
Command: getComponentCommand(apiServer, s),
|
||||
VolumeMounts: []api.VolumeMount{certsVolumeMount(), k8sVolumeMount()},
|
||||
LivenessProbe: componentProbe(8080, "/healthz"),
|
||||
Resources: componentResources("250m"),
|
||||
}, certsVolume(s), k8sVolume(s)),
|
||||
kubeControllerManager: componentPod(api.Container{
|
||||
Name: kubeControllerManager,
|
||||
Image: images.GetCoreImage(images.KubeControllerManagerImage, s, s.EnvParams["hyperkube_image"]),
|
||||
Command: getComponentCommand(controllerManager, s),
|
||||
VolumeMounts: []api.VolumeMount{k8sVolumeMount()},
|
||||
LivenessProbe: componentProbe(10252, "/healthz"),
|
||||
Resources: componentResources("200m"),
|
||||
}, k8sVolume(s)),
|
||||
kubeScheduler: componentPod(api.Container{
|
||||
Name: kubeScheduler,
|
||||
Image: images.GetCoreImage(images.KubeSchedulerImage, s, s.EnvParams["hyperkube_image"]),
|
||||
Command: getComponentCommand(scheduler, s),
|
||||
LivenessProbe: componentProbe(10251, "/healthz"),
|
||||
Resources: componentResources("100m"),
|
||||
}),
|
||||
}
|
||||
|
||||
// Add etcd static pod spec only if external etcd is not configured
|
||||
if len(s.InitFlags.API.Etcd.ExternalEndpoints) == 0 {
|
||||
staticPodSpecs[etcd] = componentPod(api.Container{
|
||||
Name: etcd,
|
||||
Command: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/var/etcd/data",
|
||||
},
|
||||
VolumeMounts: []api.VolumeMount{certsVolumeMount(), etcdVolumeMount(), k8sVolumeMount()},
|
||||
Image: images.GetCoreImage(images.KubeEtcdImage, s, s.EnvParams["etcd_image"]),
|
||||
LivenessProbe: componentProbe(2379, "/health"),
|
||||
Resources: componentResources("200m"),
|
||||
SecurityContext: &api.SecurityContext{
|
||||
SELinuxOptions: &api.SELinuxOptions{
|
||||
// TODO: This implies our etcd container is not being restricted by
|
||||
// SELinux. This is not optimal and would be nice to adjust in future
|
||||
// so it can create and write /var/lib/etcd, but for now this avoids
|
||||
// recommending setenforce 0 system-wide.
|
||||
Type: "unconfined_t",
|
||||
},
|
||||
},
|
||||
}, certsVolume(s), etcdVolume(s), k8sVolume(s))
|
||||
}
|
||||
|
||||
manifestsPath := path.Join(s.EnvParams["kubernetes_dir"], "manifests")
|
||||
if err := os.MkdirAll(manifestsPath, 0700); err != nil {
|
||||
return fmt.Errorf("<master/manifests> failed to create directory %q [%v]", manifestsPath, err)
|
||||
}
|
||||
for name, spec := range staticPodSpecs {
|
||||
filename := path.Join(manifestsPath, name+".json")
|
||||
serialized, err := json.MarshalIndent(spec, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("<master/manifests> failed to marshall manifest for %q to JSON [%v]", name, err)
|
||||
}
|
||||
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), filename); err != nil {
|
||||
return fmt.Errorf("<master/manifests> failed to create static pod manifest file for %q (%q) [%v]", name, filename, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// etcdVolume exposes a path on the host in order to guarantee data survival during reboot.
|
||||
func etcdVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
|
||||
return api.Volume{
|
||||
Name: "etcd",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["host_etcd_path"]},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func etcdVolumeMount() api.VolumeMount {
|
||||
return api.VolumeMount{
|
||||
Name: "etcd",
|
||||
MountPath: "/var/etcd",
|
||||
}
|
||||
}
|
||||
|
||||
// certsVolume exposes host SSL certificates to pod containers.
|
||||
func certsVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
|
||||
return api.Volume{
|
||||
Name: "certs",
|
||||
VolumeSource: api.VolumeSource{
|
||||
// TODO(phase1+) make path configurable
|
||||
HostPath: &api.HostPathVolumeSource{Path: "/etc/ssl/certs"},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func certsVolumeMount() api.VolumeMount {
|
||||
return api.VolumeMount{
|
||||
Name: "certs",
|
||||
MountPath: "/etc/ssl/certs",
|
||||
}
|
||||
}
|
||||
|
||||
func k8sVolume(s *kubeadmapi.KubeadmConfig) api.Volume {
|
||||
return api.Volume{
|
||||
Name: "pki",
|
||||
VolumeSource: api.VolumeSource{
|
||||
HostPath: &api.HostPathVolumeSource{Path: s.EnvParams["kubernetes_dir"]},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func k8sVolumeMount() api.VolumeMount {
|
||||
return api.VolumeMount{
|
||||
Name: "pki",
|
||||
MountPath: "/etc/kubernetes/",
|
||||
ReadOnly: true,
|
||||
}
|
||||
}
|
||||
|
||||
func componentResources(cpu string) api.ResourceRequirements {
|
||||
return api.ResourceRequirements{
|
||||
Requests: api.ResourceList{
|
||||
api.ResourceName(api.ResourceCPU): resource.MustParse(cpu),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func componentProbe(port int, path string) *api.Probe {
|
||||
return &api.Probe{
|
||||
Handler: api.Handler{
|
||||
HTTPGet: &api.HTTPGetAction{
|
||||
Host: "127.0.0.1",
|
||||
Path: path,
|
||||
Port: intstr.FromInt(port),
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 15,
|
||||
TimeoutSeconds: 15,
|
||||
}
|
||||
}
|
||||
|
||||
func componentPod(container api.Container, volumes ...api.Volume) api.Pod {
|
||||
return api.Pod{
|
||||
TypeMeta: unversioned.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: api.ObjectMeta{
|
||||
Name: container.Name,
|
||||
Namespace: "kube-system",
|
||||
Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
|
||||
},
|
||||
Spec: api.PodSpec{
|
||||
Containers: []api.Container{container},
|
||||
HostNetwork: true,
|
||||
Volumes: volumes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func getComponentCommand(component string, s *kubeadmapi.KubeadmConfig) (command []string) {
|
||||
baseFlags := map[string][]string{
|
||||
apiServer: {
|
||||
"--insecure-bind-address=127.0.0.1",
|
||||
"--etcd-servers=http://127.0.0.1:2379",
|
||||
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota",
|
||||
"--service-cluster-ip-range=" + s.InitFlags.Services.CIDR.String(),
|
||||
"--service-account-key-file=" + pkiDir + "/apiserver-key.pem",
|
||||
"--client-ca-file=" + pkiDir + "/ca.pem",
|
||||
"--tls-cert-file=" + pkiDir + "/apiserver.pem",
|
||||
"--tls-private-key-file=" + pkiDir + "/apiserver-key.pem",
|
||||
"--token-auth-file=" + pkiDir + "/tokens.csv",
|
||||
"--secure-port=443",
|
||||
"--allow-privileged",
|
||||
},
|
||||
controllerManager: {
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect",
|
||||
"--master=127.0.0.1:8080",
|
||||
"--cluster-name=" + DefaultClusterName,
|
||||
"--root-ca-file=" + pkiDir + "/ca.pem",
|
||||
"--service-account-private-key-file=" + pkiDir + "/apiserver-key.pem",
|
||||
"--cluster-signing-cert-file=" + pkiDir + "/ca.pem",
|
||||
"--cluster-signing-key-file=" + pkiDir + "/ca-key.pem",
|
||||
"--insecure-experimental-approve-all-kubelet-csrs-for-group=system:kubelet-bootstrap",
|
||||
},
|
||||
scheduler: {
|
||||
"--address=127.0.0.1",
|
||||
"--leader-elect",
|
||||
"--master=127.0.0.1:8080",
|
||||
},
|
||||
proxy: {},
|
||||
}
|
||||
|
||||
if s.EnvParams["hyperkube_image"] != "" {
|
||||
command = []string{"/hyperkube", component}
|
||||
} else {
|
||||
command = []string{"/usr/local/bin/kube-" + component}
|
||||
}
|
||||
|
||||
command = append(command, s.EnvParams["component_loglevel"])
|
||||
command = append(command, baseFlags[component]...)
|
||||
|
||||
if component == apiServer {
|
||||
// Check if the user decided to use an external etcd cluster
|
||||
if len(s.InitFlags.API.Etcd.ExternalEndpoints) > 0 {
|
||||
command = append(command, fmt.Sprintf("--etcd-servers=%s", strings.Join(s.InitFlags.API.Etcd.ExternalEndpoints, ",")))
|
||||
} else {
|
||||
command = append(command, "--etcd-servers=http://127.0.0.1:2379")
|
||||
}
|
||||
|
||||
// Is etcd secured?
|
||||
if s.InitFlags.API.Etcd.ExternalCAFile != "" {
|
||||
command = append(command, fmt.Sprintf("--etcd-cafile=%s", s.InitFlags.API.Etcd.ExternalCAFile))
|
||||
}
|
||||
if s.InitFlags.API.Etcd.ExternalCertFile != "" && s.InitFlags.API.Etcd.ExternalKeyFile != "" {
|
||||
etcdClientFileArg := fmt.Sprintf("--etcd-certfile=%s", s.InitFlags.API.Etcd.ExternalCertFile)
|
||||
etcdKeyFileArg := fmt.Sprintf("--etcd-keyfile=%s", s.InitFlags.API.Etcd.ExternalKeyFile)
|
||||
command = append(command, etcdClientFileArg, etcdKeyFileArg)
|
||||
}
|
||||
}
|
||||
|
||||
if component == controllerManager {
|
||||
if s.InitFlags.CloudProvider != "" {
|
||||
command = append(command, "--cloud-provider="+s.InitFlags.CloudProvider)
|
||||
|
||||
// Only append the --cloud-config option if there's a such file
|
||||
// TODO(phase1+) this won't work unless it's in one of the few directories we bind-mount
|
||||
if _, err := os.Stat(DefaultCloudConfigPath); err == nil {
|
||||
command = append(command, "--cloud-config="+DefaultCloudConfigPath)
|
||||
}
|
||||
}
|
||||
|
||||
if s.InitFlags.PodNetwork.CIDR.IP != nil {
|
||||
// Let the controller-manager allocate Node CIDRs for the Pod network.
|
||||
// Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
|
||||
command = append(command, "--allocate-node-cidrs=true", "--cluster-cidr="+s.InitFlags.PodNetwork.CIDR.String())
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
180
cmd/kubeadm/app/master/pki.go
Normal file
180
cmd/kubeadm/app/master/pki.go
Normal file
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
ipallocator "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
)
|
||||
|
||||
func newCertificateAuthority() (*rsa.PrivateKey, *x509.Certificate, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||
}
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: "kubernetes",
|
||||
}
|
||||
|
||||
cert, err := certutil.NewSelfSignedCACert(config, key)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create self-signed certificate [%v]", err)
|
||||
}
|
||||
|
||||
return key, cert, nil
|
||||
}
|
||||
|
||||
func newServerKeyAndCert(s *kubeadmapi.KubeadmConfig, caCert *x509.Certificate, caKey *rsa.PrivateKey, altNames certutil.AltNames) (*rsa.PrivateKey, *x509.Certificate, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unabel to create private key [%v]", err)
|
||||
}
|
||||
|
||||
internalAPIServerFQDN := []string{
|
||||
"kubernetes",
|
||||
"kubernetes.default",
|
||||
"kubernetes.default.svc",
|
||||
fmt.Sprintf("kubernetes.default.svc.%s", s.InitFlags.Services.DNSDomain),
|
||||
}
|
||||
|
||||
internalAPIServerVirtualIP, err := ipallocator.GetIndexedIP(&s.InitFlags.Services.CIDR, 1)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to allocate IP address for the API server from the given CIDR (%q) [%v]", &s.InitFlags.Services.CIDR, err)
|
||||
}
|
||||
|
||||
altNames.IPs = append(altNames.IPs, internalAPIServerVirtualIP)
|
||||
altNames.DNSNames = append(altNames.DNSNames, internalAPIServerFQDN...)
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: "kube-apiserver",
|
||||
AltNames: altNames,
|
||||
}
|
||||
cert, err := certutil.NewSignedCert(config, key, caCert, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
||||
}
|
||||
|
||||
return key, cert, nil
|
||||
}
|
||||
|
||||
func newClientKeyAndCert(caCert *x509.Certificate, caKey *rsa.PrivateKey) (*rsa.PrivateKey, *x509.Certificate, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to create private key [%v]", err)
|
||||
}
|
||||
|
||||
config := certutil.Config{
|
||||
CommonName: "kubernetes-admin",
|
||||
}
|
||||
cert, err := certutil.NewSignedCert(config, key, caCert, caKey)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("unable to sign certificate [%v]", err)
|
||||
}
|
||||
|
||||
return key, cert, nil
|
||||
}
|
||||
|
||||
func writeKeysAndCert(pkiPath string, name string, key *rsa.PrivateKey, cert *x509.Certificate) error {
|
||||
var (
|
||||
publicKeyPath = path.Join(pkiPath, fmt.Sprintf("%s-pub.pem", name))
|
||||
privateKeyPath = path.Join(pkiPath, fmt.Sprintf("%s-key.pem", name))
|
||||
certificatePath = path.Join(pkiPath, fmt.Sprintf("%s.pem", name))
|
||||
)
|
||||
|
||||
if key != nil {
|
||||
if err := certutil.WriteKey(privateKeyPath, certutil.EncodePrivateKeyPEM(key)); err != nil {
|
||||
return fmt.Errorf("unable to write private key file (%q) [%v]", privateKeyPath, err)
|
||||
}
|
||||
if pubKey, err := certutil.EncodePublicKeyPEM(&key.PublicKey); err == nil {
|
||||
if err := certutil.WriteKey(publicKeyPath, pubKey); err != nil {
|
||||
return fmt.Errorf("unable to write public key file (%q) [%v]", publicKeyPath, err)
|
||||
}
|
||||
} else {
|
||||
return fmt.Errorf("unable to encode public key to PEM [%v]", err)
|
||||
}
|
||||
}
|
||||
|
||||
if cert != nil {
|
||||
if err := certutil.WriteCert(certificatePath, certutil.EncodeCertPEM(cert)); err != nil {
|
||||
return fmt.Errorf("unable to write certificate file (%q) [%v]", certificatePath, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func newServiceAccountKey() (*rsa.PrivateKey, error) {
|
||||
key, err := certutil.NewPrivateKey()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
// CreatePKIAssets will create and write to disk all PKI assets necessary to establish the control plane.
|
||||
// It first generates a self-signed CA certificate, a server certificate (signed by the CA) and a key for
|
||||
// signing service account tokens. It returns CA key and certificate, which is convenient for use with
|
||||
// client config funcs.
|
||||
func CreatePKIAssets(s *kubeadmapi.KubeadmConfig) (*rsa.PrivateKey, *x509.Certificate, error) {
|
||||
var (
|
||||
err error
|
||||
altNames certutil.AltNames
|
||||
)
|
||||
|
||||
altNames.IPs = append(altNames.IPs, s.InitFlags.API.AdvertiseAddrs...)
|
||||
altNames.DNSNames = append(altNames.DNSNames, s.InitFlags.API.ExternalDNSNames...)
|
||||
|
||||
pkiPath := path.Join(s.EnvParams["host_pki_path"])
|
||||
|
||||
caKey, caCert, err := newCertificateAuthority()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while creating CA keys and certificate - %v", err)
|
||||
}
|
||||
|
||||
if err := writeKeysAndCert(pkiPath, "ca", caKey, caCert); err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while saving CA keys and certificate - %v", err)
|
||||
}
|
||||
|
||||
apiKey, apiCert, err := newServerKeyAndCert(s, caCert, caKey, altNames)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while creating API server keys and certificate - %v", err)
|
||||
}
|
||||
|
||||
if err := writeKeysAndCert(pkiPath, "apiserver", apiKey, apiCert); err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while saving API server keys and certificate - %v", err)
|
||||
}
|
||||
|
||||
saKey, err := newServiceAccountKey()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while creating service account signing keys [%v]", err)
|
||||
}
|
||||
|
||||
if err := writeKeysAndCert(pkiPath, "sa", saKey, nil); err != nil {
|
||||
return nil, nil, fmt.Errorf("<master/pki> failure while saving service account signing keys - %v", err)
|
||||
}
|
||||
|
||||
// TODO(phase1+) print a summary of SANs used and checksums (signatures) of each of the certificates
|
||||
fmt.Printf("<master/pki> created keys and certificates in %q\n", pkiPath)
|
||||
return caKey, caCert, nil
|
||||
}
|
||||
64
cmd/kubeadm/app/master/tokens.go
Normal file
64
cmd/kubeadm/app/master/tokens.go
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
Copyright 2016 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 master
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
"k8s.io/kubernetes/pkg/util/uuid"
|
||||
)
|
||||
|
||||
func generateTokenIfNeeded(s *kubeadmapi.KubeadmConfig) error {
|
||||
ok, err := kubeadmutil.UseGivenTokenIfValid(s)
|
||||
// TODO(phase1+) @krousey: I know it won't happen with the way it is currently implemented, but this doesn't handle case where ok is true and err is non-nil.
|
||||
if !ok {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = kubeadmutil.GenerateToken(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("<master/tokens> generated token: %q\n", s.Secrets.GivenToken)
|
||||
} else {
|
||||
fmt.Println("<master/tokens> accepted provided token")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func CreateTokenAuthFile(s *kubeadmapi.KubeadmConfig) error {
|
||||
tokenAuthFilePath := path.Join(s.EnvParams["host_pki_path"], "tokens.csv")
|
||||
if err := generateTokenIfNeeded(s); err != nil {
|
||||
return fmt.Errorf("<master/tokens> failed to generate token(s) [%v]", err)
|
||||
}
|
||||
if err := os.MkdirAll(s.EnvParams["host_pki_path"], 0700); err != nil {
|
||||
return fmt.Errorf("<master/tokens> failed to create directory %q [%v]", s.EnvParams["host_pki_path"], err)
|
||||
}
|
||||
serialized := []byte(fmt.Sprintf("%s,kubeadm-node-csr,%s,system:kubelet-bootstrap\n", s.Secrets.BearerToken, uuid.NewUUID()))
|
||||
// DumpReaderToFile create a file with mode 0600
|
||||
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), tokenAuthFilePath); err != nil {
|
||||
return fmt.Errorf("<master/tokens> failed to save token auth file (%q) [%v]", tokenAuthFilePath, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
117
cmd/kubeadm/app/node/csr.go
Normal file
117
cmd/kubeadm/app/node/csr.go
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
Copyright 2016 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 node
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
"k8s.io/kubernetes/pkg/apis/certificates"
|
||||
unversionedcertificates "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/certificates/unversioned"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/csr"
|
||||
certutil "k8s.io/kubernetes/pkg/util/cert"
|
||||
)
|
||||
|
||||
// PerformTLSBootstrap creates a RESTful client in order to execute certificate signing request.
|
||||
func PerformTLSBootstrap(s *kubeadmapi.KubeadmConfig, apiEndpoint string, caCert []byte) (*clientcmdapi.Config, error) {
|
||||
// TODO(phase1+) try all the api servers until we find one that works
|
||||
bareClientConfig := kubeadmutil.CreateBasicClientConfig("kubernetes", apiEndpoint, caCert)
|
||||
|
||||
nodeName, err := os.Hostname()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> failed to get node hostname [%v]", err)
|
||||
}
|
||||
|
||||
bootstrapClientConfig, err := clientcmd.NewDefaultClientConfig(
|
||||
*kubeadmutil.MakeClientConfigWithToken(
|
||||
bareClientConfig, "kubernetes", fmt.Sprintf("kubelet-%s", nodeName), s.Secrets.BearerToken,
|
||||
),
|
||||
&clientcmd.ConfigOverrides{},
|
||||
).ClientConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> failed to create API client configuration [%v]", err)
|
||||
}
|
||||
|
||||
client, err := unversionedcertificates.NewForConfig(bootstrapClientConfig)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> failed to create API client [%v]", err)
|
||||
}
|
||||
csrClient := client.CertificateSigningRequests()
|
||||
|
||||
// TODO(phase1+) checkCertsAPI() has a side-effect of making first attempt of communicating with the API,
|
||||
// we should _make it more explicit_ and have a user-settable _retry timeout_ to account for potential connectivity issues
|
||||
// (for example user may be bringing up machines in parallel and for some reasons master is slow to boot)
|
||||
|
||||
if err := checkCertsAPI(bootstrapClientConfig); err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> fialed to proceed due to API compatibility issue - %v", err)
|
||||
}
|
||||
|
||||
fmt.Println("<node/csr> created API client to obtain unique certificate for this node, generating keys and certificate signing request")
|
||||
|
||||
key, err := certutil.MakeEllipticPrivateKeyPEM()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> failed to generating private key [%v]", err)
|
||||
}
|
||||
|
||||
cert, err := csr.RequestNodeCertificate(csrClient, key, nodeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/csr> failed to request signed certificate from the API server [%v]", err)
|
||||
}
|
||||
|
||||
// TODO(phase1+) print some basic info about the cert
|
||||
fmt.Println("<node/csr> received signed certificate from the API server, generating kubelet configuration")
|
||||
|
||||
finalConfig := kubeadmutil.MakeClientConfigWithCerts(
|
||||
bareClientConfig, "kubernetes", fmt.Sprintf("kubelet-%s", nodeName),
|
||||
key, cert,
|
||||
)
|
||||
|
||||
return finalConfig, nil
|
||||
}
|
||||
|
||||
func checkCertsAPI(config *restclient.Config) error {
|
||||
discoveryClient, err := discovery.NewDiscoveryClientForConfig(config)
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create API discovery client [%v]", err)
|
||||
}
|
||||
|
||||
serverGroups, err := discoveryClient.ServerGroups()
|
||||
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to retrieve a list of supported API objects [%v]", err)
|
||||
}
|
||||
|
||||
for _, group := range serverGroups.Groups {
|
||||
if group.Name == certificates.GroupName {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
version, err := discoveryClient.ServerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to obtain API version [%v]", err)
|
||||
}
|
||||
|
||||
return fmt.Errorf("API version %s does not support certificates API, use v1.4.0 or newer", version.String())
|
||||
}
|
||||
79
cmd/kubeadm/app/node/discovery.go
Normal file
79
cmd/kubeadm/app/node/discovery.go
Normal file
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
Copyright 2016 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 node
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
jose "github.com/square/go-jose"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
func RetrieveTrustedClusterInfo(s *kubeadmapi.KubeadmConfig) (*clientcmdapi.Config, error) {
|
||||
host, port := s.JoinFlags.MasterAddrs[0].String(), 9898
|
||||
requestURL := fmt.Sprintf("http://%s:%d/cluster-info/v1/?token-id=%s", host, port, s.Secrets.TokenID)
|
||||
req, err := http.NewRequest("GET", requestURL, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/discovery> failed to consturct an HTTP request [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Printf("<node/discovery> created cluster info discovery client, requesting info from %q\n", requestURL)
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/discovery> failed to request cluster info [%v]", err)
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
io.Copy(buf, res.Body)
|
||||
res.Body.Close()
|
||||
|
||||
object, err := jose.ParseSigned(buf.String())
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/discovery> failed to parse response as JWS object [%v]", err)
|
||||
}
|
||||
|
||||
fmt.Println("<node/discovery> cluster info object received, verifying signature using given token")
|
||||
|
||||
output, err := object.Verify(s.Secrets.Token)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("<node/discovery> failed to verify JWS signature of received cluster info object [%v]", err)
|
||||
}
|
||||
|
||||
clusterInfo := kubeadmapi.ClusterInfo{}
|
||||
|
||||
if err := json.Unmarshal(output, &clusterInfo); err != nil {
|
||||
return nil, fmt.Errorf("<node/discovery> failed to decode received cluster info object [%v]", err)
|
||||
}
|
||||
|
||||
if len(clusterInfo.CertificateAuthorities) == 0 || len(clusterInfo.Endpoints) == 0 {
|
||||
return nil, fmt.Errorf("<node/discovery> cluster info object is invalid - no endpoint(s) and/or root CA certificate(s) found")
|
||||
}
|
||||
|
||||
// TODO(phase1+) print summary info about the CA certificate, along with the the checksum signature
|
||||
// we also need an ability for the user to configure the client to validate recieved CA cert agains a checksum
|
||||
fmt.Printf("<node/discovery> cluster info signature and contents are valid, will use API endpoints %v\n", clusterInfo.Endpoints)
|
||||
|
||||
apiServer := clusterInfo.Endpoints[0]
|
||||
caCert := []byte(clusterInfo.CertificateAuthorities[0])
|
||||
|
||||
return PerformTLSBootstrap(s, apiServer, caCert)
|
||||
}
|
||||
97
cmd/kubeadm/app/util/kubeconfig.go
Normal file
97
cmd/kubeadm/app/util/kubeconfig.go
Normal file
@@ -0,0 +1,97 @@
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
// TODO: "k8s.io/client-go/client/tools/clientcmd/api"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
)
|
||||
|
||||
func CreateBasicClientConfig(clusterName string, serverURL string, caCert []byte) *clientcmdapi.Config {
|
||||
cluster := clientcmdapi.NewCluster()
|
||||
cluster.Server = serverURL
|
||||
cluster.CertificateAuthorityData = caCert
|
||||
|
||||
config := clientcmdapi.NewConfig()
|
||||
config.Clusters[clusterName] = cluster
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
func MakeClientConfigWithCerts(config *clientcmdapi.Config, clusterName string, userName string, clientKey []byte, clientCert []byte) *clientcmdapi.Config {
|
||||
newConfig := config
|
||||
name := fmt.Sprintf("%s@%s", userName, clusterName)
|
||||
|
||||
authInfo := clientcmdapi.NewAuthInfo()
|
||||
authInfo.ClientKeyData = clientKey
|
||||
authInfo.ClientCertificateData = clientCert
|
||||
|
||||
context := clientcmdapi.NewContext()
|
||||
context.Cluster = clusterName
|
||||
context.AuthInfo = userName
|
||||
|
||||
newConfig.AuthInfos[userName] = authInfo
|
||||
newConfig.Contexts[name] = context
|
||||
newConfig.CurrentContext = name
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
func MakeClientConfigWithToken(config *clientcmdapi.Config, clusterName string, userName string, token string) *clientcmdapi.Config {
|
||||
newConfig := config
|
||||
name := fmt.Sprintf("%s@%s", userName, clusterName)
|
||||
|
||||
authInfo := clientcmdapi.NewAuthInfo()
|
||||
authInfo.Token = token
|
||||
|
||||
context := clientcmdapi.NewContext()
|
||||
context.Cluster = clusterName
|
||||
context.AuthInfo = userName
|
||||
|
||||
newConfig.AuthInfos[userName] = authInfo
|
||||
newConfig.Contexts[name] = context
|
||||
newConfig.CurrentContext = name
|
||||
|
||||
return newConfig
|
||||
}
|
||||
|
||||
func WriteKubeconfigIfNotExists(s *kubeadmapi.KubeadmConfig, name string, kubeconfig *clientcmdapi.Config) error {
|
||||
if err := os.MkdirAll(s.EnvParams["kubernetes_dir"], 0700); err != nil {
|
||||
return fmt.Errorf("<util/kubeconfig> failed to create directory %q [%v]", s.EnvParams["kubernetes_dir"], err)
|
||||
}
|
||||
|
||||
filename := path.Join(s.EnvParams["kubernetes_dir"], fmt.Sprintf("%s.conf", name))
|
||||
// Create and open the file, only if it does not already exist.
|
||||
f, err := os.OpenFile(filename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0600)
|
||||
if err != nil {
|
||||
return fmt.Errorf("<util/kubeconfig> failed to create %q, it already exists [%v]", filename, err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
if err := clientcmd.WriteToFile(*kubeconfig, filename); err != nil {
|
||||
return fmt.Errorf("<util/kubeconfig> failed to write to %q [%v]", filename, err)
|
||||
}
|
||||
|
||||
fmt.Printf("<util/kubeconfig> created %q\n", filename)
|
||||
return nil
|
||||
}
|
||||
85
cmd/kubeadm/app/util/tokens.go
Normal file
85
cmd/kubeadm/app/util/tokens.go
Normal file
@@ -0,0 +1,85 @@
|
||||
/*
|
||||
Copyright 2016 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 util
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/api"
|
||||
)
|
||||
|
||||
const (
|
||||
TokenIDLen = 6
|
||||
TokenBytes = 8
|
||||
)
|
||||
|
||||
func RandBytes(length int) ([]byte, string, error) {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
// It's only the tokenID that doesn't care about raw byte slice,
|
||||
// so we just encoded it in place and ignore bytes slice where we
|
||||
// do not want it
|
||||
return b, hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
func GenerateToken(s *kubeadmapi.KubeadmConfig) error {
|
||||
_, tokenID, err := RandBytes(TokenIDLen / 2)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
tokenBytes, token, err := RandBytes(TokenBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Secrets.TokenID = tokenID
|
||||
s.Secrets.BearerToken = token
|
||||
s.Secrets.Token = tokenBytes
|
||||
s.Secrets.GivenToken = fmt.Sprintf("%s.%s", tokenID, token)
|
||||
return nil
|
||||
}
|
||||
|
||||
func UseGivenTokenIfValid(s *kubeadmapi.KubeadmConfig) (bool, error) {
|
||||
if s.Secrets.GivenToken == "" {
|
||||
return false, nil // not given
|
||||
}
|
||||
fmt.Println("<util/tokens> validating provided token")
|
||||
givenToken := strings.Split(strings.ToLower(s.Secrets.GivenToken), ".")
|
||||
// TODO(phase1+) print desired format
|
||||
// TODO(phase1+) could also print more specific messages in each case
|
||||
invalidErr := "<util/tokens> provided token is invalid - %s"
|
||||
if len(givenToken) != 2 {
|
||||
return false, fmt.Errorf(invalidErr, "not in 2-part dot-separated format")
|
||||
}
|
||||
if len(givenToken[0]) != TokenIDLen {
|
||||
return false, fmt.Errorf(invalidErr, fmt.Sprintf(
|
||||
"length of first part is incorrect [%d (given) != %d (expected) ]",
|
||||
len(givenToken[0]), TokenIDLen))
|
||||
}
|
||||
tokenBytes := []byte(givenToken[1])
|
||||
s.Secrets.TokenID = givenToken[0]
|
||||
s.Secrets.BearerToken = givenToken[1]
|
||||
s.Secrets.Token = tokenBytes
|
||||
return true, nil // given and valid
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2014 The Kubernetes Authors.
|
||||
Copyright 2016 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.
|
||||
@@ -17,8 +17,17 @@ limitations under the License.
|
||||
package main
|
||||
|
||||
import (
|
||||
_ "github.com/square/go-jose"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app"
|
||||
)
|
||||
|
||||
// TODO(phase1+): check for root
|
||||
func main() {
|
||||
if err := app.Run(); err != nil {
|
||||
fmt.Printf(app.AlphaWarningOnExit)
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
Reference in New Issue
Block a user