-
Notifications
You must be signed in to change notification settings - Fork 500
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
fix some orphan pods cleaner bugs #878
Changes from all commits
71aaeb4
9791178
e833814
931c65f
1a35286
7a3caf0
017bd4e
45a0c72
cf90ec8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,17 +18,35 @@ import ( | |
"github.com/pingcap/tidb-operator/pkg/apis/pingcap.com/v1alpha1" | ||
"github.com/pingcap/tidb-operator/pkg/controller" | ||
"github.com/pingcap/tidb-operator/pkg/label" | ||
v1 "k8s.io/api/core/v1" | ||
"k8s.io/apimachinery/pkg/api/errors" | ||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/client-go/kubernetes" | ||
corelisters "k8s.io/client-go/listers/core/v1" | ||
) | ||
|
||
const ( | ||
skipReasonOrphanPodsCleanerIsNotPDOrTiKV = "orphan pods cleaner: member type is not pd or tikv" | ||
skipReasonOrphanPodsCleanerPVCNameIsEmpty = "orphan pods cleaner: pvcName is empty" | ||
skipReasonOrphanPodsCleanerPVCIsFound = "orphan pods cleaner: pvc is found" | ||
skipReasonOrphanPodsCleanerIsNotPDOrTiKV = "orphan pods cleaner: member type is not pd or tikv" | ||
skipReasonOrphanPodsCleanerPVCNameIsEmpty = "orphan pods cleaner: pvcName is empty" | ||
skipReasonOrphanPodsCleanerPVCIsFound = "orphan pods cleaner: pvc is found" | ||
skipReasonOrphanPodsCleanerPodIsNotPending = "orphan pods cleaner: pod is not pending" | ||
skipReasonOrphanPodsCleanerPodIsNotFound = "orphan pods cleaner: pod does not exist anymore" | ||
skipReasonOrphanPodsCleanerPodChanged = "orphan pods cleaner: pod changed before deletion" | ||
) | ||
|
||
// OrphanPodsCleaner implements the logic for cleaning the orphan pods(has no pvc) | ||
// | ||
// In scaling out and failover, we will try to delete the old PVC to prevent it | ||
// from being used by the new pod. However, the PVC might not be deleted | ||
// immediately in the apiserver because of finalizers (e.g. | ||
// kubernetes.io/pvc-protection) and the statefulset controller may not have | ||
// received PVC delete event when it tries to create the new replica and the | ||
// new pod will be pending forever because no PVC to use. We need to clean | ||
// these orphan pods and let the statefulset controller to create PVC(s) for | ||
// them. | ||
// | ||
// https://github.com/kubernetes/kubernetes/blob/84fe3db5cf58bf0fc8ff792b885465ceaf70a435/pkg/controller/statefulset/stateful_pod_control.go#L175-L199 | ||
// | ||
type OrphanPodsCleaner interface { | ||
Clean(*v1alpha1.TidbCluster) (map[string]string, error) | ||
} | ||
|
@@ -37,13 +55,15 @@ type orphanPodsCleaner struct { | |
podLister corelisters.PodLister | ||
podControl controller.PodControlInterface | ||
pvcLister corelisters.PersistentVolumeClaimLister | ||
kubeCli kubernetes.Interface | ||
} | ||
|
||
// NewOrphanPodsCleaner returns a OrphanPodsCleaner | ||
func NewOrphanPodsCleaner(podLister corelisters.PodLister, | ||
podControl controller.PodControlInterface, | ||
pvcLister corelisters.PersistentVolumeClaimLister) OrphanPodsCleaner { | ||
return &orphanPodsCleaner{podLister, podControl, pvcLister} | ||
pvcLister corelisters.PersistentVolumeClaimLister, | ||
kubeCli kubernetes.Interface) OrphanPodsCleaner { | ||
return &orphanPodsCleaner{podLister, podControl, pvcLister, kubeCli} | ||
} | ||
|
||
func (opc *orphanPodsCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string, error) { | ||
|
@@ -68,6 +88,12 @@ func (opc *orphanPodsCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string | |
continue | ||
} | ||
|
||
if pod.Status.Phase != v1.PodPending { | ||
skipReason[podName] = skipReasonOrphanPodsCleanerPodIsNotPending | ||
DanielZhangQD marked this conversation as resolved.
Show resolved
Hide resolved
|
||
continue | ||
} | ||
|
||
// TODO support multiple pvcs case? | ||
var pvcName string | ||
for _, vol := range pod.Spec.Volumes { | ||
if vol.PersistentVolumeClaim != nil { | ||
|
@@ -80,7 +106,9 @@ func (opc *orphanPodsCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string | |
continue | ||
} | ||
|
||
_, err := opc.pvcLister.PersistentVolumeClaims(ns).Get(pvcName) | ||
var err error | ||
// check informer cache | ||
_, err = opc.pvcLister.PersistentVolumeClaims(ns).Get(pvcName) | ||
if err == nil { | ||
skipReason[podName] = skipReasonOrphanPodsCleanerPVCIsFound | ||
continue | ||
|
@@ -89,6 +117,33 @@ func (opc *orphanPodsCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string | |
return skipReason, err | ||
} | ||
|
||
// if PVC not found in cache, re-check from apiserver directly to make sure the PVC really not exist | ||
_, err = opc.kubeCli.CoreV1().PersistentVolumeClaims(ns).Get(pvcName, metav1.GetOptions{}) | ||
DanielZhangQD marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err == nil { | ||
skipReason[podName] = skipReasonOrphanPodsCleanerPVCIsFound | ||
continue | ||
} | ||
if !errors.IsNotFound(err) { | ||
return skipReason, err | ||
} | ||
|
||
// if the PVC is not found in apiserver (also informer cache) and the | ||
// phase of the Pod is Pending, delete it and let the stateful | ||
// controller to create the pod and its PVC(s) again | ||
cofyc marked this conversation as resolved.
Show resolved
Hide resolved
|
||
apiPod, err := opc.kubeCli.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{}) | ||
if errors.IsNotFound(err) { | ||
skipReason[podName] = skipReasonOrphanPodsCleanerPodIsNotFound | ||
continue | ||
} | ||
if err != nil { | ||
return skipReason, err | ||
} | ||
// try our best to avoid deleting wrong object in apiserver | ||
// TODO upgrade to use deleteOption.Preconditions.ResourceVersion in client-go 1.14+ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
if apiPod.UID != pod.UID || apiPod.ResourceVersion != pod.ResourceVersion { | ||
skipReason[podName] = skipReasonOrphanPodsCleanerPodChanged | ||
continue | ||
tennix marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
err = opc.podControl.DeletePod(tc, pod) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. only delete pod if the pod status is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. sorry, I forgot it...added in 9791178 |
||
if err != nil { | ||
glog.Errorf("orphan pods cleaner: failed to clean orphan pod: %s/%s, %v", ns, podName, err) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Start
will start a new goroutine, so no need to usego ...
here.