DEVOPSFixes
Home/Blog/mount-gcs-bucket-as-pvc-in-gke-cluster
GCPKubernetesGKEGCS BucketStoragePersistent VolumePVCGCS FuseWorkload IdentityCSI Driver

How to Mount a GCS Bucket as a Persistent Volume in GKE (Step-by-Step)

Published
6 June 2026
Read time
7 min read
Environment
Google Kubernetes Engine (GKE) Kubernetes v1.33+ / Google Cloud Storage (GCS) / GCS Fuse CSI Driver

Environment: Google Kubernetes Engine (GKE) · Google Cloud Storage (GCS) · GCS Fuse CSI Driver


Who Is This For?

If you are a DevOps engineer or platform engineer running workloads on GKE and need your pods to read and write files stored in a Google Cloud Storage (GCS) bucket — without spinning up a Filestore instance or managing a custom NFS setup — this guide is for you.

By the end of this tutorial, you will have a GCS bucket mounted as a Persistent Volume Claim (PVC) inside your Kubernetes namespace, accessible to any pod, using the GCS Fuse CSI driver and Workload Identity.


The Problem

Applications running in Kubernetes (GKE) often need access to files stored in a Google Cloud Storage (GCS) bucket. However, pods cannot directly use a GCS bucket as a filesystem — GCS is an object store, not a POSIX filesystem.

To enable applications to read and write data as if it were a normal directory, the GCS bucket must be mounted as a Persistent Volume Claim (PVC) using the GCS Fuse CSI driver, which handles the translation layer between the object store and the filesystem interface.


Step 1: Enable GCS Fuse CSI Driver in GKE

To mount a Google Cloud Storage (GCS) bucket in a Kubernetes (GKE) cluster, the GCS Fuse CSI driver must be enabled as a cluster add-on. This is a one-time cluster-level setup and does not affect existing node pools, pods, or deployments.

Check if GCS Fuse CSI Driver is Already Enabled

Run the following command to verify whether the driver is already active in your cluster:

gcloud container clusters describe fl-data-stag-as1 \
  --region asia-south1 \
  --project fl-direct-vod-control-plane | grep gcsfuse

If you see gcsfuseCsiDriver in the output, the driver is already enabled and you can skip to Step 2.

If the driver is not enabled, run:

gcloud container clusters update fl-data-stag-as1 \
  --update-addons GcsFuseCsiDriver=ENABLED \
  --region asia-south1 \
  --project fl-direct-vod-control-plane

After running this command, the GKE cluster enters a brief update state. This process is non-disruptive — it only enables the add-on.

Verify the GCS Fuse Pods

Once the update is complete, check the kube-system namespace. You should see GCS Fuse CSI driver pods running as a DaemonSet — one pod per node.

GCS Fuse CSI driver DaemonSet pods running in kube-system namespace

GCS Fuse Driver setup is complete. Now move on to the core configuration.


Step 2: Create a Google Service Account (GSA)

To allow Kubernetes workloads to access a GCS bucket, you first need to create a Google Service Account (GSA) in the GCP project where your GKE cluster is running. This account will be used as the identity for bucket access.

Replace <your-project-name> with your actual GCP project.

Create the Service Account

  1. Open the Google Cloud Console.
  2. Select your project <your-project-name>.
  3. Navigate to: IAM & Admin → Service Accounts
  4. Click + CREATE SERVICE ACCOUNT.
  5. Enter the following details:
    • Service account name: gcs-bucket-access-sa
    • Service account ID: gcs-bucket-access-sa (auto-generated)
    • Description: Service account for mounting a GCS bucket using GCS Fuse
  6. Click CREATE AND CONTINUE.
  7. Skip role assignment for now — permissions will be granted at the bucket level.
  8. Click DONE.

Why skip role assignment here? Granting access at the bucket level (instead of the project level) follows the principle of least privilege — the service account can only access the specific bucket you choose.


Step 3: Grant Permission to the GCS Bucket

Now grant the newly created GSA access to your target GCS bucket. This allows the Kubernetes workload to read and write objects when the bucket is mounted via GCS Fuse.

  1. Open the Google Cloud Console.
  2. Navigate to Cloud Storage → Buckets.
  3. Select your target bucket.
  4. Go to the Permissions tab.
  5. Click GRANT ACCESS.
  6. In the New principals field, enter the email of the service account created in the previous step.
  7. Assign the role: Storage Object Admin

This role grants the workload full read/write access to objects inside the bucket.

Note: If your bucket is in the same project as your GKE cluster, you can grant the permission directly to the service account as shown. If the bucket is in a different project, make sure you are granting access in the project that owns the bucket.

GCS bucket IAM permissions tab showing Storage Object Admin role assigned to the service account


Step 4: Create a Kubernetes Service Account (KSA) with Workload Identity

Workload Identity is the recommended way to let GKE pods authenticate to Google Cloud services without using service account key files. It links a Kubernetes Service Account (KSA) to a Google Service Account (GSA).

Why Workload Identity Instead of a Key File?

Using a JSON key file requires storing a secret in your cluster, rotating it manually, and managing its lifecycle. Workload Identity eliminates all of this — the pod automatically receives short-lived credentials scoped to the GSA.

Create the Kubernetes Service Account

Create the KSA in the namespace where your application will run:

apiVersion: v1
kind: ServiceAccount
metadata:
  name: gcs-fuse-ksa
  namespace: <your-cluster-namespace>
  annotations:
    iam.gke.io/gcp-service-account: <your-gcp-service-account-email>

Apply the configuration:

kubectl apply -f k8s-svc-account.yaml

Bind the KSA to the GSA

Run the following gcloud command to create the Workload Identity binding:

gcloud iam service-accounts add-iam-policy-binding \
  <your-gcp-service-account-email> \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:<your-project-id>.svc.id.goog[<your-namespace>/<ksa-service-account-name>]" \
  --project=<your-project-id>

Once this runs successfully, the binding is complete. Pods using gcs-fuse-ksa can now securely access the GCS bucket without any key files.


Step 5: Create the Persistent Volume (PV)

Why Do We Need a PV and PVC for a GCS Bucket?

You might ask: if we're just mounting a cloud bucket, why do we need Kubernetes storage abstractions?

Kubernetes does not allow pods to mount storage directly. It uses a layered abstraction:

  • PersistentVolume (PV) — Represents the actual storage resource. Here, it points to the GCS bucket via the GCS Fuse CSI driver.
  • PersistentVolumeClaim (PVC) — A request for storage from a pod or deployment.
  • Pod / Deployment — Mounts the PVC as a filesystem path inside the container.

This abstraction means your application code doesn't need to know anything about GCS — it just reads and writes files at a mount path.

PV Manifest

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-flpvodsrcstag
spec:
  accessModes:
    - ReadWriteMany
  capacity:
    storage: 10Gi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: ""
  claimRef:
    namespace: <your-namespace>
    name: pvc-flpvodsrcstag
  csi:
    driver: gcsfuse.csi.storage.gke.io
    volumeHandle: <your-bucket-name>
    volumeAttributes:
      gcsfuseLoggingSeverity: warning
      mountOptions: "implicit-dirs"

implicit-dirs — This mount option is required if your bucket contains objects with path-style prefixes (e.g., folder/file.txt) that were not explicitly created as directory objects. Without it, those paths may appear empty.


Step 6: Create the Persistent Volume Claim (PVC)

The PVC requests the storage defined by the PV and makes it available for pods to mount.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-flpvodsrcstag
  namespace: <your-namespace>
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 10Gi
  storageClassName: ""
  volumeName: pv-flpvodsrcstag

Apply both the PV and PVC:

kubectl apply -f pv.yaml
kubectl apply -f pvc.yaml

Verify the PVC is bound:

kubectl get pvc -n <your-namespace>

The STATUS column should show Bound. If it shows Pending, check that the PV claimRef namespace and name exactly match the PVC.


Step 7: Verify the Mount Using a Sample Deployment

Once the PV and PVC are created, deploy a simple container to verify the GCS bucket is successfully mounted inside the pod.

# =============================================================================
# GCS Bucket Mount Verification Deployment
# Bucket    : <your-bucket-name>
# PVC       : pvc-flpvodsrcstag
# Namespace : <your-namespace>
# KSA       : gcs-fuse-ksa
# =============================================================================

apiVersion: apps/v1
kind: Deployment
metadata:
  name: gcs-bucket-verification
  namespace: <your-namespace>
  labels:
    app: gcs-bucket-verification
spec:
  replicas: 1
  selector:
    matchLabels:
      app: gcs-bucket-verification
  template:
    metadata:
      labels:
        app: gcs-bucket-verification
      annotations:
        gke-gcsfuse/volumes: "true"
    spec:
      serviceAccountName: gcs-fuse-ksa
      terminationGracePeriodSeconds: 10

      containers:
        - name: bucket-browser
          image: alpine:3.19
          imagePullPolicy: IfNotPresent

          command: ["/bin/sh", "-c"]
          args:
            - |
              echo "============================================"
              echo " GCS Bucket Mount Verification"
              echo "============================================"

              if [ -d "/mnt/gcs-data" ] && ls /mnt/gcs-data > /dev/null 2>&1; then
                echo "STATUS : CONNECTED"
                echo "PATH   : /mnt/gcs-data"
              else
                echo "STATUS : FAILED - Bucket not accessible"
              fi

              echo "============================================"
              tail -f /dev/null

          volumeMounts:
            - name: gcs-bucket
              mountPath: /mnt/gcs-data

      volumes:
        - name: gcs-bucket
          persistentVolumeClaim:
            claimName: pvc-flpvodsrcstag

Apply and check:

kubectl apply -f verification-deployment.yaml
kubectl logs deployment/gcs-bucket-verification -n <your-namespace>

Once successfully mounted, the output will look like this:

GCS Fuse verification deployment pod showing STATUS CONNECTED in logs

You can also shell into the pod to inspect the bucket contents directly:

kubectl exec -it <pod-name> -n <your-namespace> -- /bin/sh
ls /mnt/gcs-data

Terminal output showing GCS bucket files listed inside the pod at /mnt/gcs-data


Troubleshooting Common Issues

PVC Stuck in Pending State

  • Confirm the PV claimRef.name and claimRef.namespace exactly match the PVC metadata.name and metadata.namespace.
  • Ensure storageClassName: "" is set on both the PV and PVC — omitting this causes Kubernetes to look for a dynamic provisioner.

Pod Stuck in Init or ContainerCreating

  • Check if the gke-gcsfuse/volumes: "true" annotation is present on the pod template.
  • Confirm the GCS Fuse DaemonSet pods are running on the node: kubectl get pods -n kube-system | grep gcsfuse

Permission Denied When Accessing Bucket Files

  • Verify the Workload Identity binding was created correctly by running: gcloud iam service-accounts get-iam-policy <your-gcp-service-account-email>
  • Confirm the GSA has the Storage Object Admin role on the target bucket, not just at the project level.

Implicit Directories Not Visible

  • Add mountOptions: "implicit-dirs" under volumeAttributes in the PV spec. This is required for buckets with objects uploaded without explicit folder creation.

Summary

Here is a quick recap of the full setup flow:

  1. Enable the GCS Fuse CSI driver on your GKE cluster
  2. Create a Google Service Account (GSA) for bucket access
  3. Grant Storage Object Admin role to the GSA on the target bucket
  4. Create a Kubernetes Service Account (KSA) and bind it to the GSA via Workload Identity
  5. Create a PersistentVolume (PV) pointing to the GCS bucket
  6. Create a PersistentVolumeClaim (PVC) to request that volume
  7. Deploy a workload using the KSA and PVC to verify the mount

That's it — the setup is complete!
If you found this blog helpful, feel free to leave a comment.

Found this helpful?
Browse more fixes or share this post with your team.
All Fixes

Comments

0

Got the same issue? Fixed it differently? Share below.

Loading comments...

Leave a comment
Be kind — this is a community space