<p>I am trying to create a deployment in GKE that uses multiple replicas. I have some static data which I want to have available in every pod. This data will not be updated, no write is required.</p>

<p>I decided to use a PV with a corresponding PVC with the ReadOnlyMany storage class. The thing is, I do not know how to actually transfer my data to the volume - since it is read-only. I tried using </p>

<pre><code>gcloud compute scp /local/path instance:/remote/path
</code></pre>

<p>but of course, I get a permission error. I then tried creating a new PV via the console. I attached it to a VM with</p>

<pre><code>gcloud compute instances attach disk
</code></pre>

<p>mounted and formatted the disk, transfered my data, unmounted the disk, detached it from the VM and finally created a PVC following <a href="https://cloud.google.com/kubernetes-engine/docs/how-to/persistent-volumes/preexisting-pd" rel="noreferrer">the documentation</a>. I changed the storage class to ReadOnlyMany, the only difference.</p>

<p>But still, when I'm trying to scale my deployment to more than one replicas I get an error saying the disk is already attached to another node.</p>

<p>So, how can I create a volume that is to be used in ReadOnlyMany and populate the disk with data? Or is there a better approach since no write is required?</p>

<p>Thanks in advance</p>

Nikolaos Paschos
<p>We can simplify a bit the whole process. On <strong>GKE</strong> you don't actually need to manually create a <code>PV</code> based on <strong>GCE Persistent Disk</strong>. All you need is to define proper <code>PVC</code> which can look as follows:</p>
<pre><code>kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: webserver-content-claim
spec:
  accessModes: [ReadOnlyMany]
  resources:
    requests:
      storage: 5Gi
</code></pre>
<p>Keep in mind that you cannot define access modes in <code>PVC</code> in a sense of putting there any specific constraints. What you basically do is simply requesting a storage that supports this particular access mode. Note that it's in a form of a list which means you may provide many different access modes that you want your <code>PV</code> to support. I explained it more in detail in <a href="https://stackoverflow.com/a/62545427/11714114">this</a> answer. But the key point here is that <strong>by setting <code>ReadOnlyMany</code> access mode in <code>PVC</code> definition you only request for a volume which supports this type of access but it doesn't mean it doesn't support other modes.</strong></p>
<p>If you don't specify <code>readOnly: true</code> in <code>volumes</code> section of your <code>Pod</code> template as @Ievgen Goichuk suggested in his answer, by default it is mounted in <code>rw</code> mode. Since <strong>GCE Persistent Disk</strong> doesn't support <code>ReadWriteMany</code> access mode, such volume cannot be mounted by other <code>Pods</code>, scheduled on different <code>nodes</code> once it is already mounted in <code>rw</code> mode by one <code>Pod</code>, scheduled on one particular <code>node</code>. Mounting it in <code>rw</code> mode by this <code>Pod</code> is possible because <strong>GCE Persistent Disk</strong> supports also <code>ReadWriteOnce</code> access mode, which according to <a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes" rel="nofollow noreferrer">the official docs</a> menas <em>&quot;the volume can be mounted as read-write by a single node&quot;</em>. That's why <code>Pods</code> scheduled on other nodes are unable to mount it.</p>
<p><strong>But let's move on to the actual solution.</strong></p>
<p>Once you create the above mentioned <code>PVC</code>, you'll see that the corresponding <code>PV</code> has also been created (<code>kubectl get pv</code>) and its <code>STATUS</code> is <code>Bound</code>.</p>
<p>Now we only need to pre-populate it somehow before we start using it in <code>ReadOnlyMany</code> access mode. I will share what works best for me.</p>
<p>If you've already uploaded your data on one of your <strong>Compute Engine</strong> instances, forming the <strong>node-pool</strong> of your worker nodes, you can skip the next step.</p>
<p>I assume you have <strong>gcloud</strong> installed on your local machine.</p>
<pre><code>gcloud compute scp /local/path instance:/remote/path
</code></pre>
<p>is the correct way to achieve that. @Nikolaos Paschos, if you get the <code>permission denied</code> error, it probably means the <code>/remote/path</code> you defiend is some restricted directory that you don't have access to as non-root user. You'll see this error if you try to copy something from your local filesystem e.g. to <code>/etc</code> directory on the remote machine. The safest way is to copy your files to your home directory to which you have access to:</p>
<pre><code>gcloud compute scp --recurse /home/&lt;username&gt;/data/* &lt;instance-name&gt;:~ --zone &lt;zone-name&gt;
</code></pre>
<p>Use <code>--recurse</code> option if you want to copy all the files and directories with their content from the source directory.</p>
<p>Once our data is uploaded to one of our worker nodes, we need to copy it to our newly created <code>PersistentVolume</code>. It can be done in a few different ways.</p>
<p>I decided to use for it a temporary <code>Pod</code> with <a href="https://kubernetes.io/docs/concepts/storage/volumes/#local" rel="nofollow noreferrer">local</a> volume.</p>
<p>To make our data, already present on one of <strong>GKE worker nodes</strong>, available also to our temporary <code>Pod</code>, let's create the following:</p>
<p><code>storage-class-local.yaml</code>:</p>
<pre><code>apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
</code></pre>
<p><code>pv-local.yaml</code>:</p>
<pre><code>apiVersion: v1
kind: PersistentVolume
metadata:
  name: local-pv
spec:
  capacity:
    storage: 10Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /home/&lt;username&gt;
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - &lt;gke-node-name&gt;
</code></pre>
<p>and <code>pvc-local.yaml</code>:</p>
<pre><code>apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: myclaim
spec:
  accessModes:
    - ReadWriteOnce
  volumeMode: Filesystem
  resources:
    requests:
      storage: 10Gi
  storageClassName: local-storage
</code></pre>
<p>In the next step let's create our temporary <code>Pod</code> which will enable us to copy our data from <code>node</code>, mounted into <code>Pod</code> as a local volume, to <code>PV</code> based on <strong>GCE Persistent Disk</strong>. It's definion may look as follows:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
    - name: myfrontend
      image: nginx
      volumeMounts:
      - mountPath: &quot;/mnt/source&quot;
        name: local-volume
      - mountPath: &quot;/mnt/destination&quot;
        name: gce-pd-volume
  volumes:
    - name: local-volume
      persistentVolumeClaim:
        claimName: myclaim
    - name: gce-pd-volume
      persistentVolumeClaim:
        claimName: webserver-content-claim
</code></pre>
<p>When the <code>Pod</code> is up and running, we can attach to it by:</p>
<pre><code>kubectl exec -ti mypod -- /bin/bash
</code></pre>
<p>And copy our files:</p>
<pre><code>cp -a /mnt/source/* /mnt/destination/
</code></pre>
<p>Now we can delete our temporary pod, local pv and pvc. Our <code>PersistentVolume</code> is already pre-populated with data and can be moutned in <code>ro</code> mode.</p>
<p>In order to test it we can ran the following <code>Deployment</code>:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
  labels:
    app: nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.14.2
        ports:
        - containerPort: 80
        volumeMounts:
        - mountPath: &quot;/usr/share/nginx/html&quot;
          name: webserver-content
      volumes:
      - name: webserver-content
        persistentVolumeClaim:
          claimName: webserver-content-claim
          readOnly: true ### don't forget about setting this option
</code></pre>
