{"id":3233,"date":"2022-02-07T14:24:29","date_gmt":"2022-02-07T02:24:29","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=3233"},"modified":"2022-02-07T14:24:36","modified_gmt":"2022-02-07T02:24:36","slug":"kubernetes-home-dashboard","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/3233\/","title":{"rendered":"Kubernetes @ Home: Dashboard"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">13<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span>\n<p>At the end of <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/3148\/\" data-type=\"URL\" data-id=\"https:\/\/www.deltics.co.nz\/blog\/posts\/3148\/\">the previous post in this series<\/a>, we reached the point where I had my <strong>Kubernetes<\/strong> cluster up and running, including a <strong>Dashboard<\/strong> service.  As I mentioned, this Dashboard is not part of a default installation.  Taking a look at how I got this up and running provides a handy introduction to some further <strong>Kubernetes<\/strong> concepts.  So let&#8217;s get into it.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\" id=\"additional-networking-terminologynodeip-clusterip-and-loadbalanced\">First.. Some More Terminology<\/h2>\n\n\n\n<p>As we dive into this area, we are going to need some additional <strong>Kubernetes<\/strong> terminology and concepts, particularly in the networking space.<\/p>\n\n\n\n<p>First of all, let&#8217;s look again at the output when we get a list of all services running in the cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"68\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=640%2C68&#038;ssl=1\" alt=\"\" class=\"wp-image-3237\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=1024%2C109&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=512%2C55&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=768%2C82&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=1536%2C164&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?resize=380%2C40&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?w=1802&amp;ssl=1 1802w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-all-services-default.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>Before we get into installing the <strong>Dashboard<\/strong> itself, there are a number of things introduced in this short and simple output we need some understanding of:<\/p>\n\n\n\n<ul><li><strong>Namespace<\/strong><\/li><li>Service <strong>Type<\/strong><ul><li>ClusterIP<\/li><li>LoadBalancer<\/li><\/ul><\/li><li><strong>Cluster IP<\/strong><\/li><li><strong>External IP<\/strong><\/li><\/ul>\n\n\n\n<p>We&#8217;ll look at service <strong>Type<\/strong> and the different IP types when we get to the <strong>Dashboard<\/strong>.  First, let&#8217;s look at <strong>Namespaces<\/strong>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"namespaces\">Namespaces<\/h2>\n\n\n\n<p>To recap: A <strong>Kubernetes<\/strong> cluster provides a pool of resources on which services and applications can run.<\/p>\n\n\n\n<p>There can be a large number of services (<a href=\"https:\/\/www.geeksforgeeks.org\/the-story-of-netflix-and-microservices\/\" data-type=\"URL\" data-id=\"https:\/\/www.geeksforgeeks.org\/the-story-of-netflix-and-microservices\/\">Netflix reportedly has a fleet of over 1,000 services<\/a> and that&#8217;s a relatively simple video streaming service) and namespaces provide a way of organising them into manageable groups within a cluster.<\/p>\n\n\n\n<p>But namespaces aren&#8217;t only relevant to services.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"what-s-in-a-namespace\">What&#8217;s In A Namespace?<\/h2>\n\n\n\n<p>Almost all objects in a <strong>Kubernetes<\/strong> cluster are scoped to a <strong>Namespace<\/strong>, and there are many, many types of objects.  But there are some exceptions.<\/p>\n\n\n\n<p>The <strong>Cluster<\/strong> itself is one obvious exception.  <strong>Nodes<\/strong> are another.<\/p>\n\n\n\n<p>Since any given <strong>node<\/strong> could be hosting services or applications from any number of N<strong>amespaces<\/strong>, a <strong>node<\/strong> itself isn&#8217;t assigned to a <strong>Namespace<\/strong>.<\/p>\n\n\n\n<p><strong>Services<\/strong> on the other hand <em>are<\/em> assigned to a <strong>namespace<\/strong>.  This means that two services can exist with the <em>same name<\/em> in different <strong>Namespaces<\/strong>.<\/p>\n\n\n\n<p><strong>Namespaces<\/strong> (like so much of <strong>Kubernetes<\/strong>) <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/namespaces\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/concepts\/overview\/working-with-objects\/namespaces\/\">are a complex area<\/a> and one that I&#8217;ve yet to delve into very deeply (<em>one of the reasons for creating this cluster was to have somewhere to do such delving!<\/em>).<\/p>\n\n\n\n<p>But there are a couple of things that are worth mentioning.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"namespaces-and-name-resolution\">Namespaces and Name Resolution<\/h2>\n\n\n\n<p>Perhaps the single most important thing for an application\/service developer to understand about <strong>Namespaces<\/strong> are the implications for <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/services-networking\/dns-pod-service\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/concepts\/services-networking\/dns-pod-service\/\">name resolution<\/a>.<\/p>\n\n\n\n<p>In simple terms, thanks to the DNS running internally within a <strong>Kubernetes<\/strong> cluster, when one service calls another in the same namespace then only the <em>service name<\/em> is required.  Services can call services in other namespaces but must use a <em>qualified<\/em> name to do so.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"namespaces-and-kubectl-context\">Namespaces and KubeCtl Context<\/h2>\n\n\n\n<p>The second most important thing to understand is that <strong>kubectl<\/strong> commands need to be told which namespace any particular request should be applied to.  Up to now we have side-stepped this requirement by using the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">-A<\/mark> option, for &#8220;all namespaces&#8221;, but in a real-world cluster with the volume of services and other objects involved this is likely to be impractical.<\/p>\n\n\n\n<p>Specifying the <strong>namespace<\/strong> on <em>every<\/em> command can quickly become tedious, especially if you are working primarily (or exclusively) in just <em>one<\/em> namespace.  Fortunately, kubectl understands this and so provides the concept of a &#8220;context&#8221; to simplify things.<\/p>\n\n\n\n<p>In simple terms, <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/configuration\/organize-cluster-access-kubeconfig\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/concepts\/configuration\/organize-cluster-access-kubeconfig\/\">a <strong>context<\/strong> identifies a <strong>user account<\/strong>, <strong>cluster<\/strong> and <strong>namespace<\/strong><\/a> that will be <em>assumed<\/em> for any <strong>kubectl<\/strong> commands using that <strong>context<\/strong>.  The cluster and namespace can be overridden by specifying alternatives via options on a <strong>kubectl<\/strong> command itself.<\/p>\n\n\n\n<p>If you override only the <strong>namespace<\/strong>, then the <strong>cluster<\/strong> used will remain the one specified in the <strong>context<\/strong>, and vice versa.<\/p>\n\n\n\n<p>Contexts are defined in a <strong>kubeconfig<\/strong> file (e.g. <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">~\/.kube\/config<\/mark>).  Depending on your environment you may find it useful to have contexts for different clusters or different user accounts within a cluster or for different namespaces etc.<\/p>\n\n\n\n<p>Any number of <strong>contexts<\/strong> may be defined in a <strong>kubeconfig<\/strong> but only one <strong>context<\/strong> is the <strong><em>current<\/em> context<\/strong> in that <strong>kubeconfig<\/strong>.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">You don&#8217;t have to keep your <strong>kubeconfig<\/strong> in <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">~\/.kube\/config<\/mark>.  This is just the place that <strong>kubectl<\/strong> looks if not otherwise told.  You can even maintain multiple <strong>kubeconfig<\/strong> files if that suits your needs, <a href=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/configure-access-multiple-clusters\/#set-the-kubeconfig-environment-variable\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/configure-access-multiple-clusters\/#set-the-kubeconfig-environment-variable\">using an environment variable to tell <strong>kubectl<\/strong> where to find them all<\/a> (they are merged to form an &#8220;uber config&#8221;).  Or you can identify a specific <strong>kubeconfig<\/strong> file to use for a particular command using the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">&#8211;kubeconfig &lt;filename&gt;<\/mark> option.<br><br><strong>kubectl<\/strong> is nothing if not flexible in this area.<\/p>\n\n\n\n<p>The <strong>current<\/strong> <strong>context<\/strong> can be identified by asking <strong>kubectl<\/strong> to list all contexts; in the resulting output the <strong>current<\/strong> <strong>context<\/strong> is identified by an asterisk (<code>*<\/code>)in the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">CURRENT<\/mark> column:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"40\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=640%2C40&#038;ssl=1\" alt=\"\" class=\"wp-image-3252\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=1024%2C64&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=512%2C32&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=768%2C48&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=1536%2C96&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?resize=380%2C24&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?w=1832&amp;ssl=1 1832w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-contexts.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>In this example, there is only one <strong>context<\/strong> so that is the current context by default.  This <strong>context<\/strong> has no <strong>namespace<\/strong> configured and so will implicitly use the <strong>default<\/strong> namespace.<\/p>\n\n\n\n<p>To illustrate, if I repeat the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">kubectl get services<\/mark> command without the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">-A<\/mark> option, then I get only those services that are in the <strong>default<\/strong> namespace.  In this case, since there is only one namespace involved, the <strong>NAMESPACE<\/strong> column is redundant and therefore not included:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"36\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=640%2C36&#038;ssl=1\" alt=\"\" class=\"wp-image-3254\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=1024%2C58&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=512%2C29&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=768%2C44&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=1536%2C87&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?resize=380%2C22&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?w=1826&amp;ssl=1 1826w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>On the other hand, if I add the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">&#8211;namespace<\/mark> option and specify the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">kubernetes-dashboard<\/mark> namespace, then I get the services that are in the <strong>kubernetes-dashboard<\/strong> namespace.  Again, the <strong>NAMESPACE<\/strong> column is omitted as redundant:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"48\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=640%2C48&#038;ssl=1\" alt=\"\" class=\"wp-image-3255\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=1024%2C76&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=512%2C38&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=768%2C57&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=1536%2C114&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?resize=380%2C28&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?w=1832&amp;ssl=1 1832w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-services-dashboard.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"more-namespaces\">More Namespaces<\/h2>\n\n\n\n<p>When we listed the services in the cluster including all namespaces (the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-red-color\">-A<\/mark> option), the output included services in 3 namespaces:<\/p>\n\n\n\n<ul><li>default<\/li><li>kube-system<\/li><li>kubernetes-dashboard<\/li><\/ul>\n\n\n\n<p>But these are not the only <strong>namespaces<\/strong> in the <strong>cluster<\/strong>, only the <strong>namespaces<\/strong> that contain <strong><em>services<\/em><\/strong>.  We can ask <strong>kubectl<\/strong> to list <em>all<\/em> namespaces explicitly by specifically asking for that:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\" style=\"font-size:14px\">kubectl get namespaces<\/pre>\n\n\n\n<p>And the output will resemble:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"78\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=640%2C78&#038;ssl=1\" alt=\"\" class=\"wp-image-3253\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=1024%2C124&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=512%2C62&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=768%2C93&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=1536%2C187&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?resize=380%2C46&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?w=1826&amp;ssl=1 1826w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubectl-get-namespaces.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>Now we see some <em>additional<\/em> namespaces:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><tbody><tr><td><strong>kube-node-lease<\/strong><\/td><td>Contains objects that are part of the heartbeat mechanism by which <strong>Kubernetes<\/strong> monitors the health of nodes in the cluster<\/td><\/tr><tr><td><strong>kube-public<\/strong><\/td><td><a href=\"https:\/\/stackoverflow.com\/a\/45934899\/123487\" data-type=\"URL\" data-id=\"https:\/\/stackoverflow.com\/a\/45934899\/123487\">Provides objects which describe configuration information about the cluster<\/a>.<\/td><\/tr><tr><td><strong>metallb-system<\/strong><\/td><td>Contains objects that are part of the <strong>MetalLB LoadBalancer<\/strong>.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>We&#8217;ll be covering the <strong>MetalLB Load Balancer<\/strong> as part of configuring the <strong>Dashboard<\/strong>, shortly.  Suffice to say that this is again something that won&#8217;t be present in a freshly initialised cluster and needs to be added.<\/p>\n\n\n\n<p>As we&#8217;ll see, this <strong>Load Balancer<\/strong> is one way to provide access to services running in a cluster for consumers that are <em>outside<\/em> of that cluster and we&#8217;ll see how it is used to make the dashboard available, once it has been installed.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"kubernetes-dashboard\">Kubernetes Dashboard<\/h2>\n\n\n\n<p>Now let&#8217;s look at the <strong>Kubernetes Dashboard<\/strong>, why we might want it and how we get it.<\/p>\n\n\n\n<p>We&#8217;ve seen that we can interrogate our <strong>Kubernetes<\/strong> cluster using various <strong>kubectl<\/strong> commands; we have barely scratched the surface of what is possible even just with that.<\/p>\n\n\n\n<p>But sometimes it is nice to have a &#8220;helicopter&#8221; view, something that can give us far more information at a glance.<\/p>\n\n\n\n<p>The <strong>Dashboard<\/strong> does this and more.<\/p>\n\n\n\n<p>To demonstrate, this is what my dashboard shows me when viewing the workloads in the <strong>kubernetes-dashboard<\/strong> namespace in my cluster:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><a href=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?ssl=1\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"476\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?resize=640%2C476&#038;ssl=1\" alt=\"\" class=\"wp-image-3258\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?resize=1024%2C762&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?resize=512%2C381&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?resize=768%2C571&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?resize=350%2C260&amp;ssl=1 350w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?w=1394&amp;ssl=1 1394w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-namespace-workloads.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/a><\/figure>\n\n\n\n<p>I can see at a glance that there are 2 healthy deployments, running on 2 healthy pods with 2 healthy <a href=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/replicaset\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/concepts\/workloads\/controllers\/replicaset\/\">replica sets<\/a>.<\/p>\n\n\n\n<p>The <strong>replica sets<\/strong> are defined by the <strong>deployments<\/strong>.  Each replica set describes the container images that need to be deployed, the minimum number of instances of each container required and the criteria by which those containers should be scaled out to additional instances.  Each instance of a running container is placed in a <strong>pod<\/strong>.<\/p>\n\n\n\n<p>Each <strong>deployment<\/strong> requested (via a replica set), and is currently running, 1 <strong>pod<\/strong>.<\/p>\n\n\n\n<p>One of those <strong>pods<\/strong> is running the container <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">kubernetesui\/metrics-scraper:v1.0.7<\/mark> on the <strong>worker1<\/strong> node and the other is running <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">kubernetesui\/dashboard:v2.4.0<\/mark> on the <strong>worker3<\/strong> node.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">We haven&#8217;t discussed <strong>pods<\/strong> yet and will do so in more detail in another post.  For now, all you need to know is that when <strong>kubernetes<\/strong> deploys a container, a <strong>pod<\/strong> is what is used to <em>host<\/em> that container.  One container per <strong>pod<\/strong>. <\/p>\n\n\n\n<p>So how do we install this dashboard?<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"installing-the-dashboard\">Installing the Dashboard<\/h2>\n\n\n\n<p>Since the dashboard runs in <strong>Kubernetes<\/strong> itself, installing it is a simple matter of asking <strong>Kubernetes<\/strong> to apply a deployment describing everything required to run that application.  Certainly, <a href=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/\">this is what the instructions suggest<\/a>:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-bash\">kubectl apply -f https:\/\/raw.githubusercontent.com\/kubernetes\/dashboard\/v2.4.0\/aio\/deploy\/recommended.yaml<\/code><\/pre>\n\n\n\n<p>However, if you do that, you get the &#8220;recommended&#8221; deployment.  This is <em>A Good Thing<\/em> if your cluster is in a production environment, as this recommended deployment is served over HTTPS and requires authentication to access.<\/p>\n\n\n\n<p>But in a home lab, this is a <em>real<\/em> pain.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"certificate-issues\">Certificate Issues<\/h2>\n\n\n\n<p>First of all, the HTTPS server uses a self-signed certificate which many modern browsers treat with varying degrees of suspicion.  To make matters worse, the particular certificate employed by the dashboard has some characteristics that make the certificate particularly untrustworthy.<\/p>\n\n\n\n<p><strong>Firefox<\/strong> at least still allows the user to accept the risk and proceed to the site, after acknowledging a warning.  <strong>Chrome<\/strong> and <strong>Edge<\/strong> simply refuse to connect you.<\/p>\n\n\n\n<p>One option here is to adjust the deployment to use a more trustworthy certificate (or at least one that Chrome-based browsers will be more tolerant of).  I tried going down <a href=\"https:\/\/github.com\/ubuntu\/microk8s\/issues\/1046#issuecomment-617190819\" data-type=\"URL\" data-id=\"https:\/\/github.com\/ubuntu\/microk8s\/issues\/1046#issuecomment-617190819\">this route<\/a> and succeeded only in elevating my blood pressure and breaking things. \ud83d\ude41<\/p>\n\n\n\n<p>Fortunately, there is another alternative.<\/p>\n\n\n\n<p>Literally.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"alternative-deployment\">Alternative Deployment<\/h2>\n\n\n\n<p>If you visit the <a href=\"https:\/\/github.com\/kubernetes\/dashboard\" data-type=\"URL\" data-id=\"https:\/\/github.com\/kubernetes\/dashboard\" target=\"_blank\" rel=\"noreferrer noopener\">github repo<\/a> which holds the dashboard deployment file referenced in the command, you will find that <a href=\"https:\/\/github.com\/kubernetes\/dashboard\/tree\/master\/aio\/deploy\" data-type=\"URL\" data-id=\"https:\/\/github.com\/kubernetes\/dashboard\/tree\/master\/aio\/deploy\" target=\"_blank\" rel=\"noreferrer noopener\">alongside the &#8220;<strong>recommended<\/strong>&#8221; deployment folder is one named &#8220;<strong>alternative<\/strong>&#8220;<\/a>.<\/p>\n\n\n\n<p>The <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">kubectl apply<\/mark> command above applies a file directly from an HTTPS URL.  While this can be fine for well known and understood deployments, it is generally recommended to instead download such files and apply them by referencing the local file system copy.  This allows you to inspect files and adjust where\/if necessary before applying it to your cluster (as we will need to do in this case).<\/p>\n\n\n\n<p>But let&#8217;s take a look at it first.<\/p>\n\n\n\n<p>I won&#8217;t reproduce the entire file since this is quite large.  The first section of main interest appears quite near the top and is this:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">kind: Service\napiVersion: v1\nmetadata:\n  labels:\n    k8s-app: kubernetes-dashboard\n  name: kubernetes-dashboard\n  namespace: kubernetes-dashboard\nspec:\n  ports:\n    - port: 80\n      targetPort: 9090\n  selector:\n    k8s-app: kubernetes-dashboard<\/code><\/pre>\n\n\n\n<p>This is the entry that describes the <strong>Service<\/strong> for the dashboard UI.  The <strong>ports<\/strong> section identifies the mapping that established the external <strong>port<\/strong> (80) by which we may connect to this service and the <strong>targetPort<\/strong> (9090) on the service that connections will be forwarded to.<\/p>\n\n\n\n<p>The external <strong>port<\/strong> is 80!<\/p>\n\n\n\n<p>This means that this <strong>alternative<\/strong> deployment serves up the UI on plain ol&#8217; bare-bones HTTP!<\/p>\n\n\n\n<p>This is highly insecure of course, which is why this is not recommended in a production scenario.  But perfect for my home lab.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">I am happy to leave certificate administration to SecOps experts.  I may be wrong but, to my mind, this falls fairly and squarely into the category of things that as an application\/service developer I need to understand the importance of but do not need to fill my head up with the details of how to make it all work.<br><br>If I absolutely have to, then I will struggle and stumble my way through the necessary processes, but since this only arises every year or so is never something that is going to become intuitive and habitual, even if the process was consistent and reliable.<br><br>For example, I have a domain certificate for the DynDNS domain that I use to identify the internet-facing side of my router to provide a secure VPN connection to my home network when I&#8217;m out and about.<br><br>That&#8217;s not something that would be sensible to ignore.<br><br>It took me about a week of dead-ends, false starts and screw-ups to sort out when I first set it up and I meticulously documented the steps involved as I figured them out.  But when I came to renew the certificate recently it again took me a week as it turned out that the process for uploading a cert onto my router had changed since I last did it!<br><br>No doubt it will change again by the time it comes around again!<br><br>I have had similar experiences in enterprise settings when instructions for updating certs on servers created by server ops teams for application teams to &#8220;manage their own servers&#8221; were similarly out-of-date by the time they were needed.  The server ops teams themselves were well aware of the changes but hadn&#8217;t bothered updating the documentation (nobody&#8217;s favorite job).<br><br>Let people who know how to manage servers manage the servers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"authentication-issues\">Authentication Issues<\/h2>\n\n\n\n<p><s>Dodging<\/s> Solving the certificate issue creates a different problem.<\/p>\n\n\n\n<p class=\"has-white-color has-vivid-red-background-color has-text-color has-background\">You may notice that I am jumping ahead a little here &#8211; we will look at how to access the dashboard soon, but for now just take it as a given that we have a solution to that.<\/p>\n\n\n\n<p>When we connect to the dashboard, we are presented with a sign-in prompt:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"181\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?resize=640%2C181&#038;ssl=1\" alt=\"\" class=\"wp-image-3277\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?resize=1024%2C289&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?resize=512%2C145&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?resize=768%2C217&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?resize=380%2C107&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?w=1356&amp;ssl=1 1356w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-no-skip.png?w=1280&amp;ssl=1 1280w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>This seems a little unfriendly, to put it mildly.<\/p>\n\n\n\n<p>Instructions are provided <a href=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/#accessing-the-dashboard-ui\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/#accessing-the-dashboard-ui\">for creating a user and token<\/a>, though this comes with the caveat that the user will have administrator access and so is only suitable for &#8220;educational purposes only&#8221;.  Again, that&#8217;s fine by me &#8211; my education is what this entire process is for!  \ud83d\ude42<\/p>\n\n\n\n<p>But it&#8217;s still a fair amount of work and the token-based login remains clunky.<\/p>\n\n\n\n<p>Besides, even if I was able to provide valid sign-in credentials, I would still be prevented from signing in because I am not using <strong>HTTPS<\/strong> and not accessing the dashboard via <strong>localhost<\/strong>.<\/p>\n\n\n\n<p>Fortunately, there is another way that doesn&#8217;t involve creating a new user.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"first-skip-login\">First: Skip Login<\/h2>\n\n\n\n<p>Much further into the dashboard configuration file, there is a section that describes the <strong>deployment<\/strong> of the dashboard UI itself.  As part of that <strong>deployment<\/strong>, the required container is described, along with <strong>args<\/strong> that are passed to the container:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">      containers:\n        - name: kubernetes-dashboard\n          image: kubernetesui\/dashboard:v2.4.0\n          ports:\n            - containerPort: 9090\n              protocol: TCP\n          args:\n            - --namespace=kubernetes-dashboard\n            - --enable-insecure-login\n          # Uncomment the following line to manually specify Kubernetes API server Host\n          # If not specified, Dashboard will attempt to auto discover the API server and connect\n          # to it. Uncomment only if the default does not work.\n          # - --apiserver-host=http:\/\/my-address:port<\/code><\/pre>\n\n\n\n<p>One interesting thing to note here is the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">&#8211;enable-insecure-login<\/mark> arg which might suggest that the warning that sign-on won&#8217;t be available whilst using an insecure connection is perhaps misleading.<\/p>\n\n\n\n<p>But even more interesting (though less obvious) is that an <em>additional<\/em> arg is supported by this container that will enable a &#8220;Skip&#8221; option in that sign-in dialog:  <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">&#8211;enable-skip-login<\/mark><\/p>\n\n\n\n<p>So we can simply add that:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">      containers:\n        - name: kubernetes-dashboard\n          image: kubernetesui\/dashboard:v2.4.0\n          ports:\n            - containerPort: 9090\n              protocol: TCP\n          args:\n            - --namespace=kubernetes-dashboard\n            - --enable-insecure-login\n            - --enable-skip-login\n          # Uncomment the following line to manually specify Kubernetes API server Host\n          # If not specified, Dashboard will attempt to auto discover the API server and connect\n          # to it. Uncomment only if the default does not work.\n          # - --apiserver-host=http:\/\/my-address:port<\/code><\/pre>\n\n\n\n<p>With this in place, the sign-in dialog now includes a &#8220;Skip&#8221; option:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"156\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=640%2C156&#038;ssl=1\" alt=\"\" class=\"wp-image-3280\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=1024%2C250&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=512%2C125&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=768%2C187&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=1536%2C374&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=2048%2C499&amp;ssl=1 2048w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?resize=380%2C93&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?w=1280&amp;ssl=1 1280w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-signin-skip.png?w=1920&amp;ssl=1 1920w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>This enables us to ignore\/bypass the sign-in requirements completely, accessing the dashboard using the dashboards own service account.<\/p>\n\n\n\n<p>Unfortunately, this account has very limited access to the cluster &#8211; virtually none in fact.  Various alert notifications in the dashboard UI will describe the problems this causes and, most obviously, there will be no useful content available.  Not even a list of namespaces in the cluster.<\/p>\n\n\n\n<p>Obviously the dashboard is going to be of no use <strong>at all<\/strong> unless we fix this.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"second-admin-access-by-default\">Second: Admin Access By Default<\/h2>\n\n\n\n<p>We can fix the dashboard&#8217;s default access to the cluster by modifying the <strong>ClusterRoleBinding<\/strong> used by the dashboard service account.<\/p>\n\n\n\n<p>The original configuration (in the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">alternative.yaml<\/mark> file) looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">apiVersion: rbac.authorization.k8s.io\/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: kubernetes-dashboard\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: kubernetes-dashboard\nsubjects:\n  - kind: ServiceAccount\n    name: kubernetes-dashboard\n    namespace: kubernetes-dashboard<\/code><\/pre>\n\n\n\n<p>What this <strong>ClusterRoleBinding<\/strong> does is associate &#8211; or bind (duh!) &#8211; some set of <strong>subjects<\/strong> with a particular role in the cluster.<\/p>\n\n\n\n<p>In this case, there is only one <strong>subject<\/strong>, the <strong>ServiceAccount<\/strong> (also called <strong>kubernetes-dashboard<\/strong>) established for the dashboard.  This <strong>ServiceAccount<\/strong> is defined elsewhere in the file, but we don&#8217;t need to be concerned with that.  To fix the permissions we just need this <strong>ClusterRoleBinding<\/strong>.<\/p>\n\n\n\n<p>The role bound to that <strong>ServiceAccount<\/strong> is specified as <strong>kubernetes-dashboard<\/strong>.  This role also is defined elsewhere in the file.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">If you&#8217;ve been keeping count, that&#8217;s at least 4 different objects all called &#8220;<strong>kubernetes-dashboard<\/strong>&#8220;.  A <strong>namespace<\/strong>, a <strong>service<\/strong>, a <strong>service account<\/strong> and a <strong>role<\/strong>.  Heck, even the <strong>ClusterRoleBinding<\/strong> uses this name as well!  (So that&#8217;s FIVE and counting&#8230;).<br><br>Obviously this doesn&#8217;t cause problems for <strong>Kubernetes<\/strong> and <em>could<\/em> be seen as indicative of good practice, given that this all comes from the <strong>kubernetes<\/strong> team.  Draw your own conclusions.<\/p>\n\n\n\n<p>Rather than modify the referenced role, we can instead simply change <em>which<\/em> role is bound to the service account.<\/p>\n\n\n\n<p>If we change it to <strong>cluster-admin<\/strong>, then this gives the <strong>kubernetes-dashboard<\/strong> service account admin access to the cluster:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">apiVersion: rbac.authorization.k8s.io\/v1\nkind: ClusterRoleBinding\nmetadata:\n  name: kubernetes-dashboard\nroleRef:\n  apiGroup: rbac.authorization.k8s.io\n  kind: ClusterRole\n  name: cluster-admin\nsubjects:\n  - kind: ServiceAccount\n    name: kubernetes-dashboard\n    namespace: kubernetes-dashboard<\/code><\/pre>\n\n\n\n<p class=\"has-white-color has-vivid-red-background-color has-text-color has-background\">It goes without saying that this is NOT something you would do in anything other than an experimental\/educational cluster!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"to-summarise\">To Summarise<\/h2>\n\n\n\n<p>To establish an easy to use\/convenient dashboard in a cluster:<\/p>\n\n\n\n<ol><li>Download the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">alternative.yaml<\/mark> deployment file.  This will deploy a dashboard served over plain HTTP<\/li><li>Modify the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">alternative.yaml<\/mark>:<ol><li>Add <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">&#8211;enable-skip-login<\/mark> to the dashboard container <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">args<\/mark><\/li><li>Change the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">roleRef.name<\/mark> in the <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-cyan-blue-color\">kubernetes-dashboard<\/mark> <strong>ClusterRoleBinding<\/strong> to <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">cluster-admin<\/mark><\/li><\/ol><\/li><\/ol>\n\n\n\n<p>If using VS Code with Kubernetes extensions installed, there will be some warnings highlighted in this <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">alternative.yaml<\/mark> file:<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"494\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?resize=640%2C494&#038;ssl=1\" alt=\"\" class=\"wp-image-3282\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?resize=1024%2C790&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?resize=512%2C395&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?resize=768%2C593&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?resize=337%2C260&amp;ssl=1 337w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-vscode-validation.png?w=1174&amp;ssl=1 1174w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>This is due to the lack of <strong>resources.limits<\/strong> in the specifications for the containers.  These particular containers are unlikely to run amok in the cluster without these limits so you can probably safely ignore this, <em>or<\/em> you can add appropriate CPU and Memory resource limits, if only to keep the validation extension happy.<\/p>\n\n\n\n<p>On no basis other than guesstimation, I chose 521MB of RAM and 25% of a CPU as the limits:<\/p>\n\n\n\n<pre class=\"wp-block-prismatic-blocks\"><code class=\"language-yaml\">         resources:\n            limits:\n              memory: 512Mi\n              cpu: 250m<\/code><\/pre>\n\n\n\n<p>This can go anywhere in the section for each container (i.e. at the same indentation level as <strong>name<\/strong>, <strong>image<\/strong> etc).<\/p>\n\n\n\n<p>Ask kubernetes to deploy the modified file:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\">kubectl apply -f alternative.yaml<\/pre>\n\n\n\n<p>And after a (very) few seconds you should see the pods up and running (using the command: <code>kubectl get pods --namespace kubernetes-dashboard<\/code>):<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img decoding=\"async\" loading=\"lazy\" width=\"640\" height=\"50\" src=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=640%2C50&#038;ssl=1\" alt=\"\" class=\"wp-image-3285\" srcset=\"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=1024%2C80&amp;ssl=1 1024w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=512%2C40&amp;ssl=1 512w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=768%2C60&amp;ssl=1 768w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=1536%2C119&amp;ssl=1 1536w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=2048%2C159&amp;ssl=1 2048w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?resize=380%2C30&amp;ssl=1 380w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?w=1280&amp;ssl=1 1280w, https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/k8s-dashboard-pods-ready-1.png?w=1920&amp;ssl=1 1920w\" sizes=\"(max-width: 640px) 100vw, 640px\" data-recalc-dims=\"1\" \/><\/figure>\n\n\n\n<p>All that remains is to look at how to access the dashboard running in these pods.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"accessing-the-dashboard\">Accessing the Dashboard<\/h2>\n\n\n\n<p>There are a number of options available to achieve this.  If you wish to use anything other than the default approach, then a further small change to the deployment will be required.<\/p>\n\n\n\n<p class=\"has-luminous-vivid-amber-background-color has-background\">Remember that Kubernetes deployments are declarative.<br><br>You can <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">kubectl apply -f<\/mark> <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-vivid-purple-color\">alternative.yaml<\/mark> file at any point then, if you make changes to the file, simply apply the modified version and Kubernetes will modify the cluster to bring it into line with the newly described contents.  You don&#8217;t need to start from scratch or explicitly undo anything each time.<\/p>\n\n\n\n<p>One approach is <a href=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/#command-line-proxy\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/web-ui-dashboard\/#command-line-proxy\">explained in the Kubernetes Dashboard instructions: running <mark style=\"background-color:rgba(0, 0, 0, 0)\" class=\"has-inline-color has-luminous-vivid-orange-color\">kubectl proxy<\/mark><\/a>.<\/p>\n\n\n\n<p>If we do this, then <strong>kubectl<\/strong> establishes a proxy to route <strong>localhost<\/strong> requests into the cluster identified by the <strong>current context<\/strong> (since the command doesn&#8217;t specify any other cluster).  However, this only works for requests made <em>on the machine running the <strong>kubectl proxy<\/strong><\/em>.<\/p>\n\n\n\n<p>The services in the cluster remain inaccessible to other devices on the same network (unless they also run <strong>kubectl proxy<\/strong>).<\/p>\n\n\n\n<p>That&#8217;s bad enough, but then there&#8217;s the URL by which services are accessed via this method.  In the case of the dashboard:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">http:\/\/localhost:8001\/api\/v1\/namespaces\/kubernetes-dashboard\/services\/https:kubernetes-dashboard:\/proxy\/<\/pre>\n\n\n\n<p>No thank you.<\/p>\n\n\n\n<p>There must be a better way.<\/p>\n\n\n\n<p>There are 2 in fact.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"port-forwarding\">Port Forwarding<\/h2>\n\n\n\n<p><a href=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/port-forward-access-application-cluster\/\" data-type=\"URL\" data-id=\"https:\/\/kubernetes.io\/docs\/tasks\/access-application-cluster\/port-forward-access-application-cluster\/\">Port forwarding is described in the Kubernetes documentation, using a MongoDB server as an example<\/a>, but is easily adapted to kubernetes dashboard.<\/p>\n\n\n\n<p>In my cluster, the pod running the dashboard is <strong>kubernetes-dashboard-88f954bb-sz8rd<\/strong> and the app is served on port 9090.<\/p>\n\n\n\n<p>To forward port 3000 (for example) on localhost to port 9090 in that container, this command is used:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-cyan-bluish-gray-background-color has-background\">kubectl port-forward kubernetes-dashboard-88f954bb-sz8rd 3000:9090 --namespace kubernetes-dashboard<\/pre>\n\n\n\n<p>With this command running, the dashboard can now be accessed directly at the chosen port (3000) on <strong>localhost<\/strong>:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted has-pale-cyan-blue-background-color has-background\">http:\/\/localhost:3000<\/pre>\n\n\n\n<p>This is a much more user-friendly URL than provided by <strong>kubectl proxy<\/strong>, but is still accessible only on the machine running the port-forwarding (and only while that command is running).<\/p>\n\n\n\n<p>This can be a useful mechanism for performing ad-hoc requests against a service but isn&#8217;t really a good long-term solution.<\/p>\n\n\n\n<p>We can do even better, but it does require some additional work.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"loadbalancing-on-a-bare-metal-cluster\">LoadBalancing on a Bare Metal Cluster<\/h2>\n\n\n\n<p>The ultimate solution I have adopted for accessing services in my cluster is to use a <strong>LoadBalancer<\/strong>.  Specifically <a href=\"https:\/\/metallb.org\/\" data-type=\"URL\" data-id=\"https:\/\/metallb.org\/\">MetalLB<\/a>.<\/p>\n\n\n\n<p>To use that, we&#8217;ll need to install the load balancer itself and make one final adjustment to the dashboard deployment.  Once that is in place (which won&#8217;t take long) I&#8217;ll show you how I was then able to use a feature of the <strong>UniFi Security Gateway<\/strong> to make my services easily accessible at very friendly URLs.<\/p>\n\n\n\n<p><strong>Spoiler Alert: <\/strong>The URL I am working towards with all this for my dashboard will be: <code>HTTP:\/\/k8s-dashboard.service<\/code><\/p>\n\n\n\n<p>We&#8217;ll look at that next time.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">13<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> At the end of the previous post in this series, we reached the point where I had my Kubernetes cluster up and running, including a Dashboard service. As I mentioned, this Dashboard is not part of a default installation. Taking a look at how I got this up and running provides a handy introduction to [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":3111,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"A longer post than anticipated, I discuss installing the Kubernetes Dashboard into my cluster, with some tips for enabling convenient (if somewhat insecure) access to the dashboard.\n\nFor lab use only!  Do not do this in production!","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[4,321],"tags":[352,359],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-Q9","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":3148,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3148\/","url_meta":{"origin":3233,"position":0},"title":"Kubernetes @ Home: PowerShell VM&#8217;s","date":"01 Feb 2022","format":false,"excerpt":"Following the Vagrant experiment (reminding me of a Bill Bailey metaphor... \"a long walk down a windy beach to a cafe that was closed\") I next set about automating my VM creation using PowerShell, with greater success. Though still not perfect, the final gap to full automation is something I\u2026","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3291,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3291\/","url_meta":{"origin":3233,"position":1},"title":"Kubernetes @ Home: Load Balancer","date":"11 Feb 2022","format":false,"excerpt":"In the previous post we installed the Kubernetes Dashboard and saw how we could access the dashboard service which itself runs in the Kubernetes cluster. We also saw how clunky that was and I promised a better way, which we turn to now. Kubernetes Services and IP Addresses To this\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3076,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3076\/","url_meta":{"origin":3233,"position":2},"title":"A Kubernetes Cluster @ Home","date":"26 Jan 2022","format":false,"excerpt":"I set myself a new challenge for the New Year: Stand Up a Kubernetes cluster of my own. At home. I have reasons (long term costs being [potentially] lower than cloud-hosted alternatives being just one of them) but honestly since Kubernetes is a dominant presence in my day-to-day work these\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":3099,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/3099\/","url_meta":{"origin":3233,"position":3},"title":"Kubernetes @ Home: Vagrant Nodes","date":"27 Jan 2022","format":false,"excerpt":"The first significant practical step in establishing my Bare Metal Kubernetes cluster was to provision 4 VM's. At this point it is worth mentioning that actually installing Kubernetes is almost trivial once you have the necessary hardware in place. Since my 'hardware' was going to be virtual, this meant that\u2026","rel":"","context":"In &quot;automation&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/kubernetes-banner.png?fit=1171%2C416&ssl=1&resize=350%2C200","width":350,"height":200},"classes":[]},{"id":2144,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2144\/","url_meta":{"origin":3233,"position":4},"title":"Smoketest 1.0 &#8211; Release and Be Damned!","date":"14 Nov 2013","format":false,"excerpt":"As I have been promising for some time (quite literally 5 years (!), I am ashamed to admit) I am finally unclenching and releasing the Smoketest framework into the wild, ready or not. The code is published and will continue to be updated in a github repository. Documentation is still\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-11-14-at-19.51.57-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":579,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/579\/","url_meta":{"origin":3233,"position":5},"title":"Let&#8217;s do the Time Warp again&#8230;","date":"13 Oct 2009","format":false,"excerpt":"Approximately 2 years ago I was present at a Delphi launch event here in Auckland. \u00a0At that time the hot news was the release of Delphi 2007. \u00a0But more significant than that (great) release was the recent publication after much SOX hoo-ing and haa-ing of a Delphi RoadMap. On that\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3233"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=3233"}],"version-history":[{"count":21,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3233\/revisions"}],"predecessor-version":[{"id":3290,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/3233\/revisions\/3290"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media\/3111"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=3233"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=3233"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=3233"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}