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/
```
pull/6/head
Kubernetes Submit Queue 2016-09-26 02:35:47 -07:00 committed by GitHub
commit c19e08ebbc
23 changed files with 2316 additions and 3 deletions

View File

@ -228,7 +228,7 @@ This is the first release tracked via the use of the [kubernetes/features](https
- **Cluster Lifecycle**
- [alpha] Ensure critical cluster infrastructure pods (Heapster, DNS, etc.) can schedule by evicting regular pods when necessary to make the critical pods schedule. ([docs](http://kubernetes.io/docs/admin/rescheduler/#guaranteed-scheduling-of-critical-add-on-pods)) ([kubernetes/features#62](https://github.com/kubernetes/features/issues/62))
- [alpha] Simplifies bootstrapping of TLS secured communication between the API server and kubelet. ([docs](http://kubernetes.io/docs/admin/master-node-communication/#kubelet-tls-bootstrap)) ([kubernetes/features#43](https://github.com/kubernetes/features/issues/43))
- [alpha] `kubeadm` tool makes install much easier. ([docs](http://kubernetes.io/docs/kubeadm/)) ([kubernetes/features#11](https://github.com/kubernetes/features/issues/11))
- [alpha] The `kubeadm` tool makes it much easier to bootstrap Kubernetes. ([docs](http://kubernetes.io/docs/getting-started-guides/kubeadm/)) ([kubernetes/features#11](https://github.com/kubernetes/features/issues/11))
- **Federation**
- [alpha] Creating a `Federated Ingress` is as simple as submitting an `Ingress` config/manifest to the Federation API Server. Federation then creates a single global VIP to load balance the incoming L7 traffic across all the registered clusters no matter in what regions the clusters are. GCE L7 LoadBalancer is the only supported implementation in this release. ([kubernetes/features#82](https://github.com/kubernetes/features/issues/82))
- [alpha] Creating a `Namespace` in federation causes matching `Namespace`s to be created in all the clusters registered with that federation. ([docs](http://kubernetes.io/docs/user-guide/federation/namespaces)) ([kubernetes/features#69](https://github.com/kubernetes/features/issues/69))

View 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"`
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

@ -15,6 +15,7 @@ cmd/kube-discovery
cmd/kube-dns
cmd/kube-proxy
cmd/kubeadm
cmd/kubeadm
cmd/kubectl
cmd/kubelet
cmd/kubernetes-discovery

View File

@ -35,6 +35,7 @@ kube::golang::server_targets() {
cmd/kube-apiserver
cmd/kube-controller-manager
cmd/kubelet
cmd/kubeadm
cmd/kubemark
cmd/hyperkube
plugin/cmd/kube-scheduler

View File

@ -9,6 +9,8 @@ all-namespaces
allocate-node-cidrs
allow-privileged
allowed-not-ready-nodes
api-advertise-addresses
api-external-dns-names
api-burst
api-prefix
api-rate
@ -176,6 +178,10 @@ experimental-keystone-url
experimental-nvidia-gpus
experimental-prefix
experimental-runtime-integration-type
external-etcd-cafile
external-etcd-certfile
external-etcd-endpoints
external-etcd-keyfile
external-hostname
external-ip
extra-peer-dirs
@ -385,6 +391,7 @@ pod-cidr
pod-eviction-timeout
pod-infra-container-image
pod-manifest-path
pod-network-cidr
pod-running
pods-per-core
policy-config-file
@ -448,6 +455,7 @@ runtime-config
runtime-integration-type
runtime-request-timeout
save-config
schedule-pods-here
scheduler-config
scheduler-name
schema-cache-dir
@ -462,6 +470,8 @@ service-account-lookup
service-account-private-key-file
service-address
service-cluster-ip-range
service-cidr
service-dns-domain
service-generator
service-node-port-range
service-node-ports
@ -522,6 +532,7 @@ update-period
upgrade-image
upgrade-target
use-kubernetes-cluster-service
use-kubernetes-version
user-whitelist
verify-only
volume-dir