2016-07-20 22:08:47 +00:00
|
|
|
/*
|
|
|
|
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 kubelet
|
|
|
|
|
|
|
|
import (
|
2018-02-02 21:12:07 +00:00
|
|
|
"context"
|
2016-07-20 22:08:47 +00:00
|
|
|
"fmt"
|
|
|
|
"math"
|
|
|
|
"net"
|
2016-11-30 07:27:27 +00:00
|
|
|
goruntime "runtime"
|
2016-07-20 22:08:47 +00:00
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang/glog"
|
2017-06-22 17:25:57 +00:00
|
|
|
"k8s.io/api/core/v1"
|
2017-01-13 17:48:50 +00:00
|
|
|
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
2017-01-25 13:13:07 +00:00
|
|
|
"k8s.io/apimachinery/pkg/api/resource"
|
2017-01-11 14:09:48 +00:00
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/types"
|
2017-06-20 22:53:54 +00:00
|
|
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
2017-11-08 22:34:54 +00:00
|
|
|
k8s_api_v1 "k8s.io/kubernetes/pkg/apis/core/v1"
|
|
|
|
v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
|
2016-07-20 22:08:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/cloudprovider"
|
2017-06-20 22:53:54 +00:00
|
|
|
"k8s.io/kubernetes/pkg/features"
|
2017-05-30 14:46:00 +00:00
|
|
|
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
|
2016-07-20 22:08:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/cadvisor"
|
|
|
|
"k8s.io/kubernetes/pkg/kubelet/events"
|
2018-06-25 23:39:05 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/nodestatus"
|
2017-01-27 14:01:55 +00:00
|
|
|
"k8s.io/kubernetes/pkg/kubelet/util"
|
2018-01-04 02:12:18 +00:00
|
|
|
"k8s.io/kubernetes/pkg/scheduler/algorithm"
|
2016-12-01 22:46:20 +00:00
|
|
|
nodeutil "k8s.io/kubernetes/pkg/util/node"
|
2016-07-20 22:08:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/version"
|
2018-02-06 08:38:41 +00:00
|
|
|
volutil "k8s.io/kubernetes/pkg/volume/util"
|
2016-07-20 22:08:47 +00:00
|
|
|
)
|
|
|
|
|
2016-09-16 22:16:08 +00:00
|
|
|
const (
|
|
|
|
// maxNamesPerImageInNodeStatus is max number of names per image stored in
|
|
|
|
// the node status.
|
|
|
|
maxNamesPerImageInNodeStatus = 5
|
|
|
|
)
|
|
|
|
|
2017-06-12 02:03:59 +00:00
|
|
|
// registerWithAPIServer registers the node with the cluster master. It is safe
|
2016-07-20 22:08:47 +00:00
|
|
|
// to call multiple times, but not concurrently (kl.registrationCompleted is
|
|
|
|
// not locked).
|
2017-06-12 02:03:59 +00:00
|
|
|
func (kl *Kubelet) registerWithAPIServer() {
|
2016-07-20 22:08:47 +00:00
|
|
|
if kl.registrationCompleted {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
step := 100 * time.Millisecond
|
2016-08-30 17:40:25 +00:00
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
for {
|
|
|
|
time.Sleep(step)
|
|
|
|
step = step * 2
|
|
|
|
if step >= 7*time.Second {
|
|
|
|
step = 7 * time.Second
|
|
|
|
}
|
|
|
|
|
2016-08-30 17:40:25 +00:00
|
|
|
node, err := kl.initialNode()
|
2016-07-20 22:08:47 +00:00
|
|
|
if err != nil {
|
2016-11-18 20:50:58 +00:00
|
|
|
glog.Errorf("Unable to construct v1.Node object for kubelet: %v", err)
|
2016-07-20 22:08:47 +00:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-08-30 17:40:25 +00:00
|
|
|
glog.Infof("Attempting to register node %s", node.Name)
|
2017-06-12 02:03:59 +00:00
|
|
|
registered := kl.tryRegisterWithAPIServer(node)
|
2016-08-30 17:40:25 +00:00
|
|
|
if registered {
|
|
|
|
glog.Infof("Successfully registered node %s", node.Name)
|
|
|
|
kl.registrationCompleted = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-12 02:03:59 +00:00
|
|
|
// tryRegisterWithAPIServer makes an attempt to register the given node with
|
2016-08-30 17:40:25 +00:00
|
|
|
// the API server, returning a boolean indicating whether the attempt was
|
|
|
|
// successful. If a node with the same name already exists, it reconciles the
|
|
|
|
// value of the annotation for controller-managed attach-detach of attachable
|
|
|
|
// persistent volumes for the node. If a node of the same name exists but has
|
|
|
|
// a different externalID value, it attempts to delete that node so that a
|
|
|
|
// later attempt can recreate it.
|
2017-06-12 02:03:59 +00:00
|
|
|
func (kl *Kubelet) tryRegisterWithAPIServer(node *v1.Node) bool {
|
2017-10-25 15:54:32 +00:00
|
|
|
_, err := kl.kubeClient.CoreV1().Nodes().Create(node)
|
2016-08-30 17:40:25 +00:00
|
|
|
if err == nil {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
|
|
|
if !apierrors.IsAlreadyExists(err) {
|
|
|
|
glog.Errorf("Unable to register node %q with API server: %v", kl.nodeName, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-10-25 15:54:32 +00:00
|
|
|
existingNode, err := kl.kubeClient.CoreV1().Nodes().Get(string(kl.nodeName), metav1.GetOptions{})
|
2016-08-30 17:40:25 +00:00
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Unable to register node %q with API server: error getting existing node: %v", kl.nodeName, err)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
if existingNode == nil {
|
|
|
|
glog.Errorf("Unable to register node %q with API server: no node instance returned", kl.nodeName)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2017-10-06 11:14:34 +00:00
|
|
|
originalNode := existingNode.DeepCopy()
|
|
|
|
if originalNode == nil {
|
|
|
|
glog.Errorf("Nil %q node object", kl.nodeName)
|
2016-12-01 22:46:20 +00:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-04-18 16:54:56 +00:00
|
|
|
glog.Infof("Node %s was previously registered", kl.nodeName)
|
|
|
|
|
|
|
|
// Edge case: the node was previously registered; reconcile
|
|
|
|
// the value of the controller-managed attach-detach
|
|
|
|
// annotation.
|
|
|
|
requiresUpdate := kl.reconcileCMADAnnotationWithExistingNode(node, existingNode)
|
|
|
|
requiresUpdate = kl.updateDefaultLabels(node, existingNode) || requiresUpdate
|
2018-06-04 23:01:16 +00:00
|
|
|
requiresUpdate = kl.reconcileExtendedResource(node, existingNode) || requiresUpdate
|
2018-04-18 16:54:56 +00:00
|
|
|
if requiresUpdate {
|
|
|
|
if _, _, err := nodeutil.PatchNodeStatus(kl.kubeClient.CoreV1(), types.NodeName(kl.nodeName), originalNode, existingNode); err != nil {
|
|
|
|
glog.Errorf("Unable to reconcile node %q with API server: error updating node: %v", kl.nodeName, err)
|
|
|
|
return false
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2016-08-30 17:40:25 +00:00
|
|
|
}
|
|
|
|
|
2018-04-18 16:54:56 +00:00
|
|
|
return true
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
2018-06-04 23:01:16 +00:00
|
|
|
// Zeros out extended resource capacity during reconciliation.
|
|
|
|
func (kl *Kubelet) reconcileExtendedResource(initialNode, node *v1.Node) bool {
|
|
|
|
requiresUpdate := false
|
|
|
|
for k := range node.Status.Capacity {
|
|
|
|
if v1helper.IsExtendedResourceName(k) {
|
|
|
|
node.Status.Capacity[k] = *resource.NewQuantity(int64(0), resource.DecimalSI)
|
|
|
|
node.Status.Allocatable[k] = *resource.NewQuantity(int64(0), resource.DecimalSI)
|
|
|
|
requiresUpdate = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return requiresUpdate
|
|
|
|
}
|
|
|
|
|
2017-06-06 11:47:40 +00:00
|
|
|
// updateDefaultLabels will set the default labels on the node
|
|
|
|
func (kl *Kubelet) updateDefaultLabels(initialNode, existingNode *v1.Node) bool {
|
|
|
|
defaultLabels := []string{
|
|
|
|
kubeletapis.LabelHostname,
|
|
|
|
kubeletapis.LabelZoneFailureDomain,
|
|
|
|
kubeletapis.LabelZoneRegion,
|
|
|
|
kubeletapis.LabelInstanceType,
|
|
|
|
kubeletapis.LabelOS,
|
|
|
|
kubeletapis.LabelArch,
|
|
|
|
}
|
|
|
|
|
|
|
|
var needsUpdate bool = false
|
|
|
|
//Set default labels but make sure to not set labels with empty values
|
|
|
|
for _, label := range defaultLabels {
|
2017-10-17 15:49:02 +00:00
|
|
|
if _, hasInitialValue := initialNode.Labels[label]; !hasInitialValue {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2017-06-06 11:47:40 +00:00
|
|
|
if existingNode.Labels[label] != initialNode.Labels[label] {
|
|
|
|
existingNode.Labels[label] = initialNode.Labels[label]
|
|
|
|
needsUpdate = true
|
|
|
|
}
|
|
|
|
|
|
|
|
if existingNode.Labels[label] == "" {
|
|
|
|
delete(existingNode.Labels, label)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return needsUpdate
|
|
|
|
}
|
|
|
|
|
2016-08-30 17:40:25 +00:00
|
|
|
// reconcileCMADAnnotationWithExistingNode reconciles the controller-managed
|
|
|
|
// attach-detach annotation on a new node and the existing node, returning
|
|
|
|
// whether the existing node must be updated.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) reconcileCMADAnnotationWithExistingNode(node, existingNode *v1.Node) bool {
|
2016-08-30 17:40:25 +00:00
|
|
|
var (
|
2018-02-06 08:38:41 +00:00
|
|
|
existingCMAAnnotation = existingNode.Annotations[volutil.ControllerManagedAttachAnnotation]
|
|
|
|
newCMAAnnotation, newSet = node.Annotations[volutil.ControllerManagedAttachAnnotation]
|
2016-08-30 17:40:25 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
if newCMAAnnotation == existingCMAAnnotation {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the just-constructed node and the existing node do
|
|
|
|
// not have the same value, update the existing node with
|
|
|
|
// the correct value of the annotation.
|
|
|
|
if !newSet {
|
|
|
|
glog.Info("Controller attach-detach setting changed to false; updating existing Node")
|
2018-02-06 08:38:41 +00:00
|
|
|
delete(existingNode.Annotations, volutil.ControllerManagedAttachAnnotation)
|
2016-08-30 17:40:25 +00:00
|
|
|
} else {
|
|
|
|
glog.Info("Controller attach-detach setting changed to true; updating existing Node")
|
|
|
|
if existingNode.Annotations == nil {
|
|
|
|
existingNode.Annotations = make(map[string]string)
|
|
|
|
}
|
2018-02-06 08:38:41 +00:00
|
|
|
existingNode.Annotations[volutil.ControllerManagedAttachAnnotation] = newCMAAnnotation
|
2016-08-30 17:40:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2016-11-18 20:50:58 +00:00
|
|
|
// initialNode constructs the initial v1.Node for this Kubelet, incorporating node
|
2016-08-30 17:40:25 +00:00
|
|
|
// labels, information from the cloud provider, and Kubelet configuration.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) initialNode() (*v1.Node, error) {
|
|
|
|
node := &v1.Node{
|
2017-01-17 03:38:19 +00:00
|
|
|
ObjectMeta: metav1.ObjectMeta{
|
2016-07-16 06:10:29 +00:00
|
|
|
Name: string(kl.nodeName),
|
2016-07-20 22:08:47 +00:00
|
|
|
Labels: map[string]string{
|
2017-05-30 14:46:00 +00:00
|
|
|
kubeletapis.LabelHostname: kl.hostname,
|
|
|
|
kubeletapis.LabelOS: goruntime.GOOS,
|
|
|
|
kubeletapis.LabelArch: goruntime.GOARCH,
|
2016-07-20 22:08:47 +00:00
|
|
|
},
|
|
|
|
},
|
2016-11-18 20:50:58 +00:00
|
|
|
Spec: v1.NodeSpec{
|
2016-07-20 22:08:47 +00:00
|
|
|
Unschedulable: !kl.registerSchedulable,
|
|
|
|
},
|
|
|
|
}
|
2017-03-29 23:21:42 +00:00
|
|
|
nodeTaints := make([]v1.Taint, 0)
|
2017-11-12 20:16:01 +00:00
|
|
|
if len(kl.registerWithTaints) > 0 {
|
|
|
|
taints := make([]v1.Taint, len(kl.registerWithTaints))
|
|
|
|
for i := range kl.registerWithTaints {
|
|
|
|
if err := k8s_api_v1.Convert_core_Taint_To_v1_Taint(&kl.registerWithTaints[i], &taints[i], nil); err != nil {
|
2017-01-13 04:18:34 +00:00
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
2017-03-29 23:21:42 +00:00
|
|
|
nodeTaints = append(nodeTaints, taints...)
|
|
|
|
}
|
|
|
|
if kl.externalCloudProvider {
|
|
|
|
taint := v1.Taint{
|
2017-05-30 14:46:00 +00:00
|
|
|
Key: algorithm.TaintExternalCloudProvider,
|
2017-03-29 23:21:42 +00:00
|
|
|
Value: "true",
|
|
|
|
Effect: v1.TaintEffectNoSchedule,
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeTaints = append(nodeTaints, taint)
|
|
|
|
}
|
|
|
|
if len(nodeTaints) > 0 {
|
|
|
|
node.Spec.Taints = nodeTaints
|
2016-08-29 22:00:02 +00:00
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
// Initially, set NodeNetworkUnavailable to true.
|
|
|
|
if kl.providerRequiresNetworkingConfiguration() {
|
2016-11-18 20:50:58 +00:00
|
|
|
node.Status.Conditions = append(node.Status.Conditions, v1.NodeCondition{
|
|
|
|
Type: v1.NodeNetworkUnavailable,
|
|
|
|
Status: v1.ConditionTrue,
|
2016-07-20 22:08:47 +00:00
|
|
|
Reason: "NoRouteCreated",
|
|
|
|
Message: "Node created without a route",
|
2016-12-03 18:57:26 +00:00
|
|
|
LastTransitionTime: metav1.NewTime(kl.clock.Now()),
|
2016-07-20 22:08:47 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if kl.enableControllerAttachDetach {
|
|
|
|
if node.Annotations == nil {
|
|
|
|
node.Annotations = make(map[string]string)
|
|
|
|
}
|
|
|
|
|
2016-08-26 16:26:52 +00:00
|
|
|
glog.Infof("Setting node annotation to enable volume controller attach/detach")
|
2018-02-06 08:38:41 +00:00
|
|
|
node.Annotations[volutil.ControllerManagedAttachAnnotation] = "true"
|
2016-08-26 16:26:52 +00:00
|
|
|
} else {
|
|
|
|
glog.Infof("Controller attach/detach is disabled for this node; Kubelet will attach and detach volumes")
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
2017-09-26 22:01:09 +00:00
|
|
|
if kl.keepTerminatedPodVolumes {
|
2017-05-10 18:42:56 +00:00
|
|
|
if node.Annotations == nil {
|
|
|
|
node.Annotations = make(map[string]string)
|
|
|
|
}
|
|
|
|
glog.Infof("Setting node annotation to keep pod volumes of terminated pods attached to the node")
|
2018-02-06 08:38:41 +00:00
|
|
|
node.Annotations[volutil.KeepTerminatedPodVolumesAnnotation] = "true"
|
2017-05-10 18:42:56 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// @question: should this be place after the call to the cloud provider? which also applies labels
|
|
|
|
for k, v := range kl.nodeLabels {
|
|
|
|
if cv, found := node.ObjectMeta.Labels[k]; found {
|
|
|
|
glog.Warningf("the node label %s=%s will overwrite default setting %s", k, v, cv)
|
|
|
|
}
|
|
|
|
node.ObjectMeta.Labels[k] = v
|
|
|
|
}
|
|
|
|
|
2017-03-29 23:21:42 +00:00
|
|
|
if kl.providerID != "" {
|
|
|
|
node.Spec.ProviderID = kl.providerID
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
if kl.cloud != nil {
|
|
|
|
instances, ok := kl.cloud.Instances()
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("failed to get instances from cloud provider")
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: We can't assume that the node has credentials to talk to the
|
|
|
|
// cloudprovider from arbitrary nodes. At most, we should talk to a
|
|
|
|
// local metadata server here.
|
2018-04-18 16:54:56 +00:00
|
|
|
var err error
|
2017-03-29 23:21:42 +00:00
|
|
|
if node.Spec.ProviderID == "" {
|
2018-02-02 21:12:07 +00:00
|
|
|
node.Spec.ProviderID, err = cloudprovider.GetInstanceProviderID(context.TODO(), kl.cloud, kl.nodeName)
|
2017-03-29 23:21:42 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
2018-02-02 21:12:07 +00:00
|
|
|
instanceType, err := instances.InstanceType(context.TODO(), kl.nodeName)
|
2016-07-20 22:08:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if instanceType != "" {
|
2017-05-30 14:46:00 +00:00
|
|
|
glog.Infof("Adding node label from cloud provider: %s=%s", kubeletapis.LabelInstanceType, instanceType)
|
|
|
|
node.ObjectMeta.Labels[kubeletapis.LabelInstanceType] = instanceType
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
// If the cloud has zone information, label the node with the zone information
|
|
|
|
zones, ok := kl.cloud.Zones()
|
|
|
|
if ok {
|
2018-02-02 21:12:07 +00:00
|
|
|
zone, err := zones.GetZone(context.TODO())
|
2016-07-20 22:08:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to get zone from cloud provider: %v", err)
|
|
|
|
}
|
|
|
|
if zone.FailureDomain != "" {
|
2017-05-30 14:46:00 +00:00
|
|
|
glog.Infof("Adding node label from cloud provider: %s=%s", kubeletapis.LabelZoneFailureDomain, zone.FailureDomain)
|
|
|
|
node.ObjectMeta.Labels[kubeletapis.LabelZoneFailureDomain] = zone.FailureDomain
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
if zone.Region != "" {
|
2017-05-30 14:46:00 +00:00
|
|
|
glog.Infof("Adding node label from cloud provider: %s=%s", kubeletapis.LabelZoneRegion, zone.Region)
|
|
|
|
node.ObjectMeta.Labels[kubeletapis.LabelZoneRegion] = zone.Region
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2018-04-18 16:54:56 +00:00
|
|
|
|
2016-12-01 19:12:32 +00:00
|
|
|
kl.setNodeStatus(node)
|
2016-07-20 22:08:47 +00:00
|
|
|
|
|
|
|
return node, nil
|
|
|
|
}
|
|
|
|
|
2018-05-23 16:51:29 +00:00
|
|
|
// setVolumeLimits updates volume limits on the node
|
|
|
|
func (kl *Kubelet) setVolumeLimits(node *v1.Node) {
|
|
|
|
if node.Status.Capacity == nil {
|
|
|
|
node.Status.Capacity = v1.ResourceList{}
|
|
|
|
}
|
|
|
|
|
|
|
|
if node.Status.Allocatable == nil {
|
|
|
|
node.Status.Allocatable = v1.ResourceList{}
|
|
|
|
}
|
|
|
|
|
|
|
|
pluginWithLimits := kl.volumePluginMgr.ListVolumePluginWithLimits()
|
|
|
|
for _, volumePlugin := range pluginWithLimits {
|
|
|
|
attachLimits, err := volumePlugin.GetVolumeLimits()
|
|
|
|
if err != nil {
|
|
|
|
glog.V(4).Infof("Error getting volume limit for plugin %s", volumePlugin.GetPluginName())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
for limitKey, value := range attachLimits {
|
|
|
|
node.Status.Capacity[v1.ResourceName(limitKey)] = *resource.NewQuantity(value, resource.DecimalSI)
|
|
|
|
node.Status.Allocatable[v1.ResourceName(limitKey)] = *resource.NewQuantity(value, resource.DecimalSI)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// syncNodeStatus should be called periodically from a goroutine.
|
|
|
|
// It synchronizes node status to master, registering the kubelet first if
|
|
|
|
// necessary.
|
|
|
|
func (kl *Kubelet) syncNodeStatus() {
|
2017-09-14 16:15:52 +00:00
|
|
|
if kl.kubeClient == nil || kl.heartbeatClient == nil {
|
2016-07-20 22:08:47 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
if kl.registerNode {
|
|
|
|
// This will exit immediately if it doesn't need to do anything.
|
2017-06-12 02:03:59 +00:00
|
|
|
kl.registerWithAPIServer()
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
if err := kl.updateNodeStatus(); err != nil {
|
|
|
|
glog.Errorf("Unable to update node status: %v", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// updateNodeStatus updates node status to master with retries.
|
|
|
|
func (kl *Kubelet) updateNodeStatus() error {
|
2018-03-14 16:02:28 +00:00
|
|
|
glog.V(5).Infof("Updating node status")
|
2016-07-20 22:08:47 +00:00
|
|
|
for i := 0; i < nodeStatusUpdateRetry; i++ {
|
2016-12-01 08:11:36 +00:00
|
|
|
if err := kl.tryUpdateNodeStatus(i); err != nil {
|
2018-05-07 16:16:46 +00:00
|
|
|
if i > 0 && kl.onRepeatedHeartbeatFailure != nil {
|
|
|
|
kl.onRepeatedHeartbeatFailure()
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
glog.Errorf("Error updating node status, will retry: %v", err)
|
|
|
|
} else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return fmt.Errorf("update node status exceeds retry count")
|
|
|
|
}
|
|
|
|
|
2018-06-22 18:55:02 +00:00
|
|
|
// tryUpdateNodeStatus tries to update node status to master.
|
2016-12-01 08:11:36 +00:00
|
|
|
func (kl *Kubelet) tryUpdateNodeStatus(tryNumber int) error {
|
2016-10-20 16:00:33 +00:00
|
|
|
// In large clusters, GET and PUT operations on Node objects coming
|
|
|
|
// from here are the majority of load on apiserver and etcd.
|
|
|
|
// To reduce the load on etcd, we are serving GET operations from
|
|
|
|
// apiserver cache (the data might be slightly delayed but it doesn't
|
2016-12-28 12:17:27 +00:00
|
|
|
// seem to cause more conflict - the delays are pretty small).
|
2016-12-01 08:11:36 +00:00
|
|
|
// If it result in a conflict, all retries are served directly from etcd.
|
2016-12-13 16:14:56 +00:00
|
|
|
opts := metav1.GetOptions{}
|
2016-12-01 08:11:36 +00:00
|
|
|
if tryNumber == 0 {
|
2017-01-27 14:01:55 +00:00
|
|
|
util.FromApiserverCache(&opts)
|
2016-10-20 16:00:33 +00:00
|
|
|
}
|
2017-09-14 16:15:52 +00:00
|
|
|
node, err := kl.heartbeatClient.Nodes().Get(string(kl.nodeName), opts)
|
2016-07-20 22:08:47 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error getting node %q: %v", kl.nodeName, err)
|
|
|
|
}
|
|
|
|
|
2017-10-06 11:14:34 +00:00
|
|
|
originalNode := node.DeepCopy()
|
|
|
|
if originalNode == nil {
|
|
|
|
return fmt.Errorf("nil %q node object", kl.nodeName)
|
2016-12-01 22:46:20 +00:00
|
|
|
}
|
|
|
|
|
2018-04-08 03:14:36 +00:00
|
|
|
if node.Spec.PodCIDR != "" {
|
|
|
|
kl.updatePodCIDR(node.Spec.PodCIDR)
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
|
2016-12-01 19:12:32 +00:00
|
|
|
kl.setNodeStatus(node)
|
2016-12-01 22:46:20 +00:00
|
|
|
// Patch the current status on the API server
|
2017-07-31 05:08:42 +00:00
|
|
|
updatedNode, _, err := nodeutil.PatchNodeStatus(kl.heartbeatClient, types.NodeName(kl.nodeName), originalNode, node)
|
2016-12-01 22:46:20 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2018-06-28 18:44:25 +00:00
|
|
|
kl.setLastObservedNodeAddresses(updatedNode.Status.Addresses)
|
2017-01-10 15:37:45 +00:00
|
|
|
// If update finishes successfully, mark the volumeInUse as reportedInUse to indicate
|
2016-09-27 23:12:57 +00:00
|
|
|
// those volumes are already updated in the node's status
|
2016-12-01 22:46:20 +00:00
|
|
|
kl.volumeManager.MarkVolumesAsReportedInUse(updatedNode.Status.VolumesInUse)
|
|
|
|
return nil
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// recordNodeStatusEvent records an event of the given type with the given
|
|
|
|
// message for the node.
|
2016-12-28 12:17:27 +00:00
|
|
|
func (kl *Kubelet) recordNodeStatusEvent(eventType, event string) {
|
2016-07-20 22:08:47 +00:00
|
|
|
glog.V(2).Infof("Recording %s event message for node %s", event, kl.nodeName)
|
|
|
|
// TODO: This requires a transaction, either both node status is updated
|
|
|
|
// and event is recorded or neither should happen, see issue #6055.
|
2016-12-28 12:17:27 +00:00
|
|
|
kl.recorder.Eventf(kl.nodeRef, eventType, event, "Node %s status is now: %s", kl.nodeName, event)
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusMachineInfo(node *v1.Node) {
|
2016-09-26 15:11:31 +00:00
|
|
|
// Note: avoid blindly overwriting the capacity in case opaque
|
|
|
|
// resources are being advertised.
|
|
|
|
if node.Status.Capacity == nil {
|
2016-11-18 20:50:58 +00:00
|
|
|
node.Status.Capacity = v1.ResourceList{}
|
2016-09-26 15:11:31 +00:00
|
|
|
}
|
|
|
|
|
2017-12-16 06:38:46 +00:00
|
|
|
var devicePluginAllocatable v1.ResourceList
|
|
|
|
var devicePluginCapacity v1.ResourceList
|
|
|
|
var removedDevicePlugins []string
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// TODO: Post NotReady if we cannot get MachineInfo from cAdvisor. This needs to start
|
|
|
|
// cAdvisor locally, e.g. for test-cmd.sh, and in integration test.
|
|
|
|
info, err := kl.GetCachedMachineInfo()
|
|
|
|
if err != nil {
|
|
|
|
// TODO(roberthbailey): This is required for test-cmd.sh to pass.
|
|
|
|
// See if the test should be updated instead.
|
2016-11-18 20:50:58 +00:00
|
|
|
node.Status.Capacity[v1.ResourceCPU] = *resource.NewMilliQuantity(0, resource.DecimalSI)
|
|
|
|
node.Status.Capacity[v1.ResourceMemory] = resource.MustParse("0Gi")
|
|
|
|
node.Status.Capacity[v1.ResourcePods] = *resource.NewQuantity(int64(kl.maxPods), resource.DecimalSI)
|
2016-07-20 22:08:47 +00:00
|
|
|
glog.Errorf("Error getting machine info: %v", err)
|
|
|
|
} else {
|
|
|
|
node.Status.NodeInfo.MachineID = info.MachineID
|
|
|
|
node.Status.NodeInfo.SystemUUID = info.SystemUUID
|
2016-09-26 15:11:31 +00:00
|
|
|
|
|
|
|
for rName, rCap := range cadvisor.CapacityFromMachineInfo(info) {
|
|
|
|
node.Status.Capacity[rName] = rCap
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
if kl.podsPerCore > 0 {
|
2016-11-18 20:50:58 +00:00
|
|
|
node.Status.Capacity[v1.ResourcePods] = *resource.NewQuantity(
|
2016-07-20 22:08:47 +00:00
|
|
|
int64(math.Min(float64(info.NumCores*kl.podsPerCore), float64(kl.maxPods))), resource.DecimalSI)
|
|
|
|
} else {
|
2016-11-18 20:50:58 +00:00
|
|
|
node.Status.Capacity[v1.ResourcePods] = *resource.NewQuantity(
|
2016-07-20 22:08:47 +00:00
|
|
|
int64(kl.maxPods), resource.DecimalSI)
|
|
|
|
}
|
2017-06-26 19:49:00 +00:00
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
if node.Status.NodeInfo.BootID != "" &&
|
|
|
|
node.Status.NodeInfo.BootID != info.BootID {
|
|
|
|
// TODO: This requires a transaction, either both node status is updated
|
|
|
|
// and event is recorded or neither should happen, see issue #6055.
|
2016-11-18 20:50:58 +00:00
|
|
|
kl.recorder.Eventf(kl.nodeRef, v1.EventTypeWarning, events.NodeRebooted,
|
2016-07-20 22:08:47 +00:00
|
|
|
"Node %s has been rebooted, boot id: %s", kl.nodeName, info.BootID)
|
|
|
|
}
|
|
|
|
node.Status.NodeInfo.BootID = info.BootID
|
2017-06-20 22:53:54 +00:00
|
|
|
|
2017-06-26 19:49:00 +00:00
|
|
|
if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
|
|
|
|
// TODO: all the node resources should use GetCapacity instead of deriving the
|
|
|
|
// capacity for every node status request
|
|
|
|
initialCapacity := kl.containerManager.GetCapacity()
|
|
|
|
if initialCapacity != nil {
|
2017-08-18 04:42:19 +00:00
|
|
|
node.Status.Capacity[v1.ResourceEphemeralStorage] = initialCapacity[v1.ResourceEphemeralStorage]
|
2017-06-20 22:53:54 +00:00
|
|
|
}
|
|
|
|
}
|
2017-08-22 22:03:47 +00:00
|
|
|
|
2017-12-16 06:38:46 +00:00
|
|
|
devicePluginCapacity, devicePluginAllocatable, removedDevicePlugins = kl.containerManager.GetDevicePluginResourceCapacity()
|
2017-10-23 23:18:49 +00:00
|
|
|
if devicePluginCapacity != nil {
|
|
|
|
for k, v := range devicePluginCapacity {
|
2018-04-18 11:16:26 +00:00
|
|
|
if old, ok := node.Status.Capacity[k]; !ok || old.Value() != v.Value() {
|
|
|
|
glog.V(2).Infof("Update capacity for %s to %d", k, v.Value())
|
|
|
|
}
|
2017-10-23 23:18:49 +00:00
|
|
|
node.Status.Capacity[k] = v
|
2017-08-22 22:03:47 +00:00
|
|
|
}
|
|
|
|
}
|
2017-12-16 06:38:46 +00:00
|
|
|
|
2017-10-23 23:18:49 +00:00
|
|
|
for _, removedResource := range removedDevicePlugins {
|
2018-02-08 08:40:56 +00:00
|
|
|
glog.V(2).Infof("Set capacity for %s to 0 on device removal", removedResource)
|
|
|
|
// Set the capacity of the removed resource to 0 instead of
|
|
|
|
// removing the resource from the node status. This is to indicate
|
|
|
|
// that the resource is managed by device plugin and had been
|
|
|
|
// registered before.
|
|
|
|
//
|
|
|
|
// This is required to differentiate the device plugin managed
|
|
|
|
// resources and the cluster-level resources, which are absent in
|
|
|
|
// node status.
|
|
|
|
node.Status.Capacity[v1.ResourceName(removedResource)] = *resource.NewQuantity(int64(0), resource.DecimalSI)
|
2017-10-23 23:18:49 +00:00
|
|
|
}
|
2017-05-25 19:29:19 +00:00
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// Set Allocatable.
|
2017-02-10 05:14:10 +00:00
|
|
|
if node.Status.Allocatable == nil {
|
|
|
|
node.Status.Allocatable = make(v1.ResourceList)
|
|
|
|
}
|
2017-07-14 06:49:46 +00:00
|
|
|
// Remove extended resources from allocatable that are no longer
|
2017-03-07 01:42:55 +00:00
|
|
|
// present in capacity.
|
|
|
|
for k := range node.Status.Allocatable {
|
|
|
|
_, found := node.Status.Capacity[k]
|
2017-07-14 06:49:46 +00:00
|
|
|
if !found && v1helper.IsExtendedResourceName(k) {
|
2017-03-07 01:42:55 +00:00
|
|
|
delete(node.Status.Allocatable, k)
|
|
|
|
}
|
|
|
|
}
|
2017-02-10 05:14:10 +00:00
|
|
|
allocatableReservation := kl.containerManager.GetNodeAllocatableReservation()
|
2016-07-20 22:08:47 +00:00
|
|
|
for k, v := range node.Status.Capacity {
|
|
|
|
value := *(v.Copy())
|
2017-02-10 05:14:10 +00:00
|
|
|
if res, exists := allocatableReservation[k]; exists {
|
|
|
|
value.Sub(res)
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2017-05-26 15:29:54 +00:00
|
|
|
if value.Sign() < 0 {
|
|
|
|
// Negative Allocatable resources don't make sense.
|
|
|
|
value.Set(0)
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
node.Status.Allocatable[k] = value
|
|
|
|
}
|
2018-04-18 11:16:26 +00:00
|
|
|
|
2017-12-16 06:38:46 +00:00
|
|
|
if devicePluginAllocatable != nil {
|
|
|
|
for k, v := range devicePluginAllocatable {
|
2018-04-18 11:16:26 +00:00
|
|
|
if old, ok := node.Status.Allocatable[k]; !ok || old.Value() != v.Value() {
|
|
|
|
glog.V(2).Infof("Update allocatable for %s to %d", k, v.Value())
|
|
|
|
}
|
2017-12-16 06:38:46 +00:00
|
|
|
node.Status.Allocatable[k] = v
|
|
|
|
}
|
|
|
|
}
|
2017-08-17 18:28:15 +00:00
|
|
|
// for every huge page reservation, we need to remove it from allocatable memory
|
|
|
|
for k, v := range node.Status.Capacity {
|
|
|
|
if v1helper.IsHugePageResourceName(k) {
|
|
|
|
allocatableMemory := node.Status.Allocatable[v1.ResourceMemory]
|
|
|
|
value := *(v.Copy())
|
|
|
|
allocatableMemory.Sub(value)
|
|
|
|
if allocatableMemory.Sign() < 0 {
|
|
|
|
// Negative Allocatable resources don't make sense.
|
|
|
|
allocatableMemory.Set(0)
|
|
|
|
}
|
|
|
|
node.Status.Allocatable[v1.ResourceMemory] = allocatableMemory
|
|
|
|
}
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set versioninfo for the node.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusVersionInfo(node *v1.Node) {
|
2016-07-20 22:08:47 +00:00
|
|
|
verinfo, err := kl.cadvisor.VersionInfo()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error getting version info: %v", err)
|
2017-01-17 03:52:53 +00:00
|
|
|
return
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
|
2017-01-17 03:52:53 +00:00
|
|
|
node.Status.NodeInfo.KernelVersion = verinfo.KernelVersion
|
|
|
|
node.Status.NodeInfo.OSImage = verinfo.ContainerOsVersion
|
2016-07-20 22:08:47 +00:00
|
|
|
|
2017-01-17 03:52:53 +00:00
|
|
|
runtimeVersion := "Unknown"
|
|
|
|
if runtimeVer, err := kl.containerRuntime.Version(); err == nil {
|
|
|
|
runtimeVersion = runtimeVer.String()
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2017-01-17 03:52:53 +00:00
|
|
|
node.Status.NodeInfo.ContainerRuntimeVersion = fmt.Sprintf("%s://%s", kl.containerRuntime.Type(), runtimeVersion)
|
|
|
|
|
|
|
|
node.Status.NodeInfo.KubeletVersion = version.Get().String()
|
|
|
|
// TODO: kube-proxy might be different version from kubelet in the future
|
|
|
|
node.Status.NodeInfo.KubeProxyVersion = version.Get().String()
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set daemonEndpoints for the node.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusDaemonEndpoints(node *v1.Node) {
|
2016-07-20 22:08:47 +00:00
|
|
|
node.Status.DaemonEndpoints = *kl.daemonEndpoints
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set images list for the node
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusImages(node *v1.Node) {
|
2016-07-20 22:08:47 +00:00
|
|
|
// Update image list of this node
|
2016-11-18 20:50:58 +00:00
|
|
|
var imagesOnNode []v1.ContainerImage
|
2016-07-20 22:08:47 +00:00
|
|
|
containerImages, err := kl.imageManager.GetImageList()
|
|
|
|
if err != nil {
|
|
|
|
glog.Errorf("Error getting image list: %v", err)
|
2017-01-17 03:52:53 +00:00
|
|
|
node.Status.Images = imagesOnNode
|
|
|
|
return
|
|
|
|
}
|
|
|
|
// sort the images from max to min, and only set top N images into the node status.
|
2018-05-22 22:56:02 +00:00
|
|
|
if int(kl.nodeStatusMaxImages) > -1 &&
|
|
|
|
int(kl.nodeStatusMaxImages) < len(containerImages) {
|
|
|
|
containerImages = containerImages[0:kl.nodeStatusMaxImages]
|
2017-01-17 03:52:53 +00:00
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
|
2017-01-17 03:52:53 +00:00
|
|
|
for _, image := range containerImages {
|
|
|
|
names := append(image.RepoDigests, image.RepoTags...)
|
|
|
|
// Report up to maxNamesPerImageInNodeStatus names per image.
|
|
|
|
if len(names) > maxNamesPerImageInNodeStatus {
|
|
|
|
names = names[0:maxNamesPerImageInNodeStatus]
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2017-01-17 03:52:53 +00:00
|
|
|
imagesOnNode = append(imagesOnNode, v1.ContainerImage{
|
|
|
|
Names: names,
|
|
|
|
SizeBytes: image.Size,
|
|
|
|
})
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2017-01-17 03:52:53 +00:00
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
node.Status.Images = imagesOnNode
|
|
|
|
}
|
|
|
|
|
|
|
|
// Set the GOOS and GOARCH for this node
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusGoRuntime(node *v1.Node) {
|
2016-11-30 07:27:27 +00:00
|
|
|
node.Status.NodeInfo.OperatingSystem = goruntime.GOOS
|
|
|
|
node.Status.NodeInfo.Architecture = goruntime.GOARCH
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set status for the node.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeStatusInfo(node *v1.Node) {
|
2016-07-20 22:08:47 +00:00
|
|
|
kl.setNodeStatusMachineInfo(node)
|
|
|
|
kl.setNodeStatusVersionInfo(node)
|
|
|
|
kl.setNodeStatusDaemonEndpoints(node)
|
|
|
|
kl.setNodeStatusImages(node)
|
|
|
|
kl.setNodeStatusGoRuntime(node)
|
2018-05-23 16:51:29 +00:00
|
|
|
if utilfeature.DefaultFeatureGate.Enabled(features.AttachVolumeLimit) {
|
|
|
|
kl.setVolumeLimits(node)
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Set Ready condition for the node.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeReadyCondition(node *v1.Node) {
|
2016-07-20 22:08:47 +00:00
|
|
|
// NOTE(aaronlevy): NodeReady condition needs to be the last in the list of node conditions.
|
|
|
|
// This is due to an issue with version skewed kubelet and master components.
|
|
|
|
// ref: https://github.com/kubernetes/kubernetes/issues/16961
|
2016-12-03 18:57:26 +00:00
|
|
|
currentTime := metav1.NewTime(kl.clock.Now())
|
2018-02-16 01:33:22 +00:00
|
|
|
newNodeReadyCondition := v1.NodeCondition{
|
|
|
|
Type: v1.NodeReady,
|
|
|
|
Status: v1.ConditionTrue,
|
|
|
|
Reason: "KubeletReady",
|
|
|
|
Message: "kubelet is posting ready status",
|
|
|
|
LastHeartbeatTime: currentTime,
|
|
|
|
}
|
2016-09-23 03:04:37 +00:00
|
|
|
rs := append(kl.runtimeState.runtimeErrors(), kl.runtimeState.networkErrors()...)
|
2018-02-16 01:33:22 +00:00
|
|
|
requiredCapacities := []v1.ResourceName{v1.ResourceCPU, v1.ResourceMemory, v1.ResourcePods}
|
|
|
|
if utilfeature.DefaultFeatureGate.Enabled(features.LocalStorageCapacityIsolation) {
|
|
|
|
requiredCapacities = append(requiredCapacities, v1.ResourceEphemeralStorage)
|
|
|
|
}
|
|
|
|
missingCapacities := []string{}
|
|
|
|
for _, resource := range requiredCapacities {
|
|
|
|
if _, found := node.Status.Capacity[resource]; !found {
|
|
|
|
missingCapacities = append(missingCapacities, string(resource))
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2018-02-16 01:33:22 +00:00
|
|
|
}
|
|
|
|
if len(missingCapacities) > 0 {
|
|
|
|
rs = append(rs, fmt.Sprintf("Missing node capacity for resources: %s", strings.Join(missingCapacities, ", ")))
|
|
|
|
}
|
|
|
|
if len(rs) > 0 {
|
2016-11-18 20:50:58 +00:00
|
|
|
newNodeReadyCondition = v1.NodeCondition{
|
|
|
|
Type: v1.NodeReady,
|
|
|
|
Status: v1.ConditionFalse,
|
2016-07-20 22:08:47 +00:00
|
|
|
Reason: "KubeletNotReady",
|
|
|
|
Message: strings.Join(rs, ","),
|
|
|
|
LastHeartbeatTime: currentTime,
|
|
|
|
}
|
|
|
|
}
|
2016-08-30 00:54:15 +00:00
|
|
|
// Append AppArmor status if it's enabled.
|
2017-07-10 21:05:46 +00:00
|
|
|
// TODO(tallclair): This is a temporary message until node feature reporting is added.
|
2016-11-18 20:50:58 +00:00
|
|
|
if newNodeReadyCondition.Status == v1.ConditionTrue &&
|
2016-08-30 00:54:15 +00:00
|
|
|
kl.appArmorValidator != nil && kl.appArmorValidator.ValidateHost() == nil {
|
|
|
|
newNodeReadyCondition.Message = fmt.Sprintf("%s. AppArmor enabled", newNodeReadyCondition.Message)
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// Record any soft requirements that were not met in the container manager.
|
|
|
|
status := kl.containerManager.Status()
|
|
|
|
if status.SoftRequirements != nil {
|
|
|
|
newNodeReadyCondition.Message = fmt.Sprintf("%s. WARNING: %s", newNodeReadyCondition.Message, status.SoftRequirements.Error())
|
|
|
|
}
|
|
|
|
|
|
|
|
readyConditionUpdated := false
|
|
|
|
needToRecordEvent := false
|
|
|
|
for i := range node.Status.Conditions {
|
2016-11-18 20:50:58 +00:00
|
|
|
if node.Status.Conditions[i].Type == v1.NodeReady {
|
2016-07-20 22:08:47 +00:00
|
|
|
if node.Status.Conditions[i].Status == newNodeReadyCondition.Status {
|
|
|
|
newNodeReadyCondition.LastTransitionTime = node.Status.Conditions[i].LastTransitionTime
|
|
|
|
} else {
|
|
|
|
newNodeReadyCondition.LastTransitionTime = currentTime
|
|
|
|
needToRecordEvent = true
|
|
|
|
}
|
|
|
|
node.Status.Conditions[i] = newNodeReadyCondition
|
|
|
|
readyConditionUpdated = true
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !readyConditionUpdated {
|
|
|
|
newNodeReadyCondition.LastTransitionTime = currentTime
|
|
|
|
node.Status.Conditions = append(node.Status.Conditions, newNodeReadyCondition)
|
|
|
|
}
|
|
|
|
if needToRecordEvent {
|
2016-11-18 20:50:58 +00:00
|
|
|
if newNodeReadyCondition.Status == v1.ConditionTrue {
|
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeReady)
|
2016-07-20 22:08:47 +00:00
|
|
|
} else {
|
2016-11-18 20:50:58 +00:00
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeNotReady)
|
2016-11-30 19:48:38 +00:00
|
|
|
glog.Infof("Node became not ready: %+v", newNodeReadyCondition)
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-18 02:40:38 +00:00
|
|
|
// setNodePIDPressureCondition for the node.
|
|
|
|
// TODO: this needs to move somewhere centralized...
|
|
|
|
func (kl *Kubelet) setNodePIDPressureCondition(node *v1.Node) {
|
|
|
|
currentTime := metav1.NewTime(kl.clock.Now())
|
|
|
|
var condition *v1.NodeCondition
|
|
|
|
|
|
|
|
// Check if NodePIDPressure condition already exists and if it does, just pick it up for update.
|
|
|
|
for i := range node.Status.Conditions {
|
|
|
|
if node.Status.Conditions[i].Type == v1.NodePIDPressure {
|
|
|
|
condition = &node.Status.Conditions[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newCondition := false
|
|
|
|
// If the NodePIDPressure condition doesn't exist, create one
|
|
|
|
if condition == nil {
|
|
|
|
condition = &v1.NodeCondition{
|
|
|
|
Type: v1.NodePIDPressure,
|
|
|
|
Status: v1.ConditionUnknown,
|
|
|
|
}
|
|
|
|
// cannot be appended to node.Status.Conditions here because it gets
|
|
|
|
// copied to the slice. So if we append to the slice here none of the
|
|
|
|
// updates we make below are reflected in the slice.
|
|
|
|
newCondition = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the heartbeat time
|
|
|
|
condition.LastHeartbeatTime = currentTime
|
|
|
|
|
|
|
|
// Note: The conditions below take care of the case when a new NodePIDPressure condition is
|
|
|
|
// created and as well as the case when the condition already exists. When a new condition
|
|
|
|
// is created its status is set to v1.ConditionUnknown which matches either
|
|
|
|
// condition.Status != v1.ConditionTrue or
|
|
|
|
// condition.Status != v1.ConditionFalse in the conditions below depending on whether
|
|
|
|
// the kubelet is under PID pressure or not.
|
|
|
|
if kl.evictionManager.IsUnderPIDPressure() {
|
|
|
|
if condition.Status != v1.ConditionTrue {
|
|
|
|
condition.Status = v1.ConditionTrue
|
|
|
|
condition.Reason = "KubeletHasInsufficientPID"
|
|
|
|
condition.Message = "kubelet has insufficient PID available"
|
|
|
|
condition.LastTransitionTime = currentTime
|
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasInsufficientPID")
|
|
|
|
}
|
|
|
|
} else if condition.Status != v1.ConditionFalse {
|
|
|
|
condition.Status = v1.ConditionFalse
|
|
|
|
condition.Reason = "KubeletHasSufficientPID"
|
|
|
|
condition.Message = "kubelet has sufficient PID available"
|
|
|
|
condition.LastTransitionTime = currentTime
|
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasSufficientPID")
|
|
|
|
}
|
|
|
|
|
|
|
|
if newCondition {
|
|
|
|
node.Status.Conditions = append(node.Status.Conditions, *condition)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-22 19:23:09 +00:00
|
|
|
// setNodeDiskPressureCondition for the node.
|
|
|
|
// TODO: this needs to move somewhere centralized...
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeDiskPressureCondition(node *v1.Node) {
|
2016-12-03 18:57:26 +00:00
|
|
|
currentTime := metav1.NewTime(kl.clock.Now())
|
2016-11-18 20:50:58 +00:00
|
|
|
var condition *v1.NodeCondition
|
2016-07-22 19:23:09 +00:00
|
|
|
|
|
|
|
// Check if NodeDiskPressure condition already exists and if it does, just pick it up for update.
|
|
|
|
for i := range node.Status.Conditions {
|
2016-11-18 20:50:58 +00:00
|
|
|
if node.Status.Conditions[i].Type == v1.NodeDiskPressure {
|
2016-07-22 19:23:09 +00:00
|
|
|
condition = &node.Status.Conditions[i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
newCondition := false
|
|
|
|
// If the NodeDiskPressure condition doesn't exist, create one
|
|
|
|
if condition == nil {
|
2016-11-18 20:50:58 +00:00
|
|
|
condition = &v1.NodeCondition{
|
|
|
|
Type: v1.NodeDiskPressure,
|
|
|
|
Status: v1.ConditionUnknown,
|
2016-07-22 19:23:09 +00:00
|
|
|
}
|
|
|
|
// cannot be appended to node.Status.Conditions here because it gets
|
|
|
|
// copied to the slice. So if we append to the slice here none of the
|
|
|
|
// updates we make below are reflected in the slice.
|
|
|
|
newCondition = true
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the heartbeat time
|
|
|
|
condition.LastHeartbeatTime = currentTime
|
|
|
|
|
2016-12-28 12:17:27 +00:00
|
|
|
// Note: The conditions below take care of the case when a new NodeDiskPressure condition is
|
2016-07-22 19:23:09 +00:00
|
|
|
// created and as well as the case when the condition already exists. When a new condition
|
2016-11-18 20:50:58 +00:00
|
|
|
// is created its status is set to v1.ConditionUnknown which matches either
|
|
|
|
// condition.Status != v1.ConditionTrue or
|
|
|
|
// condition.Status != v1.ConditionFalse in the conditions below depending on whether
|
2016-07-22 19:23:09 +00:00
|
|
|
// the kubelet is under disk pressure or not.
|
|
|
|
if kl.evictionManager.IsUnderDiskPressure() {
|
2016-11-18 20:50:58 +00:00
|
|
|
if condition.Status != v1.ConditionTrue {
|
|
|
|
condition.Status = v1.ConditionTrue
|
2016-07-22 19:23:09 +00:00
|
|
|
condition.Reason = "KubeletHasDiskPressure"
|
|
|
|
condition.Message = "kubelet has disk pressure"
|
|
|
|
condition.LastTransitionTime = currentTime
|
2016-11-18 20:50:58 +00:00
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasDiskPressure")
|
2016-07-22 19:23:09 +00:00
|
|
|
}
|
2017-01-17 03:52:53 +00:00
|
|
|
} else if condition.Status != v1.ConditionFalse {
|
|
|
|
condition.Status = v1.ConditionFalse
|
|
|
|
condition.Reason = "KubeletHasNoDiskPressure"
|
|
|
|
condition.Message = "kubelet has no disk pressure"
|
|
|
|
condition.LastTransitionTime = currentTime
|
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, "NodeHasNoDiskPressure")
|
2016-07-22 19:23:09 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if newCondition {
|
|
|
|
node.Status.Conditions = append(node.Status.Conditions, *condition)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// record if node schedulable change.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) recordNodeSchedulableEvent(node *v1.Node) {
|
2018-05-30 03:57:37 +00:00
|
|
|
kl.lastNodeUnschedulableLock.Lock()
|
|
|
|
defer kl.lastNodeUnschedulableLock.Unlock()
|
|
|
|
if kl.lastNodeUnschedulable != node.Spec.Unschedulable {
|
2016-07-20 22:08:47 +00:00
|
|
|
if node.Spec.Unschedulable {
|
2016-11-18 20:50:58 +00:00
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeNotSchedulable)
|
2016-07-20 22:08:47 +00:00
|
|
|
} else {
|
2016-11-18 20:50:58 +00:00
|
|
|
kl.recordNodeStatusEvent(v1.EventTypeNormal, events.NodeSchedulable)
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
2018-05-30 03:57:37 +00:00
|
|
|
kl.lastNodeUnschedulable = node.Spec.Unschedulable
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-27 23:12:57 +00:00
|
|
|
// Update VolumesInUse field in Node Status only after states are synced up at least once
|
|
|
|
// in volume reconciler.
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) setNodeVolumesInUseStatus(node *v1.Node) {
|
2016-09-27 23:12:57 +00:00
|
|
|
// Make sure to only update node status after reconciler starts syncing up states
|
|
|
|
if kl.volumeManager.ReconcilerStatesHasBeenSynced() {
|
|
|
|
node.Status.VolumesInUse = kl.volumeManager.GetVolumesInUse()
|
|
|
|
}
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// setNodeStatus fills in the Status fields of the given Node, overwriting
|
|
|
|
// any fields that are currently set.
|
|
|
|
// TODO(madhusudancs): Simplify the logic for setting node conditions and
|
2016-08-02 22:13:54 +00:00
|
|
|
// refactor the node status condition code out to a different file.
|
2016-12-01 19:12:32 +00:00
|
|
|
func (kl *Kubelet) setNodeStatus(node *v1.Node) {
|
2018-03-14 16:02:28 +00:00
|
|
|
for i, f := range kl.setNodeStatusFuncs {
|
|
|
|
glog.V(5).Infof("Setting node status at position %v", i)
|
2016-07-20 22:08:47 +00:00
|
|
|
if err := f(node); err != nil {
|
2016-12-01 19:12:32 +00:00
|
|
|
glog.Warningf("Failed to set some node status fields: %s", err)
|
2016-07-20 22:08:47 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-28 18:44:25 +00:00
|
|
|
func (kl *Kubelet) setLastObservedNodeAddresses(addresses []v1.NodeAddress) {
|
|
|
|
kl.lastObservedNodeAddressesMux.Lock()
|
|
|
|
defer kl.lastObservedNodeAddressesMux.Unlock()
|
|
|
|
kl.lastObservedNodeAddresses = addresses
|
|
|
|
}
|
|
|
|
func (kl *Kubelet) getLastObservedNodeAddresses() []v1.NodeAddress {
|
|
|
|
kl.lastObservedNodeAddressesMux.Lock()
|
|
|
|
defer kl.lastObservedNodeAddressesMux.Unlock()
|
|
|
|
return kl.lastObservedNodeAddresses
|
|
|
|
}
|
|
|
|
|
2016-07-20 22:08:47 +00:00
|
|
|
// defaultNodeStatusFuncs is a factory that generates the default set of
|
|
|
|
// setNodeStatus funcs
|
2016-11-18 20:50:58 +00:00
|
|
|
func (kl *Kubelet) defaultNodeStatusFuncs() []func(*v1.Node) error {
|
2016-07-20 22:08:47 +00:00
|
|
|
// initial set of node status update handlers, can be modified by Option's
|
2016-11-18 20:50:58 +00:00
|
|
|
withoutError := func(f func(*v1.Node)) func(*v1.Node) error {
|
|
|
|
return func(n *v1.Node) error {
|
2016-07-20 22:08:47 +00:00
|
|
|
f(n)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2018-06-25 23:39:05 +00:00
|
|
|
// if cloud is not nil, we expect the cloud resource sync manager to exist
|
|
|
|
var nodeAddressesFunc func() ([]v1.NodeAddress, error)
|
|
|
|
if kl.cloud != nil {
|
|
|
|
nodeAddressesFunc = kl.cloudResourceSyncManager.NodeAddresses
|
|
|
|
}
|
2016-11-18 20:50:58 +00:00
|
|
|
return []func(*v1.Node) error{
|
2018-06-25 23:39:05 +00:00
|
|
|
nodestatus.NodeAddress(kl.nodeIP, kl.nodeIPValidator, kl.hostname, kl.externalCloudProvider, kl.cloud, nodeAddressesFunc),
|
2016-07-20 22:08:47 +00:00
|
|
|
withoutError(kl.setNodeStatusInfo),
|
2018-06-25 23:55:38 +00:00
|
|
|
nodestatus.OutOfDiskCondition(kl.clock.Now, kl.recordNodeStatusEvent),
|
2018-06-26 00:08:22 +00:00
|
|
|
nodestatus.MemoryPressureCondition(kl.clock.Now, kl.evictionManager.IsUnderMemoryPressure, kl.recordNodeStatusEvent),
|
2016-07-22 19:23:09 +00:00
|
|
|
withoutError(kl.setNodeDiskPressureCondition),
|
2017-12-18 02:40:38 +00:00
|
|
|
withoutError(kl.setNodePIDPressureCondition),
|
2016-07-20 22:08:47 +00:00
|
|
|
withoutError(kl.setNodeReadyCondition),
|
|
|
|
withoutError(kl.setNodeVolumesInUseStatus),
|
|
|
|
withoutError(kl.recordNodeSchedulableEvent),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-09-30 21:07:13 +00:00
|
|
|
// Validate given node IP belongs to the current host
|
2017-05-03 15:24:58 +00:00
|
|
|
func validateNodeIP(nodeIP net.IP) error {
|
2016-09-30 21:07:13 +00:00
|
|
|
// Honor IP limitations set in setNodeStatus()
|
2017-05-03 15:24:58 +00:00
|
|
|
if nodeIP.To4() == nil && nodeIP.To16() == nil {
|
|
|
|
return fmt.Errorf("nodeIP must be a valid IP address")
|
|
|
|
}
|
|
|
|
if nodeIP.IsLoopback() {
|
2016-09-30 21:07:13 +00:00
|
|
|
return fmt.Errorf("nodeIP can't be loopback address")
|
|
|
|
}
|
2017-05-03 15:24:58 +00:00
|
|
|
if nodeIP.IsMulticast() {
|
|
|
|
return fmt.Errorf("nodeIP can't be a multicast address")
|
|
|
|
}
|
|
|
|
if nodeIP.IsLinkLocalUnicast() {
|
|
|
|
return fmt.Errorf("nodeIP can't be a link-local unicast address")
|
|
|
|
}
|
|
|
|
if nodeIP.IsUnspecified() {
|
|
|
|
return fmt.Errorf("nodeIP can't be an all zeros address")
|
2016-09-30 21:07:13 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
addrs, err := net.InterfaceAddrs()
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
for _, addr := range addrs {
|
|
|
|
var ip net.IP
|
|
|
|
switch v := addr.(type) {
|
|
|
|
case *net.IPNet:
|
|
|
|
ip = v.IP
|
|
|
|
case *net.IPAddr:
|
|
|
|
ip = v.IP
|
|
|
|
}
|
2017-05-03 15:24:58 +00:00
|
|
|
if ip != nil && ip.Equal(nodeIP) {
|
2016-09-30 21:07:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
2017-05-03 15:24:58 +00:00
|
|
|
return fmt.Errorf("Node IP: %q not found in the host's network interfaces", nodeIP.String())
|
2016-09-30 21:07:13 +00:00
|
|
|
}
|