volumes.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. /*
  2. Copyright 2017 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. "os"
  17. "path/filepath"
  18. "strings"
  19. "k8s.io/api/core/v1"
  20. "k8s.io/apimachinery/pkg/util/sets"
  21. kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
  22. kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
  23. staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
  24. )
  25. const (
  26. caCertsVolumeName = "ca-certs"
  27. caCertsVolumePath = "/etc/ssl/certs"
  28. flexvolumeDirVolumeName = "flexvolume-dir"
  29. flexvolumeDirVolumePath = "/usr/libexec/kubernetes/kubelet-plugins/volume/exec"
  30. )
  31. // caCertsExtraVolumePaths specifies the paths that can be conditionally mounted into the apiserver and controller-manager containers
  32. // as /etc/ssl/certs might be or contain a symlink to them. It's a variable since it may be changed in unit testing. This var MUST
  33. // NOT be changed in normal codepaths during runtime.
  34. var caCertsExtraVolumePaths = []string{"/etc/pki", "/usr/share/ca-certificates", "/usr/local/share/ca-certificates", "/etc/ca-certificates"}
  35. // getHostPathVolumesForTheControlPlane gets the required hostPath volumes and mounts for the control plane
  36. func getHostPathVolumesForTheControlPlane(cfg *kubeadmapi.ClusterConfiguration) controlPlaneHostPathMounts {
  37. hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
  38. hostPathFileOrCreate := v1.HostPathFileOrCreate
  39. mounts := newControlPlaneHostPathMounts()
  40. // HostPath volumes for the API Server
  41. // Read-only mount for the certificates directory
  42. // TODO: Always mount the K8s Certificates directory to a static path inside of the container
  43. mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true, &hostPathDirectoryOrCreate)
  44. // Read-only mount for the ca certs (/etc/ssl/certs) directory
  45. mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true, &hostPathDirectoryOrCreate)
  46. // If external etcd is specified, mount the directories needed for accessing the CA/serving certs and the private key
  47. if cfg.Etcd.External != nil {
  48. etcdVols, etcdVolMounts := getEtcdCertVolumes(cfg.Etcd.External, cfg.CertificatesDir)
  49. mounts.AddHostPathMounts(kubeadmconstants.KubeAPIServer, etcdVols, etcdVolMounts)
  50. }
  51. // HostPath volumes for the controller manager
  52. // Read-only mount for the certificates directory
  53. // TODO: Always mount the K8s Certificates directory to a static path inside of the container
  54. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeCertificatesVolumeName, cfg.CertificatesDir, cfg.CertificatesDir, true, &hostPathDirectoryOrCreate)
  55. // Read-only mount for the ca certs (/etc/ssl/certs) directory
  56. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsVolumeName, caCertsVolumePath, caCertsVolumePath, true, &hostPathDirectoryOrCreate)
  57. // Read-only mount for the controller manager kubeconfig file
  58. controllerManagerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ControllerManagerKubeConfigFileName)
  59. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeConfigVolumeName, controllerManagerKubeConfigFile, controllerManagerKubeConfigFile, true, &hostPathFileOrCreate)
  60. // Mount for the flexvolume directory (/usr/libexec/kubernetes/kubelet-plugins/volume/exec) directory
  61. // Flexvolume dir must NOT be readonly as it is used for third-party plugins to integrate with their storage backends via unix domain socket.
  62. if stat, err := os.Stat(flexvolumeDirVolumePath); err == nil && stat.IsDir() {
  63. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate)
  64. }
  65. // HostPath volumes for the scheduler
  66. // Read-only mount for the scheduler kubeconfig file
  67. schedulerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
  68. mounts.NewHostPathMount(kubeadmconstants.KubeScheduler, kubeadmconstants.KubeConfigVolumeName, schedulerKubeConfigFile, schedulerKubeConfigFile, true, &hostPathFileOrCreate)
  69. // On some systems were we host-mount /etc/ssl/certs, it is also required to mount additional directories.
  70. // This is needed due to symlinks pointing from files in /etc/ssl/certs to these directories.
  71. for _, caCertsExtraVolumePath := range caCertsExtraVolumePaths {
  72. if isExtraVolumeMountNeeded(caCertsExtraVolumePath) {
  73. caCertsExtraVolumeName := strings.Replace(caCertsExtraVolumePath, "/", "-", -1)[1:]
  74. mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsExtraVolumeName, caCertsExtraVolumePath, caCertsExtraVolumePath, true, &hostPathDirectoryOrCreate)
  75. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsExtraVolumeName, caCertsExtraVolumePath, caCertsExtraVolumePath, true, &hostPathDirectoryOrCreate)
  76. }
  77. }
  78. // Merge user defined mounts and ensure unique volume and volume mount
  79. // names
  80. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeAPIServer, cfg.APIServer.ExtraVolumes)
  81. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeControllerManager, cfg.ControllerManager.ExtraVolumes)
  82. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeScheduler, cfg.Scheduler.ExtraVolumes)
  83. return mounts
  84. }
  85. // controlPlaneHostPathMounts is a helper struct for handling all the control plane's hostPath mounts in an easy way
  86. type controlPlaneHostPathMounts struct {
  87. // volumes is a nested map that forces a unique volumes. The outer map's
  88. // keys are a string that should specify the target component to add the
  89. // volume to. The values (inner map) of the outer map are maps with string
  90. // keys and v1.Volume values. The inner map's key should specify the volume
  91. // name.
  92. volumes map[string]map[string]v1.Volume
  93. // volumeMounts is a nested map that forces a unique volume mounts. The
  94. // outer map's keys are a string that should specify the target component
  95. // to add the volume mount to. The values (inner map) of the outer map are
  96. // maps with string keys and v1.VolumeMount values. The inner map's key
  97. // should specify the volume mount name.
  98. volumeMounts map[string]map[string]v1.VolumeMount
  99. }
  100. func newControlPlaneHostPathMounts() controlPlaneHostPathMounts {
  101. return controlPlaneHostPathMounts{
  102. volumes: map[string]map[string]v1.Volume{},
  103. volumeMounts: map[string]map[string]v1.VolumeMount{},
  104. }
  105. }
  106. func (c *controlPlaneHostPathMounts) NewHostPathMount(component, mountName, hostPath, containerPath string, readOnly bool, hostPathType *v1.HostPathType) {
  107. vol := staticpodutil.NewVolume(mountName, hostPath, hostPathType)
  108. c.addComponentVolume(component, vol)
  109. volMount := staticpodutil.NewVolumeMount(mountName, containerPath, readOnly)
  110. c.addComponentVolumeMount(component, volMount)
  111. }
  112. func (c *controlPlaneHostPathMounts) AddHostPathMounts(component string, vols []v1.Volume, volMounts []v1.VolumeMount) {
  113. for _, v := range vols {
  114. c.addComponentVolume(component, v)
  115. }
  116. for _, v := range volMounts {
  117. c.addComponentVolumeMount(component, v)
  118. }
  119. }
  120. // AddExtraHostPathMounts adds host path mounts and overwrites the default
  121. // paths in the case that a user specifies the same volume/volume mount name.
  122. func (c *controlPlaneHostPathMounts) AddExtraHostPathMounts(component string, extraVols []kubeadmapi.HostPathMount) {
  123. for _, extraVol := range extraVols {
  124. fmt.Printf("[controlplane] Adding extra host path mount %q to %q\n", extraVol.Name, component)
  125. hostPathType := extraVol.PathType
  126. c.NewHostPathMount(component, extraVol.Name, extraVol.HostPath, extraVol.MountPath, extraVol.ReadOnly, &hostPathType)
  127. }
  128. }
  129. func (c *controlPlaneHostPathMounts) GetVolumes(component string) map[string]v1.Volume {
  130. return c.volumes[component]
  131. }
  132. func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) map[string]v1.VolumeMount {
  133. return c.volumeMounts[component]
  134. }
  135. func (c *controlPlaneHostPathMounts) addComponentVolume(component string, vol v1.Volume) {
  136. if _, ok := c.volumes[component]; !ok {
  137. c.volumes[component] = map[string]v1.Volume{}
  138. }
  139. c.volumes[component][vol.Name] = vol
  140. }
  141. func (c *controlPlaneHostPathMounts) addComponentVolumeMount(component string, volMount v1.VolumeMount) {
  142. if _, ok := c.volumeMounts[component]; !ok {
  143. c.volumeMounts[component] = map[string]v1.VolumeMount{}
  144. }
  145. c.volumeMounts[component][volMount.Name] = volMount
  146. }
  147. // getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster
  148. func getEtcdCertVolumes(etcdCfg *kubeadmapi.ExternalEtcd, k8sCertificatesDir string) ([]v1.Volume, []v1.VolumeMount) {
  149. certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile}
  150. certDirs := sets.NewString()
  151. for _, certPath := range certPaths {
  152. certDir := filepath.Dir(certPath)
  153. // Ignore ".", which is the result of passing an empty path.
  154. // Also ignore the cert directories that already may be mounted; /etc/ssl/certs, /etc/pki or Kubernetes CertificatesDir
  155. // If the etcd certs are in there, it's okay, we don't have to do anything
  156. extraVolumePath := false
  157. for _, caCertsExtraVolumePath := range caCertsExtraVolumePaths {
  158. if strings.HasPrefix(certDir, caCertsExtraVolumePath) {
  159. extraVolumePath = true
  160. break
  161. }
  162. }
  163. if certDir == "." || extraVolumePath || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, k8sCertificatesDir) {
  164. continue
  165. }
  166. // Filter out any existing hostpath mounts in the list that contains a subset of the path
  167. alreadyExists := false
  168. for _, existingCertDir := range certDirs.List() {
  169. // If the current directory is a parent of an existing one, remove the already existing one
  170. if strings.HasPrefix(existingCertDir, certDir) {
  171. certDirs.Delete(existingCertDir)
  172. } else if strings.HasPrefix(certDir, existingCertDir) {
  173. // If an existing directory is a parent of the current one, don't add the current one
  174. alreadyExists = true
  175. }
  176. }
  177. if alreadyExists {
  178. continue
  179. }
  180. certDirs.Insert(certDir)
  181. }
  182. volumes := []v1.Volume{}
  183. volumeMounts := []v1.VolumeMount{}
  184. pathType := v1.HostPathDirectoryOrCreate
  185. for i, certDir := range certDirs.List() {
  186. name := fmt.Sprintf("etcd-certs-%d", i)
  187. volumes = append(volumes, staticpodutil.NewVolume(name, certDir, &pathType))
  188. volumeMounts = append(volumeMounts, staticpodutil.NewVolumeMount(name, certDir, true))
  189. }
  190. return volumes, volumeMounts
  191. }
  192. // isExtraVolumeMountNeeded specifies whether /etc/pki should be host-mounted into the containers
  193. // On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
  194. // due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
  195. func isExtraVolumeMountNeeded(caCertsExtraVolumePath string) bool {
  196. if _, err := os.Stat(caCertsExtraVolumePath); err == nil {
  197. return true
  198. }
  199. return false
  200. }