Improve reporting about rolling updates (#391)
This commit is contained in:
parent
688d252752
commit
7907f95d2f
|
|
@ -220,3 +220,7 @@ The operator is capable of maintaining roles of multiple kinds within a Postgres
|
||||||
3. **Per-cluster robot users** are also roles for processes originating from external systems but defined for an individual Postgres cluster in its manifest. A typical example is a role for connections from an application that uses the database.
|
3. **Per-cluster robot users** are also roles for processes originating from external systems but defined for an individual Postgres cluster in its manifest. A typical example is a role for connections from an application that uses the database.
|
||||||
|
|
||||||
4. **Human users** originate from the Teams API that returns list of the team members given a team id. Operator differentiates between (a) product teams that own a particular Postgres cluster and are granted admin rights to maintain it, and (b) Postgres superuser teams that get the superuser access to all PG databases running in a k8s cluster for the purposes of maintaining and troubleshooting.
|
4. **Human users** originate from the Teams API that returns list of the team members given a team id. Operator differentiates between (a) product teams that own a particular Postgres cluster and are granted admin rights to maintain it, and (b) Postgres superuser teams that get the superuser access to all PG databases running in a k8s cluster for the purposes of maintaining and troubleshooting.
|
||||||
|
|
||||||
|
## Understanding rolling update of Spilo pods
|
||||||
|
|
||||||
|
The operator logs reasons for a rolling update with the `info` level and a diff between the old and new StatefulSet specs with the `debug` level. To benefit from numerous escape characters in the latter log entry, view it in CLI with `echo -e`. Note that the resultant message will contain some noise because the `PodTemplate` used by the operator is yet to be updated with the default values used internally in Kubernetes.
|
||||||
|
|
|
||||||
|
|
@ -321,7 +321,9 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *comp
|
||||||
needsRollUpdate = true
|
needsRollUpdate = true
|
||||||
reasons = append(reasons, "new statefulset's container specification doesn't match the current one")
|
reasons = append(reasons, "new statefulset's container specification doesn't match the current one")
|
||||||
} else {
|
} else {
|
||||||
needsRollUpdate, reasons = c.compareContainers(c.Statefulset, statefulSet)
|
var containerReasons []string
|
||||||
|
needsRollUpdate, containerReasons = c.compareContainers(c.Statefulset, statefulSet)
|
||||||
|
reasons = append(reasons, containerReasons...)
|
||||||
}
|
}
|
||||||
if len(c.Statefulset.Spec.Template.Spec.Containers) == 0 {
|
if len(c.Statefulset.Spec.Template.Spec.Containers) == 0 {
|
||||||
c.logger.Warningf("statefulset %q has no container", util.NameFromMeta(c.Statefulset.ObjectMeta))
|
c.logger.Warningf("statefulset %q has no container", util.NameFromMeta(c.Statefulset.ObjectMeta))
|
||||||
|
|
@ -329,7 +331,6 @@ func (c *Cluster) compareStatefulSetWith(statefulSet *v1beta1.StatefulSet) *comp
|
||||||
}
|
}
|
||||||
// In the comparisons below, the needsReplace and needsRollUpdate flags are never reset, since checks fall through
|
// In the comparisons below, the needsReplace and needsRollUpdate flags are never reset, since checks fall through
|
||||||
// and the combined effect of all the changes should be applied.
|
// and the combined effect of all the changes should be applied.
|
||||||
// TODO: log all reasons for changing the statefulset, not just the last one.
|
|
||||||
// TODO: make sure this is in sync with generatePodTemplate, ideally by using the same list of fields to generate
|
// TODO: make sure this is in sync with generatePodTemplate, ideally by using the same list of fields to generate
|
||||||
// the template and the diff
|
// the template and the diff
|
||||||
if c.Statefulset.Spec.Template.Spec.ServiceAccountName != statefulSet.Spec.Template.Spec.ServiceAccountName {
|
if c.Statefulset.Spec.Template.Spec.ServiceAccountName != statefulSet.Spec.Template.Spec.ServiceAccountName {
|
||||||
|
|
@ -416,23 +417,23 @@ func newCheck(msg string, cond containerCondition) containerCheck {
|
||||||
|
|
||||||
// compareContainers: compare containers from two stateful sets
|
// compareContainers: compare containers from two stateful sets
|
||||||
// and return:
|
// and return:
|
||||||
// * whether or not roll update is needed
|
// * whether or not a rolling update is needed
|
||||||
// * a list of reasons in a human readable format
|
// * a list of reasons in a human readable format
|
||||||
func (c *Cluster) compareContainers(setA, setB *v1beta1.StatefulSet) (bool, []string) {
|
func (c *Cluster) compareContainers(setA, setB *v1beta1.StatefulSet) (bool, []string) {
|
||||||
reasons := make([]string, 0)
|
reasons := make([]string, 0)
|
||||||
needsRollUpdate := false
|
needsRollUpdate := false
|
||||||
checks := []containerCheck{
|
checks := []containerCheck{
|
||||||
newCheck("new statefulset's container %d name doesn't match the current one",
|
newCheck("new statefulset's container %s (index %d) name doesn't match the current one",
|
||||||
func(a, b v1.Container) bool { return a.Name != b.Name }),
|
func(a, b v1.Container) bool { return a.Name != b.Name }),
|
||||||
newCheck("new statefulset's container %d image doesn't match the current one",
|
newCheck("new statefulset's container %s (index %d) image doesn't match the current one",
|
||||||
func(a, b v1.Container) bool { return a.Image != b.Image }),
|
func(a, b v1.Container) bool { return a.Image != b.Image }),
|
||||||
newCheck("new statefulset's container %d ports don't match the current one",
|
newCheck("new statefulset's container %s (index %d) ports don't match the current one",
|
||||||
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Ports, b.Ports) }),
|
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Ports, b.Ports) }),
|
||||||
newCheck("new statefulset's container %d resources don't match the current ones",
|
newCheck("new statefulset's container %s (index %d) resources don't match the current ones",
|
||||||
func(a, b v1.Container) bool { return !compareResources(&a.Resources, &b.Resources) }),
|
func(a, b v1.Container) bool { return !compareResources(&a.Resources, &b.Resources) }),
|
||||||
newCheck("new statefulset's container %d environment doesn't match the current one",
|
newCheck("new statefulset's container %s (index %d) environment doesn't match the current one",
|
||||||
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Env, b.Env) }),
|
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.Env, b.Env) }),
|
||||||
newCheck("new statefulset's container %d environment sources don't match the current one",
|
newCheck("new statefulset's container %s (index %d) environment sources don't match the current one",
|
||||||
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.EnvFrom, b.EnvFrom) }),
|
func(a, b v1.Container) bool { return !reflect.DeepEqual(a.EnvFrom, b.EnvFrom) }),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -441,7 +442,7 @@ func (c *Cluster) compareContainers(setA, setB *v1beta1.StatefulSet) (bool, []st
|
||||||
for _, check := range checks {
|
for _, check := range checks {
|
||||||
if check.condition(containerA, containerB) {
|
if check.condition(containerA, containerB) {
|
||||||
needsRollUpdate = true
|
needsRollUpdate = true
|
||||||
reasons = append(reasons, fmt.Sprintf(check.reason, index))
|
reasons = append(reasons, fmt.Sprintf(check.reason, containerA.Name, index))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -132,16 +132,17 @@ func (c *Cluster) preScaleDown(newStatefulSet *v1beta1.StatefulSet) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setRollingUpdateFlagForStatefulSet sets the indicator or the rolling upgrade requirement
|
// setRollingUpdateFlagForStatefulSet sets the indicator or the rolling update requirement
|
||||||
// in the StatefulSet annotation.
|
// in the StatefulSet annotation.
|
||||||
func (c *Cluster) setRollingUpdateFlagForStatefulSet(sset *v1beta1.StatefulSet, val bool) {
|
func (c *Cluster) setRollingUpdateFlagForStatefulSet(sset *v1beta1.StatefulSet, val bool) {
|
||||||
anno := sset.GetAnnotations()
|
anno := sset.GetAnnotations()
|
||||||
c.logger.Debugf("rolling upgrade flag has been set to %t", val)
|
|
||||||
if anno == nil {
|
if anno == nil {
|
||||||
anno = make(map[string]string)
|
anno = make(map[string]string)
|
||||||
}
|
}
|
||||||
|
|
||||||
anno[rollingUpdateStatefulsetAnnotationKey] = strconv.FormatBool(val)
|
anno[rollingUpdateStatefulsetAnnotationKey] = strconv.FormatBool(val)
|
||||||
sset.SetAnnotations(anno)
|
sset.SetAnnotations(anno)
|
||||||
|
c.logger.Debugf("statefulset's rolling update annotation has been set to %t", val)
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyRollingUpdateFlagforStatefulSet sets the rolling update flag for the cluster's StatefulSet
|
// applyRollingUpdateFlagforStatefulSet sets the rolling update flag for the cluster's StatefulSet
|
||||||
|
|
@ -176,9 +177,9 @@ func (c *Cluster) getRollingUpdateFlagFromStatefulSet(sset *v1beta1.StatefulSet,
|
||||||
return flag
|
return flag
|
||||||
}
|
}
|
||||||
|
|
||||||
// mergeRollingUpdateFlagUsingCache return the value of the rollingUpdate flag from the passed
|
// mergeRollingUpdateFlagUsingCache returns the value of the rollingUpdate flag from the passed
|
||||||
// statefulset, however, the value can be cleared if there is a cached flag in the cluster that
|
// statefulset, however, the value can be cleared if there is a cached flag in the cluster that
|
||||||
// is set to false (the disrepancy could be a result of a failed StatefulSet update).s
|
// is set to false (the discrepancy could be a result of a failed StatefulSet update)
|
||||||
func (c *Cluster) mergeRollingUpdateFlagUsingCache(runningStatefulSet *v1beta1.StatefulSet) bool {
|
func (c *Cluster) mergeRollingUpdateFlagUsingCache(runningStatefulSet *v1beta1.StatefulSet) bool {
|
||||||
var (
|
var (
|
||||||
cachedStatefulsetExists, clearRollingUpdateFromCache, podsRollingUpdateRequired bool
|
cachedStatefulsetExists, clearRollingUpdateFromCache, podsRollingUpdateRequired bool
|
||||||
|
|
@ -198,7 +199,7 @@ func (c *Cluster) mergeRollingUpdateFlagUsingCache(runningStatefulSet *v1beta1.S
|
||||||
c.logger.Infof("clearing the rolling update flag based on the cached information")
|
c.logger.Infof("clearing the rolling update flag based on the cached information")
|
||||||
podsRollingUpdateRequired = false
|
podsRollingUpdateRequired = false
|
||||||
} else {
|
} else {
|
||||||
c.logger.Infof("found a statefulset with an unfinished pods rolling update")
|
c.logger.Infof("found a statefulset with an unfinished rolling update of the pods")
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package cluster
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"k8s.io/api/core/v1"
|
"k8s.io/api/core/v1"
|
||||||
policybeta1 "k8s.io/api/policy/v1beta1"
|
policybeta1 "k8s.io/api/policy/v1beta1"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
|
@ -280,6 +281,7 @@ func (c *Cluster) syncStatefulSet() error {
|
||||||
podsRollingUpdateRequired = true
|
podsRollingUpdateRequired = true
|
||||||
c.setRollingUpdateFlagForStatefulSet(desiredSS, podsRollingUpdateRequired)
|
c.setRollingUpdateFlagForStatefulSet(desiredSS, podsRollingUpdateRequired)
|
||||||
}
|
}
|
||||||
|
|
||||||
c.logStatefulSetChanges(c.Statefulset, desiredSS, false, cmp.reasons)
|
c.logStatefulSetChanges(c.Statefulset, desiredSS, false, cmp.reasons)
|
||||||
|
|
||||||
if !cmp.replace {
|
if !cmp.replace {
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ func (c *Cluster) logStatefulSetChanges(old, new *v1beta1.StatefulSet, isUpdate
|
||||||
if !reflect.DeepEqual(old.Annotations, new.Annotations) {
|
if !reflect.DeepEqual(old.Annotations, new.Annotations) {
|
||||||
c.logger.Debugf("metadata.annotation diff\n%s\n", util.PrettyDiff(old.Annotations, new.Annotations))
|
c.logger.Debugf("metadata.annotation diff\n%s\n", util.PrettyDiff(old.Annotations, new.Annotations))
|
||||||
}
|
}
|
||||||
c.logger.Debugf("spec diff\n%s\n", util.PrettyDiff(old.Spec, new.Spec))
|
c.logger.Debugf("spec diff between old and new statefulsets: \n%s\n", util.PrettyDiff(old.Spec, new.Spec))
|
||||||
|
|
||||||
if len(reasons) > 0 {
|
if len(reasons) > 0 {
|
||||||
for _, reason := range reasons {
|
for _, reason := range reasons {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue