manifests.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  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. "path/filepath"
  18. "strconv"
  19. "strings"
  20. "github.com/pkg/errors"
  21. v1 "k8s.io/api/core/v1"
  22. "k8s.io/klog"
  23. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  24. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  25. "k8s.io/kubernetes/cmd/kubeadm/app/features"
  26. "k8s.io/kubernetes/cmd/kubeadm/app/images"
  27. certphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
  28. kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
  29. staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
  30. utilsnet "k8s.io/utils/net"
  31. )
  32. // CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane.
  33. func CreateInitStaticPodManifestFiles(manifestDir, kustomizeDir string, cfg *kubeadmapi.InitConfiguration) error {
  34. klog.V(1).Infoln("[control-plane] creating static Pod files")
  35. return CreateStaticPodFiles(manifestDir, kustomizeDir, &cfg.ClusterConfiguration, &cfg.LocalAPIEndpoint, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler)
  36. }
  37. // GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current configuration
  38. // NB. this methods holds the information about how kubeadm creates static pod manifests.
  39. func GetStaticPodSpecs(cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint) map[string]v1.Pod {
  40. // Get the required hostpath mounts
  41. mounts := getHostPathVolumesForTheControlPlane(cfg)
  42. // Prepare static pod specs
  43. staticPodSpecs := map[string]v1.Pod{
  44. kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
  45. Name: kubeadmconstants.KubeAPIServer,
  46. Image: images.GetKubernetesImage(kubeadmconstants.KubeAPIServer, cfg),
  47. ImagePullPolicy: v1.PullIfNotPresent,
  48. Command: getAPIServerCommand(cfg, endpoint),
  49. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer)),
  50. LivenessProbe: staticpodutil.LivenessProbe(staticpodutil.GetAPIServerProbeAddress(endpoint), "/healthz", int(endpoint.BindPort), v1.URISchemeHTTPS),
  51. Resources: staticpodutil.ComponentResources("250m"),
  52. Env: kubeadmutil.GetProxyEnvVars(),
  53. }, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
  54. kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
  55. Name: kubeadmconstants.KubeControllerManager,
  56. Image: images.GetKubernetesImage(kubeadmconstants.KubeControllerManager, cfg),
  57. ImagePullPolicy: v1.PullIfNotPresent,
  58. Command: getControllerManagerCommand(cfg),
  59. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager)),
  60. LivenessProbe: staticpodutil.LivenessProbe(staticpodutil.GetControllerManagerProbeAddress(cfg), "/healthz", kubeadmconstants.KubeControllerManagerPort, v1.URISchemeHTTPS),
  61. Resources: staticpodutil.ComponentResources("200m"),
  62. Env: kubeadmutil.GetProxyEnvVars(),
  63. }, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
  64. kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
  65. Name: kubeadmconstants.KubeScheduler,
  66. Image: images.GetKubernetesImage(kubeadmconstants.KubeScheduler, cfg),
  67. ImagePullPolicy: v1.PullIfNotPresent,
  68. Command: getSchedulerCommand(cfg),
  69. VolumeMounts: staticpodutil.VolumeMountMapToSlice(mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler)),
  70. LivenessProbe: staticpodutil.LivenessProbe(staticpodutil.GetSchedulerProbeAddress(cfg), "/healthz", kubeadmconstants.KubeSchedulerPort, v1.URISchemeHTTPS),
  71. Resources: staticpodutil.ComponentResources("100m"),
  72. Env: kubeadmutil.GetProxyEnvVars(),
  73. }, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
  74. }
  75. return staticPodSpecs
  76. }
  77. // CreateStaticPodFiles creates all the requested static pod files.
  78. func CreateStaticPodFiles(manifestDir, kustomizeDir string, cfg *kubeadmapi.ClusterConfiguration, endpoint *kubeadmapi.APIEndpoint, componentNames ...string) error {
  79. // gets the StaticPodSpecs, actualized for the current ClusterConfiguration
  80. klog.V(1).Infoln("[control-plane] getting StaticPodSpecs")
  81. specs := GetStaticPodSpecs(cfg, endpoint)
  82. // creates required static pod specs
  83. for _, componentName := range componentNames {
  84. // retrieves the StaticPodSpec for given component
  85. spec, exists := specs[componentName]
  86. if !exists {
  87. return errors.Errorf("couldn't retrieve StaticPodSpec for %q", componentName)
  88. }
  89. // if kustomizeDir is defined, customize the static pod manifest
  90. if kustomizeDir != "" {
  91. kustomizedSpec, err := staticpodutil.KustomizeStaticPod(&spec, kustomizeDir)
  92. if err != nil {
  93. return errors.Wrapf(err, "failed to kustomize static pod manifest file for %q", componentName)
  94. }
  95. spec = *kustomizedSpec
  96. }
  97. // writes the StaticPodSpec to disk
  98. if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
  99. return errors.Wrapf(err, "failed to create static pod manifest file for %q", componentName)
  100. }
  101. klog.V(1).Infof("[control-plane] wrote static Pod manifest for component %q to %q\n", componentName, kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir))
  102. }
  103. return nil
  104. }
  105. // getAPIServerCommand builds the right API server command from the given config object and version
  106. func getAPIServerCommand(cfg *kubeadmapi.ClusterConfiguration, localAPIEndpoint *kubeadmapi.APIEndpoint) []string {
  107. defaultArguments := map[string]string{
  108. "advertise-address": localAPIEndpoint.AdvertiseAddress,
  109. "insecure-port": "0",
  110. "enable-admission-plugins": "NodeRestriction",
  111. "service-cluster-ip-range": cfg.Networking.ServiceSubnet,
  112. "service-account-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPublicKeyName),
  113. "client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName),
  114. "tls-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerCertName),
  115. "tls-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKeyName),
  116. "kubelet-client-certificate": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientCertName),
  117. "kubelet-client-key": filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerKubeletClientKeyName),
  118. "enable-bootstrap-token-auth": "true",
  119. "secure-port": fmt.Sprintf("%d", localAPIEndpoint.BindPort),
  120. "allow-privileged": "true",
  121. "kubelet-preferred-address-types": "InternalIP,ExternalIP,Hostname",
  122. // add options to configure the front proxy. Without the generated client cert, this will never be useable
  123. // so add it unconditionally with recommended values
  124. "requestheader-username-headers": "X-Remote-User",
  125. "requestheader-group-headers": "X-Remote-Group",
  126. "requestheader-extra-headers-prefix": "X-Remote-Extra-",
  127. "requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
  128. "requestheader-allowed-names": "front-proxy-client",
  129. "proxy-client-cert-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientCertName),
  130. "proxy-client-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyClientKeyName),
  131. }
  132. command := []string{"kube-apiserver"}
  133. // If the user set endpoints for an external etcd cluster
  134. if cfg.Etcd.External != nil {
  135. defaultArguments["etcd-servers"] = strings.Join(cfg.Etcd.External.Endpoints, ",")
  136. // Use any user supplied etcd certificates
  137. if cfg.Etcd.External.CAFile != "" {
  138. defaultArguments["etcd-cafile"] = cfg.Etcd.External.CAFile
  139. }
  140. if cfg.Etcd.External.CertFile != "" && cfg.Etcd.External.KeyFile != "" {
  141. defaultArguments["etcd-certfile"] = cfg.Etcd.External.CertFile
  142. defaultArguments["etcd-keyfile"] = cfg.Etcd.External.KeyFile
  143. }
  144. } else {
  145. // Default to etcd static pod on localhost
  146. // localhost IP family should be the same that the AdvertiseAddress
  147. etcdLocalhostAddress := "127.0.0.1"
  148. if utilsnet.IsIPv6String(localAPIEndpoint.AdvertiseAddress) {
  149. etcdLocalhostAddress = "::1"
  150. }
  151. defaultArguments["etcd-servers"] = fmt.Sprintf("https://%s", net.JoinHostPort(etcdLocalhostAddress, strconv.Itoa(kubeadmconstants.EtcdListenClientPort)))
  152. defaultArguments["etcd-cafile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.EtcdCACertName)
  153. defaultArguments["etcd-certfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientCertName)
  154. defaultArguments["etcd-keyfile"] = filepath.Join(cfg.CertificatesDir, kubeadmconstants.APIServerEtcdClientKeyName)
  155. // Apply user configurations for local etcd
  156. if cfg.Etcd.Local != nil {
  157. if value, ok := cfg.Etcd.Local.ExtraArgs["advertise-client-urls"]; ok {
  158. defaultArguments["etcd-servers"] = value
  159. }
  160. }
  161. }
  162. // TODO: The following code should be removed after dual-stack is GA.
  163. // Note: The user still retains the ability to explicitly set feature-gates and that value will overwrite this base value.
  164. if enabled, present := cfg.FeatureGates[features.IPv6DualStack]; present {
  165. defaultArguments["feature-gates"] = fmt.Sprintf("%s=%t", features.IPv6DualStack, enabled)
  166. }
  167. if cfg.APIServer.ExtraArgs == nil {
  168. cfg.APIServer.ExtraArgs = map[string]string{}
  169. }
  170. cfg.APIServer.ExtraArgs["authorization-mode"] = getAuthzModes(cfg.APIServer.ExtraArgs["authorization-mode"])
  171. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.APIServer.ExtraArgs)...)
  172. return command
  173. }
  174. // getAuthzModes gets the authorization-related parameters to the api server
  175. // Node,RBAC is the default mode if nothing is passed to kubeadm. User provided modes override
  176. // the default.
  177. func getAuthzModes(authzModeExtraArgs string) string {
  178. defaultMode := []string{
  179. kubeadmconstants.ModeNode,
  180. kubeadmconstants.ModeRBAC,
  181. }
  182. if len(authzModeExtraArgs) > 0 {
  183. mode := []string{}
  184. for _, requested := range strings.Split(authzModeExtraArgs, ",") {
  185. if isValidAuthzMode(requested) {
  186. mode = append(mode, requested)
  187. } else {
  188. klog.Warningf("ignoring unknown kube-apiserver authorization-mode %q", requested)
  189. }
  190. }
  191. // only return the user provided mode if at least one was valid
  192. if len(mode) > 0 {
  193. klog.Warningf("the default kube-apiserver authorization-mode is %q; using %q",
  194. strings.Join(defaultMode, ","),
  195. strings.Join(mode, ","),
  196. )
  197. return strings.Join(mode, ",")
  198. }
  199. }
  200. return strings.Join(defaultMode, ",")
  201. }
  202. func isValidAuthzMode(authzMode string) bool {
  203. allModes := []string{
  204. kubeadmconstants.ModeNode,
  205. kubeadmconstants.ModeRBAC,
  206. kubeadmconstants.ModeWebhook,
  207. kubeadmconstants.ModeABAC,
  208. kubeadmconstants.ModeAlwaysAllow,
  209. kubeadmconstants.ModeAlwaysDeny,
  210. }
  211. for _, mode := range allModes {
  212. if authzMode == mode {
  213. return true
  214. }
  215. }
  216. return false
  217. }
  218. // calcNodeCidrSize determines the size of the subnets used on each node, based
  219. // on the pod subnet provided. For IPv4, we assume that the pod subnet will
  220. // be /16 and use /24. If the pod subnet cannot be parsed, the IPv4 value will
  221. // be used (/24).
  222. //
  223. // For IPv6, the algorithm will do two three. First, the node CIDR will be set
  224. // to a multiple of 8, using the available bits for easier readability by user.
  225. // Second, the number of nodes will be 512 to 64K to attempt to maximize the
  226. // number of nodes (see NOTE below). Third, pod networks of /113 and larger will
  227. // be rejected, as the amount of bits available is too small.
  228. //
  229. // A special case is when the pod network size is /112, where /120 will be used,
  230. // only allowing 256 nodes and 256 pods.
  231. //
  232. // If the pod network size is /113 or larger, the node CIDR will be set to the same
  233. // size and this will be rejected later in validation.
  234. //
  235. // NOTE: Currently, the design allows a maximum of 64K nodes. This algorithm splits
  236. // the available bits to maximize the number used for nodes, but still have the node
  237. // CIDR be a multiple of eight.
  238. //
  239. func calcNodeCidrSize(podSubnet string) (string, bool) {
  240. maskSize := "24"
  241. isIPv6 := false
  242. if ip, podCidr, err := net.ParseCIDR(podSubnet); err == nil {
  243. if utilsnet.IsIPv6(ip) {
  244. var nodeCidrSize int
  245. isIPv6 = true
  246. podNetSize, totalBits := podCidr.Mask.Size()
  247. switch {
  248. case podNetSize == 112:
  249. // Special case, allows 256 nodes, 256 pods/node
  250. nodeCidrSize = 120
  251. case podNetSize < 112:
  252. // Use multiple of 8 for node CIDR, with 512 to 64K nodes
  253. nodeCidrSize = totalBits - ((totalBits-podNetSize-1)/8-1)*8
  254. default:
  255. // Not enough bits, will fail later, when validate
  256. nodeCidrSize = podNetSize
  257. }
  258. maskSize = strconv.Itoa(nodeCidrSize)
  259. }
  260. }
  261. return maskSize, isIPv6
  262. }
  263. // getControllerManagerCommand builds the right controller manager command from the given config object and version
  264. func getControllerManagerCommand(cfg *kubeadmapi.ClusterConfiguration) []string {
  265. kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
  266. caFile := filepath.Join(cfg.CertificatesDir, kubeadmconstants.CACertName)
  267. defaultArguments := map[string]string{
  268. "bind-address": "127.0.0.1",
  269. "leader-elect": "true",
  270. "kubeconfig": kubeconfigFile,
  271. "authentication-kubeconfig": kubeconfigFile,
  272. "authorization-kubeconfig": kubeconfigFile,
  273. "client-ca-file": caFile,
  274. "requestheader-client-ca-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.FrontProxyCACertName),
  275. "root-ca-file": caFile,
  276. "service-account-private-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.ServiceAccountPrivateKeyName),
  277. "cluster-signing-cert-file": caFile,
  278. "cluster-signing-key-file": filepath.Join(cfg.CertificatesDir, kubeadmconstants.CAKeyName),
  279. "use-service-account-credentials": "true",
  280. "controllers": "*,bootstrapsigner,tokencleaner",
  281. }
  282. // If using external CA, pass empty string to controller manager instead of ca.key/ca.crt path,
  283. // so that the csrsigning controller fails to start
  284. if res, _ := certphase.UsingExternalCA(cfg); res {
  285. defaultArguments["cluster-signing-key-file"] = ""
  286. defaultArguments["cluster-signing-cert-file"] = ""
  287. }
  288. // Let the controller-manager allocate Node CIDRs for the Pod network.
  289. // Each node will get a subspace of the address CIDR provided with --pod-network-cidr.
  290. if cfg.Networking.PodSubnet != "" {
  291. defaultArguments["allocate-node-cidrs"] = "true"
  292. defaultArguments["cluster-cidr"] = cfg.Networking.PodSubnet
  293. if cfg.Networking.ServiceSubnet != "" {
  294. defaultArguments["service-cluster-ip-range"] = cfg.Networking.ServiceSubnet
  295. }
  296. }
  297. // Set cluster name
  298. if cfg.ClusterName != "" {
  299. defaultArguments["cluster-name"] = cfg.ClusterName
  300. }
  301. // TODO: The following code should be remvoved after dual-stack is GA.
  302. // Note: The user still retains the ability to explicitly set feature-gates and that value will overwrite this base value.
  303. enabled, present := cfg.FeatureGates[features.IPv6DualStack]
  304. if present {
  305. defaultArguments["feature-gates"] = fmt.Sprintf("%s=%t", features.IPv6DualStack, enabled)
  306. }
  307. if cfg.Networking.PodSubnet != "" {
  308. if enabled {
  309. // any errors will be caught during validation
  310. subnets := strings.Split(cfg.Networking.PodSubnet, ",")
  311. for _, podSubnet := range subnets {
  312. if maskSize, isIPv6 := calcNodeCidrSize(podSubnet); isIPv6 {
  313. defaultArguments["node-cidr-mask-size-ipv6"] = maskSize
  314. } else {
  315. defaultArguments["node-cidr-mask-size-ipv4"] = maskSize
  316. }
  317. }
  318. } else {
  319. maskSize, _ := calcNodeCidrSize(cfg.Networking.PodSubnet)
  320. defaultArguments["node-cidr-mask-size"] = maskSize
  321. }
  322. }
  323. command := []string{"kube-controller-manager"}
  324. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.ControllerManager.ExtraArgs)...)
  325. return command
  326. }
  327. // getSchedulerCommand builds the right scheduler command from the given config object and version
  328. func getSchedulerCommand(cfg *kubeadmapi.ClusterConfiguration) []string {
  329. kubeconfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
  330. defaultArguments := map[string]string{
  331. "bind-address": "127.0.0.1",
  332. "leader-elect": "true",
  333. "kubeconfig": kubeconfigFile,
  334. "authentication-kubeconfig": kubeconfigFile,
  335. "authorization-kubeconfig": kubeconfigFile,
  336. }
  337. // TODO: The following code should be remvoved after dual-stack is GA.
  338. // Note: The user still retains the ability to explicitly set feature-gates and that value will overwrite this base value.
  339. if enabled, present := cfg.FeatureGates[features.IPv6DualStack]; present {
  340. defaultArguments["feature-gates"] = fmt.Sprintf("%s=%t", features.IPv6DualStack, enabled)
  341. }
  342. command := []string{"kube-scheduler"}
  343. command = append(command, kubeadmutil.BuildArgumentListFromMap(defaultArguments, cfg.Scheduler.ExtraArgs)...)
  344. return command
  345. }