Skip to content

Commit

Permalink
Merge pull request #51199 from kow3ns/set-hostname-once
Browse files Browse the repository at this point in the history
Automatic merge from submit-queue.

Allow Hostname and Subdomain to be set if empty

**What this PR does / why we need it**:
This PR allows the Hostname and Subdomain field of v1.PodSpec to be set when empty, and modifies the StatefulSet controller to set them when empty. 

For #48327: 
We have merged #50942 to ensure that the Hostname and Subdomain fields are set when a new Pod is created. Users should upgrade to 1.6.9 and perform a rolling restart of all Pods in their StatefulSets to ensure that these fields are set prior to an upgrade to 1.7.5.
We have merged #51149 and #51044 to rollback the attempted mutation introduced in #44137.
This PR allows the Hostname and Subdomain field to be set exactly once, so that when users fail to read the notes, and encounter this issue, their clusters should self heal (even though they will experience a temporary network disruption for Pods in their StatefulSets.)

```release-note
StatefulSet will now fill the `hostname` and `subdomain` fields if they're empty on existing Pods it owns. This allows it to self-correct the issue where StatefulSet Pod DNS entries disappear after upgrading to v1.7.x (#48327).
```
  • Loading branch information
Kubernetes Submit Queue committed Sep 27, 2017
2 parents 1a9e43f + 4373f33 commit 9790389
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 15 deletions.
10 changes: 10 additions & 0 deletions pkg/api/validation/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -2706,6 +2706,16 @@ func ValidatePodUpdate(newPod, oldPod *api.Pod) field.ErrorList {

// handle updateable fields by munging those fields prior to deep equal comparison.
mungedPod := *newPod

// allow hostname and subdomain to be updated if they are empty. This allows for migration between the beta
// annotations and the GA field when upgrading between Kubernetes 1.6.x and 1.7.x.
if oldPod.Spec.Hostname == "" {
mungedPod.Spec.Hostname = oldPod.Spec.Hostname
}
if oldPod.Spec.Subdomain == "" {
mungedPod.Spec.Subdomain = oldPod.Spec.Subdomain
}

// munge spec.containers[*].image
var newContainers []api.Container
for ix, container := range mungedPod.Spec.Containers {
Expand Down
48 changes: 48 additions & 0 deletions pkg/api/validation/validation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5781,6 +5781,54 @@ func TestValidatePodUpdate(t *testing.T) {
"metadata.annotations[kubernetes.io/config.mirror]",
"changed mirror pod annotation",
},
{
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Hostname: "bar"},
},
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Hostname: ""},
},
"",
"update empty hostname",
},
{
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Subdomain: "bar"},
},
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Subdomain: ""},
},
"",
"update empty subdomain",
},
{
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Hostname: "bar"},
},
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Hostname: "baz"},
},
"spec: Forbidden",
"update hostname",
},
{
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Subdomain: "bar"},
},
api.Pod{
ObjectMeta: metav1.ObjectMeta{Name: "foo"},
Spec: api.PodSpec{Subdomain: "baz"},
},
"spec: Forbidden",
"update subdomain",
},
}

for _, test := range tests {
Expand Down
28 changes: 15 additions & 13 deletions pkg/controller/statefulset/stateful_set_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,21 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
replicas[i].Name)
return &status, nil
}
// Enforce the StatefulSet invariants - we do this without respect to the Pod's readiness so that the endpoints
// controller can be notified of identity changes if a Pod becomes unready due to a DNS inconsistency with respect
// to the Pods identity.
if !identityMatches(set, replicas[i]) || !storageMatches(set, replicas[i]) {
// Make a deep copy so we don't mutate the shared cache
copy, err := scheme.Scheme.DeepCopy(replicas[i])
if err != nil {
return &status, err
}
replica := copy.(*v1.Pod)
if err := ssc.podControl.UpdateStatefulPod(updateSet, replica); err != nil {
return &status, err
}
}

// If we have a Pod that has been created but is not running and ready we can not make progress.
// We must ensure that all for each Pod, when we create it, all of its predecessors, with respect to its
// ordinal, are Running and Ready.
Expand All @@ -415,19 +430,6 @@ func (ssc *defaultStatefulSetControl) updateStatefulSet(
replicas[i].Name)
return &status, nil
}
// Enforce the StatefulSet invariants
if identityMatches(set, replicas[i]) && storageMatches(set, replicas[i]) {
continue
}
// Make a deep copy so we don't mutate the shared cache
copy, err := scheme.Scheme.DeepCopy(replicas[i])
if err != nil {
return &status, err
}
replica := copy.(*v1.Pod)
if err := ssc.podControl.UpdateStatefulPod(updateSet, replica); err != nil {
return &status, err
}
}

// At this point, all of the current Replicas are Running and Ready, we can consider termination.
Expand Down
11 changes: 9 additions & 2 deletions pkg/controller/statefulset/stateful_set_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ func identityMatches(set *apps.StatefulSet, pod *v1.Pod) bool {
return ordinal >= 0 &&
set.Name == parent &&
pod.Name == getPodName(set, ordinal) &&
pod.Namespace == set.Namespace
pod.Namespace == set.Namespace &&
pod.Spec.Hostname != "" &&
(pod.Spec.Subdomain != "" || set.Spec.ServiceName == "")
}

// storageMatches returns true if pod's Volumes cover the set of PersistentVolumeClaims
Expand Down Expand Up @@ -195,7 +197,12 @@ func initIdentity(set *apps.StatefulSet, pod *v1.Pod) {
func updateIdentity(set *apps.StatefulSet, pod *v1.Pod) {
pod.Name = getPodName(set, getOrdinal(pod))
pod.Namespace = set.Namespace

if pod.Spec.Hostname == "" {
pod.Spec.Hostname = pod.Name
}
if pod.Spec.Subdomain == "" {
pod.Spec.Subdomain = set.Spec.ServiceName
}
}

// isRunningAndReady returns true if pod is in the PodRunning Phase, if it has a condition of PodReady, and if the init
Expand Down
16 changes: 16 additions & 0 deletions pkg/controller/statefulset/stateful_set_utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,22 @@ func TestUpdateIdentity(t *testing.T) {
if !identityMatches(set, pod) {
t.Error("updateIdentity failed to update the Pods namespace")
}
pod.Spec.Hostname = ""
pod.Spec.Subdomain = ""
updateIdentity(set, pod)
if pod.Spec.Hostname != pod.Name || pod.Spec.Subdomain != set.Spec.ServiceName {
t.Errorf("want hostame=%s subdomain=%s got hostname=%s subdomain=%s",
pod.Name,
set.Spec.ServiceName,
pod.Spec.Hostname,
set.Spec.ServiceName)
}
pod.Spec.Hostname = "foo"
pod.Spec.Subdomain = "bar"
updateIdentity(set, pod)
if pod.Spec.Hostname != "foo" || pod.Spec.Subdomain != "bar" {
t.Errorf("want hostame=foo subdomain=bar got hostname=%s subdomain=%s", pod.Spec.Hostname, set.Spec.ServiceName)
}
}

func TestUpdateStorage(t *testing.T) {
Expand Down

0 comments on commit 9790389

Please sign in to comment.