2016-08-18 12:38:18 +00:00
/ *
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 .
* /
2016-09-07 12:53:11 +00:00
package master
2016-08-18 12:38:18 +00:00
import (
"bytes"
"encoding/json"
"fmt"
"os"
"path"
2016-09-20 10:37:02 +00:00
"strings"
2016-08-18 12:38:18 +00:00
2017-01-11 14:09:48 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2016-09-29 06:36:18 +00:00
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
2016-09-19 15:11:05 +00:00
"k8s.io/kubernetes/cmd/kubeadm/app/images"
2016-08-18 12:38:18 +00:00
"k8s.io/kubernetes/pkg/api/resource"
api "k8s.io/kubernetes/pkg/api/v1"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/util/intstr"
2016-12-09 20:16:37 +00:00
"github.com/blang/semver"
2016-08-18 12:38:18 +00:00
)
2016-09-24 13:40:10 +00:00
// Static pod definitions in golang form are included below so that `kubeadm init` can get going.
2016-08-18 12:38:18 +00:00
const (
2016-09-19 15:11:05 +00:00
DefaultClusterName = "kubernetes"
2016-12-07 14:24:02 +00:00
DefaultCloudConfigPath = "/etc/kubernetes/cloud-config"
2016-09-07 16:37:02 +00:00
2017-01-17 00:41:56 +00:00
etcd = "etcd"
apiServer = "apiserver"
controllerManager = "controller-manager"
scheduler = "scheduler"
proxy = "proxy"
kubeAPIServer = "kube-apiserver"
kubeControllerManager = "kube-controller-manager"
kubeScheduler = "kube-scheduler"
kubeProxy = "kube-proxy"
authorizationPolicyFile = "abac_policy.json"
authorizationWebhookConfigFile = "webhook_authz.conf"
2016-08-18 12:38:18 +00:00
)
2016-12-09 20:16:37 +00:00
var (
// Minimum version of kube-apiserver that supports --kubelet-preferred-address-types
2016-12-13 15:51:15 +00:00
preferredAddressAPIServerMinVersion = semver . MustParse ( "1.5.0" )
// Minimum version of kube-apiserver that has to have --anonymous-auth=false set
anonAuthDisableAPIServerMinVersion = semver . MustParse ( "1.5.0" )
2016-12-09 20:16:37 +00:00
)
2016-09-19 15:11:05 +00:00
// WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk
// where kubelet will pick and schedule them.
2016-10-17 08:00:22 +00:00
func WriteStaticPodManifests ( cfg * kubeadmapi . MasterConfiguration ) error {
2016-11-07 17:18:33 +00:00
volumes := [ ] api . Volume { k8sVolume ( cfg ) }
volumeMounts := [ ] api . VolumeMount { k8sVolumeMount ( ) }
if isCertsVolumeMountNeeded ( ) {
volumes = append ( volumes , certsVolume ( cfg ) )
volumeMounts = append ( volumeMounts , certsVolumeMount ( ) )
}
if isPkiVolumeMountNeeded ( ) {
volumes = append ( volumes , pkiVolume ( cfg ) )
volumeMounts = append ( volumeMounts , pkiVolumeMount ( ) )
}
2016-09-20 10:37:02 +00:00
// Prepare static pod specs
2016-08-18 12:38:18 +00:00
staticPodSpecs := map [ string ] api . Pod {
2016-09-07 16:37:02 +00:00
kubeAPIServer : componentPod ( api . Container {
Name : kubeAPIServer ,
2016-10-29 01:24:59 +00:00
Image : images . GetCoreImage ( images . KubeAPIServerImage , cfg , kubeadmapi . GlobalEnvParams . HyperkubeImage ) ,
2016-10-19 12:30:03 +00:00
Command : getAPIServerCommand ( cfg ) ,
2016-11-07 17:18:33 +00:00
VolumeMounts : volumeMounts ,
2016-08-18 12:38:18 +00:00
LivenessProbe : componentProbe ( 8080 , "/healthz" ) ,
Resources : componentResources ( "250m" ) ,
2016-11-25 16:56:38 +00:00
Env : getProxyEnvVars ( ) ,
2016-11-07 17:18:33 +00:00
} , volumes ... ) ,
2016-09-07 16:37:02 +00:00
kubeControllerManager : componentPod ( api . Container {
Name : kubeControllerManager ,
2016-10-29 01:24:59 +00:00
Image : images . GetCoreImage ( images . KubeControllerManagerImage , cfg , kubeadmapi . GlobalEnvParams . HyperkubeImage ) ,
2016-10-19 12:30:03 +00:00
Command : getControllerManagerCommand ( cfg ) ,
2016-11-07 17:18:33 +00:00
VolumeMounts : volumeMounts ,
2016-08-18 12:38:18 +00:00
LivenessProbe : componentProbe ( 10252 , "/healthz" ) ,
Resources : componentResources ( "200m" ) ,
2016-11-25 16:56:38 +00:00
Env : getProxyEnvVars ( ) ,
2016-11-07 17:18:33 +00:00
} , volumes ... ) ,
2016-09-07 16:37:02 +00:00
kubeScheduler : componentPod ( api . Container {
Name : kubeScheduler ,
2016-10-29 01:24:59 +00:00
Image : images . GetCoreImage ( images . KubeSchedulerImage , cfg , kubeadmapi . GlobalEnvParams . HyperkubeImage ) ,
2016-10-19 12:30:03 +00:00
Command : getSchedulerCommand ( cfg ) ,
2016-08-18 12:38:18 +00:00
LivenessProbe : componentProbe ( 10251 , "/healthz" ) ,
Resources : componentResources ( "100m" ) ,
2016-11-25 16:56:38 +00:00
Env : getProxyEnvVars ( ) ,
2016-08-18 12:38:18 +00:00
} ) ,
}
2016-09-20 10:37:02 +00:00
// Add etcd static pod spec only if external etcd is not configured
2016-10-17 08:00:22 +00:00
if len ( cfg . Etcd . Endpoints ) == 0 {
2016-09-20 10:37:02 +00:00
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" ,
2017-01-04 16:43:12 +00:00
"--data-dir=/var/lib/etcd" ,
2016-09-20 10:37:02 +00:00
} ,
VolumeMounts : [ ] api . VolumeMount { certsVolumeMount ( ) , etcdVolumeMount ( ) , k8sVolumeMount ( ) } ,
2016-10-29 01:24:59 +00:00
Image : images . GetCoreImage ( images . KubeEtcdImage , cfg , kubeadmapi . GlobalEnvParams . EtcdImage ) ,
2016-09-20 10:37:02 +00:00
LivenessProbe : componentProbe ( 2379 , "/health" ) ,
Resources : componentResources ( "200m" ) ,
2016-09-21 15:35:22 +00:00
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.
2016-11-23 00:25:31 +00:00
Type : "spc_t" ,
2016-09-21 15:35:22 +00:00
} ,
} ,
2016-10-17 08:00:22 +00:00
} , certsVolume ( cfg ) , etcdVolume ( cfg ) , k8sVolume ( cfg ) )
2016-09-20 10:37:02 +00:00
}
2016-10-29 01:24:59 +00:00
manifestsPath := path . Join ( kubeadmapi . GlobalEnvParams . KubernetesDir , "manifests" )
2016-08-18 12:38:18 +00:00
if err := os . MkdirAll ( manifestsPath , 0700 ) ; err != nil {
2016-12-07 07:21:28 +00:00
return fmt . Errorf ( "failed to create directory %q [%v]" , manifestsPath , err )
2016-08-18 12:38:18 +00:00
}
for name , spec := range staticPodSpecs {
filename := path . Join ( manifestsPath , name + ".json" )
serialized , err := json . MarshalIndent ( spec , "" , " " )
if err != nil {
2016-12-07 07:21:28 +00:00
return fmt . Errorf ( "failed to marshal manifest for %q to JSON [%v]" , name , err )
2016-08-18 12:38:18 +00:00
}
if err := cmdutil . DumpReaderToFile ( bytes . NewReader ( serialized ) , filename ) ; err != nil {
2016-12-07 07:21:28 +00:00
return fmt . Errorf ( "failed to create static pod manifest file for %q (%q) [%v]" , name , filename , err )
2016-08-18 12:38:18 +00:00
}
}
return nil
}
2016-09-20 10:37:02 +00:00
// etcdVolume exposes a path on the host in order to guarantee data survival during reboot.
2016-10-17 08:00:22 +00:00
func etcdVolume ( cfg * kubeadmapi . MasterConfiguration ) api . Volume {
2016-09-11 13:39:35 +00:00
return api . Volume {
Name : "etcd" ,
VolumeSource : api . VolumeSource {
2016-10-29 01:24:59 +00:00
HostPath : & api . HostPathVolumeSource { Path : kubeadmapi . GlobalEnvParams . HostEtcdPath } ,
2016-09-11 13:39:35 +00:00
} ,
}
}
func etcdVolumeMount ( ) api . VolumeMount {
return api . VolumeMount {
Name : "etcd" ,
2017-01-04 16:43:12 +00:00
MountPath : "/var/lib/etcd" ,
2016-09-11 13:39:35 +00:00
}
}
2016-11-07 17:18:33 +00:00
func isCertsVolumeMountNeeded ( ) bool {
// Always return true for now. We may add conditional logic here for images which do not require host mounting /etc/ssl
// hyperkube for example already has valid ca-certificates installed
return true
}
2016-09-20 10:37:02 +00:00
// certsVolume exposes host SSL certificates to pod containers.
2016-10-17 08:00:22 +00:00
func certsVolume ( cfg * kubeadmapi . MasterConfiguration ) api . Volume {
2016-09-20 10:37:02 +00:00
return api . Volume {
Name : "certs" ,
VolumeSource : api . VolumeSource {
2016-09-19 19:05:14 +00:00
// TODO(phase1+) make path configurable
2016-09-20 10:37:02 +00:00
HostPath : & api . HostPathVolumeSource { Path : "/etc/ssl/certs" } ,
} ,
}
}
func certsVolumeMount ( ) api . VolumeMount {
return api . VolumeMount {
Name : "certs" ,
MountPath : "/etc/ssl/certs" ,
}
}
2016-11-07 17:18:33 +00:00
func isPkiVolumeMountNeeded ( ) bool {
// On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
// due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
if _ , err := os . Stat ( "/etc/pki" ) ; err == nil {
return true
}
return false
}
func pkiVolume ( cfg * kubeadmapi . MasterConfiguration ) api . Volume {
2016-08-18 12:38:18 +00:00
return api . Volume {
2016-12-09 20:16:37 +00:00
Name : "pki" ,
2016-11-07 17:18:33 +00:00
VolumeSource : api . VolumeSource {
// TODO(phase1+) make path configurable
HostPath : & api . HostPathVolumeSource { Path : "/etc/pki" } ,
} ,
}
}
func pkiVolumeMount ( ) api . VolumeMount {
return api . VolumeMount {
Name : "pki" ,
MountPath : "/etc/pki" ,
}
}
func k8sVolume ( cfg * kubeadmapi . MasterConfiguration ) api . Volume {
return api . Volume {
Name : "k8s" ,
2016-08-18 12:38:18 +00:00
VolumeSource : api . VolumeSource {
2016-10-29 01:24:59 +00:00
HostPath : & api . HostPathVolumeSource { Path : kubeadmapi . GlobalEnvParams . KubernetesDir } ,
2016-08-18 12:38:18 +00:00
} ,
}
}
2016-09-15 14:40:42 +00:00
func k8sVolumeMount ( ) api . VolumeMount {
2016-08-18 12:38:18 +00:00
return api . VolumeMount {
2016-11-07 17:18:33 +00:00
Name : "k8s" ,
2016-09-15 14:40:42 +00:00
MountPath : "/etc/kubernetes/" ,
2016-08-18 12:38:18 +00:00
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 ,
2016-10-01 13:25:27 +00:00
FailureThreshold : 8 ,
2016-08-18 12:38:18 +00:00
}
}
func componentPod ( container api . Container , volumes ... api . Volume ) api . Pod {
return api . Pod {
2016-12-03 18:57:26 +00:00
TypeMeta : metav1 . TypeMeta {
2016-08-18 12:38:18 +00:00
APIVersion : "v1" ,
Kind : "Pod" ,
} ,
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-08-18 12:38:18 +00:00
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 ,
} ,
}
}
2016-09-07 12:53:11 +00:00
2016-12-07 07:21:28 +00:00
func getComponentBaseCommand ( component string ) [ ] string {
2016-10-29 01:24:59 +00:00
if kubeadmapi . GlobalEnvParams . HyperkubeImage != "" {
2016-12-07 07:21:28 +00:00
return [ ] string { "/hyperkube" , component }
2016-09-07 16:37:02 +00:00
}
2016-12-07 07:21:28 +00:00
return [ ] string { "kube-" + component }
2016-10-19 12:30:03 +00:00
}
2016-09-07 16:37:02 +00:00
2016-12-07 07:21:28 +00:00
func getAPIServerCommand ( cfg * kubeadmapi . MasterConfiguration ) [ ] string {
command := append ( getComponentBaseCommand ( apiServer ) ,
2016-10-19 12:30:03 +00:00
"--insecure-bind-address=127.0.0.1" ,
"--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,ResourceQuota" ,
"--service-cluster-ip-range=" + cfg . Networking . ServiceSubnet ,
2016-12-07 07:21:28 +00:00
"--service-account-key-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/apiserver-key.pem" ,
"--client-ca-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/ca.pem" ,
"--tls-cert-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/apiserver.pem" ,
"--tls-private-key-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/apiserver-key.pem" ,
"--token-auth-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/tokens.csv" ,
2016-11-08 19:27:16 +00:00
fmt . Sprintf ( "--secure-port=%d" , cfg . API . Port ) ,
2016-10-19 12:30:03 +00:00
"--allow-privileged" ,
)
2016-09-19 19:05:14 +00:00
2017-01-17 00:41:56 +00:00
if cfg . AuthorizationMode != "" {
command = append ( command , "--authorization-mode=" + cfg . AuthorizationMode )
switch cfg . AuthorizationMode {
2017-01-19 15:27:59 +00:00
case "ABAC" :
2017-01-17 00:41:56 +00:00
command = append ( command , "--authorization-policy-file=" + path . Join ( kubeadmapi . GlobalEnvParams . KubernetesDir , authorizationPolicyFile ) )
2017-01-19 15:27:59 +00:00
case "Webhook" :
2017-01-17 00:41:56 +00:00
command = append ( command , "--authorization-webhook-config-file=" + path . Join ( kubeadmapi . GlobalEnvParams . KubernetesDir , authorizationWebhookConfigFile ) )
}
}
2016-10-19 12:30:03 +00:00
// Use first address we are given
if len ( cfg . API . AdvertiseAddresses ) > 0 {
command = append ( command , fmt . Sprintf ( "--advertise-address=%s" , cfg . API . AdvertiseAddresses [ 0 ] ) )
2016-09-19 19:05:14 +00:00
}
2016-12-09 20:16:37 +00:00
if len ( cfg . KubernetesVersion ) != 0 {
// If the k8s version is v1.5-something, this argument is set and makes `kubectl logs` and `kubectl exec`
// work on bare-metal where hostnames aren't usually resolvable
// Omit the "v" in the beginning, otherwise semver will fail
k8sVersion , err := semver . Parse ( cfg . KubernetesVersion [ 1 : ] )
2016-12-13 15:51:15 +00:00
// If the k8s version is greater than this version, it supports telling it which way it should contact kubelets
if err == nil && k8sVersion . GTE ( preferredAddressAPIServerMinVersion ) {
2016-12-09 20:16:37 +00:00
command = append ( command , "--kubelet-preferred-address-types=InternalIP,ExternalIP,Hostname" )
2016-12-13 15:51:15 +00:00
}
// This is a critical "bugfix". Any version above this is vulnarable unless a RBAC/ABAC-authorizer is provided (which kubeadm doesn't for the time being)
if err == nil && k8sVersion . GTE ( anonAuthDisableAPIServerMinVersion ) {
command = append ( command , "--anonymous-auth=false" )
2016-12-09 20:16:37 +00:00
}
}
2016-10-19 12:30:03 +00:00
// Check if the user decided to use an external etcd cluster
if len ( cfg . Etcd . Endpoints ) > 0 {
command = append ( command , fmt . Sprintf ( "--etcd-servers=%s" , strings . Join ( cfg . Etcd . Endpoints , "," ) ) )
} else {
command = append ( command , "--etcd-servers=http://127.0.0.1:2379" )
}
2016-09-15 14:40:42 +00:00
2016-10-19 12:30:03 +00:00
// Is etcd secured?
if cfg . Etcd . CAFile != "" {
command = append ( command , fmt . Sprintf ( "--etcd-cafile=%s" , cfg . Etcd . CAFile ) )
}
if cfg . Etcd . CertFile != "" && cfg . Etcd . KeyFile != "" {
etcdClientFileArg := fmt . Sprintf ( "--etcd-certfile=%s" , cfg . Etcd . CertFile )
etcdKeyFileArg := fmt . Sprintf ( "--etcd-keyfile=%s" , cfg . Etcd . KeyFile )
command = append ( command , etcdClientFileArg , etcdKeyFileArg )
}
2016-12-07 14:24:02 +00:00
if cfg . CloudProvider != "" {
command = append ( command , "--cloud-provider=" + cfg . CloudProvider )
// Only append the --cloud-config option if there's a such file
if _ , err := os . Stat ( DefaultCloudConfigPath ) ; err == nil {
command = append ( command , "--cloud-config=" + DefaultCloudConfigPath )
}
}
2016-12-07 07:21:28 +00:00
return command
2016-10-19 12:30:03 +00:00
}
2016-12-07 07:21:28 +00:00
func getControllerManagerCommand ( cfg * kubeadmapi . MasterConfiguration ) [ ] string {
command := append ( getComponentBaseCommand ( controllerManager ) ,
2016-10-19 12:30:03 +00:00
"--address=127.0.0.1" ,
"--leader-elect" ,
"--master=127.0.0.1:8080" ,
"--cluster-name=" + DefaultClusterName ,
2016-12-07 07:21:28 +00:00
"--root-ca-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/ca.pem" ,
"--service-account-private-key-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/apiserver-key.pem" ,
"--cluster-signing-cert-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/ca.pem" ,
"--cluster-signing-key-file=" + kubeadmapi . GlobalEnvParams . HostPKIPath + "/ca-key.pem" ,
2017-01-17 00:41:56 +00:00
"--insecure-experimental-approve-all-kubelet-csrs-for-group=kubeadm:kubelet-bootstrap" ,
2016-10-19 12:30:03 +00:00
)
if cfg . CloudProvider != "" {
command = append ( command , "--cloud-provider=" + cfg . CloudProvider )
// Only append the --cloud-config option if there's a such file
if _ , err := os . Stat ( DefaultCloudConfigPath ) ; err == nil {
command = append ( command , "--cloud-config=" + DefaultCloudConfigPath )
2016-10-07 19:27:03 +00:00
}
2016-09-07 12:53:11 +00:00
}
2016-10-19 12:30:03 +00:00
// 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.
if cfg . Networking . PodSubnet != "" {
command = append ( command , "--allocate-node-cidrs=true" , "--cluster-cidr=" + cfg . Networking . PodSubnet )
}
2016-12-07 07:21:28 +00:00
return command
2016-10-19 12:30:03 +00:00
}
2016-12-07 07:21:28 +00:00
func getSchedulerCommand ( cfg * kubeadmapi . MasterConfiguration ) [ ] string {
return append ( getComponentBaseCommand ( scheduler ) ,
2016-10-19 12:30:03 +00:00
"--address=127.0.0.1" ,
"--leader-elect" ,
"--master=127.0.0.1:8080" ,
)
}
2016-12-07 07:21:28 +00:00
func getProxyCommand ( cfg * kubeadmapi . MasterConfiguration ) [ ] string {
return getComponentBaseCommand ( proxy )
2016-09-07 12:53:11 +00:00
}
2016-11-25 16:56:38 +00:00
func getProxyEnvVars ( ) [ ] api . EnvVar {
envs := [ ] api . EnvVar { }
for _ , env := range os . Environ ( ) {
pos := strings . Index ( env , "=" )
if pos == - 1 {
// malformed environment variable, skip it.
continue
}
name := env [ : pos ]
value := env [ pos + 1 : ]
if strings . HasSuffix ( strings . ToLower ( name ) , "_proxy" ) && value != "" {
envVar := api . EnvVar { Name : name , Value : value }
envs = append ( envs , envVar )
}
}
return envs
}