Skip to content
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

Add switch to reclaim pv when scale in tikv or pd #1037

Merged
merged 29 commits into from
Nov 6, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
0ea0f8e
add switch to clean the defer delete pvc
Oct 21, 2019
721706d
add unit tests
Oct 21, 2019
fdeed35
add DeferPVCDelete field to TidbClusterSpec
Oct 21, 2019
80ca029
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 21, 2019
2fcf533
update .gitignore
Oct 21, 2019
24d4fae
update crd
Oct 21, 2019
f41b7ed
add e2e test for disable defer pvc delete
Oct 22, 2019
0eaece9
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 28, 2019
9311e12
Update pkg/manager/member/pvc_cleaner.go
Oct 28, 2019
e955830
change field name from deferPVCDelete to enablePVReclaim
Oct 28, 2019
2633210
address comments
Oct 29, 2019
800ced3
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 29, 2019
35e91a1
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 29, 2019
64147d3
Update pkg/manager/member/pvc_cleaner.go
Oct 30, 2019
817c0a7
merge branch master into add-switch-to-clean-pvc
Oct 30, 2019
3b56cf0
Merge branch 'add-switch-to-clean-pvc' of github.com:onlymellb/tidb-o…
Oct 30, 2019
c40620b
merge branch master into add-switch-to-clean-pvc
Oct 31, 2019
e411654
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 31, 2019
6a72903
fix e2e test
Oct 31, 2019
1c56965
fix CI
Oct 31, 2019
8ab7448
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Oct 31, 2019
c2a0c57
Merge branch 'master' into add-switch-to-clean-pvc
cofyc Nov 1, 2019
5f4d70f
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Nov 3, 2019
b88f75a
Merge branch 'add-switch-to-clean-pvc' of github.com:onlymellb/tidb-o…
Nov 3, 2019
d6f45db
skip patch pv when enable pv reclaim
Nov 3, 2019
e3652df
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Nov 5, 2019
f34ccc9
add err info to e2e log
Nov 5, 2019
b93dacc
merge branch master into add-switch-to-clean-pvc
Nov 5, 2019
e081ac4
Merge remote-tracking branch 'upstream/master' into add-switch-to-cle…
Nov 6, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ tests/images/e2e/tidb-cluster/
tests/images/e2e/tidb-backup/
tests/images/e2e/tidb-operator/
tests/images/e2e/manifests/
manifests/crd-verify.yaml
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Yisaer better to update verify script to use a temporary file

*.tar
tmp/
data/
Expand All @@ -30,8 +31,6 @@ coverage.out
vendor
tests/e2e/e2e.test
.orig
tkc

apiserver.local.config/
manifests/crd-verify.yaml
/tkctl
Expand Down
1 change: 1 addition & 0 deletions charts/tidb-cluster/templates/tidb-cluster.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ metadata:
{{- end }}
spec:
pvReclaimPolicy: {{ .Values.pvReclaimPolicy }}
enablePVReclaim: {{ .Values.enablePVReclaim }}
timezone: {{ .Values.timezone | default "UTC" }}
enableTLSCluster: {{ .Values.enableTLSCluster | default false }}
enableTLSClient: {{ .Values.enableTLSClient | default false }}
Expand Down
6 changes: 5 additions & 1 deletion charts/tidb-cluster/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,15 @@ schedulerName: tidb-scheduler
# timezone is the default system timzone for TiDB
timezone: UTC

# reclaim policy of a PV, default: Retain.
# reclaim policy of PV, default: Retain.
# you must set it to Retain to ensure data safety in production environment.
# https://pingcap.com/docs/v3.0/tidb-in-kubernetes/reference/configuration/local-pv/#data-security
pvReclaimPolicy: Retain

# Whether enable PV reclaim.
# When enabling, scale in pd or tikv will trigger PV reclaim automatically
enablePVReclaim: false

# services is the service list to expose, default is ClusterIP
# can be ClusterIP | NodePort | LoadBalancer
services:
Expand Down
2 changes: 2 additions & 0 deletions manifests/crd.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ spec:
description: TidbClusterSpec describes the attributes that a user creates
on a tidb cluster
properties:
enablePVReclaim:
type: boolean
enableTLSCluster:
description: Enable TLS connection between TiDB server compoments
type: boolean
Expand Down
6 changes: 6 additions & 0 deletions pkg/apis/pingcap/v1alpha1/openapi_generated.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions pkg/apis/pingcap/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ type TidbClusterSpec struct {
// Services list non-headless services type used in TidbCluster
Services []Service `json:"services,omitempty"`
PVReclaimPolicy corev1.PersistentVolumeReclaimPolicy `json:"pvReclaimPolicy,omitempty"`
EnablePVReclaim bool `json:"enablePVReclaim,omitempty"`
Timezone string `json:"timezone,omitempty"`
// Enable TLS connection between TiDB server compoments
EnableTLSCluster bool `json:"enableTLSCluster,omitempty"`
Expand Down
4 changes: 2 additions & 2 deletions pkg/controller/pv_control.go
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@ func (fpc *FakePVControl) PatchPVReclaimPolicy(_ *v1alpha1.TidbCluster, pv *core
defer fpc.updatePVTracker.Inc()
if fpc.updatePVTracker.ErrorReady() {
defer fpc.updatePVTracker.Reset()
return fpc.updatePVTracker.err
return fpc.updatePVTracker.GetError()
}
pv.Spec.PersistentVolumeReclaimPolicy = reclaimPolicy

Expand All @@ -212,7 +212,7 @@ func (fpc *FakePVControl) UpdateMetaInfo(tc *v1alpha1.TidbCluster, pv *corev1.Pe
defer fpc.updatePVTracker.Inc()
if fpc.updatePVTracker.ErrorReady() {
defer fpc.updatePVTracker.Reset()
return nil, fpc.updatePVTracker.err
return nil, fpc.updatePVTracker.GetError()
}
ns := tc.GetNamespace()
pvcName := pv.Spec.ClaimRef.Name
Expand Down
3 changes: 3 additions & 0 deletions pkg/controller/tidbcluster/tidb_cluster_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,9 +170,12 @@ func NewController(
kubeCli,
),
mm.NewRealPVCCleaner(
kubeCli,
podInformer.Lister(),
pvcControl,
pvcInformer.Lister(),
pvInformer.Lister(),
pvControl,
),
recorder,
),
Expand Down
165 changes: 157 additions & 8 deletions pkg/manager/member/pvc_cleaner.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import (
"github.com/pingcap/tidb-operator/pkg/label"
corev1 "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"
glog "k8s.io/klog"
)
Expand All @@ -31,7 +33,14 @@ const (
skipReasonPVCCleanerPVCNotHasLock = "pvc cleaner: pvc not has schedule lock"
skipReasonPVCCleanerPodWaitingForScheduling = "pvc cleaner: waiting for pod scheduling"
skipReasonPVCCleanerPodNotFound = "pvc cleaner: the corresponding pod of pvc has not been found"
skipReasonPVCCleanerWaitingForPVCSync = "pvc cleaner: waiting for pvc's meta info to be synced"
skipReasonPVCCleanerPVCNotBound = "pvc cleaner: the pvc is not bound"
skipReasonPVCCleanerPVCNotHasPodNameAnn = "pvc cleaner: pvc has no pod name annotation"
skipReasonPVCCleanerIsNotDeferDeletePVC = "pvc cleaner: pvc has not been marked as defer delete pvc"
skipReasonPVCCleanerPVCeferencedByPod = "pvc cleaner: pvc is still referenced by a pod"
skipReasonPVCCleanerNotFoundPV = "pvc cleaner: not found pv bound to pvc"
skipReasonPVCCleanerPVCHasBeenDeleted = "pvc cleaner: pvc has been deleted"
skipReasonPVCCleanerPVCNotFound = "pvc cleaner: not found pvc from apiserver"
skipReasonPVCCleanerPVCChanged = "pvc cleaner: pvc changed before deletion"
)

// PVCCleaner implements the logic for cleaning the pvc related resource
Expand All @@ -40,37 +49,161 @@ type PVCCleanerInterface interface {
}

type realPVCCleaner struct {
kubeCli kubernetes.Interface
podLister corelisters.PodLister
pvcControl controller.PVCControlInterface
pvcLister corelisters.PersistentVolumeClaimLister
pvLister corelisters.PersistentVolumeLister
pvControl controller.PVControlInterface
}

// NewRealPVCCleaner returns a realPVCCleaner
func NewRealPVCCleaner(
kubeCli kubernetes.Interface,
podLister corelisters.PodLister,
pvcControl controller.PVCControlInterface,
pvcLister corelisters.PersistentVolumeClaimLister) PVCCleanerInterface {
pvcLister corelisters.PersistentVolumeClaimLister,
pvLister corelisters.PersistentVolumeLister,
pvControl controller.PVControlInterface) PVCCleanerInterface {
return &realPVCCleaner{
kubeCli,
podLister,
pvcControl,
pvcLister,
pvLister,
pvControl,
}
}

func (rpc *realPVCCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string, error) {
if skipReason, err := rpc.cleanScheduleLock(tc); err != nil {
return skipReason, err
}

if !tc.Spec.EnablePVReclaim {
// disable PV reclaim, return directly.
return nil, nil
}
return rpc.reclaimPV(tc)
}

func (rpc *realPVCCleaner) reclaimPV(tc *v1alpha1.TidbCluster) (map[string]string, error) {
ns := tc.GetNamespace()
tcName := tc.GetName()

// for unit test
skipReason := map[string]string{}

selector, err := label.New().Instance(tc.GetLabels()[label.InstanceLabelKey]).Selector()
pvcs, err := rpc.listAllPVCs(tc)
if err != nil {
return skipReason, fmt.Errorf("cluster %s/%s assemble label selector failed, err: %v", ns, tcName, err)
return skipReason, err
}

pvcs, err := rpc.pvcLister.PersistentVolumeClaims(ns).List(selector)
for _, pvc := range pvcs {
pvcName := pvc.GetName()
l := label.Label(pvc.Labels)
if !(l.IsPD() || l.IsTiKV()) {
skipReason[pvcName] = skipReasonPVCCleanerIsNotPDOrTiKV
continue
}

if pvc.Status.Phase != corev1.ClaimBound {
// If pvc is not bound yet, it will not be processed
skipReason[pvcName] = skipReasonPVCCleanerPVCNotBound
continue
}

if pvc.DeletionTimestamp != nil {
// PVC has been deleted, skip it
skipReason[pvcName] = skipReasonPVCCleanerPVCHasBeenDeleted
continue
}

if len(pvc.Annotations[label.AnnPVCDeferDeleting]) == 0 {
DanielZhangQD marked this conversation as resolved.
Show resolved Hide resolved
// This pvc has not been marked as defer delete PVC, can't reclaim the PV bound to this PVC
skipReason[pvcName] = skipReasonPVCCleanerIsNotDeferDeletePVC
continue
}

// PVC has been marked as defer delete PVC, try to reclaim the PV bound to this PVC
podName, exist := pvc.Annotations[label.AnnPodNameKey]
if !exist {
// PVC has not pod name annotation, this is an unexpected PVC, skip it
skipReason[pvcName] = skipReasonPVCCleanerPVCNotHasPodNameAnn
continue
}

_, err := rpc.podLister.Pods(ns).Get(podName)
if err == nil {
// PVC is still referenced by this pod, can't reclaim PV
skipReason[pvcName] = skipReasonPVCCleanerPVCeferencedByPod
continue
}
if !errors.IsNotFound(err) {
return skipReason, fmt.Errorf("cluster %s/%s get pvc %s pod %s from local cache failed, err: %v", ns, tcName, pvcName, podName, err)
onlymellb marked this conversation as resolved.
Show resolved Hide resolved
}

// if pod not found in cache, re-check from apiserver directly to make sure the pod really not exist
_, err = rpc.kubeCli.CoreV1().Pods(ns).Get(podName, metav1.GetOptions{})
onlymellb marked this conversation as resolved.
Show resolved Hide resolved
if err == nil {
// PVC is still referenced by this pod, can't reclaim PV
skipReason[pvcName] = skipReasonPVCCleanerPVCeferencedByPod
continue
}
if !errors.IsNotFound(err) {
return skipReason, fmt.Errorf("cluster %s/%s get pvc %s pod %s from apiserver failed, err: %v", ns, tcName, pvcName, podName, err)
}

// Without pd or tikv pod reference this defer delete PVC, start to reclaim PV
pvName := pvc.Spec.VolumeName
pv, err := rpc.pvLister.Get(pvName)
if err != nil {
if errors.IsNotFound(err) {
skipReason[pvcName] = skipReasonPVCCleanerNotFoundPV
continue
}
return skipReason, fmt.Errorf("cluster %s/%s get pvc %s pv %s failed, err: %v", ns, tcName, pvcName, pvName, err)
}

if pv.Spec.PersistentVolumeReclaimPolicy != corev1.PersistentVolumeReclaimDelete {
err := rpc.pvControl.PatchPVReclaimPolicy(tc, pv, corev1.PersistentVolumeReclaimDelete)
onlymellb marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return skipReason, fmt.Errorf("cluster %s/%s patch pv %s to %s failed, err: %v", ns, tcName, pvName, corev1.PersistentVolumeReclaimDelete, err)
}
glog.Infof("cluster %s/%s patch pv %s to policy %s success", ns, tcName, pvName, corev1.PersistentVolumeReclaimDelete)
}

apiPVC, err := rpc.kubeCli.CoreV1().PersistentVolumeClaims(ns).Get(pvcName, metav1.GetOptions{})
if err != nil {
if errors.IsNotFound(err) {
skipReason[pvcName] = skipReasonPVCCleanerPVCNotFound
continue
}
return skipReason, fmt.Errorf("cluster %s/%s get pvc %s failed, err: %v", ns, tcName, pvcName, err)
}

if apiPVC.UID != pvc.UID || apiPVC.ResourceVersion != pvc.ResourceVersion {
skipReason[pvcName] = skipReasonPVCCleanerPVCChanged
continue
}

if err := rpc.pvcControl.DeletePVC(tc, pvc); err != nil {
return skipReason, fmt.Errorf("cluster %s/%s delete pvc %s failed, err: %v", ns, tcName, pvcName, err)
}
glog.Infof("cluster %s/%s reclaim pv %s success, pvc %s", ns, tcName, pvName, pvcName)
}
return skipReason, nil
}

func (rpc *realPVCCleaner) cleanScheduleLock(tc *v1alpha1.TidbCluster) (map[string]string, error) {
ns := tc.GetNamespace()
tcName := tc.GetName()
// for unit test
skipReason := map[string]string{}

pvcs, err := rpc.listAllPVCs(tc)
if err != nil {
return skipReason, fmt.Errorf("cluster %s/%s list pvc failed, selector: %s, err: %v", ns, tcName, selector, err)
return skipReason, err
}

for _, pvc := range pvcs {
Expand Down Expand Up @@ -98,8 +231,8 @@ func (rpc *realPVCCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string, e

podName, exist := pvc.Annotations[label.AnnPodNameKey]
if !exist {
// waiting for pvc's meta info to be synced
skipReason[pvcName] = skipReasonPVCCleanerWaitingForPVCSync
// PVC has no pod name annotation, this is an unexpected PVC, skip it
skipReason[pvcName] = skipReasonPVCCleanerPVCNotHasPodNameAnn
continue
}

Expand Down Expand Up @@ -136,6 +269,22 @@ func (rpc *realPVCCleaner) Clean(tc *v1alpha1.TidbCluster) (map[string]string, e
return skipReason, nil
}

func (rpc *realPVCCleaner) listAllPVCs(tc *v1alpha1.TidbCluster) ([]*corev1.PersistentVolumeClaim, error) {
ns := tc.GetNamespace()
tcName := tc.GetName()

selector, err := label.New().Instance(tc.GetLabels()[label.InstanceLabelKey]).Selector()
if err != nil {
return nil, fmt.Errorf("cluster %s/%s assemble label selector failed, err: %v", ns, tcName, err)
}

pvcs, err := rpc.pvcLister.PersistentVolumeClaims(ns).List(selector)
if err != nil {
return nil, fmt.Errorf("cluster %s/%s list pvc failed, selector: %s, err: %v", ns, tcName, selector, err)
}
return pvcs, nil
}

var _ PVCCleanerInterface = &realPVCCleaner{}

type FakePVCCleaner struct {
Expand Down
Loading