<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Lucy Llewellyn</title>
    <description>The latest articles on DEV Community by Lucy Llewellyn (@lucyllewy).</description>
    <link>https://dev.to/lucyllewy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F88967%2Fbfe139bd-796a-4764-9b2b-b499ea49e51a.jpg</url>
      <title>DEV Community: Lucy Llewellyn</title>
      <link>https://dev.to/lucyllewy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/lucyllewy"/>
    <language>en</language>
    <item>
      <title>Configure automatic NFS Persistent Volumes on Kubernetes K3s</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Wed, 04 Aug 2021 01:56:50 +0000</pubDate>
      <link>https://dev.to/lucyllewy/configure-automatic-nfs-persistent-volumes-on-kubernetes-k3s-3jh6</link>
      <guid>https://dev.to/lucyllewy/configure-automatic-nfs-persistent-volumes-on-kubernetes-k3s-3jh6</guid>
      <description>&lt;p&gt;Using NFS persistent volumes is a relatively easy, for Kubernetes, on-ramp to using the Kubernetes storage infrastructure.&lt;/p&gt;

&lt;p&gt;Before following this guide, you should have an installed Kubernetes cluster. If you don't, check out the guide how to &lt;a href="https://dev.to/diddledani/installing-k3s-in-a-cluster-of-three-nodes-18fd"&gt;Install K3s&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the NFS share
&lt;/h2&gt;

&lt;p&gt;We will share a directory on the primary cluster node for all the other nodes to access. I will assume that you want to share a filesystem mounted at &lt;code&gt;/mnt/storage-disk&lt;/code&gt;.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the NFS service:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nfs-kernel-server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Edit the file &lt;code&gt;/etc/exports&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;nano &lt;span class="nt"&gt;-w&lt;/span&gt; /etc/exports
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add a new line at the bottom with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   /mnt/storage-disk *(rw,sync,no_subtree_check,no_root_squash,anonuid=65534,anongid=65534)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save and exit the editor with ctrl+x followed by y to indicate that we want to save the file and finally enter to confirm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load the configuration we just wrote into the NFS server:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;exportfs &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Install nfs client onto each of the other nodes
&lt;/h2&gt;

&lt;p&gt;On each of the other nodes, we need the nfs client to be installed or pods will fail to schedule and start. On &lt;code&gt;k8s-2&lt;/code&gt; and &lt;code&gt;k8s-3&lt;/code&gt; run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-y&lt;/span&gt; nfs-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configuring the NFS provisioner
&lt;/h2&gt;

&lt;p&gt;We could create a Persistent Volume and Persistent Volume Claim manually, but there's an automated method using a Provisioner. This is a special configuration that takes our NFS share and automatically slices it up into Persistent Volumes whenever a new Persistent Volume Claim is created up till the provisioned storage space is exhausted.&lt;/p&gt;

&lt;p&gt;To install the provisioner we'll first get &lt;code&gt;helm&lt;/code&gt; because it simplifies the installation inordinately!&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install &lt;code&gt;helm&lt;/code&gt; on the primary cluster node:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl &lt;span class="nt"&gt;-fsSL&lt;/span&gt; https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Configure &lt;code&gt;helm&lt;/code&gt; to access the provisioner repository:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Using &lt;code&gt;helm&lt;/code&gt; install the provisioner:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo env &lt;/span&gt;&lt;span class="nv"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/rancher/k3s/k3s.yaml &lt;span class="se"&gt;\&lt;/span&gt;
   helm &lt;span class="nb"&gt;install &lt;/span&gt;nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--set&lt;/span&gt; nfs.server&lt;span class="o"&gt;=&lt;/span&gt;k8s-1 &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;--set&lt;/span&gt; nfs.path&lt;span class="o"&gt;=&lt;/span&gt;/mnt/storage-disk
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are many options that may be set with the &lt;code&gt;--set&lt;/code&gt; flag. Each option must be supplied with their own &lt;code&gt;--set option.name=value&lt;/code&gt; parameter pair. For the full list of parameters see &lt;a href="https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner/blob/master/charts/nfs-subdir-external-provisioner/README.md#configuration" rel="noopener noreferrer"&gt;the configuration section of the helm chart readme for the NFS provisioner&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating a test deployment to verify the provisioner
&lt;/h2&gt;

&lt;p&gt;Now we will test that the provisioner is working by creating a test claim and a test deployment that uses the claim.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create and edit a file called &lt;code&gt;test-claim.yaml&lt;/code&gt;:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   nano &lt;span class="nt"&gt;-w&lt;/span&gt; test-claim.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Paste the following content into the editor:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PersistentVolumeClaim&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim&lt;/span&gt;
   &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;storageClassName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-client&lt;/span&gt;
     &lt;span class="na"&gt;accessModes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ReadWriteMany&lt;/span&gt;
     &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="na"&gt;storage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1Mi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;apiVersion&lt;/code&gt; must be set to &lt;code&gt;v1&lt;/code&gt; so that K8s knows which fields are acceptible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;kind&lt;/code&gt; must be &lt;code&gt;PersistendVolumeClaim&lt;/code&gt; to inform K8s that we are creating a PVC.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;metadata.name&lt;/code&gt; entry is an arbitrary name for the PVC but we will use it to tie the PVC to the pod in the following steps.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;spec.storageClassName&lt;/code&gt; must be &lt;code&gt;nfs-client&lt;/code&gt; because this is what the default installation using &lt;code&gt;helm&lt;/code&gt; sets up.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;spec.accessModes&lt;/code&gt; should be a list with a single item of value &lt;code&gt;ReadWriteMany&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The available access modes are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ReadWriteOnce -- the volume can be mounted as read-write by a single node&lt;/li&gt;
&lt;li&gt;ReadOnlyMany -- the volume can be mounted read-only by many nodes&lt;/li&gt;
&lt;li&gt;ReadWriteMany -- the volume can be mounted as read-write by many nodes&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;The &lt;code&gt;spec.resources.requests.storage&lt;/code&gt; should be a number suffixed with &lt;code&gt;Mi&lt;/code&gt;, &lt;code&gt;Gi&lt;/code&gt;, or &lt;code&gt;Ti&lt;/code&gt; indicating Mebibytes (&lt;code&gt;1024*1024&lt;/code&gt; bytes), Gibibytes (&lt;code&gt;1024*1024*1024&lt;/code&gt; bytes), or Tebibytes (&lt;code&gt;1024*1024*1024*1024&lt;/code&gt; bytes). E.g. &lt;code&gt;5Gi&lt;/code&gt; would equal five Gibibytes or &lt;code&gt;5*1024*1024*1024&lt;/code&gt; bytes. If there is insufficient space in the NFS share then this PVC will stay in a pending state.&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save and exit the editor with ctrl+x followed by y to indicate that we want to save the file and finally enter to confirm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create and edit a file called &lt;code&gt;test-pod.yaml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   nano &lt;span class="nt"&gt;-w&lt;/span&gt; test-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Paste the following content into the editor:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Pod&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod&lt;/span&gt;
   &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-pod&lt;/span&gt;
       &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gcr.io/google_containers/busybox:1.24&lt;/span&gt;
       &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/bin/sh"&lt;/span&gt;
       &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-c"&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;touch&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;/mnt/SUCCESS&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;||&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;exit&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
       &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
         &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-pvc&lt;/span&gt;
           &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/mnt"&lt;/span&gt;
     &lt;span class="na"&gt;restartPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Never"&lt;/span&gt;
     &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nfs-pvc&lt;/span&gt;
         &lt;span class="na"&gt;persistentVolumeClaim&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
           &lt;span class="na"&gt;claimName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;test-claim&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;apiVersion&lt;/code&gt; must be set to &lt;code&gt;v1&lt;/code&gt; so that K8s knows which fields are acceptible.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;kind&lt;/code&gt; must be &lt;code&gt;Pod&lt;/code&gt; to inform K8s that we are creating a pod.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The &lt;code&gt;metadata.name&lt;/code&gt; entry is an arbitrary name for the pod.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The &lt;code&gt;spec.containers&lt;/code&gt; field takes a list of container definitions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; field is an arbitrary name for the container.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;image&lt;/code&gt; field is the docker-compatible container image name. Here we are using a container hosted by google called &lt;code&gt;busybox&lt;/code&gt; with version &lt;code&gt;1.24&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;command&lt;/code&gt; field is the command to run inside the container when it is launched.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;args&lt;/code&gt; field is a list of arguments to pass to the command specified above.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;volumeMounts&lt;/code&gt; field is a list of mount definitions for inside the container.

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; field in the mount definition is the name of the volume as configured in &lt;code&gt;.spec.volumes&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;mountPath&lt;/code&gt; field tells K8s where to mount the filesystem from the volume into the container.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;p&gt;The &lt;code&gt;volumes&lt;/code&gt; field is a list of volumes to make available for containers within this pod.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The volume &lt;code&gt;name&lt;/code&gt; field is an arbitrary name that we use to reference in the &lt;code&gt;.spec.containers.volumeMounts.name&lt;/code&gt; field.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;persistentVolumeClaim.claimName&lt;/code&gt; field is the name of a PVC. We configured this in our &lt;code&gt;test-claim.yaml&lt;/code&gt; file's &lt;code&gt;.metadata.name&lt;/code&gt; field, i.e. &lt;code&gt;test-claim&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Save and exit the editor with ctrl+x followed by y to indicate that we want to save the file and finally enter to confirm.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Apply the claim and the pod definitions to the cluster:&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; test-claim.yaml &lt;span class="nt"&gt;-f&lt;/span&gt; test-pod.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The container should now download its image, run and exit. Left behind will be a new folder called &lt;code&gt;/mnt/storage-disk/default-test-claim-pvc-*&lt;/code&gt; where the &lt;code&gt;*&lt;/code&gt; is a UUID. Inside this folder should be a single file called &lt;code&gt;SUCCESS&lt;/code&gt; indicating that the PVC successfully provisioned, mounted into the container, and the container was able to write a file.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>nfs</category>
      <category>volumes</category>
    </item>
    <item>
      <title>Install and access the K8s Web UI Dashboard on a K3s cluster</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Wed, 04 Aug 2021 01:52:54 +0000</pubDate>
      <link>https://dev.to/lucyllewy/install-and-access-the-k8s-web-ui-dashboard-on-a-k3s-cluster-4370</link>
      <guid>https://dev.to/lucyllewy/install-and-access-the-k8s-web-ui-dashboard-on-a-k3s-cluster-4370</guid>
      <description>&lt;p&gt;While I don't find the dashboard very useful for configuring anything in the cluster, it can be helpful to find a resource you've lost track of or discover resources you didn't know were there.&lt;/p&gt;

&lt;p&gt;Before following this guide, you should have an installed kubernetes cluster. If you don't, check out the guide how to &lt;a href="https://dev.to/diddledani/installing-k3s-in-a-cluster-of-three-nodes-18fd"&gt;Install K3s&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the dashboard
&lt;/h2&gt;

&lt;p&gt;To install the dashboard we need to run the following one command on the primary cluster node (in my example, this is &lt;code&gt;k8s-1&lt;/code&gt;). K3s installations require the command be prefixed with &lt;code&gt;sudo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This URL includes the version number, so you will want to double check that it is up-to-date when following the instruction. The latest version will show up at the &lt;a href="https://github.com/kubernetes/dashboard/releases/latest" rel="noopener noreferrer"&gt;K8s dashboard latest release page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy95v3r4qh572yqok01pe.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy95v3r4qh572yqok01pe.png" alt="Screenshot of terminal output from installing the K8s Web UI Dashboard" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the admin user
&lt;/h2&gt;

&lt;p&gt;Accessing the dashboard requires that we supply a token to authorise ourselves. This account is not created by the command above.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the primary cluster node create a file called &lt;code&gt;dashboard.admin-user.yml&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-user&lt;/span&gt;
     &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-dashboard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I recommend using &lt;code&gt;nano&lt;/code&gt; to create the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   nano &lt;span class="nt"&gt;-w&lt;/span&gt; dashboard.admin-user.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To save the file and exit &lt;code&gt;nano&lt;/code&gt; once you've copied the contents into the terminal type ctrl+x followed by y to confirm that you want to save the file and finally enter.&lt;/p&gt;

&lt;p&gt;When applied to the cluster this will create our user account called &lt;code&gt;admin-user&lt;/code&gt; and store it into the &lt;code&gt;kubernetes-dashboard&lt;/code&gt; namespace, which was created by the installation step above.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;apiVersion&lt;/code&gt; must be set to &lt;code&gt;v1&lt;/code&gt; so that K8s knows which fields are acceptible.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;kind&lt;/code&gt; must be set to &lt;code&gt;ServiceAccount&lt;/code&gt; to tell K8s that we're attempting to create a user.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Also on the primary cluster node create another file called &lt;code&gt;dashboard.admin-user-role.yml&lt;/code&gt; with the following content:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;   &lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
   &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
   &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-user&lt;/span&gt;
   &lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
     &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cluster-admin&lt;/span&gt;
   &lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
     &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;admin-user&lt;/span&gt;
     &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kubernetes-dashboard&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When applied to the cluster this will create a "role binding" to bind our user account to the cluster admin role.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;apiVersion&lt;/code&gt; must be set to &lt;code&gt;rbac.authorization.k8s.io/v1&lt;/code&gt; to tell K8s that we're using the RBAC API version 1. A different &lt;code&gt;apiVersion&lt;/code&gt; will likely change the fields available for use below, so will break our attempt to apply to the cluster.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;kind&lt;/code&gt; field must be set to &lt;code&gt;ClusterRoleBinding&lt;/code&gt; to tell K8s that we're attempting to create a Cluster Role Binding that binds an account to a cluster role.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;metadata.name&lt;/code&gt; entry is an arbitrary name for the role binding but it makes sense to name this the same as the user account that we're binding.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subjects&lt;/code&gt; list must include one or more items. Each item in the list must have a &lt;code&gt;kind&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;namespace&lt;/code&gt; field.

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;kind&lt;/code&gt; field must be set to &lt;code&gt;ServiceAccount&lt;/code&gt; since that is what we used to create our &lt;code&gt;admin-user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;name&lt;/code&gt; should be set to the same as that we used in the &lt;code&gt;dashboard.admin-user.yml&lt;/code&gt; file, i.e. &lt;code&gt;admin-user&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;namespace&lt;/code&gt; should be set to the same name as that we used in the &lt;code&gt;dashboard-admin-user.yml&lt;/code&gt; file so that K8s can find the correct service account, i.e. &lt;code&gt;kubernetes-dashboard&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;The &lt;code&gt;roleRef&lt;/code&gt; configuration specifies the role that each user in the &lt;code&gt;subjects&lt;/code&gt; list is assigned to.

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;apiGroup&lt;/code&gt; field must be set to &lt;code&gt;rbac.authorization.k8s.io&lt;/code&gt; to tell K8s that we're referencing a role-based access control role.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;kind&lt;/code&gt; field should be set to that of the accounts in the &lt;code&gt;subjects&lt;/code&gt; list are granted the role of &lt;code&gt;cluster-admin&lt;/code&gt; - This is a default role in K8s.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Apply these configurations to the cluster with the following command:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl create &lt;span class="nt"&gt;-f&lt;/span&gt; dashboard.admin-user.yml &lt;span class="nt"&gt;-f&lt;/span&gt; 
dashboard.admin-user-role.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Accessing the dashboard
&lt;/h2&gt;

&lt;p&gt;Accessing the dashboard is tricky because you need to both access over HTTPS, or via &lt;code&gt;localhost&lt;/code&gt; (i.e. from the same machine). You also need to get a token to authorise your access with. I will show how to access remotely over HTTPS (&lt;strong&gt;do not&lt;/strong&gt; do this if your cluster is on a public network!):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the primary cluster node edit the &lt;code&gt;kubernetes-dashboard&lt;/code&gt; service:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo env &lt;/span&gt;&lt;span class="nv"&gt;EDITOR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;nano kubectl edit service kubernetes-dashboard &lt;span class="nt"&gt;-n&lt;/span&gt; kubernetes-dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Move the cursor down to the line that includes &lt;code&gt;type: ClusterIP&lt;/code&gt; and change it to &lt;code&gt;type: NodePort&lt;/code&gt; with the same indentation. (This is the configuration path &lt;code&gt;.spec.type&lt;/code&gt;)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Save and exit the editor with ctrl+x followed by y to indicate that we want to save the file and finally enter to confirm.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the primary cluster node run the following to determine the port number to connect to the Web UI:
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl get service kubernetes-dashboard &lt;span class="nt"&gt;-n&lt;/span&gt; kubernetes-dashboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my installation this prints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   NAME                   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)         AGE
   kubernetes-dashboard   NodePort   10.43.128.69   &amp;lt;none&amp;gt;        443:31235/TCP   126m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The important part is the &lt;code&gt;PORT&lt;/code&gt; column which has the value &lt;code&gt;443:31235&lt;/code&gt; here. The second number, after the colon (here it is &lt;code&gt;31235&lt;/code&gt;), is the port we will use to connect. The hostname is the primary node address. In my case this will mean an address of &lt;code&gt;https://k8s-1:31235/&lt;/code&gt;. Load this address in your web browser and accept the warning about a self-signed security certificate to load the UI.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;On the primary cluster node run the following to get your token:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kubernetes-dashboard get secret &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;kubectl &lt;span class="nt"&gt;-n&lt;/span&gt; kubernetes-dashboard get sa/admin-user &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.secrets[0].name}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; go-template&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{{.data.token | base64decode}}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy and paste the output into the field on the Web UI login screen.&lt;/p&gt;

&lt;p&gt;You should now be able to navigate to the &lt;code&gt;nodes&lt;/code&gt; item in the left-hand menu of the UI to see your nodes:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq8wjqm2vsqzhxm03rjt.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgq8wjqm2vsqzhxm03rjt.png" alt="Screenshot of the Web UI Dashboard showing the nodes view" width="800" height="539"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>webui</category>
      <category>dashboard</category>
    </item>
    <item>
      <title>Installing k3s in a cluster of three nodes</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Wed, 04 Aug 2021 01:20:15 +0000</pubDate>
      <link>https://dev.to/lucyllewy/installing-k3s-in-a-cluster-of-three-nodes-18fd</link>
      <guid>https://dev.to/lucyllewy/installing-k3s-in-a-cluster-of-three-nodes-18fd</guid>
      <description>&lt;p&gt;To get going with Kubernetes (K8s) we need to install a distribution of K8s. For this tutorial I will use &lt;a href="https://k3s.io" rel="noopener noreferrer"&gt;K3s by Rancher Labs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You should replace my username and node names in the instructions below. For reference, my username is &lt;code&gt;dllewellyn&lt;/code&gt;, and my nodes are named as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;k8s-1&lt;/li&gt;
&lt;li&gt;k8s-2&lt;/li&gt;
&lt;li&gt;k8s-3&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start the first node
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Login to the first node:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ssh dllewellyn@k8s-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;On the first node run the K3s installation script that automates everything - enter your sudo password if you are prompted:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Get the token for joining more nodes to the cluster - save this somewhere you can easily copy and paste:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   &lt;span class="nb"&gt;sudo cat&lt;/span&gt; /var/lib/rancher/k3s/server/node-token
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ipbws1idebu3haxq415.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ipbws1idebu3haxq415.png" alt="Screenshot of terminal output of installing the first node" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the second node
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Login to the second node in a new terminal session:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   ssh dllewellyn@k8s-2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Install and join the node to the primary node we installed above - if your second node cannot resolve the primary node's name to its IP address then use the IP address directly - Replace &lt;code&gt;mynodetoken&lt;/code&gt; with the node-token you saved above:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;   curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | &lt;span class="nv"&gt;K3S_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://k8s-1:6443 &lt;span class="nv"&gt;K3S_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mynodetoken sh -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;K3S_URL&lt;/code&gt; is the URL of your primary server in the cluster.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;K3S_TOKEN&lt;/code&gt; will be used to authenticate the new node when joining the cluster. It must match with the token in the file &lt;code&gt;/var/lib/rancher/k3s/server/node-token&lt;/code&gt; on the primary server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl35mh40r0xi6tkilifxn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl35mh40r0xi6tkilifxn.png" alt="Screenshot of terminal output of installing the second node" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start the third node
&lt;/h2&gt;

&lt;p&gt;This is done exactly the same way as the second node, so the steps are omitted. Repeat the same steps you used for the second node, ensuring that you are in a session on the third node:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ssh dllewellyn@k8s-3
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F567d5b9eybjs0wr508ag.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F567d5b9eybjs0wr508ag.png" alt="Screenshot of terminal output of installing the third node" width="800" height="261"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>k3s</category>
      <category>cluster</category>
    </item>
    <item>
      <title>Getting started with Flutter on Ubuntu</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Tue, 02 Feb 2021 20:27:35 +0000</pubDate>
      <link>https://dev.to/lucyllewy/getting-started-with-flutter-on-ubuntu-45ee</link>
      <guid>https://dev.to/lucyllewy/getting-started-with-flutter-on-ubuntu-45ee</guid>
      <description>&lt;p&gt;Recently there was an announcement from Ubuntu that the desktop team are working on a &lt;a href="https://discourse.ubuntu.com/t/refreshing-the-ubuntu-desktop-installer/20659" rel="noopener noreferrer"&gt;replacement for the Ubiquity installer&lt;/a&gt;. The really interesting part of the post by Martin Wimpress, head of the Ubuntu Desktop team at Canonical, is that the new installer will be built using Flutter.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://flutter.dev" rel="noopener noreferrer"&gt;Flutter&lt;/a&gt; is a cross-platform User Interface framework that can target Linux, macOS, Windows, Android, and iOS all from the same source code. I have been aware of Flutter for some time now but have been trepidatious in jumping in to sample the water, because I am completely unfamilier with the Dart programming language and was worried about making the time investment.&lt;/p&gt;

&lt;p&gt;With the news from Ubuntu I decided that now is a good time to get my feet wet and find out what this new shiny is all about. To that end, I have been installed Flutter and managed to get the sample application running on Ubuntu! There were a few gotchas, however, so below I've summarised the important steps to get a fully functional toolchain set up:&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing Flutter
&lt;/h2&gt;

&lt;p&gt;First up, we install the Flutter Snap Package. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;flutter &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default this will install the commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;flutter&lt;/li&gt;
&lt;li&gt;flutter.dart&lt;/li&gt;
&lt;li&gt;flutter.openurl&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can reduce the typing required to call &lt;code&gt;dart&lt;/code&gt;, along with reducing the cognitive load when translating any instructions that do not expect the &lt;code&gt;flutter.&lt;/code&gt; prefix. Run the following to map &lt;code&gt;flutter.dart&lt;/code&gt; to the name &lt;code&gt;dart&lt;/code&gt; so you can call it without the prefix:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;alias &lt;/span&gt;flutter.dart dart
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need the Android Studio. The instructions supplied by Google require you to download and extract a &lt;code&gt;.tar.gz&lt;/code&gt; file to a place of your choosing. That's too much work. Instead, install the community-maintained Snap Package with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;android-studio &lt;span class="nt"&gt;--classic&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Find the Android Studio icon in your desktop applications menu, such as the dash in Gnome or the K menu in KDE, or execute in the terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;android-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run through the first-install wizard accepting all the defaults. We won't be using Android Studio for anything other than its ability to maintain the Android SDK and emulators.&lt;/p&gt;

&lt;p&gt;Now, flutter needs to know the location of our Android Studio snap, or you will be unable to build an app even if you're not targetting Android, so run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter config &lt;span class="nt"&gt;--android-studio-dir&lt;/span&gt; /snap/android-studio/current/android-studio
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We need to accept the Android licenses to use Flutter, so run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter doctor &lt;span class="nt"&gt;--android-licenses&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read through each license it presents (you're gonna read them, right?) and accept them with a &lt;code&gt;Y&lt;/code&gt; keypress followed by enter when prompted.&lt;/p&gt;

&lt;p&gt;If you are likely to be developing for Linux, macOS, or Windows in your Flutter projects, you need to update to the &lt;strong&gt;dev&lt;/strong&gt; branch of Flutter and enable each toolchain. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# switch to dev branch&lt;/span&gt;
flutter channel dev
&lt;span class="c"&gt;# update Flutter to the latest dev branch revision&lt;/span&gt;
flutter upgrade
&lt;span class="c"&gt;# enable Linux toolchain&lt;/span&gt;
flutter config &lt;span class="nt"&gt;--enable-linux-desktop&lt;/span&gt;
&lt;span class="c"&gt;# enable macOS toolchain&lt;/span&gt;
flutter config &lt;span class="nt"&gt;--enable-macos-desktop&lt;/span&gt;
&lt;span class="c"&gt;# enable Windows toolchain&lt;/span&gt;
flutter config &lt;span class="nt"&gt;--enable-windows-desktop&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;Note: while you can set-up your project for macOS and Windows support you must use those operating systems to actually build an app targeting each.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Finally, we check that everything is correctly set-up. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter doctor
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything is good, it'll output similar to below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel dev, 1.26.0-17.1.pre, on Linux, locale en_GB.UTF-8)
[✓] Android toolchain - develop for Android devices (Android SDK version 30.0.3)
[✓] Chrome - develop for the web
[✓] Linux toolchain - develop for Linux desktop
[✓] Android Studio
[✓] Connected device (2 available)

• No issues found!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Flutter by building a sample
&lt;/h2&gt;

&lt;p&gt;Create a new directory to hold our sample app. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;flutter-example
&lt;span class="nb"&gt;cd &lt;/span&gt;flutter-example
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now we must create the app structure. Run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter create &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will scaffold out a new Flutter app with support for each of the platforms that are enabled. By default the enabled platforms are Android, iOS, and Web. If you enabled the desktop platforms above then it will also scaffold out those.&lt;/p&gt;

&lt;p&gt;The main application code is stored inside the &lt;code&gt;lib/&lt;/code&gt; folder, while each platform will have a respective folder to hold the native C and Java code. Normally you'd use Flutter plugins to enable native features and rely on coding your app in Dart. Flutter plugins can be discovered at &lt;a href="https://pub.dev/" rel="noopener noreferrer"&gt;pub.dev&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Test the app with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flutter run &lt;span class="nt"&gt;-d&lt;/span&gt; linux
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will build and run the sample app.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fltnik5dm43w5t1hy4948.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fltnik5dm43w5t1hy4948.png" alt="Flutter default app running in Ubuntu" width="800" height="491"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It will also start a debugger server that you can connect to in your web browser; the URL will be in the output text when you run the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flutter run -d linux
Running "flutter pub get" in flutter-sample...                            392ms
Launching lib/main.dart on Linux in debug mode...
Building Linux application...                                           
Activating Dart DevTools...                                         4.5s
Syncing files to device Linux...                                    89ms

Flutter run key commands.
r Hot reload. 🔥🔥🔥
R Hot restart.
h Repeat this help message.
d Detach (terminate "flutter run" but leave application running).
c Clear the screen
q Quit (terminate the application on the device).
An Observatory debugger and profiler on Linux is available at:
http://127.0.0.1:38399/PRAhGqkGwN0=/

Flutter DevTools, a Flutter debugger and profiler, on Linux is available at:
http://127.0.0.1:9100?uri=http%3A%2F%2F127.0.0.1%3A38399%2FPRAhGqkGwN0%3D%2F
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The debugging web page looks like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgnqate7mndvvq6mzt6yn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fgnqate7mndvvq6mzt6yn.png" alt="Flutter debugger running in Firefox debugging the default Flutter app" width="800" height="506"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;In this post, I showed how to install and configure Flutter and the Android toolchain, without hitting the issues that I encountered myself. I also showed creating and debugging a sample Flutter app, and enabling the toolchain for Linux, macOS, and Windows desktop apps.&lt;/p&gt;

</description>
      <category>ubuntu</category>
      <category>flutter</category>
    </item>
    <item>
      <title>Use GitHub Actions or any CI/CD based on Docker containers to build your Snap Packages</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Wed, 30 Dec 2020 20:54:46 +0000</pubDate>
      <link>https://dev.to/lucyllewy/use-github-actions-or-any-ci-cd-based-on-docker-containers-to-build-your-snap-packages-4fd4</link>
      <guid>https://dev.to/lucyllewy/use-github-actions-or-any-ci-cd-based-on-docker-containers-to-build-your-snap-packages-4fd4</guid>
      <description>&lt;p&gt;When it comes to Snap Packages the ease of entry is incredibly low. This is especially true with the &lt;a href="https://snapcraft.io/build"&gt;Snapcraft Build Service&lt;/a&gt;, which is hosted at Snapcraft.io. The Build Service ties into a Snap Package author’s GitHub account to automatically build a Snap Package from a linked repository. When the Snap Package has finished building the Build Service will then release the Snap into the ‘edge’ track.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem
&lt;/h2&gt;

&lt;p&gt;The Build Service has the drawback that, while being quite easy to get started with, it is very restricted in its ability to perform customised release schedules and structures. Another problem is that developers often have their own processes already in place to build and test their app using services such as &lt;a href="https://travis-ci.com/"&gt;Travis CI&lt;/a&gt; or &lt;a href="https://github.com/features/actions"&gt;GitHub Actions&lt;/a&gt;. These services are much easier to program to tailor their behaviour than the Snapcraft Build Service.&lt;/p&gt;

&lt;p&gt;I maintain many Snap Packages and have run-up against the limitations of the Snapcraft Build Service for several of them. The affected Snap Packages that are causing frustration include those where the Upstream project has several Release Types. One of these problematic Snap packages is &lt;a href="https://openra.net/"&gt;OpenRA&lt;/a&gt;, which has two types of release: Stable and Playtest. A frequent feature request for the &lt;a href="https://snapcraft.io/openra"&gt;OpenRA Snap Package&lt;/a&gt; has been that I release the Playtests in addition to the Stable releases, but the Build Service has prevented me from achieving this goal.&lt;/p&gt;

&lt;p&gt;With the readily available resource of services like Travis-CI and GitHub Actions, I have been investigating the possibility of building my Snap Packages using one of these. The Snapcraft Team have long provided a &lt;a href="https://hub.docker.com/r/snapcore/snapcraft"&gt;Container Image of Snapcraft&lt;/a&gt; in Docker Hub, but until now they have only been able to provide an AMD64 variant. Unfortunately, this means that there is limited scope for compiling Snap Packages using the Container Image for architectures other than the 64bit x86 platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;Enter from stage right &lt;a href="https://docs.docker.com/buildx/working-with-buildx/"&gt;Docker Buildx&lt;/a&gt;, which can run Container Image Builds for multiple architectures. Container Images are built for the architectures specified at build time and tied into a single namespace via a Container Image Manifest on a Container Registry such as Docker Hub. When using GitHub Actions, you can get a fully functional Buildx setup to run your builds with the &lt;a href="https://github.com/marketplace/actions/docker-buildx"&gt;Docker Buildx Action&lt;/a&gt;. The Action utilises &lt;a href="https://www.qemu.org/"&gt;qemu&lt;/a&gt; user-mode emulation to transparently run the multiple architecture builds on GitHub Actions, which would normally be limited to the amd64 and i386 architectures.&lt;/p&gt;

&lt;h3&gt;
  
  
  Buildx
&lt;/h3&gt;

&lt;p&gt;Step 1 of my master plan uses Buildx to release an experimental build of Snapcraft under my own Docker Hub namespace which is compiled for Core, Core18, and Core20 for all supported Snap Architectures (excepting s390x for the Core variant because there appears to be a BASH-related issue when combined with qemu-user.) &lt;a href="https://hub.docker.com/r/diddledan/snapcraft"&gt;My Snapcraft Container Image is on Docker Hub at diddledan/snapcraft&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Qemu
&lt;/h3&gt;

&lt;p&gt;Now for the really fun part, and this is going to be a whirlwind: we have the readily available build of Snapcraft for all architectures in a convenient Container Image, but how do we use that to build our Packages for the Snap Store?&lt;/p&gt;

&lt;p&gt;Hold onto your hats, because here we go... We can use &lt;code&gt;qemu-user-static&lt;/code&gt; to execute the foreign-architecture containers built above to build for the Snap Store!&lt;/p&gt;

&lt;p&gt;We simply need to wire-up the GitHub Actions or Travis-CI configuration, which configures &lt;code&gt;qemu-user-static&lt;/code&gt; and runs our build. Below I’ll show my own GitHub Actions configuration:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Release Snap&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;master&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build-stable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="c1"&gt;# Let's build for many architectures..&lt;/span&gt;
    &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;matrix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;architecture&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux/amd64&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux/386&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux/arm64&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;linux/arm/v7&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Checkout the code of our Repository&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;

    &lt;span class="c1"&gt;# Do the build!&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;build&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
        &lt;span class="s"&gt;# Enable docker daemon support for --platform parameter&lt;/span&gt;
        &lt;span class="s"&gt;echo '{"experimental": true}' | sudo tee /etc/docker/daemon.json &amp;gt; /dev/null&lt;/span&gt;
        &lt;span class="s"&gt;sudo systemctl restart docker&lt;/span&gt;

        &lt;span class="s"&gt;# Configure qemu-user-static&lt;/span&gt;
        &lt;span class="s"&gt;docker run --rm --tty \&lt;/span&gt;
          &lt;span class="s"&gt;--security-opt apparmor:unconfined \&lt;/span&gt;
          &lt;span class="s"&gt;--cap-add SYS_ADMIN \&lt;/span&gt;
          &lt;span class="s"&gt;multiarch/qemu-user-static --reset -p yes&lt;/span&gt;

        &lt;span class="s"&gt;# Run snapcraft&lt;/span&gt;
        &lt;span class="s"&gt;docker run --rm --tty \&lt;/span&gt;
          &lt;span class="s"&gt;--security-opt apparmor:unconfined \&lt;/span&gt;
          &lt;span class="s"&gt;--cap-add SYS_ADMIN \&lt;/span&gt;
          &lt;span class="s"&gt;--device /dev/fuse \&lt;/span&gt;
          &lt;span class="s"&gt;--volume /sys \&lt;/span&gt;
          &lt;span class="s"&gt;--volume /sys/fs/cgroup:/sys/fs/cgroup:ro \&lt;/span&gt;
          &lt;span class="s"&gt;--volume $GITHUB_WORKSPACE:$GITHUB_WORKSPACE \&lt;/span&gt;
          &lt;span class="s"&gt;--workdir $GITHUB_WORKSPACE \&lt;/span&gt;
          &lt;span class="s"&gt;--platform "${{ matrix.architecture }}" \&lt;/span&gt;
          &lt;span class="s"&gt;--env PLAYTEST="${{ matrix.playtest }}" \&lt;/span&gt;
          &lt;span class="s"&gt;diddledan/snapcraft:core18&lt;/span&gt;

        &lt;span class="s"&gt;echo ::set-output name=snap::$(find $GITHUB_WORKSPACE -maxdepth 1 -type f -name "*.snap" | head -n1)&lt;/span&gt;

    &lt;span class="c1"&gt;# Release this Snap to the channel we choose.&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;snapcore/action-publish@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;store_login&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.STORE_LOGIN }}&lt;/span&gt;
        &lt;span class="na"&gt;snap&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ steps.build.outputs.snap }}&lt;/span&gt;
        &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;beta&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Store credentials for publishing
&lt;/h3&gt;

&lt;p&gt;The only thing left is to add a &lt;code&gt;STORE_LOGIN&lt;/code&gt; secret to the GitHub Repository with the contents of &lt;code&gt;store-credentials.secret&lt;/code&gt;, which you generate with the following command in a Linux Terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;snapcraft export-login &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--snaps&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SNAP_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
   &lt;span class="nt"&gt;--acls&lt;/span&gt; package_access,package_push,package_update,package_release &lt;span class="se"&gt;\&lt;/span&gt;
   store-credentials.secret
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make sure you replace &lt;code&gt;$SNAP_NAME&lt;/code&gt; with the name of your snap and you’ll be set.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;And that is the last step; my plan is complete. With the build of Snapcraft in a Container Image for all Snap Architectures, combined with &lt;code&gt;qemu-user-static&lt;/code&gt;, we can now build Snap Packages using GitHub Actions, Travis-CI, or any other competing Continuous Integration runner, for all architectures. We can now release to the Snap Store in whichever track we desire using rules that we choose and codify into our build setup.&lt;/p&gt;

&lt;p&gt;That's it from me, folks. Now it's your turn to Snap something awesome using this simple setup and your choice of CI provider! I can't wait to see what you build.&lt;/p&gt;

</description>
      <category>snapcraft</category>
      <category>github</category>
      <category>actions</category>
      <category>docker</category>
    </item>
    <item>
      <title>GPG-sign your Git commits and remember your SSH key passwords in WSL2 including Yubikey PGP support</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Wed, 30 Dec 2020 20:40:39 +0000</pubDate>
      <link>https://dev.to/lucyllewy/gpg-sign-your-git-commits-and-remember-your-ssh-key-passwords-in-wsl2-including-yubikey-pgp-support-39oe</link>
      <guid>https://dev.to/lucyllewy/gpg-sign-your-git-commits-and-remember-your-ssh-key-passwords-in-wsl2-including-yubikey-pgp-support-39oe</guid>
      <description>&lt;p&gt;This is a follow-up to my &lt;a href="https://snapcraft.ninja/2020/07/29/systemd-snap-packages-wsl2/"&gt;WSL2 hack enabling Systemd&lt;/a&gt; to run enabling all the awesome features such as service management and session management. The session management enables most graphical or GUI applications in the WSL2 Distro to function without issue when combined with an appropriate Windows-based X11 server such as &lt;a href="https://www.microsoft.com/store/productId/9NLP712ZMN9Q"&gt;X410&lt;/a&gt;, &lt;a href="https://mobaxterm.mobatek.net/"&gt;MobaXTerm&lt;/a&gt; or &lt;a href="https://sourceforge.net/projects/vcxsrv/"&gt;VCXSRV&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;So, you're working away in your WSL2 distro and then you want to sign a GIT commit with your PGP key which is backed by a Hardware Security Module like a YubiKey. Or you want to SSH into a remote system using an SSH key that has a long and difficult to remember password. Both activities can be improved or enabled by using Windows-based "Agents". For PGP we can use GPG4Win's &lt;code&gt;gpg-agent&lt;/code&gt; and for SSH we can use the SSH agent that is shipped with Windows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;p&gt;We only need to install GPG4Win, because Windows comes with the SSH Agent out of the box. So, to install GPG4Win we'll use winget. In a CMD or PowerShell window run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight batchfile"&gt;&lt;code&gt;&lt;span class="kd"&gt;winget&lt;/span&gt;&lt;span class="err"&gt;.exe&lt;/span&gt; &lt;span class="kd"&gt;install&lt;/span&gt; &lt;span class="kd"&gt;gpg4win&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Loading your keys
&lt;/h2&gt;

&lt;p&gt;Next we load your Private SSH or PGP keys or HSM-backed Public PGP keys into the Windows agents. For SSH keys this is easy; simply copy the keys to &lt;code&gt;C:\Users\&amp;lt;your-username&amp;gt;\.ssh\&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For PGP keys, use the Start Menu to open Kleopatra. If you are using an HSM you only need the public key as a file or the fingerprint ID to lookup the public key on a key server. To import a file-based key select "File" and then "Import" (or press &lt;code&gt;ctrl+I&lt;/code&gt;), locate your key file in the browser, and click "Open". To lookup a public key on a key server with the key ID select "File" and then "Lookup on server" (or press &lt;code&gt;ctrl+shift+I&lt;/code&gt;). In the dialog that opens enter your key's fingerprint ID, click search, select the correct key from the list and finally click "Import".&lt;/p&gt;

&lt;h2&gt;
  
  
  Ensuring the agents start automatically
&lt;/h2&gt;

&lt;p&gt;For SSH Agent this is easy to do with PowerShell. Open the run dialog by pressing &lt;code&gt;win+r&lt;/code&gt; and type powershell into the text box and finally pressing &lt;code&gt;ctrl+shift+enter&lt;/code&gt; to start it in a privileged mode. Now we type the following into the PowerShell window and then you can close it because we're finished there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh-agent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-StartupType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;automatic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Start-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ssh-agent&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start the GPG agent open a new unprivileged PowerShell window and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'C:\Program Files (x86)\GnuPG\bin\gpg-connect-agent.exe'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/bye&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, that only starts the agent once for your current session. To get it started every time you login you can open a privileged PowerShell like we did with the SSH agent above and run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Register-ScheduledJob&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;GPGAgent&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Trigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;New-JobTrigger&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-AtLogOn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-RunNow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ScriptBlock&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${env:ProgramFiles(x86)}&lt;/span&gt;&lt;span class="s2"&gt;/GnuPG/bin/gpg-connect-agent.exe"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;/bye&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Tying the agents to WSL2
&lt;/h2&gt;

&lt;p&gt;First you need your Distro to have the &lt;a href="https://github.com/wslutilities/wslu"&gt;WSLUtilities&lt;/a&gt; installed. On Ubuntu these are already present. Follow the steps on the linked GitHub page for your Distro.&lt;/p&gt;

&lt;p&gt;Next, we need &lt;code&gt;socat&lt;/code&gt; if it isn't already installed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;apt update
&lt;span class="nb"&gt;sudo &lt;/span&gt;apt &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-yyq&lt;/span&gt; socat
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, place the following code into a file in your Distro at &lt;code&gt;/etc/profile.d/wsl2-ssh-gpg-agents.sh&lt;/code&gt;. This presumes that your Distro automatically parses files in &lt;code&gt;/etc/profile.d&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;NPIPERELAY_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://github.com/NZSmartie/npiperelay/releases/download/v0.1/npiperelay.exe"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WSL_DISTRO_NAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nv"&gt;APPDATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;wslvar appdata&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;APPDATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;APPDATA&lt;/span&gt;&lt;span class="p"&gt;//\\/\/&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nv"&gt;NPIPERELAY_WIN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APPDATA&lt;/span&gt;&lt;span class="s2"&gt;/wsl2-ssh-gpg-npiperelay.exe"&lt;/span&gt;
    &lt;span class="nv"&gt;NPIPERELAY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;wslpath &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY_WIN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;curl &lt;span class="nt"&gt;-L&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY_URL&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;fi&lt;/span&gt;
    &lt;span class="c"&gt;#####&lt;/span&gt;
    &lt;span class="c"&gt;## Autorun for the gpg-relay bridge&lt;/span&gt;
    &lt;span class="c"&gt;##&lt;/span&gt;
    &lt;span class="nv"&gt;SOCAT_PID_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.gnupg/socat-gpg.pid
    &lt;span class="nv"&gt;SOCAT_PID_FILE2&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.gnupg/socat-gpg.pid.2

    &lt;span class="k"&gt;for &lt;/span&gt;GPG_SOCK &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;&lt;span class="s2"&gt;/.gnupg/S.gpg-agent"&lt;/span&gt; &lt;span class="s2"&gt;"/run/user/&lt;/span&gt;&lt;span class="nv"&gt;$UID&lt;/span&gt;&lt;span class="s2"&gt;/gnupg/S.gpg-agent"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
        if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ss &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GPG_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
            &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GPG_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            &lt;span class="nb"&gt;mkdir&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;dirname&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GPG_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
            setsid &lt;span class="nt"&gt;--fork&lt;/span&gt; socat UNIX-LISTEN:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$GPG_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,fork EXEC:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY&lt;/span&gt;&lt;span class="s2"&gt; -ei -ep -s -a "&lt;/span&gt;&lt;span class="s1"&gt;'"'&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$APPDATA&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;/gnupg/S.gpg-agent&lt;span class="s1"&gt;'"'&lt;/span&gt;,nofork
        &lt;span class="k"&gt;fi
    done&lt;/span&gt;

    &lt;span class="c"&gt;#####&lt;/span&gt;
    &lt;span class="c"&gt;## Autorun for the ssh-relay bridge&lt;/span&gt;
    &lt;span class="c"&gt;##&lt;/span&gt;
    &lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$HOME&lt;/span&gt;/.ssh/agent.sock
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; ss &lt;span class="nt"&gt;-a&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-q&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
        setsid &lt;span class="nt"&gt;--fork&lt;/span&gt; socat UNIX-LISTEN:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$SSH_AUTH_SOCK&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;,fork EXEC:&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$NPIPERELAY&lt;/span&gt;&lt;span class="s2"&gt; -ei -s //./pipe/openssh-ssh-agent"&lt;/span&gt;,nofork
    &lt;span class="k"&gt;fi
fi&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Finishing up
&lt;/h2&gt;

&lt;p&gt;You're done. Just restart your WSL2 session and you'll be able to use the GPG and SSH agents hosted on Windows from within the Linux environment. This also works for Yubikey-backed PGP keys!&lt;/p&gt;

&lt;p&gt;I hope to return to this soon to automate the installation the way that Damion Gans did for the Systemd hack.&lt;/p&gt;

</description>
      <category>gpg</category>
      <category>ssh</category>
      <category>yubikey</category>
      <category>wsl</category>
    </item>
    <item>
      <title>Keyoxide Proof</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Tue, 11 Aug 2020 22:46:09 +0000</pubDate>
      <link>https://dev.to/lucyllewy/keyoxide-proof-212</link>
      <guid>https://dev.to/lucyllewy/keyoxide-proof-212</guid>
      <description>&lt;p&gt;This is an OpenPGP proof that connects &lt;a href="https://keyoxide.org/4c9cbfad0069d6799660bcd540c2d9580349ed21" rel="noopener noreferrer"&gt;my OpenPGP key&lt;/a&gt; to &lt;a href="https://dev.to/lucyllewy"&gt;this dev.to account&lt;/a&gt;. For details check out &lt;a href="https://keyoxide.org/guides/openpgp-proofs" rel="noopener noreferrer"&gt;https://keyoxide.org/guides/openpgp-proofs&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;[Verifying my OpenPGP key: openpgp4fpr:4c9cbfad0069d6799660bcd540c2d9580349ed21]&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Lazy Loading images in WordPress</title>
      <dc:creator>Lucy Llewellyn</dc:creator>
      <pubDate>Tue, 16 Jun 2020 22:11:53 +0000</pubDate>
      <link>https://dev.to/lucyllewy/lazy-loading-images-in-wordpress-10a0</link>
      <guid>https://dev.to/lucyllewy/lazy-loading-images-in-wordpress-10a0</guid>
      <description>&lt;p&gt;While WordPress Core is working towards their own Native Lazy Loading of images, I have been using something for a while already to do the job. I'm sure I found this from, or was inspired by, someone else's code but I don't recall where. If it is your code, please leave a comment so that I may correctly attribute it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step one
&lt;/h2&gt;

&lt;p&gt;The first step is to hook into the &lt;code&gt;wp_get_attachment_image_attributes&lt;/code&gt; filter to override the in-built &lt;code&gt;src&lt;/code&gt;, &lt;code&gt;srcset&lt;/code&gt;, and &lt;code&gt;sizes&lt;/code&gt; attributes set by Core. We rename these attributes to &lt;code&gt;data-$attribute_name&lt;/code&gt;. The new image data doesn't do anything by itself. We must move it to the correct place using a small shim in the second code snippet below: (Both snippets may be placed into your theme's &lt;code&gt;functions.php&lt;/code&gt; file)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;add_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_get_attachment_image_attributes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'lazyload_images'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;lazyload_images&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attr&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nv"&gt;$return&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$attr&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'sizes'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'src'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s1"&gt;'srcset'&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"data-&lt;/span&gt;&lt;span class="nv"&gt;$key&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="nv"&gt;$return&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nv"&gt;$key&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nv"&gt;$value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="nv"&gt;$return&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'class'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="mf"&gt;.&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;' lazyload'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nv"&gt;$return&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'loading'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'lazy'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nv"&gt;$return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step two
&lt;/h2&gt;

&lt;p&gt;For the second, and final, step, we print a small bit of JavaScript into the footer of our site to load a fallback script. Alternatively, we rewrite the &lt;code&gt;data-$attribute_name&lt;/code&gt; attributes back to their original names for native lazy loading. This code detects at load time whether the browser supports native lazy loading, or we need to use something else to mimic it. In this case the fallback script we use is &lt;a href="https://github.com/aFarkas/lazysizes"&gt;LazySizes.js&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight php"&gt;&lt;code&gt;&lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="nf"&gt;add_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt; &lt;span class="s1"&gt;'wp_footer'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'native_lazyload_handler'&lt;/span&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;function&lt;/span&gt; &lt;span class="n"&gt;native_lazyload_handler&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="cp"&gt;?&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;script&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;loading&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;HTMLImageElement&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prototype&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;images&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;img.lazyload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;images&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;img&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sizes&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;srcset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;img&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;srcset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Dynamically import the LazySizes library&lt;/span&gt;
        &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;script&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;script&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;src&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://cdnjs.cloudflare.com/ajax/libs/lazysizes/5.2.2/lazysizes.min.js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;appendChild&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;script&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/script&amp;gt;&lt;/span&gt;
    &lt;span class="cp"&gt;&amp;lt;?php&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Last thoughts
&lt;/h2&gt;

&lt;p&gt;You might recall, we also add the class name of &lt;code&gt;lazyload&lt;/code&gt; in the first snippet. So, if the browser doesn't support native lazy loading then the LazySizes.js fallback loads. This uses the &lt;code&gt;data-$attribute_name&lt;/code&gt; attributes unchanged. Those we also set in the first snippet.&lt;/p&gt;

&lt;p&gt;If, however, the browser does support native lazy loading then we simply rewrite the &lt;code&gt;data-$attribute_name&lt;/code&gt; back to their original names. Then we let the browser do its thing. In this case, we don't load any extra JavaScript.&lt;/p&gt;

&lt;p&gt;For even faster load times we set the LazySizes script to the &lt;code&gt;async&lt;/code&gt; load method. With this, the browser loads it asynchronously without blocking the page render. This means that the Time Till First Meaningful Paint is quicker because the browser doesn't wait for the script to load. Once LazySizes loads, or the attributes rewritten for Native Lazy Loading, then the images pop into place as soon as they are visible. Both browser native lazy loading and LazySizes 5.0+ support loading without the page jumping about as the images are loaded. To do this, they use the &lt;code&gt;width&lt;/code&gt; and &lt;code&gt;height&lt;/code&gt; attributes. These attributes indicate the correct aspect ratio of the image, which the browser and LazySizes use to cut out the right amount of space for the image.&lt;/p&gt;

&lt;p&gt;--&lt;/p&gt;

&lt;p&gt;Photo by &lt;a href="https://www.pexels.com/@matilda-wormwood?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Matilda Wormwood&lt;/a&gt; from &lt;a href="https://www.pexels.com/photo/close-up-photo-of-woman-using-her-mobile-phone-4100843/?utm_content=attributionCopyText&amp;amp;utm_medium=referral&amp;amp;utm_source=pexels"&gt;Pexels&lt;/a&gt;&lt;/p&gt;

</description>
      <category>wordpress</category>
      <category>lazyloading</category>
      <category>performance</category>
    </item>
  </channel>
</rss>
