1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[Kubernetes] ReleasedなPVは削除しないで使い回そう

Last updated at Posted at 2024-02-02

概要

Status: ReleasedなPVを再クレームしたい時はPVを削除して再作成しようねという記事を見かけるが、「PVは使いまわせるよ」と言いたくて執筆しました。

TL;DR

「PVCを削除したらPVがReleased Statusになった。同じPVCに再度Bindしたい」
という時はPV.spec.claimRef.uidを削除すると要件を満たせます。
PVのステータスがReleased -> Availableに遷移し、前にBindしていたPVC専用(予約済み)のPVとなります。
また、.spec.claimRefごと削除すると未予約なAvailable PVになります。

動作検証

ToDo

  • storageClass,PV,PVC-1を作成してBoundさせる
  • PVC-1を削除するとPVのステータスがReleasedになることを確認する
  • PVのclaimRefを見てみる
  • PVのclaimRefを編集する
  • PVがAvailableに戻っていることを確認する <- 検証したいこと1
  • 同じstorageClassを参照するPVC-2を作る
  • (PVはPVC-1専用になっているため)PVC-2にはBindされないことを確認する <- 検証したいこと2
  • PVC-1を作成すると、PVがBindされることを確認する <- 検証したいこと3

storageClass,PV-1,PVC-1を作成してBoundさせる

storageClass manifest
❯ cat sc.yaml 
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: example-sc
provisioner: kubernetes.io/no-provisioner 
reclaimPolicy: Retain
volumeBindingMode: Immediate
persistentVolumeClaim manifest
❯ cat pvc.yaml 
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: example-pvc
spec:
  storageClassName: example-sc
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
persistentVolume manifest
❯ cat pv.yaml 
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  storageClassName: example-sc
  capacity:
    storage: 10Mi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  local:
    path: /mnt/localdisk
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - nwk01
ls        
pv.yaml  pvc.yaml  sc.yaml

❯ k apply -f .
persistentvolume/example-pv created
persistentvolumeclaim/example-pvc created
storageclass.storage.k8s.io/example-sc created

❯ k get sc,pv,pvc
NAME                                     PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/example-sc   kubernetes.io/no-provisioner   Retain          Immediate           false                  58s

NAME                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/example-pv   10Mi       RWO            Retain           Bound    exam/example-pvc   example-sc              58s

NAME                                STATUS   VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/example-pvc   Bound    example-pv   10Mi       RWO            example-sc     58s

PVC-1を削除するとPVのステータスがReleasedになることを確認する

❯ k delete pvc example-pvc 
persistentvolumeclaim "example-pvc" deleted

❯ k get pv,pvc
NAME                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/example-pv   10Mi       RWO            Retain           Released   exam/example-pvc   example-sc              3m8s

PVのclaimRefを見てみる

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
  ...
spec:
  ...
  claimRef:
    apiVersion: v1
    kind: PersistentVolumeClaim
    name: example-pvc
    namespace: exam
    resourceVersion: "32159631"
    uid: f6915120-f26f-4ab8-9bfc-f26d48c35391

PVを作成した時には存在しなかったclaimRefという項目が生えています。
kubernetes(kube-controller-manager)はこの項目を閲覧・操作することでPVとPVCの紐付けを管理しています。

PVのclaimRefを編集する

$ k edit pv example-pv
# .spec.claimRef.uid行を削除する
persistentvolume/example-pv edited

または

# json formatで取得しつつ、削除したいリソースパスを確認
❯ k get pv example-pv -ojson | jq ".spec.claimRef.uid" 
"f6915120-f26f-4ab8-9bfc-f26d48c35391"

❯ k patch pv example-pv --type json -p '[{"op": "remove", "path": "/spec/claimRef/uid"}]'
persistentvolume/example-pv patched

PVがAvailableに戻っていることを確認する

❯ k get pv                 
NAME         CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
example-pv   10Mi       RWO            Retain           Available   exam/example-pvc   example-sc              27m

Availableになったので再利用できそうです。
しかしClaimにはexam/example-pvcの名前が残っていますね。

同じstorageClassを参照するPVC-2を作る

persistentVolumeClaim-2 manifest
❯ cat pvc2.yaml 
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: example-pvc2
spec:
  storageClassName: example-sc
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Mi
❯ k apply -f pvc2.yaml 
persistentvolumeclaim/example-pvc2 created

❯ k get pvc,pv
NAME                                 STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/example-pvc2   Pending                                      example-sc     3m47s

NAME                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/example-pv   10Mi       RWO            Retain           Available   exam/example-pvc   example-sc              33m

example-pvはAvailableであり、example-pvexample-pvc2は同じstorageClassを参照しているため、通常であればBindされます。
しかし、3分経ってもBindされません。example-pvexample-pvcに予約されているためです。

PVC-1を作成すると、PVがBindされることを確認する

# 最初に作ったpvc.yamlをそのまま適用する
❯ k apply -f pvc.yaml 
persistentvolumeclaim/example-pvc created

❯ k get pv,pvc
NAME                          CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/example-pv   10Mi       RWO            Retain           Bound    exam/example-pvc   example-sc              39m

NAME                                 STATUS    VOLUME       CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/example-pvc    Bound     example-pv   10Mi       RWO            example-sc     6s
persistentvolumeclaim/example-pvc2   Pending                                          example-sc     10m

想定通り、example-pvc2を差し置いてexample-pvcexample-pvがBindされました。検証完了!


ここから下はオマケ

PV Controllerのコードを読んでみる

> git clone https://github.com/kubernetes/kubernetes --depth 1
# https://github.com/kubernetes/kubernetes/tree/2c5105e7b80f97ab2da0d47a6db6875aec38331c

PV Controllerとは?

  • kubernetesコアコンポーネントの1つにkube-controller-managerというものがある
  • kube-controller-managerが複数のコントローラを実行し、クラスタ内のリソースをいい感じに制御している
  • そのうちの1つにPV Controllerというものがあり、PV,PVCリソースを制御している

つらつらと書く

// cmd/kube-controller-manager/app/core.go#L35
func main() {
	command := app.NewControllerManagerCommand() // メイン処理が記載されている
	code := cli.Run(command)
	os.Exit(code)
}


// cmd/kube-controller-manager/app/controllermanager.go#L104
func NewControllerManagerCommand() *cobra.Command {
    ...
	cmd := &cobra.Command{
		RunE: func(cmd *cobra.Command, args []string) error {
            ...
  			c, err := s.Config(KnownControllers(), ControllersDisabledByDefault(), ControllerAliases()) // ここでコントローラ名一覧(40個以上)を取得する
            ...
			return Run(context.Background(), c.Complete()) // <-- メイン処理


// cmd/kube-controller-manager/app/controllermanager.go#L182
func Run(ctx context.Context, c *config.CompletedConfig) error {
    ...
	run := func(ctx context.Context, controllerDescriptors map[string]*ControllerDescriptor) {
        ...
        if err := StartControllers(ctx, controllerContext, controllerDescriptors, unsecuredMux, healthzHandler); err != nil { // main

            
// cmd/kube-controller-manager/app/controllermanager.go#L626
func StartControllers(ctx context.Context, controllerCtx ControllerContext, controllerDescriptors map[string]*ControllerDescriptor,
	unsecuredMux *mux.PathRecorderMux, healthzHandler *controllerhealthz.MutableHealthzHandler) error {
        ...
		check, err := StartController(ctx, controllerCtx, serviceAccountTokenControllerDescriptor, unsecuredMux) // main


// cmd/kube-controller-manager/app/controllermanager.go#L680
func StartController(ctx context.Context, controllerCtx ControllerContext, controllerDescriptor *ControllerDescriptor,
	unsecuredMux *mux.PathRecorderMux) (healthz.HealthChecker, error) {
    ...
	initFunc := controllerDescriptor.GetInitFunc() // ここで取得したinitFuncで各コントローラを実行している
	ctrl, started, err := initFunc(klog.NewContext(ctx, klog.LoggerWithName(logger, controllerName)), controllerCtx, controllerName)

// cmd/kube-controller-manager/app/core.go#L329
// newPersistentVolumeBinderControllerDescriptorというdescriptorを通じて、initFuncとして呼び出される
func startPersistentVolumeBinderController(ctx context.Context, controllerContext ControllerContext, controllerName string) (controller.Interface, bool, error) {
    // ここで取得したInformerを利用して各リソースの変更を監視・検知する
	params := persistentvolumecontroller.ControllerParameters{
		KubeClient:                controllerContext.ClientBuilder.ClientOrDie("persistent-volume-binder"),
		SyncPeriod:                controllerContext.ComponentConfig.PersistentVolumeBinderController.PVClaimBinderSyncPeriod.Duration,
		VolumePlugins:             plugins,
		Cloud:                     controllerContext.Cloud,
		ClusterName:               controllerContext.ComponentConfig.KubeCloudShared.ClusterName,
		VolumeInformer:            controllerContext.InformerFactory.Core().V1().PersistentVolumes(),
		ClaimInformer:             controllerContext.InformerFactory.Core().V1().PersistentVolumeClaims(),
		ClassInformer:             controllerContext.InformerFactory.Storage().V1().StorageClasses(),
		PodInformer:               controllerContext.InformerFactory.Core().V1().Pods(),
		NodeInformer:              controllerContext.InformerFactory.Core().V1().Nodes(),
		EnableDynamicProvisioning: controllerContext.ComponentConfig.PersistentVolumeBinderController.VolumeConfiguration.EnableDynamicProvisioning,
	}
	volumeController, volumeControllerErr := persistentvolumecontroller.NewController(ctx, params)
    ...
	go volumeController.Run(ctx)


ここまではkube-controller-managerの話
======
ここからPV controllerの話


// pkg/controller/volume/persistentvolume/pv_controller_base.go#L302
func (ctrl *PersistentVolumeController) Run(ctx context.Context) {
    ...
    // PVを管理するvolumeWorkerとPVCを管理するclaimWorkerを起動する
	go wait.UntilWithContext(ctx, ctrl.volumeWorker, time.Second) 
	go wait.UntilWithContext(ctx, ctrl.claimWorker, time.Second)

// pkg/controller/volume/persistentvolume/pv_controller_base.go#L492
func (ctrl *PersistentVolumeController) volumeWorker(ctx context.Context) {
	logger := klog.FromContext(ctx)
	workFunc := func(ctx context.Context) bool {
    keyObj, quit := ctrl.volumeQueue.Get() // queueからkeyを取得する
       ...
       volume, err := ctrl.volumeLister.Get(name) // kubernetes上のVolumeリソースを取得する
       if err == nil {
			ctrl.updateVolume(ctx, volume) // 同期処理
	}
    // ひたすらworkFuncを実行し続ける
	for {
		if quit := workFunc(ctx); quit {
			logger.Info("Volume worker queue shutting down")
			return
		}
	}
}

// pkg/controller/volume/persistentvolume/pv_controller_base.go#L202
func (ctrl *PersistentVolumeController) updateVolume(ctx context.Context, volume *v1.PersistentVolume) {
    err = ctrl.syncVolume(ctx, volume)

ようやく辿り着いた

// pkg/controller/volume/persistentvolume/pv_controller.go#L561
func (ctrl *PersistentVolumeController) syncVolume(ctx context.Context, volume *v1.PersistentVolume) error {
    if volume.Spec.ClaimRef == nil {
		if _, err := ctrl.updateVolumePhase(ctx, volume, v1.VolumeAvailable, ""); err != nil {
			return err
		}
		return nil
	} else /* pv.Spec.ClaimRef != nil */ {
		// Volume is bound to a claim.
		if volume.Spec.ClaimRef.UID == "" {
			// The PV is reserved for a PVC; that PVC has not yet been
			// bound to this PV; the PVC sync will handle it.
			logger.V(4).Info("Synchronizing PersistentVolume, volume is pre-bound to claim", "PVC", klog.KRef(volume.Spec.ClaimRef.Namespace, volume.Spec.ClaimRef.Name), "volumeName", volume.Name)
			if _, err := ctrl.updateVolumePhase(ctx, volume, v1.VolumeAvailable, ""); err != nil {
				// Nothing was saved; we will fall back into the same
				// condition in the next call to this method
				return err
			}
			return nil
		}
  • もしvolume.Spec.ClaimRef全部がない ( --> 作成直後 or ClaimRefを削除した状態 ) ならば PVのStatusをAvailableにする
  • もしvolume.Spec.ClaimRefはあるけどuidがない場合は、ClaimRef.name,ClaimRef.namespaceで指定されているPVにpre-boundした状態にする
    • StatusはAvailable
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?