<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Roo Thorp</title><link>https://roo.dev/posts/</link><description>Recent content in Posts on Roo Thorp</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>Fri, 31 Dec 2021 00:00:00 +0000</lastBuildDate><atom:link href="https://roo.dev/posts/index.xml" rel="self" type="application/rss+xml"/><item><title>DigitalOcean Kubernetes Challenge</title><link>https://roo.dev/posts/2021/12/digitalocean-kubernetes-challenge/</link><pubDate>Fri, 31 Dec 2021 00:00:00 +0000</pubDate><guid>https://roo.dev/posts/2021/12/digitalocean-kubernetes-challenge/</guid><description>I&amp;rsquo;ve recently been doing DigitalOcean&amp;rsquo;s Kubernetes Challenge, part of which is creating a writeup for the project. So here it is!
DO gave a variety of options for projects in this challenge, I chose to deploy a scalable message queue. The brief was as follows:
A critical component of all the scalable architectures are message queues used to store and distribute messages to multiple parties and introduce buffering. Kafka is widely used in this space and there are multiple operators like Strimzi or to deploy it.</description><content type="html"><![CDATA[<p>I&rsquo;ve recently been doing <a href="https://www.digitalocean.com/community/pages/kubernetes-challenge">DigitalOcean&rsquo;s Kubernetes Challenge</a>, part of which is creating a writeup for the project. So here it is!</p>
<p>DO gave a variety of options for projects in this challenge, I chose to deploy a scalable message queue. The brief was as follows:</p>
<blockquote>
<p>A critical component of all the scalable architectures are message queues used to store and distribute messages to multiple parties and introduce buffering. Kafka is widely used in this space and there are multiple operators like Strimzi or to deploy it. For this project, use a sample app to demonstrate how your message queue works.</p>
</blockquote>
<p>I&rsquo;ve been interested in learning a bit more about message queues for a while, especially <a href="https://www.rabbitmq.com/">RabbitMQ</a> (which I decided to use over <a href="https://kafka.apache.org/">Kafka</a>), and thought this would be a good opportunity to do so. Additionally, it also looked like a great chance to learn and make use of <a href="https://www.pulumi.com/">Pulumi</a>. You can see the code for the project on my GitHub <a href="https://github.com/roothorp/digitalocean_challenge">here</a>.</p>
<ol>
<li><a href="#creating-a-digitalocean-kubernetes-cluster-with-pulumi">Creating a DigitalOcean Kubernetes Cluster with Pulumi</a></li>
<li><a href="#setting-up-the-rabbitmq-operator">Setting Up the RabbitMQ Operator</a></li>
<li><a href="#creating-a-sample-app">Creating a Sample App</a></li>
<li><a href="#going-forward">Going Forward</a></li>
</ol>
<h2 id="creating-a-digitalocean-kubernetes-cluster-with-pulumi">Creating a DigitalOcean Kubernetes Cluster with Pulumi</h2>
<p>Pulumi describes itself in its docs as:</p>
<blockquote>
<p>A modern infrastructure as code platform that allows you to use familiar programming languages and tools to build, deploy, and manage cloud infrastructure.</p>
</blockquote>
<p>I&rsquo;ve previously used <a href="https://www.terraform.io/">Terraform</a> at work, and Pulumi serves a similar purpose, but rather than using Hashicorp Configuration Language, Pulumi lets you use a <a href="https://www.pulumi.com/docs/intro/languages/">variety of languages</a>; Node, Python, .NET, and Go, which lets you avoid learning a new domain specific language when setting up Infrastructure as Code. This is especially useful for matching the language being used in a larger project; for example, in this project I am using Go, and Pulumi lets me write my infrastructure in Go too.</p>
<p>Pulumi features a <a href="https://www.pulumi.com/registry/">registry</a>, allowing various cloud providers to provide packages that can be used to provision infrastructure. Obviously for this challenge I&rsquo;ll be using DigitalOcean Kubernetes, which is easily done as DO has <a href="https://www.pulumi.com/registry/packages/digitalocean/">their own package</a> on the Pulumi registry. My use case is pretty straightforward - I just need a Kubernetes cluster - and the API docs in the registry are helpful, so I can pretty quickly get something like this:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-go" data-lang="go"><span style="display:flex;"><span><span style="color:#66d9ef">func</span> <span style="color:#a6e22e">main</span>() {
</span></span><span style="display:flex;"><span>    <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">Run</span>(<span style="color:#66d9ef">func</span>(<span style="color:#a6e22e">ctx</span> <span style="color:#f92672">*</span><span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">Context</span>) <span style="color:#66d9ef">error</span> {
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// As you could probably guess, NewKubernetesCluster is the function to create a new cluster,
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#75715e">// passing my context, the name for the cluster, and a config struct.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#a6e22e">cluster</span>, <span style="color:#a6e22e">err</span> <span style="color:#f92672">:=</span> <span style="color:#a6e22e">digitalocean</span>.<span style="color:#a6e22e">NewKubernetesCluster</span>(<span style="color:#a6e22e">ctx</span>, <span style="color:#e6db74">&#34;roo-k8s&#34;</span>, <span style="color:#f92672">&amp;</span><span style="color:#a6e22e">digitalocean</span>.<span style="color:#a6e22e">KubernetesClusterArgs</span>{
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// Another config struct for the nodepool, where I set the name, node size, and node count. 
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">NodePool</span>: <span style="color:#a6e22e">digitalocean</span>.<span style="color:#a6e22e">KubernetesClusterNodePoolArgs</span>{
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">Name</span>:      <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;roo-k8s-pool&#34;</span>),
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">Size</span>:      <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;s-2vcpu-4gb&#34;</span>),
</span></span><span style="display:flex;"><span>                <span style="color:#a6e22e">NodeCount</span>: <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#ae81ff">3</span>),
</span></span><span style="display:flex;"><span>            },
</span></span><span style="display:flex;"><span>            <span style="color:#75715e">// ... and also set the region and Kubernetes version!
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>            <span style="color:#a6e22e">Region</span>:  <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;lon1&#34;</span>),
</span></span><span style="display:flex;"><span>            <span style="color:#a6e22e">Version</span>: <span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">String</span>(<span style="color:#e6db74">&#34;1.21.5-do.0&#34;</span>),
</span></span><span style="display:flex;"><span>        })
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">if</span> <span style="color:#a6e22e">err</span> <span style="color:#f92672">!=</span> <span style="color:#66d9ef">nil</span> {
</span></span><span style="display:flex;"><span>            <span style="color:#66d9ef">return</span> <span style="color:#a6e22e">err</span>
</span></span><span style="display:flex;"><span>        }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#75715e">// We export the Kubeconfig for the cluster, letting us use it later.
</span></span></span><span style="display:flex;"><span><span style="color:#75715e"></span>        <span style="color:#a6e22e">ctx</span>.<span style="color:#a6e22e">Export</span>(<span style="color:#e6db74">&#34;kubeconfig&#34;</span>, <span style="color:#a6e22e">cluster</span>.<span style="color:#a6e22e">KubeConfigs</span>.<span style="color:#a6e22e">Index</span>(<span style="color:#a6e22e">pulumi</span>.<span style="color:#a6e22e">Int</span>(<span style="color:#ae81ff">0</span>)).<span style="color:#a6e22e">RawConfig</span>())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#66d9ef">return</span> <span style="color:#66d9ef">nil</span>
</span></span><span style="display:flex;"><span>    })
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We could customize further, or build out more infrastructure, but this gives us a quick and easy Kubernetes cluster to work with. However, for this to actually provision anything, we need to provide credentials for DO. We can set the <code>DIGITALOCEAN_TOKEN</code> environment variable which will be picked up and used, but Pulumi also lets us store secrets as part of the stack:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ pulumi config set digitalocean:token XXXXXXXXXXXXXX --secret
</span></span></code></pre></div><p>This allows anyone with access to the stack to make use of the secret - useful if multiple people are working on a project, or if you&rsquo;re working across multiple machines.</p>
<p>Now that we&rsquo;ve set this up, we can simply call <code>pulumi up</code> and get a preview of the changes to be made:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>     Type                                     Name                     Plan       
</span></span><span style="display:flex;"><span> +   pulumi:pulumi:Stack                      <span style="color:#66d9ef">do</span>-k8s-challenge-do-k8s  create     
</span></span><span style="display:flex;"><span> +   └─ digitalocean:index:KubernetesCluster  roo-k8s                  create     
</span></span><span style="display:flex;"><span> 
</span></span><span style="display:flex;"><span>Resources:
</span></span><span style="display:flex;"><span>    + <span style="color:#ae81ff">2</span> to create
</span></span></code></pre></div><p>Double check that the plan is what we want (in this case, just create a Pulumi stack, and provision a DO Kubernetes cluster), and accept! Give it a few minutes, and we have a Kubernetes cluster ready to go! For projects like this, where I&rsquo;m constantly tweaking and re-provisioning infrastructure, the speed at which DO provisions is super useful. To be safe, we can double check that we can see the cluster in the DO UI:</p>
<p><img src="/images/digitalocean_ui.png" alt="Screenshot of DigitalOcean UI, showing the Kubernetes cluster and node pool successfully provisioned." title="Success!"></p>
<p>Now we can use <code>pulumi stack output kubeconfig</code> to output our kubeconfig, and use that to connect to our cluster!</p>
<p>And when we&rsquo;re finished with the cluster, we can easily call <code>pulumi destroy</code> to bring down all the infrastructure we&rsquo;ve provisioned - super useful for keeping costs down when developing, and making sure we haven&rsquo;t accidentally created anything and left it running.</p>
<h2 id="setting-up-the-rabbitmq-operator">Setting Up the RabbitMQ Operator</h2>
<p>RabbitMQ provides <a href="https://github.com/rabbitmq/cluster-operator">an Operator</a> for use in Kubernetes - this means we can get RabbitMQ up and running quickly with a few commands, covered in <a href="https://www.rabbitmq.com/kubernetes/operator/quickstart-operator.html">their tutorial</a>. We <code>kubectl apply</code> the operator YAML to create the operator and the required CRDs:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ kubectl apply -f <span style="color:#e6db74">&#34;https://github.com/rabbitmq/cluster-operator/releases/latest/download/cluster-operator.yml&#34;</span>
</span></span><span style="display:flex;"><span>namespace/rabbitmq-system created
</span></span><span style="display:flex;"><span>customresourcedefinition.apiextensions.k8s.io/rabbitmqclusters.rabbitmq.com created
</span></span><span style="display:flex;"><span>serviceaccount/rabbitmq-cluster-operator created
</span></span><span style="display:flex;"><span>role.rbac.authorization.k8s.io/rabbitmq-cluster-leader-election-role created
</span></span><span style="display:flex;"><span>clusterrole.rbac.authorization.k8s.io/rabbitmq-cluster-operator-role created
</span></span><span style="display:flex;"><span>rolebinding.rbac.authorization.k8s.io/rabbitmq-cluster-leader-election-rolebinding created
</span></span><span style="display:flex;"><span>clusterrolebinding.rbac.authorization.k8s.io/rabbitmq-cluster-operator-rolebinding created
</span></span><span style="display:flex;"><span>deployment.apps/rabbitmq-cluster-operator created
</span></span></code></pre></div><p>Then we can configure and create a RabbitMQ cluster ourselves in another YAML file:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">rabbitmq.com/v1beta1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">RabbitmqCluster</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">roo-test</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Use the latest image, with an alpine base, for smaller images - quicker deploys!</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">image</span>: <span style="color:#ae81ff">rabbitmq:3.9.11-management-alpine</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># 3 replicas - one for each node - helps ensure availability.</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">3</span>
</span></span><span style="display:flex;"><span>  <span style="color:#75715e"># Resource constraints so that the cluster is quick without consuming all the resources!</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">limits</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">1000m</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">1200Mi</span>
</span></span></code></pre></div><p>Another <code>kubectl apply</code> and we&rsquo;ve created a RabbitMQ cluster! We can quickly check everything is working properly:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ kubectl get pods
</span></span><span style="display:flex;"><span>NAME                READY   STATUS    RESTARTS   AGE
</span></span><span style="display:flex;"><span>roo-test-server-0   1/1     Running   <span style="color:#ae81ff">0</span>          56s
</span></span><span style="display:flex;"><span>roo-test-server-1   1/1     Running   <span style="color:#ae81ff">0</span>          56s
</span></span><span style="display:flex;"><span>roo-test-server-2   1/1     Running   <span style="color:#ae81ff">0</span>          56s
</span></span></code></pre></div><p>Perfect! Additionally, there&rsquo;s a <code>perf-test</code> image available to check the cluster is functional. I made a simple bash script to grab the username, password, and service for the cluster, pass those parameters to perf-test, and output the logs when ready:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ ./perf-test.sh 
</span></span><span style="display:flex;"><span>pod/perf-test created
</span></span><span style="display:flex;"><span>pod/perf-test condition met
</span></span><span style="display:flex;"><span>id: test-192228-278, time: 1.000s, sent: <span style="color:#ae81ff">3244</span> msg/s, received: <span style="color:#ae81ff">3294</span> msg/s, min/median/75th/95th/99th consumer latency: 18511/318671/373425/411837/434261 µs
</span></span><span style="display:flex;"><span>id: test-192228-278, time: 2.000s, sent: <span style="color:#ae81ff">9034</span> msg/s, received: <span style="color:#ae81ff">10894</span> msg/s, min/median/75th/95th/99th consumer latency: 433388/761260/813535/916186/962179 µs
</span></span><span style="display:flex;"><span>id: test-192228-278, time: 3.000s, sent: <span style="color:#ae81ff">4539</span> msg/s, received: <span style="color:#ae81ff">19396</span> msg/s, min/median/75th/95th/99th consumer latency: 635194/956662/1053517/1480561/1801464 µs
</span></span></code></pre></div><p>We can see our RabbitMQ cluster is functional, and is properly handling messages for the test pod. However, I&rsquo;d like to delve a bit deeper into RabbitMQ to further my understanding, so instead of using <code>perf-test</code>, I&rsquo;m going to create my own senders and receivers to work with.</p>
<h2 id="creating-a-sample-app">Creating a Sample App</h2>
<p>RabbitMQ provides some <a href="https://www.rabbitmq.com/getstarted.html">tutorials</a>, including for Go, which I&rsquo;m going to follow to build a sample app while learning about RabbitMQ. I&rsquo;m not going to just repeat everything covered in the tutorials, so we&rsquo;ll skip over that, but I have made a few crucial changes to the code given in the tutorials; we loop 10 times in the sender program (instead of just sending one message), and construct the connection string (username, password, and IP) from environment variables, rather than hard-coding it.</p>
<p>Now I have two fairly straightforward Go programs; one that is generating 10 random numbers and publishing them to a queue, and another that is reading from the queue. In order for this to run in Kubernetes, we need to containerize the binaries, which is easily done with <code>docker build</code> and a Dockerfile:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-Dockerfile" data-lang="Dockerfile"><span style="display:flex;"><span><span style="color:#66d9ef">FROM</span><span style="color:#e6db74"> golang:1.17-alpine</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">WORKDIR</span><span style="color:#e6db74"> /app</span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> go.mod ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> go.sum ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> go mod download<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">COPY</span> send.go ./<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">RUN</span> go build send.go<span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010">
</span></span></span><span style="display:flex;"><span><span style="color:#960050;background-color:#1e0010"></span><span style="color:#66d9ef">CMD</span> [<span style="color:#e6db74">&#34;./send&#34;</span>]<span style="color:#960050;background-color:#1e0010">
</span></span></span></code></pre></div><p>This Dockerfile is for the &lsquo;send&rsquo; image, and I do the exact same thing for the receive one (but pointing to the other file). Once they&rsquo;re built, I can tag and push them to a container registry; in this case Docker Hub:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ docker tag roothorp/sender roothorp/sender:0.1
</span></span><span style="display:flex;"><span>$ docker push roothorp/sender:0.1
</span></span></code></pre></div><p>Now our images are available to the world at large, including our Kubernetes cluster. Adding to the YAML file we created the RabbitMQ cluster with earlier, we can create a deployment for the receiver:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">apps/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Deployment</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>: 
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">receiver</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">roo-test</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">replicas</span>: <span style="color:#ae81ff">1</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">selector</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">matchLabels</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">app</span>: <span style="color:#ae81ff">receiver</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">app</span>: <span style="color:#ae81ff">receiver</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">receive</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">roothorp/receiver:0.1</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">limits</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">200m</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">200Mi</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">RABBITMQ_USERNAME</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">roo-test-default-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">key</span>: <span style="color:#ae81ff">username</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">RABBITMQ_PASSWORD</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">roo-test-default-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">key</span>: <span style="color:#ae81ff">password</span>
</span></span></code></pre></div><p>A deployment lets us <em>declaratively</em> state how we want our pods, and ensures that state is maintained. In this example we just want a single receiver, but if that pod were to crash, the deployment would restart the pod and ensure it stays up. If we wanted to scale, we can simply increase the replicas and re-apply the yaml, and Kubernetes spins up more pods for us.</p>
<p>Additionally, we&rsquo;re setting environment variables for the pod here from a secret. Earlier I said that I&rsquo;d modified the sender and receiver examples to build their connection string from environment variables, and this is why. The RabbitMQ operator sets up a <code>default-user</code> secret containing (amongst other things) the username and password to access the queue, and Kubernetes lets us easily and safely access them here, passing them down to our program. For the service IP, Kubernetes sets environment variables for hosts and ports of services available on the cluster, so we can get the IP for the RabbitMQ service from the <code>ROO_TEST_SERVICE_HOST</code> variable (<code>roo test</code> being the cluster&rsquo;s name). Connecting to services (as well as doing so via DNS) is covered in more detail in the <a href="https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/">Kubernetes docs</a>.</p>
<p>For our sender we can do something similar, but this time using a Job instead of a Deployment. Unlike a deployment (which would continually restart the pod), Jobs watch for the pod to complete (exit gracefully), and allow us to set a target number of completions, as well as a parallelism. Once enough pods have completed, the job itself is completed. We&rsquo;ll add this to our YAML:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-yaml" data-lang="yaml"><span style="display:flex;"><span><span style="color:#f92672">apiVersion</span>: <span style="color:#ae81ff">batch/v1</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">kind</span>: <span style="color:#ae81ff">Job</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">metadata</span>: 
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">name</span>: <span style="color:#ae81ff">sender</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">app</span>: <span style="color:#ae81ff">roo-test</span>
</span></span><span style="display:flex;"><span><span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">completions</span>: <span style="color:#ae81ff">10</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">parallelism</span>: <span style="color:#ae81ff">2</span>
</span></span><span style="display:flex;"><span>  <span style="color:#f92672">template</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">metadata</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">labels</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">app</span>: <span style="color:#ae81ff">sender</span>
</span></span><span style="display:flex;"><span>    <span style="color:#f92672">spec</span>:
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">restartPolicy</span>: <span style="color:#ae81ff">Never</span>
</span></span><span style="display:flex;"><span>      <span style="color:#f92672">containers</span>:
</span></span><span style="display:flex;"><span>      - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">send</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">image</span>: <span style="color:#ae81ff">roothorp/sender:0.2</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">resources</span>:
</span></span><span style="display:flex;"><span>          <span style="color:#f92672">limits</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">cpu</span>: <span style="color:#ae81ff">200m</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">memory</span>: <span style="color:#ae81ff">200Mi</span>
</span></span><span style="display:flex;"><span>        <span style="color:#f92672">env</span>:
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">RABBITMQ_USERNAME</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">roo-test-default-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">key</span>: <span style="color:#ae81ff">username</span>
</span></span><span style="display:flex;"><span>          - <span style="color:#f92672">name</span>: <span style="color:#ae81ff">RABBITMQ_PASSWORD</span>
</span></span><span style="display:flex;"><span>            <span style="color:#f92672">valueFrom</span>:
</span></span><span style="display:flex;"><span>              <span style="color:#f92672">secretKeyRef</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">name</span>: <span style="color:#ae81ff">roo-test-default-user</span>
</span></span><span style="display:flex;"><span>                <span style="color:#f92672">key</span>: <span style="color:#ae81ff">password</span>
</span></span></code></pre></div><p>As you can see, configuration is much the same as for the deployment, but we specify <code>completions</code> and <code>parallelism</code> too. In this case, each run of the sender sends 10 messages, so 10 completions means 100 messages, and 2 parallel just means that we&rsquo;ll have 2 pods running at any given time.</p>
<p>We can again <code>kubectl apply</code>, and see our pods appear:</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ kubectl get pods
</span></span><span style="display:flex;"><span>NAME                        READY   STATUS      RESTARTS   AGE
</span></span><span style="display:flex;"><span>receiver-6b8874bc68-qgxvz   1/1     Running     <span style="color:#ae81ff">0</span>          83m
</span></span><span style="display:flex;"><span>roo-test-server-0           1/1     Running     <span style="color:#ae81ff">1</span>          9m26s
</span></span><span style="display:flex;"><span>roo-test-server-1           1/1     Running     <span style="color:#ae81ff">1</span>          10m
</span></span><span style="display:flex;"><span>roo-test-server-2           1/1     Running     <span style="color:#ae81ff">0</span>          102m
</span></span><span style="display:flex;"><span>sender-58xm8                0/1     Completed   <span style="color:#ae81ff">0</span>          10m
</span></span><span style="display:flex;"><span>sender-5qqm4                0/1     Completed   <span style="color:#ae81ff">0</span>          9m58s
</span></span><span style="display:flex;"><span>sender-dh46g                0/1     Completed   <span style="color:#ae81ff">0</span>          9m55s
</span></span><span style="display:flex;"><span>sender-fn2gf                0/1     Completed   <span style="color:#ae81ff">0</span>          9m51s
</span></span><span style="display:flex;"><span>sender-g8hb6                0/1     Completed   <span style="color:#ae81ff">0</span>          9m52s
</span></span><span style="display:flex;"><span>sender-kftll                0/1     Completed   <span style="color:#ae81ff">0</span>          9m57s
</span></span><span style="display:flex;"><span>sender-kt46f                0/1     Completed   <span style="color:#ae81ff">0</span>          9m56s
</span></span><span style="display:flex;"><span>sender-lzxzh                0/1     Completed   <span style="color:#ae81ff">0</span>          10m
</span></span><span style="display:flex;"><span>sender-pq86c                0/1     Completed   <span style="color:#ae81ff">0</span>          9m54s
</span></span><span style="display:flex;"><span>sender-qbv8z                0/1     Completed   <span style="color:#ae81ff">0</span>          9m53s
</span></span></code></pre></div><p>Our 10 completed senders from our job, our 3 RabbitMQ pods, and a single receiver pod. We can check the receiver pod&rsquo;s logs and see the messages arriving (I&rsquo;ve trimmed them, lest this code block be longer than the rest of the blog post):</p>
<div class="highlight"><pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-bash" data-lang="bash"><span style="display:flex;"><span>$ kubectl logs receiver-6b8874bc68-qgxvz
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">81</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">887</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">847</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">59</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">81</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">318</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">425</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">540</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">456</span>
</span></span><span style="display:flex;"><span>2021/12/31 03:11:51 Received a message: <span style="color:#ae81ff">300</span>
</span></span></code></pre></div><p>And there we have our basic Message Queue!</p>
<h2 id="going-forward">Going Forward</h2>
<p>This wraps up a pretty basic RabbitMQ setup and test on DigitalOcean&rsquo;s Kubernetes offering. There are a handful of things that I&rsquo;d like to add to this project in the future, which I thought I should quickly cover here (for my sake as much as anyone elses):</p>
<p>Improving the Docker images, primarily their size, is pretty straightforward and would make things move quicker. Currently the images are about 300MB, which is incredibly big for a simple program like the one I&rsquo;m using here. Utilizing <a href="https://docs.docker.com/develop/develop-images/multistage-build/">multi-stage builds</a> to build in the larger <code>golang</code> base images before moving to a slimmer image (such as <code>scratch</code>) can drastically reduce the size of images, as well as whittle down the build time thanks to caching.</p>
<p>Furthering my implementation (and understanding) of RabbitMQ. While I got a nice understanding of the basics of RabbitMQ, delving deeper into this project and building out a more comprehensive application that makes use of RabbitMQ (rather than providing an example input and output) sounds interesting and a natural next step. Finishing the provided tutorials and moving on to creating a larger app would be ideal, and a good opportunity to get to grips with other technologies I&rsquo;m interested in. This would include writing tests for the apps; I neglected to do this for these straigtforward examples, but writing tests is a great habit to get into as I go.</p>
<p>Finally, making use of GitHub actions to automate much of the deployment would be a great thing to learn (and would reduce a lot of repetitive building, pushing, applying). Having a workflow that tests and lints, builds images, brings up or modifies infra, and applies Kubernetes configuration would be perfect!</p>
<p>Hopefully this provided some insight into how I approached this challenge, and perhaps taught somebody something. At the very least, I hope it was an interesting read - if you have any questions or thoughts, feel free to contact me - my details are available on the front page of this site. Thank you, and happy new year!</p>
]]></content></item><item><title>Hello World!</title><link>https://roo.dev/posts/2021/01/hello-world/</link><pubDate>Thu, 14 Jan 2021 00:00:00 +0000</pubDate><guid>https://roo.dev/posts/2021/01/hello-world/</guid><description>This is my first blog post as I flesh out the site - my first time using Hugo - and move away from my old page (which was essentially just my name). I&amp;rsquo;ve been meaning to do this for some time.
Hopefully in future I can post more blog posts about whatever I&amp;rsquo;m working on at the moment. For now, I&amp;rsquo;m going to iron the kinks out of this new site!</description><content type="html"><![CDATA[<p>This is my first blog post as I flesh out the site - my first time using Hugo - and move away from my old page (which was essentially just my name). I&rsquo;ve been meaning to do this for some time.</p>
<p>Hopefully in future I can post more blog posts about whatever I&rsquo;m working on at the moment. For now, I&rsquo;m going to iron the kinks out of this new site!</p>
<p>As mentioned on my <a href="/about">about page</a>, please contact me if you have any inquiries!</p>
]]></content></item></channel></rss>