mirror of https://github.com/k3s-io/k3s
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
commit
c19e08ebbc
|
@ -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))
|
||||
|
|
|
@ -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"`
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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]
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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())
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -15,6 +15,7 @@ cmd/kube-discovery
|
|||
cmd/kube-dns
|
||||
cmd/kube-proxy
|
||||
cmd/kubeadm
|
||||
cmd/kubeadm
|
||||
cmd/kubectl
|
||||
cmd/kubelet
|
||||
cmd/kubernetes-discovery
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue