1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123 |
- /*
- Copyright 2016 The Kubernetes Authors.
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package preflight
- import (
- "bufio"
- "bytes"
- "crypto/tls"
- "crypto/x509"
- "encoding/json"
- "fmt"
- "io"
- "io/ioutil"
- "net"
- "net/http"
- "net/url"
- "os"
- "path/filepath"
- "runtime"
- "strings"
- "time"
- "github.com/PuerkitoBio/purell"
- "github.com/blang/semver"
- "github.com/pkg/errors"
- netutil "k8s.io/apimachinery/pkg/util/net"
- "k8s.io/apimachinery/pkg/util/sets"
- versionutil "k8s.io/apimachinery/pkg/util/version"
- "k8s.io/klog"
- kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
- kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
- "k8s.io/kubernetes/cmd/kubeadm/app/images"
- utilruntime "k8s.io/kubernetes/cmd/kubeadm/app/util/runtime"
- "k8s.io/kubernetes/cmd/kubeadm/app/util/system"
- "k8s.io/kubernetes/pkg/master/ports"
- "k8s.io/kubernetes/pkg/registry/core/service/ipallocator"
- "k8s.io/kubernetes/pkg/util/initsystem"
- ipvsutil "k8s.io/kubernetes/pkg/util/ipvs"
- kubeadmversion "k8s.io/kubernetes/pkg/version"
- utilsexec "k8s.io/utils/exec"
- )
- const (
- bridgenf = "/proc/sys/net/bridge/bridge-nf-call-iptables"
- bridgenf6 = "/proc/sys/net/bridge/bridge-nf-call-ip6tables"
- ipv4Forward = "/proc/sys/net/ipv4/ip_forward"
- ipv6DefaultForwarding = "/proc/sys/net/ipv6/conf/default/forwarding"
- externalEtcdRequestTimeout = time.Duration(10 * time.Second)
- externalEtcdRequestRetries = 3
- externalEtcdRequestInterval = time.Duration(5 * time.Second)
- )
- var (
- minExternalEtcdVersion = semver.MustParse(kubeadmconstants.MinExternalEtcdVersion)
- )
- // Error defines struct for communicating error messages generated by preflight checks
- type Error struct {
- Msg string
- }
- // Error implements the standard error interface
- func (e *Error) Error() string {
- return fmt.Sprintf("[preflight] Some fatal errors occurred:\n%s%s", e.Msg, "[preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...`")
- }
- // Preflight identifies this error as a preflight error
- func (e *Error) Preflight() bool {
- return true
- }
- // Checker validates the state of the system to ensure kubeadm will be
- // successful as often as possible.
- type Checker interface {
- Check() (warnings, errorList []error)
- Name() string
- }
- // ContainerRuntimeCheck verifies the container runtime.
- type ContainerRuntimeCheck struct {
- runtime utilruntime.ContainerRuntime
- }
- // Name returns label for RuntimeCheck.
- func (ContainerRuntimeCheck) Name() string {
- return "CRI"
- }
- // Check validates the container runtime
- func (crc ContainerRuntimeCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating the container runtime")
- if err := crc.runtime.IsRunning(); err != nil {
- errorList = append(errorList, err)
- }
- return warnings, errorList
- }
- // ServiceCheck verifies that the given service is enabled and active. If we do not
- // detect a supported init system however, all checks are skipped and a warning is
- // returned.
- type ServiceCheck struct {
- Service string
- CheckIfActive bool
- Label string
- }
- // Name returns label for ServiceCheck. If not provided, will return based on the service parameter
- func (sc ServiceCheck) Name() string {
- if sc.Label != "" {
- return sc.Label
- }
- return fmt.Sprintf("Service-%s", strings.Title(sc.Service))
- }
- // Check validates if the service is enabled and active.
- func (sc ServiceCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating if the service is enabled and active")
- initSystem, err := initsystem.GetInitSystem()
- if err != nil {
- return []error{err}, nil
- }
- warnings = []error{}
- if !initSystem.ServiceExists(sc.Service) {
- warnings = append(warnings, errors.Errorf("%s service does not exist", sc.Service))
- return warnings, nil
- }
- if !initSystem.ServiceIsEnabled(sc.Service) {
- warnings = append(warnings,
- errors.Errorf("%s service is not enabled, please run '%s'",
- sc.Service, initSystem.EnableCommand(sc.Service)))
- }
- if sc.CheckIfActive && !initSystem.ServiceIsActive(sc.Service) {
- errorList = append(errorList,
- errors.Errorf("%s service is not active, please run 'systemctl start %s.service'",
- sc.Service, sc.Service))
- }
- return warnings, errorList
- }
- // FirewalldCheck checks if firewalld is enabled or active. If it is, warn the user that there may be problems
- // if no actions are taken.
- type FirewalldCheck struct {
- ports []int
- }
- // Name returns label for FirewalldCheck.
- func (FirewalldCheck) Name() string {
- return "Firewalld"
- }
- // Check validates if the firewall is enabled and active.
- func (fc FirewalldCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating if the firewall is enabled and active")
- initSystem, err := initsystem.GetInitSystem()
- if err != nil {
- return []error{err}, nil
- }
- warnings = []error{}
- if !initSystem.ServiceExists("firewalld") {
- return nil, nil
- }
- if initSystem.ServiceIsActive("firewalld") {
- warnings = append(warnings,
- errors.Errorf("firewalld is active, please ensure ports %v are open or your cluster may not function correctly",
- fc.ports))
- }
- return warnings, errorList
- }
- // PortOpenCheck ensures the given port is available for use.
- type PortOpenCheck struct {
- port int
- label string
- }
- // Name returns name for PortOpenCheck. If not known, will return "PortXXXX" based on port number
- func (poc PortOpenCheck) Name() string {
- if poc.label != "" {
- return poc.label
- }
- return fmt.Sprintf("Port-%d", poc.port)
- }
- // Check validates if the particular port is available.
- func (poc PortOpenCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infof("validating availability of port %d", poc.port)
- errorList = []error{}
- ln, err := net.Listen("tcp", fmt.Sprintf(":%d", poc.port))
- if err != nil {
- errorList = append(errorList, errors.Errorf("Port %d is in use", poc.port))
- }
- if ln != nil {
- ln.Close()
- }
- return nil, errorList
- }
- // IsPrivilegedUserCheck verifies user is privileged (linux - root, windows - Administrator)
- type IsPrivilegedUserCheck struct{}
- // Name returns name for IsPrivilegedUserCheck
- func (IsPrivilegedUserCheck) Name() string {
- return "IsPrivilegedUser"
- }
- // IsDockerSystemdCheck verifies if Docker is setup to use systemd as the cgroup driver.
- type IsDockerSystemdCheck struct{}
- // Name returns name for IsDockerSystemdCheck
- func (IsDockerSystemdCheck) Name() string {
- return "IsDockerSystemdCheck"
- }
- // DirAvailableCheck checks if the given directory either does not exist, or is empty.
- type DirAvailableCheck struct {
- Path string
- Label string
- }
- // Name returns label for individual DirAvailableChecks. If not known, will return based on path.
- func (dac DirAvailableCheck) Name() string {
- if dac.Label != "" {
- return dac.Label
- }
- return fmt.Sprintf("DirAvailable-%s", strings.Replace(dac.Path, "/", "-", -1))
- }
- // Check validates if a directory does not exist or empty.
- func (dac DirAvailableCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infof("validating the existence and emptiness of directory %s", dac.Path)
- errorList = []error{}
- // If it doesn't exist we are good:
- if _, err := os.Stat(dac.Path); os.IsNotExist(err) {
- return nil, nil
- }
- f, err := os.Open(dac.Path)
- if err != nil {
- errorList = append(errorList, errors.Wrapf(err, "unable to check if %s is empty", dac.Path))
- return nil, errorList
- }
- defer f.Close()
- _, err = f.Readdirnames(1)
- if err != io.EOF {
- errorList = append(errorList, errors.Errorf("%s is not empty", dac.Path))
- }
- return nil, errorList
- }
- // FileAvailableCheck checks that the given file does not already exist.
- type FileAvailableCheck struct {
- Path string
- Label string
- }
- // Name returns label for individual FileAvailableChecks. If not known, will return based on path.
- func (fac FileAvailableCheck) Name() string {
- if fac.Label != "" {
- return fac.Label
- }
- return fmt.Sprintf("FileAvailable-%s", strings.Replace(fac.Path, "/", "-", -1))
- }
- // Check validates if the given file does not already exist.
- func (fac FileAvailableCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infof("validating the existence of file %s", fac.Path)
- errorList = []error{}
- if _, err := os.Stat(fac.Path); err == nil {
- errorList = append(errorList, errors.Errorf("%s already exists", fac.Path))
- }
- return nil, errorList
- }
- // FileExistingCheck checks that the given file does not already exist.
- type FileExistingCheck struct {
- Path string
- Label string
- }
- // Name returns label for individual FileExistingChecks. If not known, will return based on path.
- func (fac FileExistingCheck) Name() string {
- if fac.Label != "" {
- return fac.Label
- }
- return fmt.Sprintf("FileExisting-%s", strings.Replace(fac.Path, "/", "-", -1))
- }
- // Check validates if the given file already exists.
- func (fac FileExistingCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infof("validating the existence of file %s", fac.Path)
- errorList = []error{}
- if _, err := os.Stat(fac.Path); err != nil {
- errorList = append(errorList, errors.Errorf("%s doesn't exist", fac.Path))
- }
- return nil, errorList
- }
- // FileContentCheck checks that the given file contains the string Content.
- type FileContentCheck struct {
- Path string
- Content []byte
- Label string
- }
- // Name returns label for individual FileContentChecks. If not known, will return based on path.
- func (fcc FileContentCheck) Name() string {
- if fcc.Label != "" {
- return fcc.Label
- }
- return fmt.Sprintf("FileContent-%s", strings.Replace(fcc.Path, "/", "-", -1))
- }
- // Check validates if the given file contains the given content.
- func (fcc FileContentCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infof("validating the contents of file %s", fcc.Path)
- f, err := os.Open(fcc.Path)
- if err != nil {
- return nil, []error{errors.Errorf("%s does not exist", fcc.Path)}
- }
- lr := io.LimitReader(f, int64(len(fcc.Content)))
- defer f.Close()
- buf := &bytes.Buffer{}
- _, err = io.Copy(buf, lr)
- if err != nil {
- return nil, []error{errors.Errorf("%s could not be read", fcc.Path)}
- }
- if !bytes.Equal(buf.Bytes(), fcc.Content) {
- return nil, []error{errors.Errorf("%s contents are not set to %s", fcc.Path, fcc.Content)}
- }
- return nil, []error{}
- }
- // InPathCheck checks if the given executable is present in $PATH
- type InPathCheck struct {
- executable string
- mandatory bool
- exec utilsexec.Interface
- label string
- suggestion string
- }
- // Name returns label for individual InPathCheck. If not known, will return based on path.
- func (ipc InPathCheck) Name() string {
- if ipc.label != "" {
- return ipc.label
- }
- return fmt.Sprintf("FileExisting-%s", strings.Replace(ipc.executable, "/", "-", -1))
- }
- // Check validates if the given executable is present in the path.
- func (ipc InPathCheck) Check() (warnings, errs []error) {
- klog.V(1).Infof("validating the presence of executable %s", ipc.executable)
- _, err := ipc.exec.LookPath(ipc.executable)
- if err != nil {
- if ipc.mandatory {
- // Return as an error:
- return nil, []error{errors.Errorf("%s not found in system path", ipc.executable)}
- }
- // Return as a warning:
- warningMessage := fmt.Sprintf("%s not found in system path", ipc.executable)
- if ipc.suggestion != "" {
- warningMessage += fmt.Sprintf("\nSuggestion: %s", ipc.suggestion)
- }
- return []error{errors.New(warningMessage)}, nil
- }
- return nil, nil
- }
- // HostnameCheck checks if hostname match dns sub domain regex.
- // If hostname doesn't match this regex, kubelet will not launch static pods like kube-apiserver/kube-controller-manager and so on.
- type HostnameCheck struct {
- nodeName string
- }
- // Name will return Hostname as name for HostnameCheck
- func (HostnameCheck) Name() string {
- return "Hostname"
- }
- // Check validates if hostname match dns sub domain regex.
- func (hc HostnameCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("checking whether the given node name is reachable using net.LookupHost")
- errorList = []error{}
- warnings = []error{}
- addr, err := net.LookupHost(hc.nodeName)
- if addr == nil {
- warnings = append(warnings, errors.Errorf("hostname \"%s\" could not be reached", hc.nodeName))
- }
- if err != nil {
- warnings = append(warnings, errors.Wrapf(err, "hostname \"%s\"", hc.nodeName))
- }
- return warnings, errorList
- }
- // HTTPProxyCheck checks if https connection to specific host is going
- // to be done directly or over proxy. If proxy detected, it will return warning.
- type HTTPProxyCheck struct {
- Proto string
- Host string
- }
- // Name returns HTTPProxy as name for HTTPProxyCheck
- func (hst HTTPProxyCheck) Name() string {
- return "HTTPProxy"
- }
- // Check validates http connectivity type, direct or via proxy.
- func (hst HTTPProxyCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating if the connectivity type is via proxy or direct")
- u := (&url.URL{Scheme: hst.Proto, Host: hst.Host}).String()
- req, err := http.NewRequest("GET", u, nil)
- if err != nil {
- return nil, []error{err}
- }
- proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req)
- if err != nil {
- return nil, []error{err}
- }
- if proxy != nil {
- return []error{errors.Errorf("Connection to %q uses proxy %q. If that is not intended, adjust your proxy settings", u, proxy)}, nil
- }
- return nil, nil
- }
- // HTTPProxyCIDRCheck checks if https connection to specific subnet is going
- // to be done directly or over proxy. If proxy detected, it will return warning.
- // Similar to HTTPProxyCheck above, but operates with subnets and uses API
- // machinery transport defaults to simulate kube-apiserver accessing cluster
- // services and pods.
- type HTTPProxyCIDRCheck struct {
- Proto string
- CIDR string
- }
- // Name will return HTTPProxyCIDR as name for HTTPProxyCIDRCheck
- func (HTTPProxyCIDRCheck) Name() string {
- return "HTTPProxyCIDR"
- }
- // Check validates http connectivity to first IP address in the CIDR.
- // If it is not directly connected and goes via proxy it will produce warning.
- func (subnet HTTPProxyCIDRCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating http connectivity to first IP address in the CIDR")
- if len(subnet.CIDR) == 0 {
- return nil, nil
- }
- _, cidr, err := net.ParseCIDR(subnet.CIDR)
- if err != nil {
- return nil, []error{errors.Wrapf(err, "error parsing CIDR %q", subnet.CIDR)}
- }
- testIP, err := ipallocator.GetIndexedIP(cidr, 1)
- if err != nil {
- return nil, []error{errors.Wrapf(err, "unable to get first IP address from the given CIDR (%s)", cidr.String())}
- }
- testIPstring := testIP.String()
- if len(testIP) == net.IPv6len {
- testIPstring = fmt.Sprintf("[%s]:1234", testIP)
- }
- url := fmt.Sprintf("%s://%s/", subnet.Proto, testIPstring)
- req, err := http.NewRequest("GET", url, nil)
- if err != nil {
- return nil, []error{err}
- }
- // Utilize same transport defaults as it will be used by API server
- proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req)
- if err != nil {
- return nil, []error{err}
- }
- if proxy != nil {
- return []error{errors.Errorf("connection to %q uses proxy %q. This may lead to malfunctional cluster setup. Make sure that Pod and Services IP ranges specified correctly as exceptions in proxy configuration", subnet.CIDR, proxy)}, nil
- }
- return nil, nil
- }
- // SystemVerificationCheck defines struct used for running the system verification node check in test/e2e_node/system
- type SystemVerificationCheck struct {
- IsDocker bool
- }
- // Name will return SystemVerification as name for SystemVerificationCheck
- func (SystemVerificationCheck) Name() string {
- return "SystemVerification"
- }
- // Check runs all individual checks
- func (sysver SystemVerificationCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("running all checks")
- // Create a buffered writer and choose a quite large value (1M) and suppose the output from the system verification test won't exceed the limit
- // Run the system verification check, but write to out buffered writer instead of stdout
- bufw := bufio.NewWriterSize(os.Stdout, 1*1024*1024)
- reporter := &system.StreamReporter{WriteStream: bufw}
- var errs []error
- var warns []error
- // All the common validators we'd like to run:
- var validators = []system.Validator{
- &system.KernelValidator{Reporter: reporter}}
- // run the docker validator only with docker runtime
- if sysver.IsDocker {
- validators = append(validators, &system.DockerValidator{Reporter: reporter})
- }
- if runtime.GOOS == "linux" {
- //add linux validators
- validators = append(validators,
- &system.OSValidator{Reporter: reporter},
- &system.CgroupsValidator{Reporter: reporter})
- }
- // Run all validators
- for _, v := range validators {
- warn, err := v.Validate(system.DefaultSysSpec)
- if err != nil {
- errs = append(errs, err)
- }
- if warn != nil {
- warns = append(warns, warn)
- }
- }
- if len(errs) != 0 {
- // Only print the output from the system verification check if the check failed
- fmt.Println("[preflight] The system verification failed. Printing the output from the verification:")
- bufw.Flush()
- return warns, errs
- }
- return warns, nil
- }
- // KubernetesVersionCheck validates Kubernetes and kubeadm versions
- type KubernetesVersionCheck struct {
- KubeadmVersion string
- KubernetesVersion string
- }
- // Name will return KubernetesVersion as name for KubernetesVersionCheck
- func (KubernetesVersionCheck) Name() string {
- return "KubernetesVersion"
- }
- // Check validates Kubernetes and kubeadm versions
- func (kubever KubernetesVersionCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating Kubernetes and kubeadm version")
- // Skip this check for "super-custom builds", where apimachinery/the overall codebase version is not set.
- if strings.HasPrefix(kubever.KubeadmVersion, "v0.0.0") {
- return nil, nil
- }
- kadmVersion, err := versionutil.ParseSemantic(kubever.KubeadmVersion)
- if err != nil {
- return nil, []error{errors.Wrapf(err, "couldn't parse kubeadm version %q", kubever.KubeadmVersion)}
- }
- k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion)
- if err != nil {
- return nil, []error{errors.Wrapf(err, "couldn't parse Kubernetes version %q", kubever.KubernetesVersion)}
- }
- // Checks if k8sVersion greater or equal than the first unsupported versions by current version of kubeadm,
- // that is major.minor+1 (all patch and pre-releases versions included)
- // NB. in semver patches number is a numeric, while prerelease is a string where numeric identifiers always have lower precedence than non-numeric identifiers.
- // thus setting the value to x.y.0-0 we are defining the very first patch - prereleases within x.y minor release.
- firstUnsupportedVersion := versionutil.MustParseSemantic(fmt.Sprintf("%d.%d.%s", kadmVersion.Major(), kadmVersion.Minor()+1, "0-0"))
- if k8sVersion.AtLeast(firstUnsupportedVersion) {
- return []error{errors.Errorf("Kubernetes version is greater than kubeadm version. Please consider to upgrade kubeadm. Kubernetes version: %s. Kubeadm version: %d.%d.x", k8sVersion, kadmVersion.Components()[0], kadmVersion.Components()[1])}, nil
- }
- return nil, nil
- }
- // KubeletVersionCheck validates installed kubelet version
- type KubeletVersionCheck struct {
- KubernetesVersion string
- exec utilsexec.Interface
- }
- // Name will return KubeletVersion as name for KubeletVersionCheck
- func (KubeletVersionCheck) Name() string {
- return "KubeletVersion"
- }
- // Check validates kubelet version. It should be not less than minimal supported version
- func (kubever KubeletVersionCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating kubelet version")
- kubeletVersion, err := GetKubeletVersion(kubever.exec)
- if err != nil {
- return nil, []error{errors.Wrap(err, "couldn't get kubelet version")}
- }
- if kubeletVersion.LessThan(kubeadmconstants.MinimumKubeletVersion) {
- return nil, []error{errors.Errorf("Kubelet version %q is lower than kubeadm can support. Please upgrade kubelet", kubeletVersion)}
- }
- if kubever.KubernetesVersion != "" {
- k8sVersion, err := versionutil.ParseSemantic(kubever.KubernetesVersion)
- if err != nil {
- return nil, []error{errors.Wrapf(err, "couldn't parse Kubernetes version %q", kubever.KubernetesVersion)}
- }
- if kubeletVersion.Major() > k8sVersion.Major() || kubeletVersion.Minor() > k8sVersion.Minor() {
- return nil, []error{errors.Errorf("the kubelet version is higher than the control plane version. This is not a supported version skew and may lead to a malfunctional cluster. Kubelet version: %q Control plane version: %q", kubeletVersion, k8sVersion)}
- }
- }
- return nil, nil
- }
- // SwapCheck warns if swap is enabled
- type SwapCheck struct{}
- // Name will return Swap as name for SwapCheck
- func (SwapCheck) Name() string {
- return "Swap"
- }
- // Check validates whether swap is enabled or not
- func (swc SwapCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating whether swap is enabled or not")
- f, err := os.Open("/proc/swaps")
- if err != nil {
- // /proc/swaps not available, thus no reasons to warn
- return nil, nil
- }
- defer f.Close()
- var buf []string
- scanner := bufio.NewScanner(f)
- for scanner.Scan() {
- buf = append(buf, scanner.Text())
- }
- if err := scanner.Err(); err != nil {
- return nil, []error{errors.Wrap(err, "error parsing /proc/swaps")}
- }
- if len(buf) > 1 {
- return nil, []error{errors.New("running with swap on is not supported. Please disable swap")}
- }
- return nil, nil
- }
- type etcdVersionResponse struct {
- Etcdserver string `json:"etcdserver"`
- Etcdcluster string `json:"etcdcluster"`
- }
- // ExternalEtcdVersionCheck checks if version of external etcd meets the demand of kubeadm
- type ExternalEtcdVersionCheck struct {
- Etcd kubeadmapi.Etcd
- }
- // Name will return ExternalEtcdVersion as name for ExternalEtcdVersionCheck
- func (ExternalEtcdVersionCheck) Name() string {
- return "ExternalEtcdVersion"
- }
- // Check validates external etcd version
- // TODO: Use the official etcd Golang client for this instead?
- func (evc ExternalEtcdVersionCheck) Check() (warnings, errorList []error) {
- klog.V(1).Infoln("validating the external etcd version")
- // Return quickly if the user isn't using external etcd
- if evc.Etcd.External.Endpoints == nil {
- return nil, nil
- }
- var config *tls.Config
- var err error
- if config, err = evc.configRootCAs(config); err != nil {
- errorList = append(errorList, err)
- return nil, errorList
- }
- if config, err = evc.configCertAndKey(config); err != nil {
- errorList = append(errorList, err)
- return nil, errorList
- }
- client := evc.getHTTPClient(config)
- for _, endpoint := range evc.Etcd.External.Endpoints {
- if _, err := url.Parse(endpoint); err != nil {
- errorList = append(errorList, errors.Wrapf(err, "failed to parse external etcd endpoint %s", endpoint))
- continue
- }
- resp := etcdVersionResponse{}
- var err error
- versionURL := fmt.Sprintf("%s/%s", endpoint, "version")
- if tmpVersionURL, err := purell.NormalizeURLString(versionURL, purell.FlagRemoveDuplicateSlashes); err != nil {
- errorList = append(errorList, errors.Wrapf(err, "failed to normalize external etcd version url %s", versionURL))
- continue
- } else {
- versionURL = tmpVersionURL
- }
- if err = getEtcdVersionResponse(client, versionURL, &resp); err != nil {
- errorList = append(errorList, err)
- continue
- }
- etcdVersion, err := semver.Parse(resp.Etcdserver)
- if err != nil {
- errorList = append(errorList, errors.Wrapf(err, "couldn't parse external etcd version %q", resp.Etcdserver))
- continue
- }
- if etcdVersion.LT(minExternalEtcdVersion) {
- errorList = append(errorList, errors.Errorf("this version of kubeadm only supports external etcd version >= %s. Current version: %s", kubeadmconstants.MinExternalEtcdVersion, resp.Etcdserver))
- continue
- }
- }
- return nil, errorList
- }
- // configRootCAs configures and returns a reference to tls.Config instance if CAFile is provided
- func (evc ExternalEtcdVersionCheck) configRootCAs(config *tls.Config) (*tls.Config, error) {
- var CACertPool *x509.CertPool
- if evc.Etcd.External.CAFile != "" {
- CACert, err := ioutil.ReadFile(evc.Etcd.External.CAFile)
- if err != nil {
- return nil, errors.Wrapf(err, "couldn't load external etcd's server certificate %s", evc.Etcd.External.CAFile)
- }
- CACertPool = x509.NewCertPool()
- CACertPool.AppendCertsFromPEM(CACert)
- }
- if CACertPool != nil {
- if config == nil {
- config = &tls.Config{}
- }
- config.RootCAs = CACertPool
- }
- return config, nil
- }
- // configCertAndKey configures and returns a reference to tls.Config instance if CertFile and KeyFile pair is provided
- func (evc ExternalEtcdVersionCheck) configCertAndKey(config *tls.Config) (*tls.Config, error) {
- var cert tls.Certificate
- if evc.Etcd.External.CertFile != "" && evc.Etcd.External.KeyFile != "" {
- var err error
- cert, err = tls.LoadX509KeyPair(evc.Etcd.External.CertFile, evc.Etcd.External.KeyFile)
- if err != nil {
- return nil, errors.Wrapf(err, "couldn't load external etcd's certificate and key pair %s, %s", evc.Etcd.External.CertFile, evc.Etcd.External.KeyFile)
- }
- if config == nil {
- config = &tls.Config{}
- }
- config.Certificates = []tls.Certificate{cert}
- }
- return config, nil
- }
- func (evc ExternalEtcdVersionCheck) getHTTPClient(config *tls.Config) *http.Client {
- if config != nil {
- transport := netutil.SetOldTransportDefaults(&http.Transport{
- TLSClientConfig: config,
- })
- return &http.Client{
- Transport: transport,
- Timeout: externalEtcdRequestTimeout,
- }
- }
- return &http.Client{Timeout: externalEtcdRequestTimeout, Transport: netutil.SetOldTransportDefaults(&http.Transport{})}
- }
- func getEtcdVersionResponse(client *http.Client, url string, target interface{}) error {
- loopCount := externalEtcdRequestRetries + 1
- var err error
- var stopRetry bool
- for loopCount > 0 {
- if loopCount <= externalEtcdRequestRetries {
- time.Sleep(externalEtcdRequestInterval)
- }
- stopRetry, err = func() (stopRetry bool, err error) {
- r, err := client.Get(url)
- if err != nil {
- loopCount--
- return false, err
- }
- defer r.Body.Close()
- if r != nil && r.StatusCode >= 500 && r.StatusCode <= 599 {
- loopCount--
- return false, errors.Errorf("server responded with non-successful status: %s", r.Status)
- }
- return true, json.NewDecoder(r.Body).Decode(target)
- }()
- if stopRetry {
- break
- }
- }
- return err
- }
- // ImagePullCheck will pull container images used by kubeadm
- type ImagePullCheck struct {
- runtime utilruntime.ContainerRuntime
- imageList []string
- }
- // Name returns the label for ImagePullCheck
- func (ImagePullCheck) Name() string {
- return "ImagePull"
- }
- // Check pulls images required by kubeadm. This is a mutating check
- func (ipc ImagePullCheck) Check() (warnings, errorList []error) {
- for _, image := range ipc.imageList {
- ret, err := ipc.runtime.ImageExists(image)
- if ret && err == nil {
- klog.V(1).Infof("image exists: %s", image)
- continue
- }
- if err != nil {
- errorList = append(errorList, errors.Wrapf(err, "failed to check if image %s exists", image))
- }
- klog.V(1).Infof("pulling %s", image)
- if err := ipc.runtime.PullImage(image); err != nil {
- errorList = append(errorList, errors.Wrapf(err, "failed to pull image %s", image))
- }
- }
- return warnings, errorList
- }
- // NumCPUCheck checks if current number of CPUs is not less than required
- type NumCPUCheck struct {
- NumCPU int
- }
- // Name returns the label for NumCPUCheck
- func (NumCPUCheck) Name() string {
- return "NumCPU"
- }
- // Check number of CPUs required by kubeadm
- func (ncc NumCPUCheck) Check() (warnings, errorList []error) {
- numCPU := runtime.NumCPU()
- if numCPU < ncc.NumCPU {
- errorList = append(errorList, errors.Errorf("the number of available CPUs %d is less than the required %d", numCPU, ncc.NumCPU))
- }
- return warnings, errorList
- }
- // IPVSProxierCheck tests if IPVS proxier can be used.
- type IPVSProxierCheck struct {
- exec utilsexec.Interface
- }
- // Name returns label for IPVSProxierCheck
- func (r IPVSProxierCheck) Name() string {
- return "IPVSProxierCheck"
- }
- // RunInitNodeChecks executes all individual, applicable to control-plane node checks.
- // The boolean flag 'isSecondaryControlPlane' controls whether we are running checks in a --join-control-plane scenario.
- // The boolean flag 'downloadCerts' controls whether we should skip checks on certificates because we are downloading them.
- // If the flag is set to true we should skip checks already executed by RunJoinNodeChecks and RunOptionalJoinNodeChecks.
- func RunInitNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.String, isSecondaryControlPlane bool, downloadCerts bool) error {
- if !isSecondaryControlPlane {
- // First, check if we're root separately from the other preflight checks and fail fast
- if err := RunRootCheckOnly(ignorePreflightErrors); err != nil {
- return err
- }
- }
- manifestsDir := filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)
- checks := []Checker{
- NumCPUCheck{NumCPU: kubeadmconstants.ControlPlaneNumCPU},
- KubernetesVersionCheck{KubernetesVersion: cfg.KubernetesVersion, KubeadmVersion: kubeadmversion.Get().GitVersion},
- FirewalldCheck{ports: []int{int(cfg.LocalAPIEndpoint.BindPort), ports.KubeletPort}},
- PortOpenCheck{port: int(cfg.LocalAPIEndpoint.BindPort)},
- PortOpenCheck{port: ports.InsecureSchedulerPort},
- PortOpenCheck{port: ports.InsecureKubeControllerManagerPort},
- FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeAPIServer, manifestsDir)},
- FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeControllerManager, manifestsDir)},
- FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.KubeScheduler, manifestsDir)},
- FileAvailableCheck{Path: kubeadmconstants.GetStaticPodFilepath(kubeadmconstants.Etcd, manifestsDir)},
- HTTPProxyCheck{Proto: "https", Host: cfg.LocalAPIEndpoint.AdvertiseAddress},
- HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.ServiceSubnet},
- HTTPProxyCIDRCheck{Proto: "https", CIDR: cfg.Networking.PodSubnet},
- }
- if !isSecondaryControlPlane {
- checks = addCommonChecks(execer, cfg.KubernetesVersion, &cfg.NodeRegistration, checks)
- // Check if IVPS kube-proxy mode is supported
- if cfg.ComponentConfigs.KubeProxy != nil && cfg.ComponentConfigs.KubeProxy.Mode == ipvsutil.IPVSProxyMode {
- checks = append(checks, IPVSProxierCheck{exec: execer})
- }
- // Check if Bridge-netfilter and IPv6 relevant flags are set
- if ip := net.ParseIP(cfg.LocalAPIEndpoint.AdvertiseAddress); ip != nil {
- if ip.To4() == nil && ip.To16() != nil {
- checks = append(checks,
- FileContentCheck{Path: bridgenf6, Content: []byte{'1'}},
- FileContentCheck{Path: ipv6DefaultForwarding, Content: []byte{'1'}},
- )
- }
- }
- // if using an external etcd
- if cfg.Etcd.External != nil {
- // Check external etcd version before creating the cluster
- checks = append(checks, ExternalEtcdVersionCheck{Etcd: cfg.Etcd})
- }
- }
- if cfg.Etcd.Local != nil {
- // Only do etcd related checks when required to install a local etcd
- checks = append(checks,
- PortOpenCheck{port: kubeadmconstants.EtcdListenClientPort},
- PortOpenCheck{port: kubeadmconstants.EtcdListenPeerPort},
- DirAvailableCheck{Path: cfg.Etcd.Local.DataDir},
- )
- }
- if cfg.Etcd.External != nil && !(isSecondaryControlPlane && downloadCerts) {
- // Only check etcd certificates when using an external etcd and not joining with automatic download of certs
- if cfg.Etcd.External.CAFile != "" {
- checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.CAFile, Label: "ExternalEtcdClientCertificates"})
- }
- if cfg.Etcd.External.CertFile != "" {
- checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.CertFile, Label: "ExternalEtcdClientCertificates"})
- }
- if cfg.Etcd.External.KeyFile != "" {
- checks = append(checks, FileExistingCheck{Path: cfg.Etcd.External.KeyFile, Label: "ExternalEtcdClientCertificates"})
- }
- }
- return RunChecks(checks, os.Stderr, ignorePreflightErrors)
- }
- // RunJoinNodeChecks executes all individual, applicable to node checks.
- func RunJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.JoinConfiguration, ignorePreflightErrors sets.String) error {
- // First, check if we're root separately from the other preflight checks and fail fast
- if err := RunRootCheckOnly(ignorePreflightErrors); err != nil {
- return err
- }
- checks := []Checker{
- DirAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName)},
- FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletKubeConfigFileName)},
- FileAvailableCheck{Path: filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.KubeletBootstrapKubeConfigFileName)},
- }
- checks = addCommonChecks(execer, "", &cfg.NodeRegistration, checks)
- if cfg.ControlPlane == nil {
- checks = append(checks, FileAvailableCheck{Path: cfg.CACertPath})
- }
- addIPv6Checks := false
- if cfg.Discovery.BootstrapToken != nil {
- ipstr, _, err := net.SplitHostPort(cfg.Discovery.BootstrapToken.APIServerEndpoint)
- if err == nil {
- checks = append(checks,
- HTTPProxyCheck{Proto: "https", Host: ipstr},
- )
- if !addIPv6Checks {
- if ip := net.ParseIP(ipstr); ip != nil {
- if ip.To4() == nil && ip.To16() != nil {
- addIPv6Checks = true
- }
- }
- }
- }
- }
- if addIPv6Checks {
- checks = append(checks,
- FileContentCheck{Path: bridgenf6, Content: []byte{'1'}},
- FileContentCheck{Path: ipv6DefaultForwarding, Content: []byte{'1'}},
- )
- }
- return RunChecks(checks, os.Stderr, ignorePreflightErrors)
- }
- // RunOptionalJoinNodeChecks executes all individual, applicable to node configuration dependant checks
- func RunOptionalJoinNodeChecks(execer utilsexec.Interface, cfg *kubeadmapi.ClusterConfiguration, ignorePreflightErrors sets.String) error {
- checks := []Checker{}
- // Check if IPVS kube-proxy mode is supported
- if cfg.ComponentConfigs.KubeProxy != nil && cfg.ComponentConfigs.KubeProxy.Mode == ipvsutil.IPVSProxyMode {
- checks = append(checks, IPVSProxierCheck{exec: execer})
- }
- return RunChecks(checks, os.Stderr, ignorePreflightErrors)
- }
- // addCommonChecks is a helper function to deplicate checks that are common between both the
- // kubeadm init and join commands
- func addCommonChecks(execer utilsexec.Interface, k8sVersion string, nodeReg *kubeadmapi.NodeRegistrationOptions, checks []Checker) []Checker {
- containerRuntime, err := utilruntime.NewContainerRuntime(execer, nodeReg.CRISocket)
- isDocker := false
- if err != nil {
- fmt.Printf("[preflight] WARNING: Couldn't create the interface used for talking to the container runtime: %v\n", err)
- } else {
- checks = append(checks, ContainerRuntimeCheck{runtime: containerRuntime})
- if containerRuntime.IsDocker() {
- isDocker = true
- checks = append(checks, ServiceCheck{Service: "docker", CheckIfActive: true})
- // Linux only
- // TODO: support other CRIs for this check eventually
- // https://github.com/kubernetes/kubeadm/issues/874
- checks = append(checks, IsDockerSystemdCheck{})
- }
- }
- // non-windows checks
- if runtime.GOOS == "linux" {
- if !isDocker {
- checks = append(checks, InPathCheck{executable: "crictl", mandatory: true, exec: execer})
- }
- checks = append(checks,
- FileContentCheck{Path: bridgenf, Content: []byte{'1'}},
- FileContentCheck{Path: ipv4Forward, Content: []byte{'1'}},
- SwapCheck{},
- InPathCheck{executable: "ip", mandatory: true, exec: execer},
- InPathCheck{executable: "iptables", mandatory: true, exec: execer},
- InPathCheck{executable: "mount", mandatory: true, exec: execer},
- InPathCheck{executable: "nsenter", mandatory: true, exec: execer},
- InPathCheck{executable: "ebtables", mandatory: false, exec: execer},
- InPathCheck{executable: "ethtool", mandatory: false, exec: execer},
- InPathCheck{executable: "socat", mandatory: false, exec: execer},
- InPathCheck{executable: "tc", mandatory: false, exec: execer},
- InPathCheck{executable: "touch", mandatory: false, exec: execer})
- }
- checks = append(checks,
- SystemVerificationCheck{IsDocker: isDocker},
- HostnameCheck{nodeName: nodeReg.Name},
- KubeletVersionCheck{KubernetesVersion: k8sVersion, exec: execer},
- ServiceCheck{Service: "kubelet", CheckIfActive: false},
- PortOpenCheck{port: ports.KubeletPort})
- return checks
- }
- // RunRootCheckOnly initializes checks slice of structs and call RunChecks
- func RunRootCheckOnly(ignorePreflightErrors sets.String) error {
- checks := []Checker{
- IsPrivilegedUserCheck{},
- }
- return RunChecks(checks, os.Stderr, ignorePreflightErrors)
- }
- // RunPullImagesCheck will pull images kubeadm needs if they are not found on the system
- func RunPullImagesCheck(execer utilsexec.Interface, cfg *kubeadmapi.InitConfiguration, ignorePreflightErrors sets.String) error {
- containerRuntime, err := utilruntime.NewContainerRuntime(utilsexec.New(), cfg.NodeRegistration.CRISocket)
- if err != nil {
- return err
- }
- checks := []Checker{
- ImagePullCheck{runtime: containerRuntime, imageList: images.GetControlPlaneImages(&cfg.ClusterConfiguration)},
- }
- return RunChecks(checks, os.Stderr, ignorePreflightErrors)
- }
- // RunChecks runs each check, displays it's warnings/errors, and once all
- // are processed will exit if any errors occurred.
- func RunChecks(checks []Checker, ww io.Writer, ignorePreflightErrors sets.String) error {
- var errsBuffer bytes.Buffer
- for _, c := range checks {
- name := c.Name()
- warnings, errs := c.Check()
- if setHasItemOrAll(ignorePreflightErrors, name) {
- // Decrease severity of errors to warnings for this check
- warnings = append(warnings, errs...)
- errs = []error{}
- }
- for _, w := range warnings {
- io.WriteString(ww, fmt.Sprintf("\t[WARNING %s]: %v\n", name, w))
- }
- for _, i := range errs {
- errsBuffer.WriteString(fmt.Sprintf("\t[ERROR %s]: %v\n", name, i.Error()))
- }
- }
- if errsBuffer.Len() > 0 {
- return &Error{Msg: errsBuffer.String()}
- }
- return nil
- }
- // setHasItemOrAll is helper function that return true if item is present in the set (case insensitive) or special key 'all' is present
- func setHasItemOrAll(s sets.String, item string) bool {
- if s.Has("all") || s.Has(strings.ToLower(item)) {
- return true
- }
- return false
- }
|