volumes.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  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. fmt.Printf("[control-plane] Adding extra host path mount %q to %q\n", extraVol.Name, component)
  127. hostPathType := extraVol.PathType
  128. c.NewHostPathMount(component, extraVol.Name, extraVol.HostPath, extraVol.MountPath, extraVol.ReadOnly, &hostPathType)
  129. }
  130. }
  131. func (c *controlPlaneHostPathMounts) GetVolumes(component string) map[string]v1.Volume {
  132. return c.volumes[component]
  133. }
  134. func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) map[string]v1.VolumeMount {
  135. return c.volumeMounts[component]
  136. }
  137. func (c *controlPlaneHostPathMounts) addComponentVolume(component string, vol v1.Volume) {
  138. if _, ok := c.volumes[component]; !ok {
  139. c.volumes[component] = map[string]v1.Volume{}
  140. }
  141. c.volumes[component][vol.Name] = vol
  142. }
  143. func (c *controlPlaneHostPathMounts) addComponentVolumeMount(component string, volMount v1.VolumeMount) {
  144. if _, ok := c.volumeMounts[component]; !ok {
  145. c.volumeMounts[component] = map[string]v1.VolumeMount{}
  146. }
  147. c.volumeMounts[component][volMount.Name] = volMount
  148. }
  149. // getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster
  150. func getEtcdCertVolumes(etcdCfg *kubeadmapi.ExternalEtcd, k8sCertificatesDir string) ([]v1.Volume, []v1.VolumeMount) {
  151. certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile}
  152. certDirs := sets.NewString()
  153. for _, certPath := range certPaths {
  154. certDir := filepath.Dir(certPath)
  155. // Ignore ".", which is the result of passing an empty path.
  156. // Also ignore the cert directories that already may be mounted; /etc/ssl/certs, /etc/pki or Kubernetes CertificatesDir
  157. // If the etcd certs are in there, it's okay, we don't have to do anything
  158. extraVolumePath := false
  159. for _, caCertsExtraVolumePath := range caCertsExtraVolumePaths {
  160. if strings.HasPrefix(certDir, caCertsExtraVolumePath) {
  161. extraVolumePath = true
  162. break
  163. }
  164. }
  165. if certDir == "." || extraVolumePath || strings.HasPrefix(certDir, caCertsVolumePath) || strings.HasPrefix(certDir, k8sCertificatesDir) {
  166. continue
  167. }
  168. // Filter out any existing hostpath mounts in the list that contains a subset of the path
  169. alreadyExists := false
  170. for _, existingCertDir := range certDirs.List() {
  171. // If the current directory is a parent of an existing one, remove the already existing one
  172. if strings.HasPrefix(existingCertDir, certDir) {
  173. certDirs.Delete(existingCertDir)
  174. } else if strings.HasPrefix(certDir, existingCertDir) {
  175. // If an existing directory is a parent of the current one, don't add the current one
  176. alreadyExists = true
  177. }
  178. }
  179. if alreadyExists {
  180. continue
  181. }
  182. certDirs.Insert(certDir)
  183. }
  184. volumes := []v1.Volume{}
  185. volumeMounts := []v1.VolumeMount{}
  186. pathType := v1.HostPathDirectoryOrCreate
  187. for i, certDir := range certDirs.List() {
  188. name := fmt.Sprintf("etcd-certs-%d", i)
  189. volumes = append(volumes, staticpodutil.NewVolume(name, certDir, &pathType))
  190. volumeMounts = append(volumeMounts, staticpodutil.NewVolumeMount(name, certDir, true))
  191. }
  192. return volumes, volumeMounts
  193. }
  194. // isExtraVolumeMountNeeded specifies whether /etc/pki should be host-mounted into the containers
  195. // On some systems were we host-mount /etc/ssl/certs, it is also required to mount /etc/pki. This is needed
  196. // due to symlinks pointing from files in /etc/ssl/certs into /etc/pki/
  197. func isExtraVolumeMountNeeded(caCertsExtraVolumePath string) bool {
  198. if _, err := os.Stat(caCertsExtraVolumePath); err == nil {
  199. return true
  200. }
  201. return false
  202. }