volumes.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  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. v1 "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. defaultFlexvolumeDirVolumePath = "/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 by default)
  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. flexvolumeDirVolumePath, ok := cfg.ControllerManager.ExtraArgs["flex-volume-plugin-dir"]
  63. if !ok {
  64. flexvolumeDirVolumePath = defaultFlexvolumeDirVolumePath
  65. }
  66. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, flexvolumeDirVolumeName, flexvolumeDirVolumePath, flexvolumeDirVolumePath, false, &hostPathDirectoryOrCreate)
  67. // HostPath volumes for the scheduler
  68. // Read-only mount for the scheduler kubeconfig file
  69. schedulerKubeConfigFile := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.SchedulerKubeConfigFileName)
  70. mounts.NewHostPathMount(kubeadmconstants.KubeScheduler, kubeadmconstants.KubeConfigVolumeName, schedulerKubeConfigFile, schedulerKubeConfigFile, true, &hostPathFileOrCreate)
  71. // On some systems were we host-mount /etc/ssl/certs, it is also required to mount additional directories.
  72. // This is needed due to symlinks pointing from files in /etc/ssl/certs to these directories.
  73. for _, caCertsExtraVolumePath := range caCertsExtraVolumePaths {
  74. if isExtraVolumeMountNeeded(caCertsExtraVolumePath) {
  75. caCertsExtraVolumeName := strings.Replace(caCertsExtraVolumePath, "/", "-", -1)[1:]
  76. mounts.NewHostPathMount(kubeadmconstants.KubeAPIServer, caCertsExtraVolumeName, caCertsExtraVolumePath, caCertsExtraVolumePath, true, &hostPathDirectoryOrCreate)
  77. mounts.NewHostPathMount(kubeadmconstants.KubeControllerManager, caCertsExtraVolumeName, caCertsExtraVolumePath, caCertsExtraVolumePath, true, &hostPathDirectoryOrCreate)
  78. }
  79. }
  80. // Merge user defined mounts and ensure unique volume and volume mount
  81. // names
  82. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeAPIServer, cfg.APIServer.ExtraVolumes)
  83. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeControllerManager, cfg.ControllerManager.ExtraVolumes)
  84. mounts.AddExtraHostPathMounts(kubeadmconstants.KubeScheduler, cfg.Scheduler.ExtraVolumes)
  85. return mounts
  86. }
  87. // controlPlaneHostPathMounts is a helper struct for handling all the control plane's hostPath mounts in an easy way
  88. type controlPlaneHostPathMounts struct {
  89. // volumes is a nested map that forces a unique volumes. The outer map's
  90. // keys are a string that should specify the target component to add the
  91. // volume to. The values (inner map) of the outer map are maps with string
  92. // keys and v1.Volume values. The inner map's key should specify the volume
  93. // name.
  94. volumes map[string]map[string]v1.Volume
  95. // volumeMounts is a nested map that forces a unique volume mounts. The
  96. // outer map's keys are a string that should specify the target component
  97. // to add the volume mount to. The values (inner map) of the outer map are
  98. // maps with string keys and v1.VolumeMount values. The inner map's key
  99. // should specify the volume mount name.
  100. volumeMounts map[string]map[string]v1.VolumeMount
  101. }
  102. func newControlPlaneHostPathMounts() controlPlaneHostPathMounts {
  103. return controlPlaneHostPathMounts{
  104. volumes: map[string]map[string]v1.Volume{},
  105. volumeMounts: map[string]map[string]v1.VolumeMount{},
  106. }
  107. }
  108. func (c *controlPlaneHostPathMounts) NewHostPathMount(component, mountName, hostPath, containerPath string, readOnly bool, hostPathType *v1.HostPathType) {
  109. vol := staticpodutil.NewVolume(mountName, hostPath, hostPathType)
  110. c.addComponentVolume(component, vol)
  111. volMount := staticpodutil.NewVolumeMount(mountName, containerPath, readOnly)
  112. c.addComponentVolumeMount(component, volMount)
  113. }
  114. func (c *controlPlaneHostPathMounts) AddHostPathMounts(component string, vols []v1.Volume, volMounts []v1.VolumeMount) {
  115. for _, v := range vols {
  116. c.addComponentVolume(component, v)
  117. }
  118. for _, v := range volMounts {
  119. c.addComponentVolumeMount(component, v)
  120. }
  121. }
  122. // AddExtraHostPathMounts adds host path mounts and overwrites the default
  123. // paths in the case that a user specifies the same volume/volume mount name.
  124. func (c *controlPlaneHostPathMounts) AddExtraHostPathMounts(component string, extraVols []kubeadmapi.HostPathMount) {
  125. for _, extraVol := range extraVols {
  126. hostPathType := extraVol.PathType
  127. c.NewHostPathMount(component, extraVol.Name, extraVol.HostPath, extraVol.MountPath, extraVol.ReadOnly, &hostPathType)
  128. }
  129. }
  130. func (c *controlPlaneHostPathMounts) GetVolumes(component string) map[string]v1.Volume {
  131. return c.volumes[component]
  132. }
  133. func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) map[string]v1.VolumeMount {
  134. return c.volumeMounts[component]
  135. }
  136. func (c *controlPlaneHostPathMounts) addComponentVolume(component string, vol v1.Volume) {
  137. if _, ok := c.volumes[component]; !ok {
  138. c.volumes[component] = map[string]v1.Volume{}
  139. }
  140. c.volumes[component][vol.Name] = vol
  141. }
  142. func (c *controlPlaneHostPathMounts) addComponentVolumeMount(component string, volMount v1.VolumeMount) {
  143. if _, ok := c.volumeMounts[component]; !ok {
  144. c.volumeMounts[component] = map[string]v1.VolumeMount{}
  145. }
  146. c.volumeMounts[component][volMount.Name] = volMount
  147. }
  148. // getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster
  149. func getEtcdCertVolumes(etcdCfg *kubeadmapi.ExternalEtcd, k8sCertificatesDir string) ([]v1.Volume, []v1.VolumeMount) {
  150. certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile}
  151. certDirs := sets.NewString()
  152. for _, certPath := range certPaths {
  153. certDir := filepath.Dir(certPath)
  154. // Ignore ".", which is the result of passing an empty path.
  155. // Also ignore the cert directories that already may be mounted; /etc/ssl/certs, /etc/pki or Kubernetes CertificatesDir
  156. // If the etcd certs are in there, it's okay, we don't have to do anything
  157. extraVolumePath := false
  158. for _, caCertsExtraVolumePath := range caCertsExtraVolumePaths {
  159. if strings.HasPrefix(certDir, caCertsExtraVolumePath) {
  160. extraVolumePath = true
  161. break
  162. }
  163. }
  164. if certDir == "." || extraVolumePath || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, k8sCertificatesDir) {
  165. continue
  166. }
  167. // Filter out any existing hostpath mounts in the list that contains a subset of the path
  168. alreadyExists := false
  169. for _, existingCertDir := range certDirs.List() {
  170. // If the current directory is a parent of an existing one, remove the already existing one
  171. if strings.HasPrefix(existingCertDir, certDir) {
  172. certDirs.Delete(existingCertDir)
  173. } else if strings.HasPrefix(certDir, existingCertDir) {
  174. // If an existing directory is a parent of the current one, don't add the current one
  175. alreadyExists = true
  176. }
  177. }
  178. if alreadyExists {
  179. continue
  180. }
  181. certDirs.Insert(certDir)
  182. }
  183. volumes := []v1.Volume{}
  184. volumeMounts := []v1.VolumeMount{}
  185. pathType := v1.HostPathDirectoryOrCreate
  186. for i, certDir := range certDirs.List() {
  187. name := fmt.Sprintf("etcd-certs-%d", i)
  188. volumes = append(volumes, staticpodutil.NewVolume(name, certDir, &pathType))
  189. volumeMounts = append(volumeMounts, staticpodutil.NewVolumeMount(name, certDir, true))
  190. }
  191. return volumes, volumeMounts
  192. }
  193. // isExtraVolumeMountNeeded specifies whether /etc/pki should be host-mounted into the containers
  194. // On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
  195. // due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
  196. func isExtraVolumeMountNeeded(caCertsExtraVolumePath string) bool {
  197. if _, err := os.Stat(caCertsExtraVolumePath); err == nil {
  198. return true
  199. }
  200. return false
  201. }