manifests.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package controlplane
  14. import (
  15. "fmt"
  16. "net"
  17. "os"
  18. "path/filepath"
  19. "strconv"
  20. "strings"
  21. "github.com/pkg/errors"
  22. "k8s.io/api/core/v1"
  23. "k8s.io/apimachinery/pkg/util/intstr"
  24. "k8s.io/apimachinery/pkg/util/version"
  25. "k8s.io/klog"
  26. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  27. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  28. "k8s.io/kubernetes/cmd/kubeadm/app/images"
  29. certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
  30. kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
  31. staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
  32. authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
  33. "k8s.io/kubernetes/pkg/master/ports"
  34. )
  35. // CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane.
  36. func CreateInitStaticPodManifestFiles(manifestDir string, cfg *kubeadmapi.InitConfiguration) error {
  37. klog.V(1).Infoln("[control-plane] creating static Pod files")
  38. return CreateStaticPodFiles(manifestDir, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler)
  39. }
  40. // GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current configuration
  41. // NB. this methods holds the information about how kubeadm creates static pod manifests.
  42. func GetStaticPodSpecs(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, k8sVersion *version.Version) map[string]v1.Pod {
  43. // Get the required hostpath mounts
  44. mounts := getHostPathVolumesForTheControlPlane(cfg)
  45. // Prepare static pod specs
  46. staticPodSpecs := map[string]v1.Pod{
  47. kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
  48. Name: kubeadmconstants.KubeAPIServer,
  49. Image: images.GetKubernetesImage(kubeadmconstants.KubeAPIServer, cfg),
  50. ImagePullPolicy: v1.PullIfNotPresent,
  51. Command: getAPIServerCommand(cfg, endpoint),
  52. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)),
  53. LivenessProbe: livenessProbe(staticpodutil.GetAPIServerProbeAddress(endpoint), int(endpoint.BindPort), v1.URISchemeHTTPS),
  54. Resources: staticpodutil.ComponentResources("250m"),
  55. Env: getProxyEnvVars(),
  56. }, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
  57. kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
  58. Name: kubeadmconstants.KubeControllerManager,
  59. Image: images.GetKubernetesImage(kubeadmconstants.KubeControllerManager, cfg),
  60. ImagePullPolicy: v1.PullIfNotPresent,
  61. Command: getControllerManagerCommand(cfg, k8sVersion),
  62. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)),
  63. LivenessProbe: livenessProbe(staticpodutil.GetControllerManagerProbeAddress(cfg), ports.InsecureKubeControllerManagerPort, v1.URISchemeHTTP),
  64. Resources: staticpodutil.ComponentResources("200m"),
  65. Env: getProxyEnvVars(),
  66. }, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
  67. kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
  68. Name: kubeadmconstants.KubeScheduler,
  69. Image: images.GetKubernetesImage(kubeadmconstants.KubeScheduler, cfg),
  70. ImagePullPolicy: v1.PullIfNotPresent,
  71. Command: getSchedulerCommand(cfg),
  72. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)),
  73. LivenessProbe: livenessProbe(staticpodutil.GetSchedulerProbeAddress(cfg), ports.InsecureSchedulerPort, v1.URISchemeHTTP),
  74. Resources: staticpodutil.ComponentResources("100m"),
  75. Env: getProxyEnvVars(),
  76. }, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
  77. }
  78. return staticPodSpecs
  79. }
  80. func livenessProbe(host string, port int, scheme v1.URIScheme) *v1.Probe {
  81. return &v1.Probe{
  82. Handler: v1.Handler{
  83. HTTPGet: &v1.HTTPGetAction{
  84. Host: host,
  85. Path: "/healthz",
  86. Port: intstr.FromInt(port),
  87. Scheme: scheme,
  88. },
  89. },
  90. InitialDelaySeconds: 15,
  91. TimeoutSeconds: 15,
  92. FailureThreshold: 8,
  93. }
  94. }
  95. // CreateStaticPodFiles creates all the requested static pod files.
  96. func CreateStaticPodFiles(manifestDir string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, componentNames ...string) error {
  97. // TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string
  98. k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
  99. if err != nil {
  100. return err
  101. }
  102. // gets the StaticPodSpecs, actualized for the current ClusterConfiguration
  103. klog.V(1).Infoln("[control-plane] getting StaticPodSpecs")
  104. specs := GetStaticPodSpecs(cfg, endpoint, k8sVersion)
  105. // creates required static pod specs
  106. for _, componentName := range componentNames {
  107. // retrieves the StaticPodSpec for given component
  108. spec, exists := specs[componentName]
  109. if !exists {
  110. return errors.Errorf("couldn't retrieve StaticPodSpec for %q", componentName)
  111. }
  112. // writes the StaticPodSpec to disk
  113. if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
  114. return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName)
  115. }
  116. klog.V(1).Infof("[control-plane] wrote static Pod manifest for component %q to %q\n", componentName, kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir))
  117. }
  118. return nil
  119. }
  120. // getAPIServerCommand builds the right API server command from the given config object and version
  121. func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) []string {
  122. defaultArguments := map[string]string{
  123. "advertise-address": localAPIEndpoint.AdvertiseAddress,
  124. "insecure-port": "0",
  125. "enable-admission-plugins": "NodeRestriction",
  126. "service-cluster-ip-range": cfg.Networking.ServiceSubnet,
  127. "service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
  128. "client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
  129. "tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
  130. "tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
  131. "kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
  132. "kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
  133. "enable-bootstrap-token-auth": "true",
  134. "secure-port": fmt.Sprintf("%d", localAPIEndpoint.BindPort),
  135. "allow-privileged": "true",
  136. "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
  137. // add options to configure the front proxy. Without the generated client cert, this will never be useable
  138. // so add it unconditionally with recommended values
  139. "requestheader-username-headers": "X-Remote-User",
  140. "requestheader-group-headers": "X-Remote-Group",
  141. "requestheader-extra-headers-prefix": "X-Remote-Extra-",
  142. "requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
  143. "requestheader-allowed-names": "front-proxy-client",
  144. "proxy-client-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientCertName),
  145. "proxy-client-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName),
  146. }
  147. command := []string{"kube-apiserver"}
  148. // If the user set endpoints for an external etcd cluster
  149. if cfg.Etcd.External != nil {
  150. defaultArguments["etcd-servers"] = strings.Join(cfg.Etcd.External.Endpoints, ",")
  151. // Use any user supplied etcd certificates
  152. if cfg.Etcd.External.CAFile != "" {
  153. defaultArguments["etcd-cafile"] = cfg.Etcd.External.CAFile
  154. }
  155. if cfg.Etcd.External.CertFile != "" && cfg.Etcd.External.KeyFile != "" {
  156. defaultArguments["etcd-certfile"] = cfg.Etcd.External.CertFile
  157. defaultArguments["etcd-keyfile"] = cfg.Etcd.External.KeyFile
  158. }
  159. } else {
  160. // Default to etcd static pod on localhost
  161. defaultArguments["etcd-servers"] = fmt.Sprintf("https://127.0.0.1:%d", kubeadmconstants.EtcdListenClientPort)
  162. defaultArguments["etcd-cafile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)
  163. defaultArguments["etcd-certfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientCertName)
  164. defaultArguments["etcd-keyfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName)
  165. // Apply user configurations for local etcd
  166. if cfg.Etcd.Local != nil {
  167. if value, ok := cfg.Etcd.Local.ExtraArgs["advertise-client-urls"]; ok {
  168. defaultArguments["etcd-servers"] = value
  169. }
  170. }
  171. }
  172. if cfg.APIServer.ExtraArgs == nil {
  173. cfg.APIServer.ExtraArgs = map[string]string{}
  174. }
  175. cfg.APIServer.ExtraArgs["authorization-mode"] = getAuthzModes(cfg.APIServer.ExtraArgs["authorization-mode"])
  176. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServer.ExtraArgs)...)
  177. return command
  178. }
  179. // getAuthzModes gets the authorization-related parameters to the api server
  180. // Node,RBAC should be fixed in this order at the beginning
  181. // AlwaysAllow and AlwaysDeny is ignored as they are only for testing
  182. func getAuthzModes(authzModeExtraArgs string) string {
  183. modes := []string{
  184. authzmodes.ModeNode,
  185. authzmodes.ModeRBAC,
  186. }
  187. if strings.Contains(authzModeExtraArgs, authzmodes.ModeABAC) {
  188. modes = append(modes, authzmodes.ModeABAC)
  189. }
  190. if strings.Contains(authzModeExtraArgs, authzmodes.ModeWebhook) {
  191. modes = append(modes, authzmodes.ModeWebhook)
  192. }
  193. return strings.Join(modes, ",")
  194. }
  195. // calcNodeCidrSize determines the size of the subnets used on each node, based
  196. // on the pod subnet provided. For IPv4, we assume that the pod subnet will
  197. // be /16 and use /24. If the pod subnet cannot be parsed, the IPv4 value will
  198. // be used (/24).
  199. //
  200. // For IPv6, the algorithm will do two three. First, the node CIDR will be set
  201. // to a multiple of 8, using the available bits for easier readability by user.
  202. // Second, the number of nodes will be 512 to 64K to attempt to maximize the
  203. // number of nodes (see NOTE below). Third, pod networks of /113 and larger will
  204. // be rejected, as the amount of bits available is too small.
  205. //
  206. // A special case is when the pod network size is /112, where /120 will be used,
  207. // only allowing 256 nodes and 256 pods.
  208. //
  209. // If the pod network size is /113 or larger, the node CIDR will be set to the same
  210. // size and this will be rejected later in validation.
  211. //
  212. // NOTE: Currently, the design allows a maximum of 64K nodes. This algorithm splits
  213. // the available bits to maximize the number used for nodes, but still have the node
  214. // CIDR be a multiple of eight.
  215. //
  216. func calcNodeCidrSize(podSubnet string) string {
  217. maskSize := "24"
  218. if ip, podCidr, err := net.ParseCIDR(podSubnet); err == nil {
  219. if ip.To4() == nil {
  220. var nodeCidrSize int
  221. podNetSize, totalBits := podCidr.Mask.Size()
  222. switch {
  223. case podNetSize == 112:
  224. // Special case, allows 256 nodes, 256 pods/node
  225. nodeCidrSize = 120
  226. case podNetSize < 112:
  227. // Use multiple of 8 for node CIDR, with 512 to 64K nodes
  228. nodeCidrSize = totalBits - ((totalBits-podNetSize-1)/8-1)*8
  229. default:
  230. // Not enough bits, will fail later, when validate
  231. nodeCidrSize = podNetSize
  232. }
  233. maskSize = strconv.Itoa(nodeCidrSize)
  234. }
  235. }
  236. return maskSize
  237. }
  238. // getControllerManagerCommand builds the right controller manager command from the given config object and version
  239. func getControllerManagerCommand(cfg *kubeadmapi.ClusterConfiguration, k8sVersion *version.Version) []string {
  240. kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
  241. caFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)
  242. defaultArguments := map[string]string{
  243. "bind-address": "127.0.0.1",
  244. "leader-elect": "true",
  245. "kubeconfig": kubeconfigFile,
  246. "authentication-kubeconfig": kubeconfigFile,
  247. "authorization-kubeconfig": kubeconfigFile,
  248. "client-ca-file": caFile,
  249. "requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
  250. "root-ca-file": caFile,
  251. "service-account-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
  252. "cluster-signing-cert-file": caFile,
  253. "cluster-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
  254. "use-service-account-credentials": "true",
  255. "controllers": "*,bootstrapsigner,tokencleaner",
  256. }
  257. // If using external CA, pass empty string to controller manager instead of ca.key/ca.crt path,
  258. // so that the csrsigning controller fails to start
  259. if res, _ := certphase.UsingExternalCA(cfg); res {
  260. defaultArguments["cluster-signing-key-file"] = ""
  261. defaultArguments["cluster-signing-cert-file"] = ""
  262. }
  263. // Let the controller-manager allocate Node CIDRs for the Pod network.
  264. // Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
  265. if cfg.Networking.PodSubnet != "" {
  266. maskSize := calcNodeCidrSize(cfg.Networking.PodSubnet)
  267. defaultArguments["allocate-node-cidrs"] = "true"
  268. defaultArguments["cluster-cidr"] = cfg.Networking.PodSubnet
  269. defaultArguments["node-cidr-mask-size"] = maskSize
  270. }
  271. command := []string{"kube-controller-manager"}
  272. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.ControllerManager.ExtraArgs)...)
  273. return command
  274. }
  275. // getSchedulerCommand builds the right scheduler command from the given config object and version
  276. func getSchedulerCommand(cfg *kubeadmapi.ClusterConfiguration) []string {
  277. defaultArguments := map[string]string{
  278. "bind-address": "127.0.0.1",
  279. "leader-elect": "true",
  280. "kubeconfig": filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName),
  281. }
  282. command := []string{"kube-scheduler"}
  283. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Scheduler.ExtraArgs)...)
  284. return command
  285. }
  286. // getProxyEnvVars builds a list of environment variables to use in the control plane containers in order to use the right proxy
  287. func getProxyEnvVars() []v1.EnvVar {
  288. envs := []v1.EnvVar{}
  289. for _, env := range os.Environ() {
  290. pos := strings.Index(env, "=")
  291. if pos == -1 {
  292. // malformed environment variable, skip it.
  293. continue
  294. }
  295. name := env[:pos]
  296. value := env[pos+1:]
  297. if strings.HasSuffix(strings.ToLower(name), "_proxy") && value != "" {
  298. envVar := v1.EnvVar{Name: name, Value: value}
  299. envs = append(envs, envVar)
  300. }
  301. }
  302. return envs
  303. }