<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
	<channel>
		<title>Posts on ghostsquad blog</title>
		<link>https://ghostsquad.me/posts/</link>
		<description>Recent content in Posts on ghostsquad blog</description>
		<generator>Hugo -- gohugo.io</generator>
		<language>en-us</language>
		<copyright>This work is licensed under a Creative Commons Attribution-NonCommercial 4.0 International License.</copyright>
		<lastBuildDate>Mon, 18 Nov 2019 09:47:28 -0800</lastBuildDate>
		<atom:link href="https://ghostsquad.me/posts/index.xml" rel="self" type="application/rss+xml" />
		
		<item>
			<title>Kubernetes on the Cheap - Part 2</title>
			<link>https://ghostsquad.me/posts/kubernetes-on-the-cheap-part-2/</link>
			<pubDate>Mon, 18 Nov 2019 09:47:28 -0800</pubDate>
			
			<guid>https://ghostsquad.me/posts/kubernetes-on-the-cheap-part-2/</guid>
			<description>If you haven&amp;rsquo;t read part 1 yet, that&amp;rsquo;s a good place to start.
In the last post, we were left with a kubernetes cluster, and a test deployment that would break once every 24 hours, because of the preemptible instances we are using. So the highest priority right now is to fix that.
The next step is going to require you to own a domain. I recommend namecheap. You can get a .</description>
			<content type="html"><![CDATA[

<p>If you haven&rsquo;t read <a href="/posts/kubernetes-on-the-cheap-part-1/">part 1</a> yet, that&rsquo;s a good place to start.</p>

<p>In the last post, we were left with a kubernetes cluster, and a test deployment that would break once every 24 hours, because of the preemptible instances we are using. So the highest priority right now is to fix that.</p>

<p>The next step is going to require you to own a domain. I recommend <a href="https://namecheap.com">namecheap</a>. You can get a <code>.com</code> for $8.88/yr for the first year, then $10.98/yr after that.</p>

<h2 id="table-of-contents">Table of Contents</h2>

<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#create-a-dns-zone">Create a DNS Zone</a></li>
<li><a href="#deploy-external-dns-controller">Deploy External DNS Controller</a></li>
<li><a href="#edit-the-hello-app-to-include-the-domain-and-updated-ttl">Edit the hello app to include the domain, and updated ttl</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>

<p>To make your life easier, I&rsquo;ve variablized the commands in this post, so that you can simply set the variables, then copy/paste the commands without much trouble.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">PROJECT_NAME</span><span class="o">=</span><span class="s2">&#34;kubernetes-on-the-cheap&#34;</span>
<span class="nb">export</span> <span class="nv">CLUSTER_NAME</span><span class="o">=</span><span class="s2">&#34;hobby-1&#34;</span>
<span class="nb">export</span> <span class="nv">DOMAIN</span><span class="o">=</span><span class="s2">&#34;foo.com&#34;</span></code></pre></div>
<h2 id="create-a-dns-zone">Create a DNS Zone</h2>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">gcloud beta dns <span class="se">\
</span><span class="se"></span>  managed-zones create <span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOMAIN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  --description<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOMAIN</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  --dns-name<span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">DOMAIN</span><span class="si">}</span><span class="s2">.&#34;</span></code></pre></div>
<p>Once the DNS zone is created, you need to update your domain registrar with the google name servers.</p>

<p>When viewing the zone, you should see an <code>NS</code> record with values like <code>ns-cloud-&lt;??&gt;.googledomains.com</code>.</p>

<p><img src="https://cloud.google.com/dns/images/zone-records.png" alt="google cloud zone records" /></p>

<p>If you chose to use namecheap, here&rsquo;s <a href="https://www.namecheap.com/support/knowledgebase/article.aspx/767/10/how-to-change-dns-for-a-domain">how to update DNS on namecheap.com</a>.</p>

<h2 id="deploy-external-dns-controller">Deploy External DNS Controller</h2>

<p>Some of these steps came from <a href="https://knative.dev/docs/serving/using-external-dns-on-gcp/">knative</a></p>

<ol>
<li><p>Create a new service account for Cloud DNS admin role.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">CLOUD_DNS_SA</span><span class="o">=</span>cloud-dns-admin

gcloud --project <span class="nv">$PROJECT_NAME</span> iam service-accounts <span class="se">\
</span><span class="se"></span>    create <span class="nv">$CLOUD_DNS_SA</span> <span class="se">\
</span><span class="se"></span>    --display-name <span class="s2">&#34;Service Account to support ACME DNS-01 challenge.&#34;</span></code></pre></div></li>

<li><p>Bind the role <code>dns.admin</code> to the newly created service account.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="c1"># Fully-qualified service account name also has project-id information.</span>
<span class="nb">export</span> <span class="nv">CLOUD_DNS_SA_FQ</span><span class="o">=</span><span class="nv">$CLOUD_DNS_SA</span>@<span class="nv">$PROJECT_NAME</span>.iam.gserviceaccount.com

gcloud projects add-iam-policy-binding <span class="nv">$PROJECT_NAME</span> <span class="se">\
</span><span class="se"></span>    --member serviceAccount:<span class="nv">$CLOUD_DNS_SA_FQ</span> <span class="se">\
</span><span class="se"></span>    --role roles/dns.admin</code></pre></div></li>

<li><p>Download the secret key file for your service account.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">gcloud iam service-accounts keys create ~/credentials.json <span class="se">\
</span><span class="se"></span>  --iam-account<span class="o">=</span><span class="nv">$CLOUD_DNS_SA_FQ</span></code></pre></div></li>

<li><p>Upload the service account credential to your cluster. This command uses the secret name <code>cloud-dns-key</code>, but you can choose a different name.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">CLOUD_DNS_SECRET_NAME</span><span class="o">=</span><span class="s2">&#34;cloud-dns-key&#34;</span>

kubectl create secret generic <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CLOUD_DNS_SECRET_NAME</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  --from-file<span class="o">=</span>credentials.json<span class="o">=</span><span class="nv">$HOME</span>/credentials.json</code></pre></div></li>

<li><p>Deploy external dns.</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">curl https://ghostsquad.me/files/kubernetes-on-the-cheap-part-2/external-dns.yaml -o external-dns.yaml
sed -i <span class="s2">&#34;s/__PROJECT_NAME__/</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span><span class="s2">/g&#34;</span> external-dns.yaml
sed -i <span class="s2">&#34;s/__CLUSTER_NAME__/</span><span class="si">${</span><span class="nv">CLUSTER_NAME</span><span class="si">}</span><span class="s2">/g&#34;</span> external-dns.yaml
sed -i <span class="s2">&#34;s/__CLOUD_DNS_SECRET_NAME__/</span><span class="si">${</span><span class="nv">CLOUD_DNS_SECRET_NAME</span><span class="si">}</span><span class="s2">/g&#34;</span> external-dns.yaml
kubectl apply -f external-dns.yaml</code></pre></div></li>
</ol>

<h2 id="edit-the-hello-app-to-include-the-domain-and-updated-ttl">Edit the hello app to include the domain, and updated ttl</h2>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">curl https://ghostsquad.me/files/kubernetes-on-the-cheap-part-2/hello.yaml -o hello-part-2.yaml
sed -i <span class="s2">&#34;s/__DOMAIN__/</span><span class="si">${</span><span class="nv">DOMAIN</span><span class="si">}</span><span class="s2">/g&#34;</span> hello-part-2.yaml
kubectl apply -f hello-part-2.yaml</code></pre></div>
<h2 id="conclusion">Conclusion</h2>

<p>We deployed external DNS so that it can keep the cluster updated with an IP address for your domain name. I even watched it go to work by scaling up to 2 nodes, then scaling back down.</p>

<p>It doesn&rsquo;t appear though that externalDNS is adding multiple A records (for each IP) when you have 2 nodes. This deserves some more research.</p>

<ul class="task-list">
<li><label><input type="checkbox" checked disabled class="task-list-item"> Snag a domain that points to the cluster, so that we don&rsquo;t have to update the ingress</label></li>
<li><label><input type="checkbox" checked disabled class="task-list-item"> Deploy <a href="https://github.com/kubernetes-sigs/external-dns">external-dns</a> to automatically update our domain with the list of IPs of hosts (when the get preempted)</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Deploy <a href="https://github.com/jetstack/cert-manager">cert-manager</a> to get HTTPS!</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Setup <a href="k8s-node-termination-handler">node-termination-handler</a> to further improve shutdowns</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Setup <a href="https://spotinst.com/pricing/">SpotInst</a> in order to handle rolling instances on a regular basis in a controlled way, and scaling the cluster temporarily while doing so.</label></li>
</ul>

<p>Stay tuned for Part 3!</p>
]]></content>
		</item>
		
		<item>
			<title>Kubernetes on the Cheap - Part 1</title>
			<link>https://ghostsquad.me/posts/kubernetes-on-the-cheap-part-1/</link>
			<pubDate>Sat, 16 Nov 2019 17:49:17 -0800</pubDate>
			
			<guid>https://ghostsquad.me/posts/kubernetes-on-the-cheap-part-1/</guid>
			<description>How to run Kubernetes for less than $6 per month?
I wanted to play around with Kubernetes for personal learning, and even some personal projects. I wanted to deploy a website with a live backend, but I didn&amp;rsquo;t really want to be bound to cPanel, or terrible performance issues on shared hosting providers. This was also an opportunity for me to learn more about deploying an application end2end and managing it myself.</description>
			<content type="html"><![CDATA[

<p>How to run Kubernetes for less than $6 per month?</p>

<p>I wanted to play around with Kubernetes for personal learning, and even some personal projects. I wanted to deploy a website with a live backend, but I didn&rsquo;t really want to be bound to cPanel, or terrible performance issues on shared hosting providers. This was also an opportunity for me to learn more about deploying an application end2end and managing it myself. This post details the exact steps to deploy a Kubernetes cluster to Google Cloud Platform, and run a single, low-utilization application for about $5/mo. It includes all the pitfalls, gotchas, shortcuts, workarounds, optimizations, etc in order to make it work, step by step.</p>

<h2 id="table-of-contents">Table of Contents</h2>

<ul>
<li><a href="#table-of-contents">Table of Contents</a></li>
<li><a href="#create-a-gcp-account--project">Create a GCP Account &amp; Project</a></li>
<li><a href="#create-a-gke-cluster">Create a GKE Cluster</a></li>
<li><a href="#resource-utilization-overview">Resource Utilization Overview</a></li>
<li><a href="#deploy-nginx-ingress">Deploy Nginx Ingress</a></li>
<li><a href="#create-a-firewall-rule-to-allow-traffic-to-the-nodes">Create a firewall rule to allow traffic to the nodes</a></li>
<li><a href="#deploy-a-simple-app">Deploy a simple app</a></li>
<li><a href="#conclusion">Conclusion</a></li>
</ul>

<h2 id="create-a-gcp-account-project">Create a GCP Account &amp; Project</h2>

<ol>
<li>You can create or select a GCP Project from the <a href="https://console.cloud.google.com/projectselector2/home/dashboard">Project Selector Page</a>.</li>
<li>Make sure that billing is enabled for your Google Cloud Platform project. <a href="https://cloud.google.com/billing/docs/how-to/modify-project">Learn how to confirm billing is enabled for your project</a>.</li>
</ol>

<p>For this article, I&rsquo;ve created a new project called <code>kubernetes-on-the-cheap</code>. For the remainder of the article, I&rsquo;ll be showing you <code>gcloud</code> or <code>kubectl</code> commands to run instead of walking through how to do this from the UI, though it&rsquo;s also pretty simple from the UI, it just requires a few more steps and screenshots.</p>

<p>Unless otherwise noted, all commands listed will be run from the GCP Cloud Shell.</p>

<p>To reduce repetition, I&rsquo;ve aliased <code>kubectl</code> to <code>k</code></p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">alias</span> <span class="nv">k</span><span class="o">=</span>kubectl</code></pre></div>
<p>or</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">echo</span> <span class="s2">&#34;alias k=kubectl&#34;</span> &gt;&gt; <span class="nv">$HOME</span>/.bashrc</code></pre></div>
<p>To make your life easier, I&rsquo;ve variablized the commands in this post, so that you can simply set the variables, then copy/paste the commands without much trouble.</p>

<h2 id="create-a-gke-cluster">Create a GKE Cluster</h2>

<p>GKE (Google Kubernetes Engine) is actually FREE on GCP&hellip; well the master nodes are. You can create a cluster without any worker nodes at no cost. That&rsquo;s the next step. First, navigate to <a href="https://console.cloud.google.com/kubernetes/list?project=kubernetes-on-the-cheap">https://console.cloud.google.com/kubernetes/list?project=kubernetes-on-the-cheap</a> but replacing the <code>project</code> with your own project name. Google has to enable the <code>Kubernetes Engine API</code>, and it takes a few minutes.</p>

<p>Here&rsquo;s the command to run, but you&rsquo;ll have to fill in a few things yourself. The important part to note here is that this creates a single-zone GKE cluster (for the master nodes). If you don&rsquo;t do this, when you create node groups, you&rsquo;ll be forced to have a minimum of 3 nodes (1 per zone). I couldn&rsquo;t find a way around there. You can read more about this in <a href="https://cloud.google.com/kubernetes-engine/docs/concepts/regional-clusters">the docs</a>. The main downside is that when the cluster upgrades, you won&rsquo;t be able to access the control plane. The data plane (where your apps run) is unaffected.</p>

<p>Most of the next commands can be run from the google cloud console via their website.</p>

<p>Run this locally, and copy the output (or visit the website below to get your home IP address)</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nv">MY_HOME_IP</span><span class="o">=</span><span class="s2">&#34;</span><span class="k">$(</span>curl https://ifconfig.co/ip<span class="k">)</span><span class="s2">&#34;</span>
<span class="nb">echo</span> <span class="s2">&#34;MY_HOME_IP=</span><span class="si">${</span><span class="nv">MY_HOME_IP</span><span class="si">}</span><span class="s2">&#34;</span></code></pre></div>
<p>Run this from the Cloud Shell console</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">PROJECT_NAME</span><span class="o">=</span><span class="s2">&#34;kubernetes-on-the-cheap&#34;</span>
<span class="nb">export</span> <span class="nv">CLUSTER_NAME</span><span class="o">=</span><span class="s2">&#34;hobby-1&#34;</span>
<span class="nb">export</span> <span class="nv">REGION</span><span class="o">=</span><span class="s2">&#34;us-west1&#34;</span>
<span class="nb">export</span> <span class="nv">ZONE_ID</span><span class="o">=</span><span class="s2">&#34;a&#34;</span>
<span class="nb">export</span> <span class="nv">ZONE</span><span class="o">=</span><span class="s2">&#34;</span><span class="si">${</span><span class="nv">REGION</span><span class="si">}</span><span class="s2">-</span><span class="si">${</span><span class="nv">ZONE_ID</span><span class="si">}</span><span class="s2">&#34;</span>

gcloud beta container <span class="se">\
</span><span class="se"></span>  --project <span class="s2">&#34;</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  clusters create <span class="s2">&#34;</span><span class="si">${</span><span class="nv">CLUSTER_NAME</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  --zone <span class="s2">&#34;</span><span class="si">${</span><span class="nv">ZONE</span><span class="si">}</span><span class="s2">&#34;</span> <span class="se">\
</span><span class="se"></span>  --no-enable-basic-auth <span class="se">\
</span><span class="se"></span>  --release-channel <span class="s2">&#34;regular&#34;</span> <span class="se">\
</span><span class="se"></span>  --machine-type <span class="s2">&#34;g1-small&#34;</span> <span class="se">\
</span><span class="se"></span>  --image-type <span class="s2">&#34;COS&#34;</span> <span class="se">\
</span><span class="se"></span>  --disk-type <span class="s2">&#34;pd-standard&#34;</span> <span class="se">\
</span><span class="se"></span>  --disk-size <span class="s2">&#34;30&#34;</span> <span class="se">\
</span><span class="se"></span>  --metadata disable-legacy-endpoints<span class="o">=</span><span class="nb">true</span> <span class="se">\
</span><span class="se"></span>  --scopes <span class="s2">&#34;https://www.googleapis.com/auth/devstorage.read_only&#34;</span>,<span class="s2">&#34;https://www.googleapis.com/auth/logging.write&#34;</span>,<span class="s2">&#34;https://www.googleapis.com/auth/monitoring&#34;</span>,<span class="s2">&#34;https://www.googleapis.com/auth/servicecontrol&#34;</span>,<span class="s2">&#34;https://www.googleapis.com/auth/service.management.readonly&#34;</span>,<span class="s2">&#34;https://www.googleapis.com/auth/trace.append&#34;</span> <span class="se">\
</span><span class="se"></span>  --preemptible <span class="se">\
</span><span class="se"></span>  --num-nodes <span class="s2">&#34;1&#34;</span> <span class="se">\
</span><span class="se"></span>  --enable-stackdriver-kubernetes <span class="se">\
</span><span class="se"></span>  --enable-ip-alias <span class="se">\
</span><span class="se"></span>  --network <span class="s2">&#34;projects/</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span><span class="s2">/global/networks/default&#34;</span> <span class="se">\
</span><span class="se"></span>  --subnetwork <span class="s2">&#34;projects/</span><span class="si">${</span><span class="nv">PROJECT_NAME</span><span class="si">}</span><span class="s2">/regions/</span><span class="si">${</span><span class="nv">REGION</span><span class="si">}</span><span class="s2">/subnetworks/default&#34;</span> <span class="se">\
</span><span class="se"></span>  --default-max-pods-per-node <span class="s2">&#34;110&#34;</span> <span class="se">\
</span><span class="se"></span>  --enable-master-authorized-networks <span class="se">\
</span><span class="se"></span>  --master-authorized-networks <span class="s2">&#34;</span><span class="si">${</span><span class="nv">MY_HOME_IP</span><span class="si">}</span><span class="s2">/32&#34;</span> <span class="se">\
</span><span class="se"></span>  --addons HorizontalPodAutoscaling <span class="se">\
</span><span class="se"></span>  --enable-autoupgrade <span class="se">\
</span><span class="se"></span>  --enable-autorepair <span class="se">\
</span><span class="se"></span>  --maintenance-window-start <span class="s2">&#34;2019-11-15T10:00:00Z&#34;</span> <span class="se">\
</span><span class="se"></span>  --maintenance-window-end <span class="s2">&#34;2019-11-15T14:00:00Z&#34;</span> <span class="se">\
</span><span class="se"></span>  --maintenance-window-recurrence <span class="s2">&#34;FREQ=WEEKLY;BYDAY=MO,TU,WE,TH&#34;</span></code></pre></div>
<table>
<thead>
<tr>
<th>option</th>
<th>description</th>
</tr>
</thead>

<tbody>
<tr>
<td><code>--zone &quot;us-west1-a&quot;</code></td>
<td>I chose <code>us-west1-a</code> because it&rsquo;s the cheapest region in the US (<code>us-central1</code> is the same low price), and that&rsquo;s also where I live. I don&rsquo;t plan on running anything that would have any significant performance impact for users from say the east coast, so this is not much of concern. I&rsquo;ll talk more about this in the next section, <a href="#zones-regions-and-other-network-to-consider">Zones, Regions, and other networking to consider</a>.</td>
</tr>

<tr>
<td><code>--machine-type &quot;g1-small&quot;</code></td>
<td><a href="https://cloud.google.com/compute/vm-instance-pricing#sharedcore">a <code>g1-small</code> is $5.11 per month when pre-emptible</a>. As of this post, the <code>f1-micro</code> is too small to run while also enabling the stackdriver addon. You can read more about preemptible instances from the offical docs: <a href="https://cloud.google.com/compute/docs/instances/preemptible">https://cloud.google.com/compute/docs/instances/preemptible</a></td>
</tr>

<tr>
<td><code>--enable-stackdriver-kubernetes</code></td>
<td>Stackdriver is <a href="https://cloud.google.com/stackdriver/">mostly free</a>, if you use it sparingly, so let&rsquo;s enable this so we can monitor our cluster.</td>
</tr>

<tr>
<td><code>--enable-master-authorized-networks</code></td>
<td>This is to restrict who can access the master nodes (K8s API). The next setting will use your home IP address.</td>
</tr>

<tr>
<td><code>--master-authorized-networks &quot;${MY_HOME_IP}/32&quot;</code></td>
<td>Restrict access to the K8s API to your home IP address. This can be updated on demand, and <a href="#updating-cluster-authorized-networks">I&rsquo;ll show you how to do that later</a>.</td>
</tr>

<tr>
<td><code>--maintennce-window-*</code></td>
<td>Choose what&rsquo;s right for you. For my purposes, I expect more traffic during the weekend, so I&rsquo;m restricting maintence to 2AM on the weekdays.</td>
</tr>
</tbody>
</table>

<h2 id="resource-utilization-overview">Resource Utilization Overview</h2>

<table>
<thead>
<tr>
<th>Resource</th>
<th align="right">Real</th>
<th align="right">Requested</th>
<th align="right">Allocatable</th>
<th align="right">Remaining (Req-Alloc)</th>
</tr>
</thead>

<tbody>
<tr>
<td>CPU</td>
<td align="right">20m</td>
<td align="right">339m</td>
<td align="right">940m</td>
<td align="right">601m</td>
</tr>

<tr>
<td>Mem</td>
<td align="right">269Mi</td>
<td align="right">488Mi</td>
<td align="right">1220Mi</td>
<td align="right">732Mi</td>
</tr>
</tbody>
</table>

<p>Much of this is coming from StackDriver addon pods. Maybe in another post, I&rsquo;ll look into an alternative solution, but for now, the remainder is more than enough for a small backend webserver.</p>

<h2 id="deploy-nginx-ingress">Deploy Nginx Ingress</h2>

<p>Loadbalancers in GCP have a minimum of $0.60/day or $18/mo charge, so to avoid creating one, we need to setup nginx ingress in a special way.</p>

<p>If nginx ingress controller is set to use the host network, it can bind on port 80 and 443. This means that if we can create a route to any given node on the cluster, 80 and 443 will route to nginx.</p>

<p>The following manifests are derived from the <a href="https://kubernetes.github.io/ingress-nginx/deploy/">manifests online</a>, but differ in the following ways:</p>

<ul>
<li>No <code>service</code> resource is necessary (or desired)</li>
<li>Remove the reference in the controller args to the <code>service</code> resource (that we aren&rsquo;t deploying)</li>
<li>Set CPU/Mem resource requests</li>
<li>Changing from <code>deployment</code> to <code>daemonset</code></li>
<li>Set <code>hostNetwork: true</code> and <code>dnsPolicy: ClusterFirstWithHostNet</code></li>
</ul>

<p>Run this to deploy the nginx ingress controller:</p>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash">kubectl apply -f https://ghostsquad.me/files/kubernetes-on-the-cheap-part-1/nginx-ingress-controller.yaml</code></pre></div>
<h2 id="create-a-firewall-rule-to-allow-traffic-to-the-nodes">Create a firewall rule to allow traffic to the nodes</h2>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">TARGET_TAG</span><span class="o">=</span><span class="k">$(</span>gcloud compute instances list --format<span class="o">=</span>json <span class="p">|</span> jq -r <span class="s1">&#39;.[0].tags.items[0]&#39;</span><span class="k">)</span>

gcloud compute <span class="se">\
</span><span class="se"></span>  --project<span class="o">=</span>kubernetes-on-the-cheap <span class="se">\
</span><span class="se"></span>  firewall-rules create http-ingress <span class="se">\
</span><span class="se"></span>  --direction<span class="o">=</span>INGRESS <span class="se">\
</span><span class="se"></span>  --priority<span class="o">=</span><span class="m">1000</span> <span class="se">\
</span><span class="se"></span>  --network<span class="o">=</span>default <span class="se">\
</span><span class="se"></span>  --action<span class="o">=</span>ALLOW <span class="se">\
</span><span class="se"></span>  --rules<span class="o">=</span>tcp:80,tcp:443 <span class="se">\
</span><span class="se"></span>  --source-ranges<span class="o">=</span><span class="m">0</span>.0.0.0/0 <span class="se">\
</span><span class="se"></span>  --target-tags<span class="o">=</span><span class="nv">$TARGET_TAG</span></code></pre></div>
<h2 id="deploy-a-simple-app">Deploy a simple app</h2>
<div class="highlight"><pre class="chroma"><code class="language-bash" data-lang="bash"><span class="nb">export</span> <span class="nv">EXT_IP</span><span class="o">=</span><span class="k">$(</span>gcloud compute instances list --format<span class="o">=</span>json <span class="p">|</span> jq -r <span class="s1">&#39;.[0].networkInterfaces[0].accessConfigs[0].natIP&#39;</span><span class="k">)</span>
<span class="nb">echo</span> <span class="nv">$EXT_IP</span>

kubectl apply -f https://ghostsquad.me/files/kubernetes-on-the-cheap-part-1/hello.yaml</code></pre></div>
<p>You should now be able to visit the IP address in <code>$EXT_IP</code> and see something like this:</p>

<pre><code>Hello, world!
Version: 1.0.0
Hostname: hello-567b7dcdc9-vgb8v
</code></pre>

<p>This works, but we have one major issue, and that&rsquo;s the node IP address will change every 24 hours because the instance is pre-emptible. (and also this means the <code>host</code> in the ingress will need to be updated every 24 hours)</p>

<p>We&rsquo;ll address this in the next part of this series!</p>

<h2 id="conclusion">Conclusion</h2>

<p>We&rsquo;ve deployed a cluster to GKE running a single node. We deployed nginx ingress to run on the host network to avoid creating a load balancer, and we deployed a simple test application.</p>

<p>The problems which we need to address in the next part are:</p>

<ul class="task-list">
<li><label><input type="checkbox" disabled class="task-list-item"> Snag a domain that points to the cluster, so that we don&rsquo;t have to update the ingress</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Deploy <a href="https://github.com/kubernetes-sigs/external-dns">external-dns</a> to automatically update our domain with the list of IPs of hosts (when the get preempted)</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Deploy <a href="https://github.com/jetstack/cert-manager">cert-manager</a> to get HTTPS!</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Setup <a href="k8s-node-termination-handler">node-termination-handler</a> to further improve shutdowns</label></li>
<li><label><input type="checkbox" disabled class="task-list-item"> Setup <a href="https://spotinst.com/pricing/">SpotInst</a> in order to handle rolling instances on a regular basis in a controlled way, and scaling the cluster temporarily while doing so.</label></li>
</ul>

<p>Head over to <a href="/posts/kubernetes-on-the-cheap-part-2/">part 2</a></p>
]]></content>
		</item>
		
		<item>
			<title>The Little Things</title>
			<link>https://ghostsquad.me/posts/the-little-things/</link>
			<pubDate>Fri, 23 Aug 2019 18:44:31 -0700</pubDate>
			
			<guid>https://ghostsquad.me/posts/the-little-things/</guid>
			<description>One thing I&amp;rsquo;ve noticed over the years is that the little things in software development often get overlooked as &amp;ldquo;bike shedding&amp;rdquo; moments, and yet there&amp;rsquo;s significant value in establishing a standard, convention, and even automation around these overlooked things. Let&amp;rsquo;s dive right in:
Git Commits Following conventional commits standards can allow you to do things like:
 Automatically generate CHANGELOGs Automatically determine semantic version bump Communicate nature of changes to teammates, public, stakeholders Used to change build pipeline behavior at runtime to publish code automatically Enhance readability of git commit history  I&amp;rsquo;m in the SRE/DevOps/PaaS space, and I often find myself wishing I had more time to write weekly newsletters or similar communications to internal teams to increase the visibility of the new features, bugfixes, and other changes that are being delivered.</description>
			<content type="html"><![CDATA[

<p>One thing I&rsquo;ve noticed over the years is that the little things in software development often get overlooked as &ldquo;bike shedding&rdquo; moments, and yet there&rsquo;s significant value in establishing a standard, convention, and even automation around these overlooked things. Let&rsquo;s dive right in:</p>

<h3 id="git-commits">Git Commits</h3>

<p>Following <a href="https://www.conventionalcommits.org/en/v1.0.0-beta.4/">conventional commits</a> standards can allow you to do things like:</p>

<ol>
<li>Automatically generate CHANGELOGs</li>
<li>Automatically determine semantic version bump</li>
<li>Communicate nature of changes to teammates, public, stakeholders</li>
<li>Used to change build pipeline behavior at runtime to publish code automatically</li>
<li>Enhance readability of git commit history</li>
</ol>

<p>I&rsquo;m in the SRE/DevOps/PaaS space, and I often find myself wishing I had more time to write weekly newsletters  or similar communications to internal teams to increase the visibility of the new features, bugfixes, and other changes that are being delivered. This includes beta releases, and getting volunteers to provide feedback on tools/services before distributing them to the wider engineering organization. With this said, I cannot overstate the value of #1 and #3 in the above list. I don&rsquo;t mean a <a href="https://about.gitlab.com/releases/">hard-to-read, never-ending list</a> either. Imagine being able to <em>generate</em> a releases page like <a href="https://about.gitlab.com/2019/08/22/gitlab-12-2-released/index.html">Gitlab&rsquo;s 12.2 release</a>. This is theoretically possible through a combination of well-written commits (that are machine parseable) and well-written epics, that can be queried.</p>

<p>Combine this with <a href="https://chris.beams.io/posts/git-commit/#seven-rules">Chris Beams&rsquo;: How to Write a Git Commit Message</a> and it makes it trivial to go back in time an figure out why something was done.</p>

<h3 id="project-structure-naming">Project Structure &amp; Naming</h3>

<p>Conventions over Configuration. This applies to a project directory structure &amp; naming too. Here&rsquo;s some benefits of coming up with and sticking to a polyglot convention, similar to <a href="https://github.com/golang-standards/project-layout">Go project structure recommendations</a>.</p>

<ol>
<li>CI/CD templated/generated pipelines

<ul>
<li>If your project name is valid DNS (and all lowercase), it makes it easy to use (without modification) as your kubernetes namespace (or namespace prefix).</li>
<li><code>Dockerfile</code> in the root of your repo? We have a pre-baked step to build/publish without any additional configuration.</li>
<li>Using <code>make</code>? A standard pipeline can make some assumptions for you about commands to run without requiring explicit configuration.</li>
</ul></li>
<li>Enhance readability &amp; maintenance by teammates and external teams. Sticking to a convention means that someone doesn&rsquo;t need to learn &ldquo;how <em>your</em> repo does it&rdquo;.</li>
<li>New projects can be <a href="https://github.com/facebook/create-react-app">templatized/generated</a>. If you have the pleasure of being able to spinup &amp; deploy greenfield applications on a regular basis, this becomes an automatable step.</li>
</ol>

<h3 id="tags-labels-annotations">Tags, Labels, Annotations</h3>

<p>Again, this is all about automation. It makes sense to have a standard set of keys and a convention for custom keys that are forward compatible with changes to the standard. Use this same standard for your cloud provider, like AWS, as well as Kubernetes resources.</p>
<div class="highlight"><pre class="chroma"><code class="language-yaml" data-lang="yaml">company.io/environment<span class="p">:</span><span class="w"> </span>prd<span class="w">
</span><span class="w"></span>company.io/team<span class="p">:</span><span class="w"> </span>sig-api<span class="w">
</span><span class="w"></span>company.io/contact<span class="p">:</span><span class="w"> </span>sig-api@company.io<span class="w">
</span><span class="w"></span>company.io/managed-by<span class="p">:</span><span class="w"> </span>terraform<span class="w">
</span><span class="w"></span>company.io/fingerprint<span class="p">:</span><span class="w"> </span>abc123<span class="w">
</span><span class="w"></span>company.io/fingerprint-type<span class="p">:</span><span class="w"> </span>sha1<span class="w">
</span><span class="w"></span>company.io/component<span class="p">:</span><span class="w"> </span>database<span class="w">
</span><span class="w"></span>company.io/part-of<span class="p">:</span><span class="w"> </span>wordpress<span class="w">
</span><span class="w"></span>custom.company.io/something-not-standard<span class="p">:</span><span class="w"> </span>this-is-project-or-team-specific</code></pre></div>
<p>Some of these are duplicates of <a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/common-labels/">recommended annotations/labels for kubernetes</a>. <a href="https://dev.to/jeroendedauw/the-fallacy-of-dry">Don&rsquo;t worry about duplication</a>. Use them both, because you&rsquo;ll apply the ones above to more things than just K8s resources. Tools that may not be written by your team could potential benefit from the k8s standard.</p>

<hr />

<p>This will certainly be a multi-part series. Stay tuned for more!</p>
]]></content>
		</item>
		
	</channel>
</rss>
