<p>Environment information:</p>
<pre><code>Computer detail: One master node and four slave nodes. All are CentOS Linux release 7.8.2003 (Core).
Kubernetes version: v1.18.0.
Zero to JupyterHub version: 0.9.0.
Helm version: v2.11.0
</code></pre>
<p>I recently try to deploy an online code environment(like Google Colab) in new lab servers via Zero to JupyterHub. Unfortunately, I failed to deploy Persistent Volume(PV) for JupyterHub and I got a failure message such below:</p>
<pre><code>Events:
  Type     Reason            Age                From               Message
  ----     ------            ----               ----               -------
  Warning  FailedScheduling  4s (x27 over 35m)  default-scheduler  running &quot;VolumeBinding&quot; filter plugin for pod &quot;hub-7b9cbbcf59-747jl&quot;: pod has unbound immediate PersistentVolumeClaims
</code></pre>
<p>I followed the installing process by the <a href="https://zero-to-jupyterhub.readthedocs.io/en/latest/setup-jupyterhub/setup-jupyterhub.html" rel="nofollow noreferrer">tutorial of JupyterHub</a>, and I was used Helm to install JupyterHub on k8s. That config file such below:</p>
<p><code>config.yaml</code></p>
<pre class="lang-yaml prettyprint-override"><code>proxy:
  secretToken: &quot;2fdeb3679d666277bdb1c93102a08f5b894774ba796e60af7957cb5677f40706&quot;
singleuser:
  storage:
    dynamic:
      storageClass: local-storage
</code></pre>
<p>Here, I was config a <code>local-storage</code> for JupyterHub, the <code>local-storage</code> was observed k8s:  <a href="https://kubernetes.io/docs/concepts/storage/storage-classes/#local" rel="nofollow noreferrer">Link</a>. And its <code>yaml</code> file
such like that:</p>
<pre class="lang-yaml prettyprint-override"><code>apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
</code></pre>
<p>Then I use <code>kubectl get storageclass</code> to check it work, I got the message below:</p>
<pre><code>NAME            PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-storage   kubernetes.io/no-provisioner   Delete          WaitForFirstConsumer   false                  64m
</code></pre>
<p>So, I thought I deployed a storage for JupyterHub, but I so naive. I am so disappointed about that because my other Pods(JupyterHub) are all running. And I have been search some solutions so long, but also failed.</p>
<p>So now, my problems are:</p>
<ol>
<li><p>What is the true way to solve the PV problems? (Better using local storage.)</p>
</li>
<li><p>Is the local storage way will using other nodes disk not only master?</p>
</li>
<li><p>In fact, my lab had a could storage service, so if Q2 answer is No, and how I using my lab could storage service to deploy PV?</p>
</li>
</ol>
<hr />
<p>I had been addressed above problem with @Arghya Sadhu's solution. But now, I got a new problem is the Pod <code>hub-db-dir</code> also pending, it result my service <code>proxy-public</code> pending.</p>
<p>The description of <code>hub-db-dir</code> such below:</p>
<pre><code>Name:           hub-7b9cbbcf59-jv49z
Namespace:      jhub
Priority:       0
Node:           &lt;none&gt;
Labels:         app=jupyterhub
                component=hub
                hub.jupyter.org/network-access-proxy-api=true
                hub.jupyter.org/network-access-proxy-http=true
                hub.jupyter.org/network-access-singleuser=true
                pod-template-hash=7b9cbbcf59
                release=jhub
Annotations:    checksum/config-map: c20a64c7c9475201046ac620b057f0fa65ad6928744f7d265bc8705c959bce2e
                checksum/secret: 1beaebb110d06103988476ec8a3117eee58d97e7dbc70c115c20048ea04e79a4
Status:         Pending
IP:
IPs:            &lt;none&gt;
Controlled By:  ReplicaSet/hub-7b9cbbcf59
Containers:
  hub:
    Image:      jupyterhub/k8s-hub:0.9.0
    Port:       8081/TCP
    Host Port:  0/TCP
    Command:
      jupyterhub
      --config
      /etc/jupyterhub/jupyterhub_config.py
      --upgrade-db
    Requests:
      cpu:      200m
      memory:   512Mi
    Readiness:  http-get http://:hub/hub/health delay=0s timeout=1s period=10s #success=1 #failure=3
    Environment:
      PYTHONUNBUFFERED:        1
      HELM_RELEASE_NAME:       jhub
      POD_NAMESPACE:           jhub (v1:metadata.namespace)
      CONFIGPROXY_AUTH_TOKEN:  &lt;set to the key 'proxy.token' in secret 'hub-secret'&gt;  Optional: false
    Mounts:
      /etc/jupyterhub/config/ from config (rw)
      /etc/jupyterhub/cull_idle_servers.py from config (rw,path=&quot;cull_idle_servers.py&quot;)
      /etc/jupyterhub/jupyterhub_config.py from config (rw,path=&quot;jupyterhub_config.py&quot;)
      /etc/jupyterhub/secret/ from secret (rw)
      /etc/jupyterhub/z2jh.py from config (rw,path=&quot;z2jh.py&quot;)
      /srv/jupyterhub from hub-db-dir (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from hub-token-vlgwz (ro)
Conditions:
  Type           Status
  PodScheduled   False
Volumes:
  config:
    Type:      ConfigMap (a volume populated by a ConfigMap)
    Name:      hub-config
    Optional:  false
  secret:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  hub-secret
    Optional:    false
  hub-db-dir:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  hub-db-dir
    ReadOnly:   false
  hub-token-vlgwz:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  hub-token-vlgwz
    Optional:    false
QoS Class:       Burstable
Node-Selectors:  &lt;none&gt;
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age                 From               Message
  ----     ------            ----                ----               -------
  Warning  FailedScheduling  61s (x43 over 56m)  default-scheduler  0/5 nodes are available: 1 node(s) had taint {node-role.kubernetes.io/master: }, that the pod didn't tolerate, 4 node(s) didn't find available persistent volumes to bind.

</code></pre>
<p>The information with <code>kubectl get pv,pvc,sc</code>.</p>
<pre><code>NAME                               STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS    AGE
persistentvolumeclaim/hub-db-dir   Pending                                      local-storage   162m

NAME                                                  PROVISIONER                    RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/local-storage (default)   kubernetes.io/no-provisioner   Delete          WaitForFirstConsumer   false                  8h

</code></pre>
<p>So, how to fix it?</p>

<p>In addition to @Arghya Sadhu answer, in order to make it work using <a href="https://kubernetes.io/blog/2018/04/13/local-persistent-volumes-beta/#enabling-smarter-scheduling-and-volume-binding" rel="nofollow noreferrer">local storage</a> you have to create a <code>PersistentVolume</code> manually.</p>
<p>For example:</p>
<pre><code>apiVersion: v1
kind: PersistentVolume
metadata:
  name: hub-db-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Retain
  storageClassName: local-storage
  local:
    path: &lt;path_to_local_volume&gt;
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - &lt;name_of_the_node&gt;
</code></pre>
<p>Then you can deploy the chart:</p>
<pre><code>helm upgrade --install $RELEASE jupyterhub/jupyterhub \
  --namespace $NAMESPACE  \
  --version=0.9.0 \
  --values config.yaml
</code></pre>
<p>The <code>config.yaml</code> file can be left as is:</p>
<pre><code>proxy:
  secretToken: &quot;&lt;token&gt;&quot;
singleuser:
  storage:
    dynamic:
      storageClass: local-storage
</code></pre>
