{"id":1435,"date":"2021-03-15T20:43:17","date_gmt":"2021-03-15T20:43:17","guid":{"rendered":"https:\/\/dft.wiki\/?p=1435"},"modified":"2026-06-09T09:21:57","modified_gmt":"2026-06-09T13:21:57","slug":"kubernetes-persistent-volumes","status":"publish","type":"post","link":"https:\/\/dft.wiki\/?p=1435","title":{"rendered":"Kubernetes Persistent Volumes"},"content":{"rendered":"<p><strong>Persistent volume type: HOST PATH<\/strong><\/p>\n<ul>\n<li>the <strong>correct choice for databases<\/strong>, such as MySQL;<\/li>\n<li>mounts a volume (file or directory) from the <strong>host node&#8217;s file system<\/strong> into the Pod;<\/li>\n<li>accessible from <strong>all pods in the same node<\/strong>;<\/li>\n<li><strong>not accessible from other nodes<\/strong> of the cluster;<\/li>\n<li>if the <strong>node crashes, the data is lost<\/strong>.<\/li>\n<\/ul>\n<p>Create the <strong>nginx-deployment-hostpath.yaml<\/strong>:<\/p>\n<pre><span style=\"color: #808080;\">apiVersion: apps\/v1\r\nkind: <strong>Deployment<\/strong>\r\nmetadata:\r\n  name: <strong>nginx-deployment<\/strong>\r\n  labels:\r\n    app: <strong>nginx-instance<\/strong>\r\nspec:\r\n  replicas: <strong>2<\/strong>\r\n  selector:\r\n    matchLabels:\r\n      app: <strong>nginx-instance<\/strong>\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: <strong>nginx-instance<\/strong>\r\n    spec:\r\n      containers:\r\n      - name: <strong>nginx-instance<\/strong>\r\n        image: <strong>nginx<\/strong>\r\n        imagePullPolicy: Always\r\n        ports:\r\n        - containerPort: <strong>80<\/strong><\/span>\r\n        volumeMounts:\r\n        - name: <strong>www<\/strong>\r\n          mountPath: <strong><span style=\"color: #339966;\">\/usr\/share\/nginx\/html<\/span><\/strong>\r\n      volumes:\r\n      - name: <strong>www<\/strong>\r\n        hostPath:\r\n          path: <strong><span style=\"color: #0000ff;\">\/mnt\/data<\/span><\/strong><\/pre>\n<p>The page may show a forbidden error because the mounted directory (<span style=\"color: #339966;\"><strong>\/usr\/share\/nginx\/html<\/strong><\/span>) inside the pod is empty. The data is physically stored in (<strong><span style=\"color: #0000ff;\">\/mnt\/data<\/span><\/strong>) on the host node.<\/p>\n<p>Create the <strong>nginx-loadbalancer.yaml<\/strong>:<\/p>\n<pre>apiVersion: v1\r\nkind: <strong>Service<\/strong>\r\nmetadata:\r\n  name: <strong>nginx-loadbalancer<\/strong>\r\n  annotations:\r\n    service.beta.kubernetes.io\/linode-loadbalancer-throttle: \"5\"\r\n  labels:\r\n    app: <strong>nginx-loadbalancer<\/strong>\r\nspec:\r\n  type: <strong>LoadBalancer<\/strong>\r\n  selector:\r\n    app: <strong>nginx-instance<\/strong>\r\n  ports:\r\n    - name: http\r\n      protocol: <strong>TCP<\/strong>\r\n      port: <strong>80<\/strong>\r\n      targetPort: <strong>80<\/strong>\r\n  sessionAffinity: None\r\n<\/pre>\n<p>Apply everything:<\/p>\n<pre class=\" prettyprinted\"><span class=\"kwd\">export<\/span><span class=\"pln\"> KUBECONFIG<\/span><span class=\"pun\">=<\/span><strong><span class=\"pln\">kube1<\/span><span class=\"pun\">-<\/span><span class=\"pln\">kubeconfig<\/span><span class=\"pun\">.<\/span><\/strong><span class=\"pln\"><strong>yaml<\/strong>\r\nkubectl apply -f <strong>nginx-deployment-hostpath.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-loadbalancer.yaml<\/strong> \r\nkubectl get services<\/span><\/pre>\n<p>Get the public IP of the load balancer and open it in the browser.<\/p>\n<p>Get the pod names and create a file inside the mounted volume using one of the pods (the bold part is the random suffix appended to the name):<\/p>\n<pre><span class=\"pln\">kubectl get pods\r\nkubectl exec -ti nginx-deployment<strong>-6fb5f9998b-qngbk<\/strong> -- \/bin\/bash -c \"echo 'TEST 1' &gt; <span style=\"color: #339966;\"><strong>\/usr\/share\/nginx\/html\/<span style=\"color: #000000;\">index.html<\/span><\/strong><\/span>\"\r\n<\/span><\/pre>\n<p>To open a shell inside the pod:<\/p>\n<pre class=\" prettyprinted\"><span class=\"pln\">kubectl exec -ti nginx-deployment<strong>-6fb5f9998b-qngbk<\/strong> -- \/bin\/bash\r\n<\/span><\/pre>\n<hr \/>\n<p><strong>Persistent volume type: NFS<\/strong><\/p>\n<ul>\n<li>mounts a shared directory from the <strong>NFS Server&#8217;s file system<\/strong> into the Pod;<\/li>\n<li>accessible from <strong>all pods on the same network<\/strong>, regardless of node or cluster;<\/li>\n<li><strong>not inside the cluster<\/strong> and must be set up manually;<\/li>\n<li>if the node or cluster <strong>crashes, the data remains<\/strong>;<\/li>\n<li>requires <strong>manual backup and management<\/strong> on the NFS Server.<\/li>\n<\/ul>\n<p>Configure an Ubuntu NFS server (a virtual machine outside the Kubernetes cluster):<\/p>\n<pre>sudo apt-get update\r\nsudo apt-get install nfs-kernel-server rpcbind -y\r\nsudo ufw allow nfs\r\nsudo nano \/etc\/exports<\/pre>\n<p>Append:<\/p>\n<pre><span style=\"color: #ff0000;\">\/files *(rw,sync,no_subtree_check,insecure)<\/span><\/pre>\n<p>This configuration is insecure and should only be used for proof of concept. At minimum, replace <span style=\"color: #ff0000;\"><strong>*<\/strong><\/span> with the IP of the Kubernetes cluster, for example <span style=\"color: #ff0000;\">192.168.1.7<\/span> or <span style=\"color: #ff0000;\">192.168.1.0\/24<\/span>.<\/p>\n<p>Activate the NFS shares:<\/p>\n<pre><span style=\"color: #000000;\">sudo exportfs -ra<\/span><\/pre>\n<p>Create the <strong>nginx-deployment-nfs.yaml<\/strong>:<\/p>\n<pre><span style=\"color: #808080;\">apiVersion: apps\/v1\r\nkind: <strong>Deployment<\/strong>\r\nmetadata:\r\n  name: <strong>nginx-deployment<\/strong>\r\n  labels:\r\n    app: <strong>nginx-instance<\/strong>\r\nspec:\r\n  replicas: <strong>2<\/strong>\r\n  selector:\r\n    matchLabels:\r\n      app: <strong>nginx-instance<\/strong>\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: <strong>nginx-instance<\/strong>\r\n    spec:\r\n      containers:\r\n      - name: <strong>nginx-instance<\/strong>\r\n        image: <strong>nginx<\/strong>\r\n        imagePullPolicy: Always\r\n        ports:\r\n        - containerPort: <strong>80<\/strong><\/span>\r\n<span style=\"color: #000000;\">        volumeMounts:\r\n        - name: <strong>www<\/strong>\r\n          mountPath: <strong>\/usr\/share\/nginx\/html\/<\/strong>\r\n      volumes:\r\n      - name: <strong>www<\/strong>\r\n        persistentVolumeClaim:\r\n          claimName: <strong>www-pvc<\/strong><\/span><\/pre>\n<p>Create the <strong>nginx-nfs-pv.yaml<\/strong>:<\/p>\n<pre>apiVersion: v1\r\nkind: <strong>PersistentVolume<\/strong>\r\nmetadata:\r\n  name: <strong>www-pv<\/strong>\r\nspec:\r\n  capacity:\r\n    storage: <strong>10Gi<\/strong>\r\n  accessModes:\r\n    - <strong>ReadWriteMany<\/strong>\r\n  nfs:\r\n    server: <span style=\"color: #ff0000;\"><strong>192.168.1.1<\/strong><\/span>\r\n    path: <span style=\"color: #ff0000;\">\"<strong>\/data\/share<\/strong>\"<\/span><\/pre>\n<p>Replace the server IP (<span style=\"color: #ff0000;\"><strong>192.168.1.1<\/strong><\/span>) and path (<span style=\"color: #ff0000;\"><strong>\/data\/share<\/strong><\/span>) with the correct values.<\/p>\n<p>Create the <strong>nginx-nfs-pvc.yaml<\/strong>:<\/p>\n<pre>apiVersion: v1\r\nkind: <strong>PersistentVolumeClaim<\/strong>\r\nmetadata:\r\n  name: <strong>www-pvc<\/strong>\r\nspec:\r\n  accessModes:\r\n    - <strong>ReadWriteMany<\/strong>\r\n  storageClassName: \"\"\r\n  resources:\r\n    requests:\r\n      storage: <strong>10Gi<\/strong><\/pre>\n<p>Create the <strong>nginx-loadbalancer.yaml<\/strong> as in the previous example.<\/p>\n<p>Apply everything:<\/p>\n<pre>export KUBECONFIG=<strong>kube1-kubeconfig.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-nfs-pv.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-nfs-pvc.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-deployment-nfs.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-loadbalancer.yaml<\/strong>\r\nkubectl get services<\/pre>\n<p>Get the public IP of the load balancer and open it in the browser.<\/p>\n<hr \/>\n<p><strong>Persistent volume type: Linode Block Storage (Volume)<\/strong><\/p>\n<ul>\n<li>a drive that can be mounted into <strong>pods of the same cluster<\/strong>;<\/li>\n<li>physically located <strong>outside the cluster<\/strong> but created through kubectl;<\/li>\n<li>if the node or cluster <strong>crashes, the data remains<\/strong>;<\/li>\n<li>can also be <strong>attached to a virtual machine<\/strong> when not attached to a node.<\/li>\n<\/ul>\n<p>Create the <strong>nginx-deployment-vol.yaml<\/strong>:<\/p>\n<pre><span style=\"color: #808080;\">apiVersion: apps\/v1\r\nkind: <strong>Deployment<\/strong>\r\nmetadata:\r\n  name: <strong>nginx-deployment<\/strong>\r\n  labels:\r\n    app: <strong>nginx-instance<\/strong>\r\nspec:\r\n  replicas: 2\r\n  selector:\r\n    matchLabels:\r\n      app: <strong>nginx-instance<\/strong>\r\n  template:\r\n    metadata:\r\n      labels:\r\n        app: <strong>nginx-instance<\/strong>\r\n    spec:\r\n      containers:\r\n      - name: <strong>nginx-instance<\/strong>\r\n        image: <strong>nginx<\/strong>\r\n        imagePullPolicy: <strong>Always<\/strong>\r\n        ports:\r\n        - containerPort: <strong>80<\/strong><\/span>\r\n        volumeMounts:\r\n        - name: <strong>www<\/strong>\r\n          mountPath: <span style=\"color: #000000;\"><strong>\/usr\/share\/nginx\/html\/<\/strong><\/span>\r\n      volumes:\r\n      - name: <strong>www<\/strong>\r\n        persistentVolumeClaim:\r\n          claimName: <strong>www-pvc<\/strong><\/pre>\n<p>Create the <strong>nginx-vol-pvc.yaml<\/strong>:<\/p>\n<pre>apiVersion: v1\r\nkind: <strong>PersistentVolumeClaim<\/strong>\r\nmetadata:\r\n  name: <strong>www-pvc<\/strong>\r\nspec:\r\n  accessModes:\r\n    - <strong>ReadWriteOnce<\/strong>\r\n  resources:\r\n    requests:\r\n      storage: <strong>10Gi<\/strong>\r\n  storageClassName: <strong>linode-block-storage\r\n<\/strong><\/pre>\n<p>Create the <strong>nginx-loadbalancer.yaml<\/strong> as in the previous example.<\/p>\n<p>Note that <strong>linode-block-storage<\/strong> is limited to <strong>ReadWriteOnce<\/strong> and supports sizes from <strong>10Gi to 10240Gi<\/strong>.<\/p>\n<ul>\n<li>ReadWriteOnce &#8211; Can only be attached to one node.<\/li>\n<li>ReadWriteMany &#8211; Can be attached to multiple nodes; used with NFS, for example.<\/li>\n<\/ul>\n<p>Apply everything:<\/p>\n<pre>export KUBECONFIG=<strong>kube1-kubeconfig.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-vol-pvc.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-deployment-vol.yaml<\/strong>\r\nkubectl apply -f <strong>nginx-loadbalancer.yaml<\/strong>\r\nkubectl get services<\/pre>\n<p>The block storage will be created automatically.<\/p>\n<p>Get the public IP of the load balancer and open it in the browser.<\/p>\n<hr \/>\n<p><strong>OTHER POSTS<\/strong><\/p>\n<p><strong>Minikube<\/strong> on Ubuntu 22.04 [<a href=\"https:\/\/dft.wiki\/?p=3087\">Link<\/a>].<\/p>\n<p><strong>MicroK8s<\/strong> on Ubuntu 22.04 [<a href=\"https:\/\/dft.wiki\/?p=3151\">Link<\/a>].<\/p>\n<p><strong>K3s<\/strong> on Ubuntu 22.04 [<a href=\"https:\/\/dft.wiki\/?p=3105\">Link<\/a>].<\/p>\n<p><strong>Kubernetes<\/strong> Persistent Volumes [<a href=\"https:\/\/dft.wiki\/?p=1435\">Link<\/a>].<\/p>\n<p><strong>Kubernetes<\/strong> Cheat Sheet [<a href=\"https:\/\/dft.wiki\/?p=1372\">Link<\/a>].<\/p>\n<p><strong>Kubernetes<\/strong> Dashboard [<a href=\"https:\/\/dft.wiki\/?p=4114\">Link<\/a>].<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Persistent volume type: HOST PATH the correct choice for databases, such as MySQL; mounts a [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4],"tags":[],"class_list":["post-1435","post","type-post","status-publish","format-standard","hentry","category-linux"],"_links":{"self":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/1435","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1435"}],"version-history":[{"count":19,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/1435\/revisions"}],"predecessor-version":[{"id":5766,"href":"https:\/\/dft.wiki\/index.php?rest_route=\/wp\/v2\/posts\/1435\/revisions\/5766"}],"wp:attachment":[{"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1435"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1435"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dft.wiki\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1435"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}