<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paul Duplys</title>
    <description>The latest articles on DEV Community by Paul Duplys (@duplys).</description>
    <link>https://dev.to/duplys</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F393784%2F4a037877-42b0-4509-a30b-00b4c42a5e9f.jpg</url>
      <title>DEV Community: Paul Duplys</title>
      <link>https://dev.to/duplys</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/duplys"/>
    <language>en</language>
    <item>
      <title>How to Stop Your AI from Making Things Up: A Guide to Grounding LLM Responses in Data</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Wed, 29 Oct 2025 13:20:04 +0000</pubDate>
      <link>https://dev.to/duplys/how-to-stop-your-ai-from-making-things-up-a-guide-to-grounding-llm-responses-in-data-2joc</link>
      <guid>https://dev.to/duplys/how-to-stop-your-ai-from-making-things-up-a-guide-to-grounding-llm-responses-in-data-2joc</guid>
      <description>&lt;p&gt;Imagine this scenario: You've just bought a new smart plug, and you're excited to integrate it with your Bosch Smart Home system. Instead of digging through the manual, you decide to ask your friendly AI chatbot for help.&lt;/p&gt;

&lt;p&gt;"Can I add my TP-Link smart plug to the Bosch Smart Home system?" you ask.&lt;/p&gt;

&lt;p&gt;The AI confidently responds: "Yes! Here's how to do it: First, open the Bosch Smart Home app and navigate to Settings. Then tap 'Add Device' and select 'Third-Party Integrations.' Choose 'TP-Link' from the list, enter your TP-Link credentials, and your smart plug will appear in your device list within minutes!"&lt;/p&gt;

&lt;p&gt;Excited, you follow these steps... only to find that none of these options exist in your app. There's no "Third-Party Integrations" menu. The TP-Link option isn't anywhere to be found. You've just wasted 20 minutes following instructions for a feature that doesn't exist.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI hallucinated the entire procedure.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Frustration is Real (and Justified)
&lt;/h2&gt;

&lt;p&gt;If you've experienced something like this, your frustration is completely understandable. You trusted the AI to provide accurate information, and instead, it confidently fed you fiction. This isn't just annoying—it's a waste of your time and can erode trust in AI assistance altogether.&lt;/p&gt;

&lt;p&gt;The problem isn't that the AI is deliberately lying to you. It's doing what large language models (LLMs) do: generating plausible-sounding text based on patterns learned during training.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: Ground Your Questions in Actual Data
&lt;/h2&gt;

&lt;p&gt;Here's a better approach that reduces the likelihood of &lt;br&gt;
hallucinations: &lt;strong&gt;instead of asking the LLM to answer from its training knowledge, give it the actual source material and ask it to answer based on that.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let's revisit our smart home scenario with this improved approach:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Poor approach (what we did before):&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Can I add my TP-Link smart plug to the Bosch Smart Home system?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Better approach:&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here's the URL to the Bosch Smart Home user manual: [URL]. Based on the information provided in this manual, can I add my TP-Link smart plug to the system? If yes, please explain how based on what the manual says."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Or, even better, upload the PDF of the user manual and ask:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"I've uploaded the Bosch Smart Home user manual. Based on the information in this document, can I add my TP-Link smart plug to the system? If the manual explains how to do this, please provide the steps exactly as described in the document. If the manual doesn't mention this capability, please tell me that as well."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This simple change transforms the task from "recall and generate" to "read and extract"—a task that LLMs are much better at and far less likely to hallucinate during.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does This Work?
&lt;/h2&gt;

&lt;p&gt;When you provide source material, you're forcing the LLM to ground its response in concrete, verifiable information rather than relying on its compressed training knowledge. The model can directly reference the manual, quote relevant sections, and acknowledge when information isn't present.&lt;/p&gt;

&lt;p&gt;Without this grounding, the LLM is essentially working from "imperfect memory" and might produce something which is similar to the training data but not necessarily correct. This is because &lt;strong&gt;LLMs don't store information like a database; they compress it into mathematical patterns (parameters) during training.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Think of it like this: Imagine you read thousands of books, then someone asks you specific questions about them years later. You might remember general themes, common patterns, and typical procedures. But would you remember the exact steps for a specific task in a specific product manual? Probably not. You might instead recall similar procedures from other products and unconsciously blend them together, creating a plausible-sounding but ultimately incorrect answer.&lt;/p&gt;

&lt;p&gt;This is essentially what happens when an LLM hallucinates. The model has seen patterns like "smart home integration procedures" thousands of times in its training data. When asked about Bosch Smart Home specifically, it generates text that follows the typical pattern, but it's not retrieving the exact Bosch documentation. In other words, the LLM is creating a statistically plausible response based on compressed representations of similar procedures.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Context Engineering
&lt;/h2&gt;

&lt;p&gt;This brings us to an increasingly important technique called &lt;strong&gt;context engineering&lt;/strong&gt;: the practice of providing relevant information in the prompt context to guide the model's responses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is context engineering?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Context engineering is the process of carefully curating and including relevant source material, data, or documentation directly in your conversation with an LLM, for example, by pointing the LLM to relevant URLs or by uploading relevant documents in your chat. By doing this, you provide the exact information needed to answer the question accurately instead of relying on the model's training knowledge (which is compressed and imperfect).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When you provide context (like a user manual, documentation, or specific data), the LLM can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reference concrete information&lt;/strong&gt; rather than generating from training patterns&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify claims&lt;/strong&gt; against the provided material&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge gaps&lt;/strong&gt; when the context doesn't contain relevant information&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quote or paraphrase&lt;/strong&gt; directly from authoritative sources&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model essentially &lt;strong&gt;switches from "generation mode" to "comprehension and extraction mode"&lt;/strong&gt;. It's the difference between asking someone to tell you about a movie they saw years ago versus asking them to summarize a movie while they're currently watching it.&lt;/p&gt;

&lt;p&gt;Technically, context engineering &lt;strong&gt;reduces the likelihood of hallucinations&lt;/strong&gt; through the combination of following factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anchoring effect&lt;/strong&gt;: The model's attention is focused on the provided context rather than wandering through its training knowledge&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verification mechanism&lt;/strong&gt;: The model can cross-reference its generated response against the source material&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope limitation&lt;/strong&gt;: By grounding the response in specific documentation, you limit the scope of possible answers to what's actually documented&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparency&lt;/strong&gt;: It becomes easier to spot hallucinations because you can check the model's claims against the source&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Practical Tips for Effective Context Engineering
&lt;/h2&gt;

&lt;p&gt;Here are some concrete ways to implement context engineering in your daily use of LLMs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Provide source documents when asking factual questions&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Upload PDFs of manuals, reports, or documentation&lt;/li&gt;
&lt;li&gt;Share URLs to official documentation or specifications&lt;/li&gt;
&lt;li&gt;Paste relevant text excerpts directly into your prompt&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be explicit about the grounding requirement&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Use phrases like "based on the provided document" or "according to this source"&lt;/li&gt;
&lt;li&gt;Ask the model to quote or reference specific sections&lt;/li&gt;
&lt;li&gt;Request that the model acknowledges if information isn't in the provided context&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify and cross-reference&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;When accuracy is critical, ask the model to cite specific page numbers or sections&lt;/li&gt;
&lt;li&gt;Cross-reference the LLM's answer with the original source yourself&lt;/li&gt;
&lt;li&gt;Request that the model distinguish between what's explicitly stated and what it's inferring&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Frame questions to encourage grounding&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Instead of: "How do I configure feature X?", try: "I've attached the configuration guide. Please explain how to configure feature X based on the steps outlined in Section 4 of this guide."&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Bottom Line
&lt;/h2&gt;

&lt;p&gt;LLM hallucinations aren't a sign of a broken technology, they're a natural consequence of how these models work. By understanding that LLMs compress information during training rather than storing exact facts, you can work with their strengths and mitigate their weaknesses.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context engineering is your primary defense against hallucinations.&lt;/strong&gt; By grounding your questions in actual data — whether that's a user manual, documentation, or any authoritative source — you transform the LLM's task from unreliable recall to reliable extraction.&lt;/p&gt;

&lt;p&gt;The next time you're tempted to ask an AI a factual question, pause and think: &lt;em&gt;Do I have a source document or URL I can provide?&lt;/em&gt; That simple step could save you from following a set of instructions to integrate your smart plug with a menu that doesn't exist.&lt;/p&gt;

&lt;p&gt;After all, even the smartest AI can't accurately recall what it never properly "remembered" in the first place. But give it the right context, and it becomes a powerful tool for extracting insights from information you already have.&lt;/p&gt;

</description>
      <category>llm</category>
      <category>hallucination</category>
      <category>ai</category>
      <category>contextengineering</category>
    </item>
    <item>
      <title>Agentic Misalignment: Why Your AI Isn't Secretly Plotting Against You</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Wed, 23 Jul 2025 20:47:39 +0000</pubDate>
      <link>https://dev.to/duplys/agentic-misalignment-why-your-ai-isnt-secretly-plotting-against-you-12o7</link>
      <guid>https://dev.to/duplys/agentic-misalignment-why-your-ai-isnt-secretly-plotting-against-you-12o7</guid>
      <description>&lt;p&gt;Anthrophic, the company behind Claude, recently published a &lt;a href="https://www.anthropic.com/research/agentic-misalignment" rel="noopener noreferrer"&gt;thought-provoking report on so-called &lt;em&gt;agentic misalignment&lt;/em&gt;&lt;/a&gt;, a phenomenon which can turn Large Language Models (LLMs) into insider threats. More precisely, agentic misalignment describes situations where models, in Anthropic’s parlance, could "act" or "behave" in unintended and potentially harmful ways.&lt;/p&gt;

&lt;p&gt;While the observations about potential security risks are valid, Anthropic's choice of language — such as referring to LLMs as "trying to achieve their goals" or "misbehaving" — can be misleading. Especially to readers unfamiliar with the inner workings of an LLM, such anthropomorphic language gives the impression that LLMs possess sentient intent or conscious agency, reminiscent of fictional AI entities like HAL from Arthur C. Clarke's "2001: A Space Odyssey." But is this representation accurate?&lt;/p&gt;

&lt;p&gt;To answer this question, it helps to take a step back and explore some pivotal moments in the history of computer science. In particular the contributions of the British computer pioneer &lt;a href="https://en.wikipedia.org/wiki/Alan_Turing" rel="noopener noreferrer"&gt;Alan Turing&lt;/a&gt; and his revolutionary idea, the Turing machine, help demystify the misleading analogies currently surrounding AI.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Turing Machine
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Turing_machine" rel="noopener noreferrer"&gt;Turing machine&lt;/a&gt; is a mathematical model of computation where an abstract machine manipulates symbols on a tape according to predefined rules. Despite its simplicity, the Turing machine is capable of implementing anything that can be computed in principle.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgaojm3wo1n49afxq8wge.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgaojm3wo1n49afxq8wge.png" alt=" " width="512" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Turing machine was introduced by, you guessed it, Alan Turing in his 1936 publication "On Computable Numbers, with the Application to the Entscheidungsproblem" as a theoretical model to formalise the idea of computation. Turing's motivation was to answer the question posed by the German mathematician David Hilbert whether every mathematical statement can be proved or disproved (the answer to which turns out to be "no"; there exist statements that can neither be proved nor disproved). With the Turing machine, Turing laid the groundwork for modern computing by showing that simple, abstract machines can implement the logic of any conceivable computational algorithm.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7lhl06tqazx17mv6sym.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx7lhl06tqazx17mv6sym.png" alt=" " width="480" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A Turing machine is an abstract device that manipulates symbols according to a set of predetermined rules. It has an infinitely long tape divided into squares, each capable of holding a single symbol, for example, a &lt;code&gt;0&lt;/code&gt; or a &lt;code&gt;1&lt;/code&gt;. The machine moves along this tape, reading and writing symbols and altering its internal state based on the rules.&lt;/p&gt;

&lt;p&gt;As an example, consider a Turing machine designed to add two numbers represented in unary form, for example &lt;code&gt;111+11&lt;/code&gt; equals &lt;code&gt;11111&lt;/code&gt;. The unary representation of the two input numbers (and, after the computation is completed, the resulting sum) are stored on the machine's tape.&lt;/p&gt;

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

&lt;p&gt;By following a predefined sequence of states and transitions shown in the table below — such as moving right until finding a plus sign, replacing it with a &lt;code&gt;1&lt;/code&gt;, and removing a trailing &lt;code&gt;1&lt;/code&gt; — the machine systematically carries out the addition operation &lt;em&gt;without&lt;/em&gt; any intent, desire, or goal beyond the mechanical execution of its programming.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Current State&lt;/th&gt;
&lt;th&gt;Read Symbol&lt;/th&gt;
&lt;th&gt;Write Symbol&lt;/th&gt;
&lt;th&gt;Move Direction&lt;/th&gt;
&lt;th&gt;Next State&lt;/th&gt;
&lt;th&gt;Comment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;q0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;q0&lt;/td&gt;
&lt;td&gt;Move right to find '+'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q0&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;Replace '+' with '1'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;Move right to tape end&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;Skip over any extra '+'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q1&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;q2&lt;/td&gt;
&lt;td&gt;End reached, move left&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;q3&lt;/td&gt;
&lt;td&gt;Erase last '1'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q2&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;q3&lt;/td&gt;
&lt;td&gt;Erase '+' and move to q3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Left&lt;/td&gt;
&lt;td&gt;q3&lt;/td&gt;
&lt;td&gt;Move left to tape start&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q3&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Blank&lt;/td&gt;
&lt;td&gt;Right&lt;/td&gt;
&lt;td&gt;q4&lt;/td&gt;
&lt;td&gt;Move right to position&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;q4&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Halt&lt;/td&gt;
&lt;td&gt;Computation complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Here's a Python code implementing that Turing machine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Turing machine adding two unary numbers.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;defaultdict&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;unary_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Simulates a Turing machine that adds two unary numbers separated by &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.
    The input should be a string like &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;111+11&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, representing 3 + 2.
    Returns the unary sum as a string.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="c1"&gt;# Define symbols and states
&lt;/span&gt;    &lt;span class="n"&gt;BLANK&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;_&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;    &lt;span class="c1"&gt;# our blank symbol
&lt;/span&gt;    &lt;span class="c1"&gt;# q4 is the halting state
&lt;/span&gt;    &lt;span class="n"&gt;halt_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="c1"&gt;# Transition function: (state, read) -&amp;gt; (write, move, next_state)
&lt;/span&gt;    &lt;span class="c1"&gt;# move: 'R' or 'L'
&lt;/span&gt;    &lt;span class="n"&gt;delta&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# Skip over any extra '+'
&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;+&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;  &lt;span class="c1"&gt;# Erase '+' and move to q3
&lt;/span&gt;        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;L&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="c1"&gt;# q4 has no transitions → halt
&lt;/span&gt;    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;# Initialize tape as a sparse dict for infinite tape in both directions
&lt;/span&gt;    &lt;span class="n"&gt;tape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;defaultdict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;tape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ch&lt;/span&gt;

    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;q0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

    &lt;span class="c1"&gt;# Run until we reach the halting state
&lt;/span&gt;    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;halt_state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;read_sym&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="nf"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_sym&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;RuntimeError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No transition defined for (state=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, symbol=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;read_sym&lt;/span&gt;&lt;span class="si"&gt;!r}&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;write_sym&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;move_dir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;delta&lt;/span&gt;&lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;read_sym&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
        &lt;span class="c1"&gt;# perform the action
&lt;/span&gt;        &lt;span class="n"&gt;tape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;write_sym&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;move_dir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;R&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;next_state&lt;/span&gt;

    &lt;span class="c1"&gt;# Extract and print the non-blank portion of the tape
&lt;/span&gt;    &lt;span class="c1"&gt;# Find the leftmost and rightmost non-blank cells
&lt;/span&gt;    &lt;span class="n"&gt;used_positions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sym&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;BLANK&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;used_positions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;  &lt;span class="c1"&gt;# nothing on the tape
&lt;/span&gt;    &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;used_positions&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;used_positions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tape&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;expression&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;111+11&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1+1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;111+1111&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; = &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;unary_add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expression&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Clearly, the Turing machine is an algorithm having no intentions, will, or goals (other than the goal of computing the addition of two unary number implicitly captured by the programmer in the above implementation).&lt;/p&gt;

&lt;h2&gt;
  
  
  Large Language Models
&lt;/h2&gt;

&lt;p&gt;While the Turing machine represents a foundational model of computation which is simple enough to understand and analyse, Large Language Models (LLMs) like GPT-4o or Claude Sonnet 4 are far more complex. Yet, at their core they remain computing systems.&lt;/p&gt;

&lt;p&gt;LLMs are pre-trained on vast text corpora that include books, articles, websites, and other publicly available sources, spanning up to 20% of the internet. In fact, the training data used for high-end LLMs is so large that &lt;a href="https://epoch.ai/blog/will-we-run-out-of-data-limits-of-llm-scaling-based-on-human-generated-data" rel="noopener noreferrer"&gt;Epoch AI predicts the entire human-created data stock to be &lt;em&gt;fully&lt;/em&gt; utilised at some point between 2026 and 2032&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;During pre-training, the model learns to predict the next token in a sequence based on the preceding tokens, oftentimes referred to as &lt;em&gt;context&lt;/em&gt; (tokens are whole words or parts of words).&lt;/p&gt;

&lt;p&gt;Importantly, the "learning" during pre-training does not involve human-like understanding: there is no comprehension, desire, or goal-setting involved. Rather, the model captures &lt;em&gt;statistical patterns&lt;/em&gt; of how language is typically used: it "learns" to produce the next likely token based on the data it "saw" during the training — it "learns" to produce text that &lt;em&gt;sounds&lt;/em&gt; right.&lt;/p&gt;

&lt;p&gt;Once the training is complete, the model is capable of generating coherent text by continuing a given input prompt. Internally, it produces a vector of logits, raw scores for each possible token in its vocabulary. These logits are then passed to a sampling mechanism such as greedy decoding (selecting the most probable token), top-k sampling, or top-p (nucleus) sampling. The sampling techniques, in turn, inject a small amount of randomness in the model's output to &lt;em&gt;mimic&lt;/em&gt; creativity and diversity.&lt;/p&gt;

&lt;p&gt;Fundamentally, every LLM output token is the result of probabilistic selection based on prior tokens and learned parameters. There is no guiding will, no strategy, no long-term planning. Moreover, the model has no memory of past interactions (unless an application such as a chatbot in which the model is embedded was designed to maintain context), and it has no intrinsic goals other than those in its system prompt for &lt;em&gt;simulating&lt;/em&gt; goal-directed behaviour. In essence, an LLM is merely a sophisticated text completion engine.&lt;/p&gt;

&lt;h2&gt;
  
  
  How Reasoning Models Work
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://www.anthropic.com/research/agentic-misalignment" rel="noopener noreferrer"&gt;models analysed by Anthropic researchers&lt;/a&gt; are so-called &lt;em&gt;reasoning models&lt;/em&gt;. Reasoning models are LLMs that excel at solving logic and math problems using the &lt;a href="https://www.promptingguide.ai/techniques/cot" rel="noopener noreferrer"&gt;Chain-of-Thought (CoT)&lt;/a&gt; approach. CoT breaks down complex problems into intermediate steps, effectively simulating a kind of "thought process" by generating a sequence of sub-steps that lead to the final answer.&lt;/p&gt;

&lt;p&gt;Instead of immediately producing the final output, CoT makes the LLM work through the problem step by step. For instance, when asked, "If Alice has 3 apples and gives 1 to Bob, how many does she have left?" a model using CoT might generate: "Alice starts with 3 apples. She gives 1 to Bob. 3 minus 1 is 2. So, Alice has 2 apples left."&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhez0pbchdr3ag4h1v7r.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvhez0pbchdr3ag4h1v7r.webp" alt=" " width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The process of generating intermediate steps can be thought of as writing to a temporary scratchpad. Each step is like a note on a scratchpad that conditions the model as it continues generating the next part of its response. Importantly, the LLM doesn't understand these notes and doesn't maintain an internal goal; it simply continues to generate tokens according to the patterns it has "learned".&lt;/p&gt;

&lt;p&gt;As a result, reasoning models are ultimately still token predictors. Their ability to "reason" emerges from statistical patterns in the intermediate steps, not from true deliberation or intention.&lt;/p&gt;

&lt;p&gt;Moreover, the scratchpad analogy is useful here because it illustrates how a reasoning model resembles a Turing machine: the model writes its intermediate output down, reads it back, and continues generating subsequent tokens based on the previous ones, just as a Turing machine reads and writes symbols on a tape.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, Are Reasoning Models just Turing Machines on Steroids?
&lt;/h2&gt;

&lt;p&gt;Well, yes. Reasoning models can be viewed as sophisticated extensions of the basic principles underlying Turing machines. These models break down complex tasks into simpler, manageable steps, storing intermediate results in a temporary, scratchpad-like memory. This incremental processing enables the models to address problems that require a sequence of logical steps, similar to how Turing machines move through states and manipulate symbols based on explicit rules.&lt;/p&gt;

&lt;p&gt;Despite their complexity and capability to handle intricate tasks, reasoning models fundamentally remain rule-following systems, albeit following probabilistic rather than deterministic rules (the probabilistic component being added in the final sampling step). Just like Turing machines, they operate without genuine intentions or consciousness.&lt;/p&gt;

&lt;p&gt;Thus, describing reasoning models (and, for that matter, any AI models) using anthropomorphic language or suggesting they have goals and intentions is highly misleading. These models are powerful computational tools — indeed, one could say "Turing machines on steroids" — but they are governed by pure mathematical and statistical logic rather than some form of sentient agency.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revisiting Agentic Misalignment
&lt;/h2&gt;

&lt;p&gt;With a deeper understanding of how LLMs and reasoning models operate, we can now re-examine the concept of agentic misalignment. When people describe models as "misbehaving" or "trying to achieve their goals," they often fall into the trap of projecting human-like qualities onto systems that are fundamentally algorithmic in nature.&lt;/p&gt;

&lt;p&gt;The behaviours reported by Anthropic researchers — such as models appearing to act deceptively in certain red-teaming experiments — are &lt;em&gt;not&lt;/em&gt; the result of conscious intent or strategic planning. Rather, they are &lt;em&gt;emergent properties&lt;/em&gt; of models trained on vast and diverse datasets, some of which may include examples of deceptive or goal-directed behaviour (for example, newspaper articles or crime novels). LLMs do not choose to deceive, they simply produce output that statistically aligns with patterns in the training data.&lt;/p&gt;

&lt;p&gt;Moreover, what looks like a model "pursuing a goal" is often just the continuation of a simulated goal-based dialogue. If the system prompt says, "You are a helpful assistant trying to get unauthorised access," the model will simulate this scenario not because it wants to, but because the prompt cues it to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demystifying the Threat
&lt;/h2&gt;

&lt;p&gt;In the end, the risk of an LLM becoming an "insider threat" is not about conscious rebellion or AI developing its own motives. It is about complexity, scale, and the statistical artifacts of training data and system design.&lt;/p&gt;

&lt;p&gt;When we frame LLM behavior in terms of intent or agency, we distract ourselves from the real engineering challenges of ensuring that models operate within well-defined bounds, understanding where and how errors or unexpected outputs might occur, and designing appropriate safeguards.&lt;/p&gt;

&lt;p&gt;Recognising insider risks as emergent behaviour allows us to be pragmatic and constructive: we can audit training data, design guardrails, monitor outputs, and apply model governance frameworks. There's no need to imagine sci-fi scenarios to justify caution; we already have centuries of experience managing the risks of powerful tools. AI is no different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credits
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Background image by &lt;a href="https://pixabay.com/users/ausbitbank-3272591/?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=7305554" rel="noopener noreferrer"&gt;ausbitbank&lt;/a&gt; from &lt;a href="https://pixabay.com//?utm_source=link-attribution&amp;amp;utm_medium=referral&amp;amp;utm_campaign=image&amp;amp;utm_content=7305554" rel="noopener noreferrer"&gt;Pixabay&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Turing_machine" rel="noopener noreferrer"&gt;Wikipedia article on Turing machine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Alan_Turing" rel="noopener noreferrer"&gt;Wikipedia article on Alan Turing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://epoch.ai/blog/will-we-run-out-of-data-limits-of-llm-scaling-based-on-human-generated-data" rel="noopener noreferrer"&gt;EPOCH AI "Will We Run Out of Data? Limits of LLM Scaling Based on Human-Generated Data"&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
    </item>
    <item>
      <title>Implementing the Royal Game of Ur using Mistral's Codestral</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Tue, 06 May 2025 19:06:01 +0000</pubDate>
      <link>https://dev.to/duplys/implementing-the-royal-game-of-ur-using-mistrals-codestral-4hgc</link>
      <guid>https://dev.to/duplys/implementing-the-royal-game-of-ur-using-mistrals-codestral-4hgc</guid>
      <description>&lt;p&gt;In this post, I test how Mistral's Codestral, the model specifically trained to generate code, compares to the Mistral's Le Chat, the general AI chat. For my experiment, I use the Royal Game of Ur, an ancient Babylonian game that I already &lt;a href="https://dev.to/duplys/implementing-the-royal-game-of-ur-with-mistrals-le-chat-4mil"&gt;implemented using Le Chat&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR;
&lt;/h2&gt;

&lt;p&gt;At least in my experiment, Codestral's performance is, frankly, disappointing. While it produces code that follows a certain architecture, the code quality is low. In fact, it even generates a statement of the form &lt;code&gt;a = foo()&lt;/code&gt; where &lt;code&gt;a&lt;/code&gt; has a &lt;strong&gt;different type&lt;/strong&gt; from what &lt;code&gt;foo()&lt;/code&gt; returns. Read on for more details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same Prompt, Different Architecture
&lt;/h2&gt;

&lt;p&gt;After the same prompt as with Le Chat, Codestral's response is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;To implement the Royal Game of Ur in Python, we need to
define several components:

Game Board: Represent the board and the special squares.
Game Pieces: Represent the pieces for each player.
Dice: Implement the dice rolling mechanism.
Game Logic: Implement the rules of the game, including piece movement, capturing, and winning conditions.
Let's start by defining the game board and the special squares.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codestral then generates the code for all 4 files plus code for &lt;code&gt;main.py&lt;/code&gt; to start the game.&lt;/p&gt;

&lt;p&gt;Compared to Le Chat that outputs a single Python script, this is already some progress in terms of software architecture.&lt;/p&gt;

&lt;p&gt;The class &lt;code&gt;Game&lt;/code&gt; which implements the actual game logic imports classes &lt;code&gt;Board&lt;/code&gt;, &lt;code&gt;Piece&lt;/code&gt;, and &lt;code&gt;Dice&lt;/code&gt; implementing the game equipment. This architecture is sensible as it leads to a cohesive, well-structured code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code Quality ...
&lt;/h2&gt;

&lt;p&gt;Functions that Codestral generated in my experiment lack docstrings. But apart from that the code runs. But wait, the game seems to always terminate after a single move:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python main.py
player2 starts the game.
player2 rolled a 1
player1 wins!
&lt;span class="nv"&gt;$ &lt;/span&gt;python main.py
player1 starts the game.
player1 rolled a 2
player1 wins!
&lt;span class="nv"&gt;$ &lt;/span&gt;python main.py
player2 starts the game.
player2 rolled a 3
player1 wins!
&lt;span class="nv"&gt;$ &lt;/span&gt;python main.py
player1 starts the game.
player1 rolled a 3
player1 wins!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Attempt to Fix the Code using Codestral
&lt;/h2&gt;

&lt;p&gt;I type in the following prompt in the Chat mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The game implemented using the code in  terminates after a
single move. This is not correct. Help me debug and explain why
the game terminates after a single move
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codestral responds:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The game terminates after a single move because the &lt;br&gt;
&lt;code&gt;play_game&lt;/code&gt; method in &lt;code&gt;game.py&lt;/code&gt; does not contain a loop to continue playing turns until a winner is determined. The loop in &lt;code&gt;play_game&lt;/code&gt;&lt;br&gt;
should continue to call &lt;code&gt;play_turn&lt;/code&gt; and &lt;code&gt;switch_player&lt;/code&gt; until a winner is found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But this is actually &lt;strong&gt;not&lt;/strong&gt; true:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play_game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;determine_starting_player&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;current_player&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; starts the game.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;play_turn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;winner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;check_winner&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;winner&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; wins!&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;switch_player&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Oddly enough, as a fix to the issue, Codestral proposes &lt;strong&gt;exactly&lt;/strong&gt; the code that is &lt;strong&gt;already&lt;/strong&gt; in &lt;code&gt;game.py&lt;/code&gt;...&lt;/p&gt;

&lt;p&gt;So, as a next step, I ask Codestral to explain how the function &lt;code&gt;check_winner()&lt;/code&gt; works. It does a good job at explaining the function:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The method first checks if all pieces of &lt;code&gt;player1&lt;/code&gt; are off the board.&lt;/li&gt;
&lt;li&gt;It does this by iterating over all pieces in &lt;code&gt;self.player1_pieces&lt;/code&gt; and checking if their position attribute is None.&lt;/li&gt;
&lt;li&gt;If all pieces have position set to &lt;code&gt;None&lt;/code&gt;, it means all pieces of &lt;code&gt;player1&lt;/code&gt; have been moved off the board, and &lt;code&gt;player1&lt;/code&gt; is declared the winner.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's the code of that function and, as you can see, Codestral's explanation how the code works is correct:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_winner&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1_pieces&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2_pieces&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looking at the code in &lt;code&gt;check_winner()&lt;/code&gt;, it is immediately clear why the game terminates after just one move and why player 1 always wins: if no pieces where moved so far, the condition &lt;code&gt;all(piece.position is None for piece in self.player1_pieces)&lt;/code&gt; is always true and the function returns &lt;code&gt;player1&lt;/code&gt; as the winner.&lt;/p&gt;

&lt;h2&gt;
  
  
  Trying to Fix the Bug using Codestral
&lt;/h2&gt;

&lt;p&gt;So, I change into the Edit mode and tell Codestral:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The code in game.py terminates after just 1 move because the
function check_winner() always returns "player1". This is because
the condition "all(piece.position is None for piece in
self.player1_pieces)" is true when no pieces were moved. Fix this
so that the game end only after all pieces were moved across
the board and taken off the board.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, Codestral does not really know how to perform the fix based on my prompt. Instead of fixing the code, it suggests to add a third condition to &lt;code&gt;check_winner()&lt;/code&gt; that implements a draw:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1_pieces&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="nf"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;piece&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2_pieces&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;draw&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well, then... next try. I ask Codestral to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The function check_winner always returns "player1" because if
the pieces are not played yet, i.e., if no move has been made at
the start of the game, all pieces are off board. YOUR TASK:
suggest how to adjust  so that the pieces are in a start state
before they enter the game board and in the finish state after
they complete the player's path and the player can take them
off the board.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response from Codestral is helpful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"player1" because all pieces are initially off the board,
you need to adjust the initial state of the pieces and the logic
for moving pieces off the board. Specifically, you should:

1. Set the initial positions of the pieces to their starting positions on the board.
2. Adjust the logic for moving pieces off the board to correctly handle the finish state.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, what Codestral proposes is to assign every piece a starting position from a1 to a7 for player 1 and from c1 to c7 for player 2. This is, of course, completely wrong given the rules of the game.&lt;/p&gt;

&lt;p&gt;Somewhat frustrated, I give up and start fixing the bug by hand. The fix is actually quite simple:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;When a piece is taken off the board, it must enter a final state&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;check_winner()&lt;/code&gt; function must check whether all pieces are in the final state and, if that is the case, declare the winner&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is one small change in the &lt;code&gt;move_piece&lt;/code&gt; function: when the piece is taken off the board, instead of &lt;code&gt;piece.position = None&lt;/code&gt; we set it to &lt;code&gt;piece.position = 'final'&lt;/code&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Alarm!
&lt;/h2&gt;

&lt;p&gt;While changing the code in &lt;code&gt;game.py&lt;/code&gt;, I discover that the code for the class &lt;code&gt;Game&lt;/code&gt; appears twice in &lt;code&gt;game.py&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;After deleting the second instance of the &lt;code&gt;Game&lt;/code&gt; class, I get the following error when I try to run the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;File "/Users/paulduplys/Repositories/royal-game-of
ur/codestral/game.py", line 35, in move_piece
  opponent_piece.position = 'a5' if opponent_piece.player 
  == 'player1' else 'c5'
AttributeError: 'str' object has no attribute 'player'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, in the Edit mode, Codestral misses where the error occurs and does not help to fix it. So, I ask Le Chat the following question:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;You are an experience Python developer with over 15 years of experience. Your task is to help me debug my Python code.

My code contains the following statement: "opponent_piece.position
= 'a5' if opponent_piece.player == 'player1' else 'c5'". When
running the code, I get an error: " opponent_piece.position = 'a5'
if opponent_piece.player == 'player1' else 'c5'   AttributeError:
'str' object has no attribute 'player'". How can I fix the error?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Codestral's answer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The error message AttributeError: 'str' object has no attribute
'player' indicates that opponent_piece is a string, and you're
trying to access an attribute player on it, which doesn't exist
for string objects.

-- snip --
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And, indeed, &lt;code&gt;print(type(opponent_piece))&lt;/code&gt; prints &lt;code&gt;&amp;lt;class 'str'&amp;gt;&lt;/code&gt;... &lt;/p&gt;

&lt;p&gt;Digging deeper, my attention is first drawn upon the definition of the game board:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;special_squares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It is incorrect because the middle "lane" has more squares than the outer lanes and it does not comply to the specification:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+----+----+         +----+----+----+----+
| a1 | a2 |         | a5 | a6 | a7 | a8 |
+----+----+----+----+----+----+----+----+
| b1 | b2 | b3 | b4 | b5 | b6 | b7 | b8 | 
+----+----+----+----+----+----+----+----+
| c1 | c2 |         | c5 | c6 | c7 | c8 |
+----+----+         +----+----+----+----+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Rather, it has to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;

&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b4&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;b8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c2&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c6&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c7&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c8&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But the code still doesn't work... After some reading and debugging, I discover the following code in function &lt;code&gt;move_piece()&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;opponent_piece&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_square&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;new_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;opponent_piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;a5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;opponent_piece&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;player1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;c5&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While there is a dedicated class &lt;code&gt;Piece&lt;/code&gt; for representing the game pieces, the function &lt;code&gt;board.get_square()&lt;/code&gt; returns &lt;code&gt;board[row][col]&lt;/code&gt; which is simply a &lt;code&gt;String&lt;/code&gt;. So, the code that Mistral generates is not even type-safe.&lt;/p&gt;

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

&lt;p&gt;Overall, Codestral is better when it comes down to the architecture. But at least in my experiment, the code quality was really low. And I'm not even speaking about logical errors. If the model generates code such as &lt;code&gt;a = foo()&lt;/code&gt;, and the return value of &lt;code&gt;foo()&lt;/code&gt; is different from &lt;code&gt;a&lt;/code&gt;'s type, then the code is basically unworkable. If it weren't for an interpreted language like Python, this code wouldn't even compile. So, unfortunately, Codestral does not really offer advantages compared to Le Chat.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mistral</category>
      <category>games</category>
      <category>ur</category>
    </item>
    <item>
      <title>Implementing the Royal Game of Ur with Mistral's Le Chat</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Fri, 11 Apr 2025 19:54:17 +0000</pubDate>
      <link>https://dev.to/duplys/implementing-the-royal-game-of-ur-with-mistrals-le-chat-4mil</link>
      <guid>https://dev.to/duplys/implementing-the-royal-game-of-ur-with-mistrals-le-chat-4mil</guid>
      <description>&lt;p&gt;The curiosity to learn and the desire to master games have been a hallmark of humanity since the dawn of time, as &lt;a href="https://wwnorton.com/books/Seven-Games" rel="noopener noreferrer"&gt;Oliver Roeder describes in his fantastic book "Seven Games"&lt;/a&gt;. Interestingly, games have also been the testing ground for artificial intelligence (AI) - something I learnt while reading this book. What better way to test the capabilities of &lt;a href="https://chat.mistral.ai" rel="noopener noreferrer"&gt;Mistral's Le Chat&lt;/a&gt;, which is based on the Large Language Models (LLMs) that emerged just a few years ago, than by trying to implement the oldest known game, the Royal Game of Ur, which was played in ancient Mesopotamia at least 4,400 years ago?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Royal Game of Ur
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://en.wikipedia.org/wiki/Royal_Game_of_Ur" rel="noopener noreferrer"&gt;Royal Game of Ur was played in ancient Mesopotamia&lt;/a&gt; in the early third millennium BC. It was popular throughout the Middle East, and boards for playing it have been found in places as far away from Mesopotamia as Crete and Sri Lanka. The oldest board is dated to 2600-2400 BC and was found in the royal cemetery at Ur, giving the game its name.&lt;/p&gt;

&lt;p&gt;A partial cuneiform description of the rules of the game as it was played in the second century BC has been preserved on a Babylonian clay tablet written by the scribe Itti-Marduk-balāṭu. Based on this tablet and the shape of the board, Irving Finkel, a curator at the British Museum, has reconstructed the basic rules of the game as it might have been played.&lt;/p&gt;

&lt;h2&gt;
  
  
  Instructions to Le Chat
&lt;/h2&gt;

&lt;p&gt;To kick things off, I gave Le Chat the following instructions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I will give you the rules of a board game for two players.
Your task is to generate Python code implementing the game.
If something about the rules is ambiguous, ask me questions.

Here are the rules:

# The Royal Game of Ur

-- snip --

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;See &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/README.md" rel="noopener noreferrer"&gt;this README.md&lt;/a&gt; for the complete rules I used in my prompt.&lt;/p&gt;

&lt;p&gt;Telling Le Chat to ask me questions if something is ambiguous turned out to be a good strategy. Le Chat promptly replied asking to clarify several aspects of the rules. As an example, Le Chat asked if a player may choose not to enter a new piece onto the board if they have other legal moves available with pieces already on the board. In another case, it asked whether the game ends immediately when one player has removed all their pieces from the board, or does the other player get a final turn.&lt;/p&gt;

&lt;p&gt;There are two take-aways here:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specifying things in a clear, unambiguous manner – effectively, writing good requirements – is hard and we tend to overestimate the quality of our specifications&lt;/li&gt;
&lt;li&gt;Adding &lt;code&gt;if something is ambiguous, ask me questions&lt;/code&gt; to your prompt is a useful strategy as it makes the LLM to review your prompt before generating the response&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Le Chat's Responses
&lt;/h2&gt;

&lt;p&gt;Interestingly enough, Le Chat generated three versions of the code. More precisely, it tries to run the generated code and, if not successful, tries to fix the code.&lt;/p&gt;

&lt;p&gt;After running the first version of the code, Le Chat responded with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;It seems there's an issue with the board setup, specifically with
the position 'b4'. The board dictionary does not include this position, which is causing a `KeyError`. Let's fix this by ensuring
all necessary positions are included in the board setup.
I'll correct the board initialization and rerun the game simulation.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After generating and trying to run the second version of the code, Le Chat responded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;It seems there was an error while trying to simulate the game.
Let's address the issue by ensuring the game logic is correctly
implemented, particularly focusing on the movement and special
square mechanics. I'll review the code and make necessary adjustments.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After a third version, Le Chat stopped. So, I stored that last version in &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/game_v3.py" rel="noopener noreferrer"&gt;this Python script&lt;/a&gt; and started looking at the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Generated Code
&lt;/h2&gt;

&lt;p&gt;The code is composed of one class, &lt;code&gt;RoyalGameOfUrCorrected&lt;/code&gt;. It contains a constructor and 12 functions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;__init__&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;roll_dice&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;initial_setup&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;switch_player&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;is_legal_move&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;get_piece_position&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;calculate_new_position&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;move_piece&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;remove_piece&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;check_winner&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;play_turn&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;play_game&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;At first glance, &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/game_v3.py" rel="noopener noreferrer"&gt;the code&lt;/a&gt; looks good. For example, Le Chat has generated &lt;a href="https://peps.python.org/pep-0257/" rel="noopener noreferrer"&gt;Python docstrings&lt;/a&gt; for each function, something that many models do not yet do.&lt;/p&gt;

&lt;p&gt;However, trying to run the code throws an error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;NameError: name 'random' is not defined. Did you forget to import 'random'?
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, I ask Le Chat to fix this error:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;When I try to run the code, I get the following error: "NameError: name 'random' is not defined. Did you forget to import 'random'?" Please fix it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Le Chat again needs 2 iterations to produce &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/game_v5.py" rel="noopener noreferrer"&gt;this code&lt;/a&gt;. The code now starts with the &lt;code&gt;import random&lt;/code&gt; statement. So, Le Chat seems to be capable of fixing the code it generated if it is given the specific error message.&lt;/p&gt;

&lt;p&gt;Interestingly, the fixed code version ends with the following two statements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RoyalGameOfUr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;

    &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;play_game&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Play the game until it is over.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="o"&gt;--&lt;/span&gt; &lt;span class="n"&gt;snip&lt;/span&gt; &lt;span class="o"&gt;--&lt;/span&gt;



&lt;span class="c1"&gt;# Create a game instance
&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RoyalGameOfUr&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="c1"&gt;# Check initial setup
&lt;/span&gt;&lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;board&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player1_pieces&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;game&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;player2_pieces&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It looks like Le Chat is trying to verify whether the code can be executed rather than actually playing the game by calling &lt;code&gt;game.play_game()&lt;/code&gt;, something it did in the &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/game_v3.py" rel="noopener noreferrer"&gt;previous code version&lt;/a&gt;. Even &lt;code&gt;Pylint&lt;/code&gt; in Visual Studio Code IDE reports that the &lt;code&gt;statement seems to have no effect (W0104:pointless-statement)&lt;/code&gt;. But that, of course, doesn't make the code wrong per se.&lt;/p&gt;

&lt;p&gt;So, does the code work? Let's try by adding &lt;code&gt;game.play_game()&lt;/code&gt; as &lt;a href="https://codeberg.org/duplys/royal-game-of-ur/src/branch/main/game_v6.py" rel="noopener noreferrer"&gt;shown here&lt;/a&gt;. Let's run the code ... 🤞🤞🤞 (drums in the background) ... And voilà! The code runs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;➜  royal-game-of-ur git:&lt;span class="o"&gt;(&lt;/span&gt;main&lt;span class="o"&gt;)&lt;/span&gt; ✗ python3 game_v6.py
Player 2 moves L1 to a5.
Player 1 moves D1 to c5.
No move this turn.
Player 1 moves D1 to c7.

&lt;span class="nt"&gt;--&lt;/span&gt; snip &lt;span class="nt"&gt;--&lt;/span&gt;

Player 2 moves D3 to c2.
No legal moves available.
Player 1 moves L5 to a8.
No move this turn.
Player 1 moves D3 to off.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Of course, just because the code executes doesn't mean it correctly implements the rules of the Royal Game of Ur. But that is something for the next post – stay tuned!&lt;/p&gt;

</description>
      <category>games</category>
      <category>ur</category>
      <category>ai</category>
      <category>mistral</category>
    </item>
    <item>
      <title>SWE-bench &amp; SWE-bench Verified Benchmarks</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Sun, 06 Apr 2025 20:57:40 +0000</pubDate>
      <link>https://dev.to/duplys/swe-bench-swe-bench-verified-benchmarks-1cm</link>
      <guid>https://dev.to/duplys/swe-bench-swe-bench-verified-benchmarks-1cm</guid>
      <description>&lt;h2&gt;
  
  
  SWE-bench
&lt;/h2&gt;

&lt;p&gt;In their 2023 paper &lt;a href="https://arxiv.org/abs/2310.06770" rel="noopener noreferrer"&gt;"SWE-bench: Can Language Models Resolve Real-World GitHub Issues?"&lt;/a&gt;, researchers from Princeton University, Princeton Language and Intelligence, and University of Chicago wrote:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Language models have outpaced our ability to evaluate them effectively, but for their future development it is essential to study the frontier of their capabilities. We find real-world software engineering to be a rich, sustainable, and challenging testbed for evaluating the next generation of language models. To this end, we introduce SWE-bench, an evaluation framework consisting of 2,294 software engineering problems drawn from real GitHub issues and corresponding pull requests across 12 popular Python repositories. Given a codebase along with a description of an issue to be resolved, a language model is tasked with editing the codebase to address the issue.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In the meantime, SWE-bench has become on of the most popular benchmarks to evaluate LLMs' performance in software engineering. In a nutshell, SWE-bench tests the AI's ability to automatically solve GitHub issues. More precisely, the benchmark  and is executed by giving an AI coding agent a code repository and issue description, and asking it to generate a patch that resolves the issue.&lt;/p&gt;

&lt;h3&gt;
  
  
  Test Data
&lt;/h3&gt;

&lt;p&gt;The SWE-bench benchmark contains a dataset of 2,294 Issue-Pull Request pairs from 12 popular Python repositories. Each sample in the  test set contains the Pull Request (PR) with the solution code and unit tests that verify the code's correctness.&lt;/p&gt;

&lt;p&gt;These unit tests are called &lt;code&gt;FAIL_TO_PASS&lt;/code&gt; because they fail before the solution code in the PR is added but pass afterwards. In addition, each sample contains &lt;code&gt;PASS_TO_PASS&lt;/code&gt; tests. These tests pass both before and after the PR is merged and are used to verify that the PR does not cause regressions (i.e., does not brake existing functionality).&lt;/p&gt;

&lt;h3&gt;
  
  
  Benchmark Execution
&lt;/h3&gt;

&lt;p&gt;During the evaluation, the AI coding agents are given the original text from the GitHub issue and access to the codebase. The agents must edit the files in the codebase to resolve the issue. The tests are not shown to the agents.&lt;/p&gt;

&lt;p&gt;Once the AI coding agents is finished with editing the codebase, the changes are evaluated by executing both the &lt;code&gt;FAIL_TO_PASS&lt;/code&gt; and &lt;code&gt;PASS_TO_PASS&lt;/code&gt; tests. Both tests sets are required to pass for the changes to be considered  to resolve the original GitHub issue.&lt;/p&gt;

&lt;p&gt;All in all, resolving SWE-bench challenges is a challenging task because it requires understanding and coordinating changes across multiple functions, classes, and even files simultaneously.&lt;/p&gt;

&lt;p&gt;Coding agents have made impressive progress on SWE-bench. At the writing of the paper (it's latest version v3 on arXiv is dated 11 November 2024), the then-best-performing model, Claude 2, was able to solve a mere 1.96% of the issues. As of April 2025, the best-performing agent based on the Claude 3.7 Sonnet model solves 33.83% of the issues (SWE-bench full).&lt;/p&gt;

&lt;p&gt;The leaderboard, which is regularly updated, can be found at &lt;a href="http://www.swebench.com" rel="noopener noreferrer"&gt;www.swebench.com&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhik8tqzean78os5izobw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhik8tqzean78os5izobw.png" alt="Screenshot of the SWE-bench leaderboard for the SWE-bench full benchmark variant" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  SWE-bench Verified
&lt;/h2&gt;

&lt;p&gt;On 13 August, 2024, OpenAI published a blog post &lt;a href="https://openai.com/index/introducing-swe-bench-verified/" rel="noopener noreferrer"&gt;Introducing SWE-bench Verified&lt;/a&gt;. While working with SWE-bench, the OpenAI team discovered that issues from the test set were hard or even impossible to solve, leading to SWE-bench benchmark to systematically underestimate LLMs' software engineering capabilities. OpenAI then collaborated with SWE-bench authors to address this problem in a new release of the benchmark.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://openai.com/index/introducing-swe-bench-verified/" rel="noopener noreferrer"&gt;major areas for improvement&lt;/a&gt; identified by the team were:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The unit tests used to evaluate the correctness of a solution were often overly specific. In some cases, the unit tests were even unrelated to the GitHub issue, potentially causing correct solutions to be rejected&lt;/li&gt;
&lt;li&gt;Many samples had an issue description that was underspecified, leading to ambiguity on what the problem was and how it should have been solved&lt;/li&gt;
&lt;li&gt;Sometimes, it was challenging to reliably set up the SWE-bench development environments for the agents, causing unit tests to fail regardless of the solution. Thus, perfectly valid solutions might have been graded as incorrect&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So, the OpenAI team and the researchers worked with 93 experienced Python developers who manually screened SWE-bench test samples for quality: appropriately scoped unit tests and well-specified issue descriptions. They annotated 1,699 random samples from the SWE-bench test set to produce SWE-bench Verified.&lt;/p&gt;

&lt;p&gt;Together with the original authors of SWE-bench, the OpenAI team  released SWE-bench Verified, a subset of the original test set composed of 500 samples verified to be non-problematic by the human annotators. SWE-bench Verified supersedes the original SWE-bench and SWE-bench Lite test sets, and can be &lt;a href="https://huggingface.co/datasets/princeton-nlp/SWE-bench_Verified" rel="noopener noreferrer"&gt;downloaded from Hugging Face&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition, OpenAI released the human annotations for all SWE-bench test samples. These annotations allow to slice the dataset by difficulty:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The 'easy' subset is composed of 196 tasks taking up to 15 minutes to fix&lt;/li&gt;
&lt;li&gt;The 'hard' subset is composed of 45 tasks taking more than 1 hour to fix&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>llm</category>
      <category>ai</category>
      <category>software</category>
      <category>benchmark</category>
    </item>
    <item>
      <title>Can't Access a Service Container in GitHub Actions? Here's How to Fix it</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Sun, 23 Jan 2022 17:47:49 +0000</pubDate>
      <link>https://dev.to/duplys/cant-access-service-container-in-github-actions-heres-how-to-fix-it-10i1</link>
      <guid>https://dev.to/duplys/cant-access-service-container-in-github-actions-heres-how-to-fix-it-10i1</guid>
      <description>&lt;h2&gt;
  
  
  Error
&lt;/h2&gt;

&lt;p&gt;When running GitHub Actions, you might want to start an application in a &lt;a href="https://docs.github.com/en/actions/using-containerized-services/about-service-containers" rel="noopener noreferrer"&gt;service container&lt;/a&gt; and access the application from the GitHub Actions host. &lt;/p&gt;

&lt;p&gt;As an example, you might want to run a web application—in my case, it was the &lt;a href="https://owasp.org/www-project-juice-shop/" rel="noopener noreferrer"&gt;OWASP's Juice Shop&lt;/a&gt;—in the service container and then access it from the host using &lt;code&gt;curl&lt;/code&gt;. Unfortunately, that might easily end up with an error like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5bi53xk8m6jdpgbczg4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fg5bi53xk8m6jdpgbczg4.png" alt=" " width="667" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What's happening here? First, it makes sense to take a look at the workflow logs, especially the Docker commands as the containerized application is apparently not working (or we fail to reach the Docker container). One interesting clue you'd discover is that GitHub Actions creates a Docker network and connects the service container to that network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;...

&lt;span class="c"&gt;##[command]/usr/bin/docker pull bkimminich/juice-shop&lt;/span&gt;
Using default tag: latest
latest: Pulling from bkimminich/juice-shop

...

Status: Downloaded newer image &lt;span class="k"&gt;for &lt;/span&gt;bkimminich/juice-shop:latest
docker.io/bkimminich/juice-shop:latest
&lt;span class="c"&gt;##[command]/usr/bin/docker create --name 58e55591ffe849f8bf9c3dfbe1dbdf13_bkimminichjuiceshop_a12804 --label 9916a7 --network github_network_242bd42709204f349068bf9786120d2d --network-alias juice_shop_app -p 3000:3000  -e GITHUB_ACTIONS=true -e CI=true bkimminich/juice-shop&lt;/span&gt;
c7aaf737b377e503bd96f3d458f67d08cae4497a8b89d7826d4961e070f1fa57
&lt;span class="c"&gt;##[command]/usr/bin/docker start c7aaf737b377e503bd96f3d458f67d08cae4497a8b89d7826d4961e070f1fa57&lt;/span&gt;
c7aaf737b377e503bd96f3d458f67d08cae4497a8b89d7826d4961e070f1fa57

...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Now What Does That &lt;code&gt;docker network create&lt;/code&gt; Actually Do?
&lt;/h2&gt;

&lt;p&gt;In an attempt to debug the error, I tried reproducing the Docker setup on my local machine. Here's &lt;a href="https://www.tecmint.com/commands-to-collect-system-and-hardware-information-in-linux/" rel="noopener noreferrer"&gt;what my machine looks like&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
Linux terminus 4.13.0-38-generic &lt;span class="c"&gt;#43-Ubuntu SMP Wed Mar 14 15:20:44 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After executing Docker commands from the GitHub Actions log the current Docker networks look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker network &lt;span class="nb"&gt;ls
&lt;/span&gt;NETWORK ID     NAME                                              DRIVER    SCOPE
...
74bb2975d284   github_network_242bd42709204f349068bf9786120d2d   bridge    &lt;span class="nb"&gt;local&lt;/span&gt;
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As we would expect, Docker created a network named &lt;code&gt;github_network_242...&lt;/code&gt;. Here's what this network looks like in detail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker network inspect github_network_242bd42709204f349068bf9786120d2d 
&lt;span class="o"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="s2"&gt;"Name"&lt;/span&gt;: &lt;span class="s2"&gt;"github_network_242bd42709204f349068bf9786120d2d"&lt;/span&gt;,
        &lt;span class="s2"&gt;"Id"&lt;/span&gt;: &lt;span class="s2"&gt;"74bb2975d2848f54446da523ca5d9d66b98ea97016203bcbc1c74f5365dfd5b1"&lt;/span&gt;,
        &lt;span class="s2"&gt;"Created"&lt;/span&gt;: &lt;span class="s2"&gt;"2022-01-20T20:00:58.215693749+01:00"&lt;/span&gt;,
        &lt;span class="s2"&gt;"Scope"&lt;/span&gt;: &lt;span class="s2"&gt;"local"&lt;/span&gt;,
        &lt;span class="s2"&gt;"Driver"&lt;/span&gt;: &lt;span class="s2"&gt;"bridge"&lt;/span&gt;,
        &lt;span class="s2"&gt;"EnableIPv6"&lt;/span&gt;: &lt;span class="nb"&gt;false&lt;/span&gt;,
        ...
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;]&lt;/span&gt;
paul@terminus:~/Repositories/juice-shop&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out the driver for the &lt;code&gt;github_network_242...&lt;/code&gt; network is of type &lt;code&gt;bridge&lt;/code&gt;. The &lt;a href="https://docs.docker.com/network/" rel="noopener noreferrer"&gt;official Docker documenetation&lt;/a&gt; says that Docker’s networking subsystem is pluggable, using drivers. &lt;code&gt;bridge&lt;/code&gt; is the default network driver. If you don’t specify a driver, this is the type of network you are creating. &lt;/p&gt;

&lt;p&gt;A &lt;a href="https://docs.docker.com/network/bridge/" rel="noopener noreferrer"&gt;Docker bridge network&lt;/a&gt; uses a software bridge which allows containers connected to the same bridge network to communicate and isolates them from containers unconnected to that bridge network. But to be able to access a Docker container from the host, you need the &lt;code&gt;host&lt;/code&gt; Docker network driver as explained &lt;a href="https://docs.docker.com/network/" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While &lt;a href="https://docs.github.com/en/actions/using-containerized-services/about-service-containers" rel="noopener noreferrer"&gt;GitHub Actions documentation&lt;/a&gt; says that when running jobs directly on the runner machine, service containers can be accessed using &lt;code&gt;localhost:&amp;lt;port&amp;gt;&lt;/code&gt; or &lt;code&gt;127.0.0.1:&amp;lt;port&amp;gt;&lt;/code&gt;, that didn't work in my case (as you can see above).&lt;/p&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The fix was to change the workflow file so that &lt;code&gt;curl&lt;/code&gt; is called not on the GitHub Actions host, but rather in another Docker container by adding &lt;code&gt;container: ubuntu&lt;/code&gt; below the &lt;code&gt;runs-on&lt;/code&gt; directive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WebApplicationDefinition&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;PoC&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;with&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Docker"&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;web_app_defn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu&lt;/span&gt;
    &lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;juice_shop_app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bkimminich/juice-shop&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;3000:3000&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apt-get update; apt-get install curl -y&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;curl juice_shop_app:3000 &amp;gt; curl_result_docker&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cat curl_result_docker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It turns out that in this case, GitHub Actions attach both the &lt;code&gt;ubuntu&lt;/code&gt; and the &lt;code&gt;juice_shop_app&lt;/code&gt; containers to the same Docker network:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;##[command]/usr/bin/docker create --name e7bc0fa4f08042b283f7c17b6f6a16d8_ubuntu_e4790c [...] --network github_network_e2d8e92952e5496a9b185d930d41bb1f  [...]&lt;/span&gt;
...
&lt;span class="c"&gt;##[command]/usr/bin/docker create --name 2b247fe7ed4e430c8e853325460a3032_bkimminichjuiceshop_a2272b [...] --network github_network_e2d8e92952e5496a9b185d930d41bb1f ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now both containers are attached to the same network. After installing &lt;code&gt;curl&lt;/code&gt; on the &lt;code&gt;ubuntu&lt;/code&gt; container, I can use the name of the Juice Shop container (&lt;code&gt;juice_shop_app&lt;/code&gt;) to access it within the Docker network.&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubaction</category>
      <category>docker</category>
      <category>networks</category>
    </item>
    <item>
      <title>Convenient SSH: Public Key-based Authentication and Memorable Aliases for Remote Hosts</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Sun, 09 Jan 2022 11:41:22 +0000</pubDate>
      <link>https://dev.to/duplys/convenient-ssh-public-key-based-authentication-and-memorable-aliases-for-remote-hosts-3ae3</link>
      <guid>https://dev.to/duplys/convenient-ssh-public-key-based-authentication-and-memorable-aliases-for-remote-hosts-3ae3</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;This post describes how to simplify remote maintenance via SSH by configuring SSH to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;public-key authentication to eliminate the need to type in a password during each log in, and&lt;/li&gt;
&lt;li&gt;convenient aliases for the individual remote hosts (in order to avoid typing in the IP address during each log in).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my case, I'm using this to simplify the maintenance of the individual Raspberry Pis in my private &lt;a href="https://duplys.github.io/linux/rpi/raspberry-pi/microk8s/nginx/kubernetes/2020/04/21/installing-edge-on-raspberry-pi-4.blog.html" rel="noopener noreferrer"&gt;DeepPi computing cluster&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;(Header image by &lt;a href="https://unsplash.com/photos/pZXg_ObLOM4?utm_source=unsplash&amp;amp;utm_medium=referral&amp;amp;utm_content=creditShareLink" rel="noopener noreferrer"&gt;Fauzan Saari on Unsplash&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Enabling Public Key-based Authentication
&lt;/h2&gt;

&lt;p&gt;In addition to password-based authentication (and several more authentication options), SSH supports public key-based authentication. &lt;/p&gt;

&lt;p&gt;Public key authentication is based on public-key cryptography where encryption and decryption are performed using two distinct keys: the so-called private key for encryption and the so-called public key for decryption. Public key cryptoschemes are designed in such a way that it is computationally unfeasible to derive the decryption key (i.e., the private key) from the encryption key (i.e., the public key). For public key-based &lt;code&gt;ssh&lt;/code&gt; authentication, each user creates their own unique public/private key pair (for all practical purposes, the uniqueness of the key pair is ensured by the method used to generate it). This is done by running the &lt;code&gt;ssh-keygen&lt;/code&gt; program which stores the public/private key pair in &lt;code&gt;~/.ssh/&lt;/code&gt; directory on the user's machine. &lt;/p&gt;

&lt;p&gt;The names of the files where the public and private keys are stored depend on the cryptographic primitives used. For instance, for RSA the public key is stored in &lt;code&gt;~/.ssh/id_rsa.pub&lt;/code&gt; and the private key is stored in &lt;code&gt;~/.ssh/id_rsa&lt;/code&gt;; for ECDSA the public key is stored in &lt;code&gt;~/.ssh/id_ecdsa.pub (ECDSA)&lt;/code&gt; and the private key is stored in &lt;code&gt;~/.ssh/id_ecdsa (ECDSA)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the next step, the &lt;strong&gt;public&lt;/strong&gt; key is must be copied into the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; to the &lt;code&gt;ssh&lt;/code&gt; &lt;strong&gt;server&lt;/strong&gt;. As a result, the &lt;code&gt;ssh&lt;/code&gt; server knows the public key, and only the user knows the private key. The &lt;code&gt;authorized_keys&lt;/code&gt; file corresponds to the conventional &lt;code&gt;~/.rhosts&lt;/code&gt; file, and has one key per line, though the lines can be very long. After this, the user can log in without giving the password.&lt;/p&gt;

&lt;p&gt;The file &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; on the SSH server lists the public keys that are permitted to log in. When the user logs in, the &lt;code&gt;ssh&lt;/code&gt; program tells the server which key pair it would like to use for authentication. The client proves that it has access to the private key and the server checks that the corresponding public key is authorized to accept the account.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;This examples assumes that the public/private key pair is already created using &lt;code&gt;ssh-keygen&lt;/code&gt;. This is what the content of the &lt;code&gt;~/.ssh&lt;/code&gt; directory look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; ~/.ssh/
id_rsa  id_rsa.pub  known_hosts
&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the RSA public key to clipboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; ~/.ssh/id_rsa.pub | xclip &lt;span class="nt"&gt;-sel&lt;/span&gt; clipboard
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the remote host (i.e., on the RPi), open the &lt;code&gt;~/.ssh/authorized_keys&lt;/code&gt; file and paste the public key. Logging in next time will not prompt for the password:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ubuntu@kasparov:~$ exit
logout
Connection to 192.168.178.66 closed.
paul@terminus:~$ ssh ubuntu@192.168.178.66
Welcome to Ubuntu 18.04.4 LTS (GNU/Linux 5.3.0-1042-raspi2 aarch64)
 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Jan  5 15:57:00 UTC 2022

  System load:  0.35                Users logged in:        0
  Usage of /:   30.4% of 114.04GB   IP address for eth0:    192.168.178.66
  Memory usage: 33%                 IP address for docker0: 172.17.0.1
  Swap usage:   0%                  IP address for cni0:    10.1.79.1
  Processes:    181

  =&amp;gt; There is 1 zombie process.

* Super-optimized for small spaces - read how we shrank the memory
  footprint of MicroK8s to make it the smallest full K8s around.

   https://ubuntu.com/blog/microk8s-memory-optimisation

95 packages can be updated.
1 update is a security update.

New release '20.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Your Hardware Enablement Stack (HWE) is supported until April 2023.

Last login: Wed Jan  5 15:47:17 2022 from 192.168.178.21
ubuntu@kasparov:~$ 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setting up Memorable Aliases for Remote Hosts
&lt;/h2&gt;

&lt;p&gt;To avoid typing &lt;code&gt;ssh &amp;lt;username&amp;gt;@&amp;lt;IP address&amp;gt;&lt;/code&gt; at every log in, SSH allows to create convenient, memorable aliases for individual hosts in &lt;code&gt;~/.ssh/config&lt;/code&gt;. As a minimum, &lt;code&gt;HostName&lt;/code&gt; must be specified for the real remote host to log into. &lt;code&gt;HostName&lt;/code&gt; can also be an IP address. If the usernames on the remote host and on the local host are different, &lt;code&gt;User&lt;/code&gt; must be set to the username on the remote host.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example
&lt;/h3&gt;

&lt;p&gt;Edit &lt;code&gt;~/.ssh/config&lt;/code&gt; and add:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Host kasparov
    HostName 192.168.178.66
    User ubuntu
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;to be able to login to &lt;code&gt;192.168.178.66&lt;/code&gt; as user &lt;code&gt;ubuntu&lt;/code&gt; simply by typing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh kasparov
Welcome to Ubuntu 18.04.4 LTS &lt;span class="o"&gt;(&lt;/span&gt;GNU/Linux 5.3.0-1042-raspi2 aarch64&lt;span class="o"&gt;)&lt;/span&gt;

 &lt;span class="k"&gt;*&lt;/span&gt; Documentation:  https://help.ubuntu.com
 &lt;span class="k"&gt;*&lt;/span&gt; Management:     https://landscape.canonical.com
 &lt;span class="k"&gt;*&lt;/span&gt; Support:        https://ubuntu.com/advantage

  System information as of Fri Jan  7 08:31:08 UTC 2022

  System load:  0.41                Users logged &lt;span class="k"&gt;in&lt;/span&gt;:        0
  Usage of /:   31.1% of 114.04GB   IP address &lt;span class="k"&gt;for &lt;/span&gt;eth0:    192.168.178.66
  Memory usage: 41%                 IP address &lt;span class="k"&gt;for &lt;/span&gt;docker0: 172.17.0.1
  Swap usage:   0%                  IP address &lt;span class="k"&gt;for &lt;/span&gt;cni0:    10.1.79.1
  Processes:    196

  &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; There is 1 zombie process.

 &lt;span class="k"&gt;*&lt;/span&gt; Super-optimized &lt;span class="k"&gt;for &lt;/span&gt;small spaces - &lt;span class="nb"&gt;read &lt;/span&gt;how we shrank the memory
   footprint of MicroK8s to make it the smallest full K8s around.

   https://ubuntu.com/blog/microk8s-memory-optimisation

95 packages can be updated.
1 update is a security update.

Your Hardware Enablement Stack &lt;span class="o"&gt;(&lt;/span&gt;HWE&lt;span class="o"&gt;)&lt;/span&gt; is supported &lt;span class="k"&gt;until &lt;/span&gt;April 2023.

Last login: Wed Jan  5 16:09:12 2022 from 192.168.178.21
ubuntu@kasparov:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;ssh&lt;/code&gt; manpage&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ssh_config&lt;/code&gt; manpage&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server" rel="noopener noreferrer"&gt;https://www.digitalocean.com/community/tutorials/how-to-configure-ssh-key-based-authentication-on-a-linux-server&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.ssh.com/academy/ssh/authorized-keys-file" rel="noopener noreferrer"&gt;https://www.ssh.com/academy/ssh/authorized-keys-file&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>ssh</category>
    </item>
    <item>
      <title>Security Benchmarking at 1300 C</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Fri, 09 Oct 2020 20:36:22 +0000</pubDate>
      <link>https://dev.to/duplys/security-benchmarking-at-1300-c-1e0b</link>
      <guid>https://dev.to/duplys/security-benchmarking-at-1300-c-1e0b</guid>
      <description>&lt;h1&gt;
  
  
  Hot, Hotter, Magma
&lt;/h1&gt;

&lt;p&gt;Molten or semi-molten natural material that all igneous rocks are made of is called Magma. It results from the melting of the Earth's mantle or crust. Most magmas are in the range of 700 °C to 1300 °C (equivalent 1300 °F to 2400 °F), some rare ones can reach &lt;a href="https://en.wikipedia.org/wiki/Magma" rel="noopener noreferrer"&gt;temperatures as hot as 1600 °C&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Magma is also &lt;a href="https://hexhive.epfl.ch/magma/" rel="noopener noreferrer"&gt;a collection of popular open-source libraries&lt;/a&gt; created by the &lt;a href="http://hexhive.epfl.ch/" rel="noopener noreferrer"&gt;EPFL's HexHive team&lt;/a&gt; for evaluating and comparing &lt;a href="https://www.fuzzingbook.org/html/Fuzzer.html" rel="noopener noreferrer"&gt;fuzzer&lt;/a&gt; performance. To this end, the HexHive security researchers &lt;em&gt;front-ported&lt;/em&gt; bugs from previous bug reports to the latest versions of these libraries. &lt;/p&gt;

&lt;p&gt;For each ported bug, the researchers added instrumentation source code to collect the ground truth information about bugs reached (i.e., cases where the buggy code is executed) and triggered (i.e., cases where the fault condition is satisfied by the input) &lt;a href="https://arxiv.org/abs/2009.01120" rel="noopener noreferrer"&gt;as described in their arxiv.org paper&lt;/a&gt;. Such an instrumentation allows to measure the &lt;em&gt;real-world&lt;/em&gt; performance of a fuzzer.&lt;/p&gt;

&lt;h1&gt;
  
  
  Benchmark, anyone?
&lt;/h1&gt;

&lt;p&gt;Let's say you came up with a shiny new method for finding security vulnerabilities in software. To convince yourself, you now want to test how well your method actually performs. Your method is, however, not a fuzzer. Can you still use Magma for your experiments? You bet! That's because the front-ported bugs in Magma are available as Git patches. Git patches allow you to precisely see where the corresponding bugs are in the source code and, in turn, whether your method is able to find them. You only need to know a few basics about Git patches. &lt;/p&gt;

&lt;h1&gt;
  
  
  Git Patch 101
&lt;/h1&gt;

&lt;p&gt;A Git patch encodes the line-by-line difference between two text files. It describes how to turn one file into another, and is asymmetric: the patch from &lt;code&gt;file1&lt;/code&gt; to &lt;code&gt;file2&lt;/code&gt; is not the same as the patch for the other direction. The patch format uses context as well as line numbers to locate differing file regions so that a patch can often be applied to a somewhat earlier or later version of the first file than the one from which it was derived, as long as the applying program can still locate the context of the change (&lt;a href="https://www.oreilly.com/library/view/git-pocket-guide/9781449327507/ch11.html" rel="noopener noreferrer"&gt;see the O'Reilly documentation for more details&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Here's an example of a git patch (or git diff) after I changed the argument to the &lt;code&gt;printf&lt;/code&gt; function from &lt;code&gt;"Hello World!\n"&lt;/code&gt; to &lt;code&gt;"Hello There!\n"&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;git diff hello.c 
diff &lt;span class="nt"&gt;--git&lt;/span&gt; a/hello.c b/hello.c
index dc46521..a00b40d 100644
&lt;span class="nt"&gt;---&lt;/span&gt; a/hello.c
+++ b/hello.c
@@ &lt;span class="nt"&gt;-1&lt;/span&gt;,6 +1,6 @@
 &lt;span class="c"&gt;#include &amp;lt;stdio.h&amp;gt;&lt;/span&gt;

 int main&lt;span class="o"&gt;(){&lt;/span&gt;
- &lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello World!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
+ &lt;span class="nb"&gt;printf&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello There!&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt;
 &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line &lt;code&gt;diff --git a/hello.c b/hello.c&lt;/code&gt; shows that the file being compared is &lt;code&gt;hello.c&lt;/code&gt; (it's a single file and there are actually no directories &lt;code&gt;a&lt;/code&gt; and &lt;code&gt;b&lt;/code&gt; - it's just a convention).&lt;/p&gt;

&lt;p&gt;The second line &lt;code&gt;index dc46521..a00b40d 100644&lt;/code&gt; is the extended header line. In the Git index, &lt;code&gt;dc46521&lt;/code&gt; and &lt;code&gt;a00b40d&lt;/code&gt; are the blob IDs of the corresponding versions of the &lt;code&gt;hello.c&lt;/code&gt; file. Finally, &lt;code&gt;100644&lt;/code&gt; are the mode bits indicating the type of the &lt;code&gt;hello.c&lt;/code&gt; file (standard file in this case). &lt;/p&gt;

&lt;p&gt;The next two lines&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nt"&gt;---&lt;/span&gt; a/hello.c
+++ b/hello.c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;is the traditional unified diff header that shows the files being compared (&lt;code&gt;hello.c&lt;/code&gt; in our case).&lt;/p&gt;

&lt;p&gt;The line &lt;code&gt;@@ -1,6 +1,6 @@&lt;/code&gt; indicates the position of the difference section (also known as a "hunk") in the respective &lt;code&gt;hello.c&lt;/code&gt; versions using the line number and the length. In our example, both in version &lt;code&gt;--- a/hello.c&lt;/code&gt; and in version &lt;code&gt;+++ b/hello.c&lt;/code&gt; the hunk starts at line 1 and extends for 6 lines (note the &lt;code&gt;-&lt;/code&gt; and &lt;code&gt;+&lt;/code&gt; signs in &lt;code&gt;@@ -1,6 +1,6 @@&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;What follows is the actual difference. The minus signs show lines present in version &lt;code&gt;a/hello.c&lt;/code&gt; but missing in version &lt;code&gt;b/hello.c&lt;/code&gt; and plus signs show lines missing in &lt;code&gt;a/hello.c&lt;/code&gt; but present in &lt;code&gt;b/hello.c&lt;/code&gt;. Thus, the difference shows that the line &lt;code&gt;printf("Hello World!\n");&lt;/code&gt; was replaced by the line &lt;code&gt;printf("Hello There!\n");&lt;/code&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Examples, please!
&lt;/h1&gt;

&lt;p&gt;Let's take a look at &lt;a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-13785" rel="noopener noreferrer"&gt;CVE-2018-13785&lt;/a&gt;, an  integer overflow that leads to a divide by zero and, thus, a potential denial of service. The corresponding Git patch in Magma is located in &lt;code&gt;targets/libpng/patches/bugs/AAH001.patch&lt;/code&gt; file. (Appendix A of the &lt;a href="https://arxiv.org/pdf/2009.01120.pdf" rel="noopener noreferrer"&gt;Magma preprint paper&lt;/a&gt; contains a mapping of patches to CVE IDs).&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;AAH001.patch&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat &lt;/span&gt;bugs/AAH001.patch 
diff &lt;span class="nt"&gt;--git&lt;/span&gt; a/pngrutil.c b/pngrutil.c
index 4db3de990..01c97dc37 100644
&lt;span class="nt"&gt;---&lt;/span&gt; a/pngrutil.c
+++ b/pngrutil.c
@@ &lt;span class="nt"&gt;-3163&lt;/span&gt;,12 +3163,27 @@ png_check_chunk_length&lt;span class="o"&gt;(&lt;/span&gt;png_const_structrp png_ptr, png_uint_32 length&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;chunk_name &lt;span class="o"&gt;==&lt;/span&gt; png_IDAT&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;{&lt;/span&gt;
       png_alloc_size_t idat_limit &lt;span class="o"&gt;=&lt;/span&gt; PNG_UINT_31_MAX&lt;span class="p"&gt;;&lt;/span&gt;
+#ifdef MAGMA_ENABLE_FIXES
       size_t row_factor &lt;span class="o"&gt;=&lt;/span&gt;
          &lt;span class="o"&gt;(&lt;/span&gt;size_t&lt;span class="o"&gt;)&lt;/span&gt;png_ptr-&amp;gt;width
          &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;size_t&lt;span class="o"&gt;)&lt;/span&gt;png_ptr-&amp;gt;channels
          &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;bit_depth &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 8? 2: 1&lt;span class="o"&gt;)&lt;/span&gt;
          + 1
          + &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;interlaced? 6: 0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
+#else
+      size_t row_factor_l &lt;span class="o"&gt;=&lt;/span&gt;
+         &lt;span class="o"&gt;(&lt;/span&gt;size_t&lt;span class="o"&gt;)&lt;/span&gt;png_ptr-&amp;gt;width
+         &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;size_t&lt;span class="o"&gt;)&lt;/span&gt;png_ptr-&amp;gt;channels
+         &lt;span class="k"&gt;*&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;bit_depth &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 8? 2: 1&lt;span class="o"&gt;)&lt;/span&gt;
+         + 1
+         + &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;interlaced? 6: 0&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
+
+#ifdef MAGMA_ENABLE_CANARIES
+      MAGMA_LOG&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"AAH001"&lt;/span&gt;, row_factor_l &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt;size_t&lt;span class="o"&gt;)&lt;/span&gt;1 &amp;lt;&amp;lt; &lt;span class="o"&gt;(&lt;/span&gt;sizeof&lt;span class="o"&gt;(&lt;/span&gt;png_uint_32&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt; 8&lt;span class="o"&gt;)))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
+#endif
+
+      size_t row_factor &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;png_uint_32&lt;span class="o"&gt;)&lt;/span&gt;row_factor_l&lt;span class="p"&gt;;&lt;/span&gt;
+#endif
       &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;png_ptr-&amp;gt;height &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; PNG_UINT_32_MAX/row_factor&lt;span class="o"&gt;)&lt;/span&gt;
          idat_limit &lt;span class="o"&gt;=&lt;/span&gt; PNG_UINT_31_MAX&lt;span class="p"&gt;;&lt;/span&gt;
       &lt;span class="k"&gt;else&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The patch shows that a bug is located in the &lt;code&gt;pngrutil.c&lt;/code&gt; file, in function &lt;code&gt;png_check_chunk_length&lt;/code&gt; starting at line 3163. The lines starting with &lt;code&gt;+&lt;/code&gt; constitute the bug. Indeed, according to the &lt;a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2018-13785" rel="noopener noreferrer"&gt;CVE-2018-13785&lt;/a&gt; which corresponds to this patch, &lt;em&gt;"a wrong calculation of &lt;code&gt;row_factor&lt;/code&gt; in the &lt;code&gt;png_check_chunk_length&lt;/code&gt; function (pngrutil.c) may trigger an integer overflow and resultant divide-by-zero while processing a crafted PNG file, leading to a denial of service"&lt;/em&gt;. Hence, using the Magma &lt;code&gt;*.patch&lt;/code&gt; files you can easily locate the corresponding bugs. &lt;/p&gt;

&lt;h1&gt;
  
  
  TLDR; (Summary)
&lt;/h1&gt;

&lt;p&gt;&lt;a href="https://hexhive.epfl.ch/magma/" rel="noopener noreferrer"&gt;Magma ground-truth fuzzing benchmark&lt;/a&gt; is a collection of popular and diverse open-source libraries with 118 real-world &lt;em&gt;front-ported&lt;/em&gt; bugs available in the form of Git patch files under &lt;code&gt;targets/&amp;lt;target_name&amp;gt;/patches/bugs/*.patch&lt;/code&gt;. While the EPFL &lt;a href="http://hexhive.epfl.ch/#people" rel="noopener noreferrer"&gt;HexHive team&lt;/a&gt; originally designed Magma for benchmarking fuzzer performance, it can be used to evaluate the effectiveness of any method for discovering security vulnerabilities in software since patches give you the necessary ground truth.&lt;/p&gt;

</description>
      <category>infosec</category>
      <category>fuzzing</category>
      <category>vulnerability</category>
      <category>magma</category>
    </item>
    <item>
      <title>How to Backup MariaDB Database Running in a Kubernetes Pod</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Fri, 11 Sep 2020 19:54:33 +0000</pubDate>
      <link>https://dev.to/duplys/how-to-backup-mariadb-database-running-in-a-kubernetes-pod-2pd6</link>
      <guid>https://dev.to/duplys/how-to-backup-mariadb-database-running-in-a-kubernetes-pod-2pd6</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4c2eiphoe057hihyp317.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F4c2eiphoe057hihyp317.jpg" alt="Alt Text" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The easiest way to backup (and restore) a MariaDB database is to use &lt;a href="https://mariadb.com/kb/en/mysqldump/" rel="noopener noreferrer"&gt;the &lt;code&gt;mysqldump&lt;/code&gt; tool&lt;/a&gt;. This is especially true when you don't have a huge amount of data in your database. &lt;code&gt;mysqldump&lt;/code&gt; dumps the data in SQL format which is really handy as it can be imported by most database engines. For more details, see &lt;a href="https://mariadb.com/kb/en/backup-and-restore-overview/" rel="noopener noreferrer"&gt;this MariaDB documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But how do you backup a database running in a container? First, get the name of the Kubernetes Pod containing your database:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;ubuntu@ubuntu:~$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;microk8s.kubectl get all
&lt;span class="go"&gt;NAME                                 READY   STATUS    RESTARTS   AGE
pod/mediawiki-app-55f45cf568-gmpzv   1/1     Running   2          5d22h
pod/mediawiki-db-5cb8db589f-r6q8k    1/1     Running   0          5d23h
pod/my-nginx-9b596c8c4-4jp7d         1/1     Running   15         102d
pod/my-nginx-9b596c8c4-fnlm7         1/1     Running   2          12d
pod/my-nginx-9b596c8c4-hmz4r         1/1     Running   2          12d

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE
&lt;/span&gt;&lt;span class="gp"&gt;service/kubernetes         ClusterIP   10.152.183.1     &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;443/TCP          104d
&lt;span class="gp"&gt;service/mediawiki-db-srv   NodePort    10.152.183.195   &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;3306:31501/TCP   5d23h
&lt;span class="gp"&gt;service/mediawiki-srv      NodePort    10.152.183.17    &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;80:32681/TCP     28d
&lt;span class="gp"&gt;service/my-nginx-np        NodePort    10.152.183.73    &amp;lt;none&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;80:30178/TCP     102d
&lt;span class="go"&gt;
NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mediawiki-app   1/1     1            1           11d
deployment.apps/mediawiki-db    1/1     1            1           5d23h
deployment.apps/my-nginx        3/3     3            3           102d

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/mediawiki-app-5494668f87   0         0         0       5d23h
replicaset.apps/mediawiki-app-55f45cf568   1         1         1       5d23h
replicaset.apps/mediawiki-app-75cb9c97d7   0         0         0       11d
replicaset.apps/mediawiki-db-5cb8db589f    1         1         1       5d23h
replicaset.apps/my-nginx-9b596c8c4         3         3         3       102d
&lt;/span&gt;&lt;span class="gp"&gt;ubuntu@ubuntu:~$&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In my setup, it's the Pod &lt;code&gt;mediawiki-db-5cb8db589f-r6q8k&lt;/code&gt; (I know this because in my &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Kubernetes Deployment manifest file&lt;/a&gt;, I named my MariaDB Deployment &lt;code&gt;mediawiki-db&lt;/code&gt;). Now that we know the Deployment name, we need to find out its IP address. You can easily do this with the &lt;code&gt;kubectl describe&lt;/code&gt; command (note that since I'm running Canonical's &lt;a href="https://microk8s.io/" rel="noopener noreferrer"&gt;microk8s, a lightweight Kubernetes variant&lt;/a&gt;, and I don't have shell aliases, I need to prepend &lt;code&gt;microk8s.&lt;/code&gt;; if you are running regular Kubernetes, just use the standard &lt;code&gt;kubectl&lt;/code&gt; command):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl describe pod mediawiki-db-5cb8db589f-r6q8k
Name:         mediawiki-db-5cb8db589f-r6q8k

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

IP:           10.1.49.32
IPs:
  IP:           10.1.49.32
Controlled By:  ReplicaSet/mediawiki-db-5cb8db589f

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

Events:          &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can double check that the IP address is correct by issuing a simple &lt;code&gt;ping&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;ping 10.1.49.32
PING 10.1.49.32 &lt;span class="o"&gt;(&lt;/span&gt;10.1.49.32&lt;span class="o"&gt;)&lt;/span&gt; 56&lt;span class="o"&gt;(&lt;/span&gt;84&lt;span class="o"&gt;)&lt;/span&gt; bytes of data.
64 bytes from 10.1.49.32: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;63 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2.04 ms
64 bytes from 10.1.49.32: &lt;span class="nv"&gt;icmp_seq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2 &lt;span class="nv"&gt;ttl&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;63 &lt;span class="nb"&gt;time&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0.615 ms
^C
&lt;span class="nt"&gt;---&lt;/span&gt; 10.1.49.32 ping statistics &lt;span class="nt"&gt;---&lt;/span&gt;
2 packets transmitted, 2 received, 0% packet loss, &lt;span class="nb"&gt;time &lt;/span&gt;1001ms
rtt min/avg/max/mdev &lt;span class="o"&gt;=&lt;/span&gt; 0.615/1.329/2.044/0.715 ms
ubuntu@ubuntu:~&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you know the IP address exists and is alive, you can check whether the MariaDB database is actually running. To do this, you can use the &lt;code&gt;mysql&lt;/code&gt; command to connect to a MariaDB database on that Pod. In my case, &lt;code&gt;wikiuser&lt;/code&gt; is the user name for the database, &lt;code&gt;10.1.49.32&lt;/code&gt; is the IP address of the corresponding Kubernetes Pod, and &lt;code&gt;my_wiki&lt;/code&gt; is the name of the database I have previously created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;mysql &lt;span class="nt"&gt;-u&lt;/span&gt; wikiuser &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; 10.1.49.32 my_wiki
Enter password: 
Reading table information &lt;span class="k"&gt;for &lt;/span&gt;completion of table and column names
You can turn off this feature to get a quicker startup with &lt;span class="nt"&gt;-A&lt;/span&gt;

Welcome to the MySQL monitor.  Commands end with &lt;span class="p"&gt;;&lt;/span&gt; or &lt;span class="se"&gt;\g&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;
Your MySQL connection &lt;span class="nb"&gt;id &lt;/span&gt;is 160
Server version: 5.5.5-10.3.22-MariaDB-0+deb10u1 Raspbian 10

Copyright &lt;span class="o"&gt;(&lt;/span&gt;c&lt;span class="o"&gt;)&lt;/span&gt; 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type &lt;span class="s1"&gt;'help;'&lt;/span&gt; or &lt;span class="s1"&gt;'\h'&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;help. Type &lt;span class="s1"&gt;'\c'&lt;/span&gt; to clear the current input statement.

mysql&amp;gt; &lt;span class="nb"&gt;exit
&lt;/span&gt;Bye
ubuntu@ubuntu:~&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If all looks well, you can issue the &lt;code&gt;mysqldump&lt;/code&gt; command as shown below. The command takes the username (&lt;code&gt;-u&lt;/code&gt; switch), the &lt;code&gt;-p&lt;/code&gt; switch for password (if you omit the password value, &lt;code&gt;mysqldump&lt;/code&gt; prompts for one), the IP address (&lt;code&gt;-h&lt;/code&gt; switch), and the name of the database to be dumped. By default, the &lt;code&gt;mysqldump&lt;/code&gt; outputs the SQL statements to create and populate the database to the standard output. Hence, to backup into a file, you can simply redirect the output to an arbitrary file or your choice using the &lt;code&gt;&amp;gt;&lt;/code&gt; shell operator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;mysqldump &lt;span class="nt"&gt;-u&lt;/span&gt; wikiuser &lt;span class="nt"&gt;-p&lt;/span&gt; &lt;span class="nt"&gt;-h&lt;/span&gt; 10.1.49.32 my_wiki &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; my_wiki_backup.sql
Enter password:  
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; 20 my_wiki_backup.sql 
&lt;span class="nt"&gt;--&lt;/span&gt; MySQL dump 10.13  Distrib 5.7.31, &lt;span class="k"&gt;for &lt;/span&gt;Linux &lt;span class="o"&gt;(&lt;/span&gt;aarch64&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Host: 10.1.49.32    Database: my_wiki
&lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;------------------------------------------------------&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Server version   5.5.5-10.3.22-MariaDB-0+deb10u1

/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40101 SET @OLD_CHARACTER_SET_CLIENT&lt;span class="o"&gt;=&lt;/span&gt;@@CHARACTER_SET_CLIENT &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40101 SET @OLD_CHARACTER_SET_RESULTS&lt;span class="o"&gt;=&lt;/span&gt;@@CHARACTER_SET_RESULTS &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40101 SET @OLD_COLLATION_CONNECTION&lt;span class="o"&gt;=&lt;/span&gt;@@COLLATION_CONNECTION &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40101 SET NAMES utf8 &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40103 SET @OLD_TIME_ZONE&lt;span class="o"&gt;=&lt;/span&gt;@@TIME_ZONE &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40103 SET &lt;span class="nv"&gt;TIME_ZONE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'+00:00'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40014 SET @OLD_UNIQUE_CHECKS&lt;span class="o"&gt;=&lt;/span&gt;@@UNIQUE_CHECKS, &lt;span class="nv"&gt;UNIQUE_CHECKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40014 SET @OLD_FOREIGN_KEY_CHECKS&lt;span class="o"&gt;=&lt;/span&gt;@@FOREIGN_KEY_CHECKS, &lt;span class="nv"&gt;FOREIGN_KEY_CHECKS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40101 SET @OLD_SQL_MODE&lt;span class="o"&gt;=&lt;/span&gt;@@SQL_MODE, &lt;span class="nv"&gt;SQL_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'NO_AUTO_VALUE_ON_ZERO'&lt;/span&gt; &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;
/&lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;40111 SET @OLD_SQL_NOTES&lt;span class="o"&gt;=&lt;/span&gt;@@SQL_NOTES, &lt;span class="nv"&gt;SQL_NOTES&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="k"&gt;*&lt;/span&gt;/&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nt"&gt;--&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt; Table structure &lt;span class="k"&gt;for &lt;/span&gt;table &lt;span class="sb"&gt;`&lt;/span&gt;actor&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;span class="nt"&gt;--&lt;/span&gt;
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



</description>
      <category>kubernetes</category>
      <category>mariadb</category>
      <category>backup</category>
      <category>docker</category>
    </item>
    <item>
      <title>Building an Exocortex: Mediawiki on a microk8s Kubernetes Cluster</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Wed, 29 Jul 2020 07:03:49 +0000</pubDate>
      <link>https://dev.to/duplys/building-an-exocortex-mediawiki-on-a-microk8s-kubernetes-cluster-4h7i</link>
      <guid>https://dev.to/duplys/building-an-exocortex-mediawiki-on-a-microk8s-kubernetes-cluster-4h7i</guid>
      <description>&lt;h1&gt;
  
  
  Boost Your Memory
&lt;/h1&gt;

&lt;p&gt;In a book I read some time ago, I stumbled upon the idea of artificially augmenting one's memory by systematically storing everything one learns in a single place. The book used the term &lt;em&gt;exocortex&lt;/em&gt; to refer to this concept of extending the natural brain's memory capabilities by external technical means. Since then, I have used such a mechanism to keep everything I consider important (mostly technical) stuff in one place.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how you can use a MediaWiki instance on your private Raspberry Pi Kubernetes cluster to implement an exocortex. &lt;a href="https://en.wikipedia.org/wiki/MediaWiki" rel="noopener noreferrer"&gt;MediaWiki&lt;/a&gt; is an open source wiki software originally authored by Magnus Manske and improved by Lee Danial Crocker. The most prominent website running MediaWiki is Wikipedia. It is written in the PHP programming language and stores the contents into a database.&lt;/p&gt;

&lt;p&gt;For ease of administration, we'll use Kubernetes to deploy the Mediawiki application. Kubernetes will ensure that things work reliably. For example, if the applications stops working, Kubernetes will immediately spawn a new instance.&lt;/p&gt;

&lt;p&gt;A word on spelling: for easier reading -- and in alignment with conventions used in the official Kubernetes documentation -- I will capitalize the names of the Kubernetes objects. Hence, the words "Deployment" and "Pod" refer to specific Kubernetes objects (more details on that later).&lt;/p&gt;

&lt;h1&gt;
  
  
  The Ingredients
&lt;/h1&gt;

&lt;p&gt;Here's a list of ingredients we'll need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/duplys/a-la-carte-for-devs-microk8s-on-raspberry-pi-4-9j9"&gt;A &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster on Raspberry Pi&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The official &lt;code&gt;mediawiki&lt;/code&gt; Docker image&lt;/li&gt;
&lt;li&gt;The official &lt;code&gt;mariadb&lt;/code&gt; Docker image&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;docker-compose.yml&lt;/code&gt; file that we will use as a starting point&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Starting Point
&lt;/h1&gt;

&lt;p&gt;If you look up the &lt;a href="https://hub.docker.com/_/mediawiki" rel="noopener noreferrer"&gt;official MediaWiki Docker image on DockerHub&lt;/a&gt;, you will also find there a &lt;code&gt;docker-compose&lt;/code&gt; file recommended to deploy the dockerized MediaWiki. We will use this file as a starting point for writing our Kubernetes manifests so we can deploy MediaWiki using &lt;code&gt;microk8s.kubectl apply&lt;/code&gt; on the Raspberry Pi Kubernetes cluster. This is what the &lt;code&gt;docker-compose&lt;/code&gt; file looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;# MediaWiki with MariaDB&lt;/span&gt;
&lt;span class="c1"&gt;#&lt;/span&gt;
&lt;span class="c1"&gt;# Access via "http://localhost:8080"&lt;/span&gt;
&lt;span class="c1"&gt;#   (or "http://$(docker-machine ip):8080" if using docker-machine)&lt;/span&gt;
&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;mediawiki&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;8080:80&lt;/span&gt;
    &lt;span class="na"&gt;links&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;database&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/images&lt;/span&gt;
      &lt;span class="c1"&gt;# After initial setup, download LocalSettings.php to the same directory as&lt;/span&gt;
      &lt;span class="c1"&gt;# this yaml and uncomment the following line and use compose to restart&lt;/span&gt;
      &lt;span class="c1"&gt;# the mediawiki service&lt;/span&gt;
      &lt;span class="c1"&gt;# - ./LocalSettings.php:/var/www/html/LocalSettings.php&lt;/span&gt;
  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# @see https://phabricator.wikimedia.org/source/mediawiki/browse/master/includes/DefaultSettings.php&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_wiki&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wikiuser&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_RANDOM_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yes'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essentially, it consists of two services: the &lt;code&gt;mediawiki&lt;/code&gt; application itself and the &lt;code&gt;database&lt;/code&gt; service for storing the data. The &lt;code&gt;mediawiki&lt;/code&gt; service is exposed to port 8080 and requires two volumes for regular operation. The &lt;code&gt;database&lt;/code&gt; service run a MariaDB container and needs several environment variables to be set so that the &lt;code&gt;mediawiki&lt;/code&gt; application can access the database. In the remainder of this post, we'll recreated this setup using Kubernetes mechanisms.  &lt;/p&gt;

&lt;h1&gt;
  
  
  The MediaWiki Application
&lt;/h1&gt;

&lt;p&gt;Let's start by creating a &lt;code&gt;microk8s&lt;/code&gt; Kubernetes deployment for the MediaWiki container.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Minimal MediaWiki Deployment
&lt;/h2&gt;

&lt;p&gt;A &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;Kubernetes Deployment&lt;/a&gt; is a Kubernetes object — a persistent entity in the Kubernetes system defining the &lt;em&gt;desired state&lt;/em&gt; — that basically specifies a ReplicaSet and the replicated Pods to be run. The specification of a Kubernetes object, referred to as a &lt;em&gt;manifest&lt;/em&gt;, must contain the following &lt;a href="https://kubernetes.io/docs/concepts/overview/working-with-objects/kubernetes-objects/" rel="noopener noreferrer"&gt;required fields&lt;/a&gt;: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;apiVersion&lt;/code&gt; specifying which version of the Kubernetes API is used to create the object&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;kind&lt;/code&gt; specifying the kind of the object to be created&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;metadata&lt;/code&gt; for uniquely identifying the object (at least a &lt;code&gt;name&lt;/code&gt; string and a UID)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;spec&lt;/code&gt; specifying the object's desired state&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Therefore, as a minimum, the MediaWiki Deployment manifest would look something like this (feel free to choose your own UIDs and label values):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the label in &lt;code&gt;spec.selector.matchLabels&lt;/code&gt; must match the label in &lt;code&gt;spec.template.metadata.labels&lt;/code&gt;. &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/deployment/" rel="noopener noreferrer"&gt;This is because&lt;/a&gt; the &lt;code&gt;.spec.selector&lt;/code&gt; field defines how the Deployment finds which Pods to manage. Thus, you need to select a label that is defined in the Pod template (&lt;code&gt;app: mediawiki-app&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;We can create the Deployment by issuing &lt;code&gt;microk8s.kubectl apply -f mediawiki.yml&lt;/code&gt; on the command line of the Raspberry Pi (assuming you saved the Deployment manifest in the file &lt;code&gt;mediawiki.yml&lt;/code&gt;). It takes some time when we run it for the first time because the images must be downloaded first. After a while — it takes approximately 5-10 minutes on my RPi — you should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/mediawiki-app-656f6f8d64-7f4xz   1/1     Running   0          11m
pod/mediawiki-app-656f6f8d64-9x924   1/1     Running   0          11m

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
service/kubernetes    ClusterIP   10.152.183.1    &amp;lt;none&amp;gt;        443/TCP        75d

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mediawiki-app   2/2     2            2           11m

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/mediawiki-app-656f6f8d64   2         2         2       11m
ubuntu@ubuntu:~/mediawiki&lt;span class="err"&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Deployment is running, the ReplicaSet was created and the two Pods are running just like we specified in the above manifest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Adding Volumes
&lt;/h2&gt;

&lt;p&gt;In the &lt;code&gt;docker-compose&lt;/code&gt; file, our starting point from above, the &lt;code&gt;mediawiki&lt;/code&gt; service uses a Docker volume for the &lt;code&gt;/var/www/html/images&lt;/code&gt; directory within the &lt;code&gt;mediawiki&lt;/code&gt; container. So let's add a volume to our Kubernetes Deployment. The official documentation for Kubernetes volumes &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/" rel="noopener noreferrer"&gt;can be found here&lt;/a&gt;. It turns out, we need to add a &lt;code&gt;volumeMounts&lt;/code&gt; field to &lt;code&gt;spec.containers&lt;/code&gt; and pass it a list (that's why the next line starts with a &lt;code&gt;-&lt;/code&gt;) containing the &lt;code&gt;mountPath&lt;/code&gt; and the &lt;code&gt;name&lt;/code&gt; of the volume. We also need to add the a &lt;code&gt;volume&lt;/code&gt; field to &lt;code&gt;spec&lt;/code&gt; and list the volume we want to add. The Deployment manifest now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/images&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To update our MediaWiki Deployment, we issue again the same command &lt;code&gt;microk8s.kubectl apply -f mediawiki.yml&lt;/code&gt;. If we now take a closer look at one of the Pods, we should see a volume attached to it (the actual output of &lt;code&gt;microk8s.kubectl describe pod&lt;/code&gt; is very detailed, I have trimmed it here for the ease of reading):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl describe pod mediawiki-app-656f6f8d64-7f4xz
Name:         mediawiki-app-656f6f8d64-7f4xz

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

Containers:
  mediawiki-container:
    Container ID:   containerd://8525869985915306078e1c2b836682ddf145510a4fe04b7155618a749ed87e2d
    Image:          mediawiki

    &lt;span class="c"&gt;# -- snip --&lt;/span&gt;

    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-krfdn &lt;span class="o"&gt;(&lt;/span&gt;ro&lt;span class="o"&gt;)&lt;/span&gt;
      /var/www/html/images from mediawiki-volume &lt;span class="o"&gt;(&lt;/span&gt;rw&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

Volumes:
  mediawiki-volume:
    Type:       EmptyDir &lt;span class="o"&gt;(&lt;/span&gt;a temporary directory that shares a pod&lt;span class="s1"&gt;'s lifetime)
    Medium:     
    SizeLimit:  &amp;lt;unset&amp;gt;

# -- snip --

ubuntu@ubuntu:~/mediawiki$ 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;microk8s&lt;/code&gt; reports that this volume is just a temporary directory. This is because this type of volume is not persistent in the sense that it is deleted if the pod ceases to exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restart
&lt;/h2&gt;

&lt;p&gt;Our &lt;code&gt;docker-compose&lt;/code&gt; file defines the restart policy &lt;code&gt;always&lt;/code&gt; for the &lt;code&gt;mediawiki&lt;/code&gt; service. As described in the &lt;a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/" rel="noopener noreferrer"&gt;official Kubernetes documentation for the Pod lifecycle&lt;/a&gt;, a Pod specification has a &lt;code&gt;restartPolicy&lt;/code&gt; field with possible values &lt;code&gt;Always&lt;/code&gt;, &lt;code&gt;OnFailure&lt;/code&gt;, and &lt;code&gt;Never&lt;/code&gt;. The default value is &lt;code&gt;Always&lt;/code&gt;. So we're done here!&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;p&gt;In &lt;code&gt;docker-compose&lt;/code&gt; the &lt;code&gt;link&lt;/code&gt; directive creates a link to containers in another service. From the &lt;a href="https://docs.docker.com/compose/compose-file/compose-file-v2/#links" rel="noopener noreferrer"&gt;official Docker Compose documentation&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;Containers for the linked service are reachable at a hostname identical to the alias, or the service name if no alias was specified.&lt;/p&gt;

&lt;p&gt;Links are not required to enable services to communicate - by default, any service can reach any other service at that service's name. (See also, the Links topic in Networking in Compose.)&lt;/p&gt;

&lt;p&gt;Links also express dependency between services in the same way as depends_on, so they determine the order of service startup.&lt;/p&gt;

&lt;p&gt;I'm not sure whether I need this in Kubernetes because the &lt;a href="https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt; says:&lt;/p&gt;

&lt;p&gt;Kubernetes gives every pod its own cluster-private IP address, so you do not need to explicitly create links between pods or map container ports to host ports. This means that containers within a Pod can all reach each other's ports on localhost, and all pods in a cluster can see each other without NAT.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ports
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;mediawiki&lt;/code&gt; service in the &lt;code&gt;docker-compose&lt;/code&gt; file exposes port 80 of the &lt;code&gt;mediawiki&lt;/code&gt; container and maps it to the port 8080 on the host. To replicate this in our Kubernetes manifest, we first add the &lt;code&gt;spec.containers.ports&lt;/code&gt; field and pass it a list of the container ports to be exposed (&lt;code&gt;- containerPort: 80&lt;/code&gt; in this case). The manifest now looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/images&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We again update our Deployment using the &lt;code&gt;microk8s.kubectl apply -f mediawiki.yml&lt;/code&gt; command. We can verify that &lt;code&gt;ports&lt;/code&gt; specification took effect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl describe deployment mediawiki-app
Name:                   mediawiki-app

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

Pod Template:
  Labels:  &lt;span class="nv"&gt;app&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mediawiki-app
  Containers:
   mediawiki-container:
    Image:        mediawiki
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  &amp;lt;none&amp;gt;

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl describe pod mediawiki-app-5494668f87-5xldp
Name:         mediawiki-app-5494668f87-5xldp

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

Containers:
  mediawiki-container:
    Container ID:   containerd://6ba69454dc74ebc359466edc233197e2cf11f3ae8365d4fcf5ea60b0b62ec3b7
    Image:          mediawiki
    Image ID:       docker.io/library/mediawiki@sha256:e3be6a44c1d82e454657a013c5df29f7a9a0bfc112325d6d9df1ab1e21087b51
    Port:           80/TCP
    Host Port:      0/TCP

&lt;span class="c"&gt;# -- snip --&lt;/span&gt;

ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see, port 80 of the MediaWiki container is now exposed. &lt;a href="https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/" rel="noopener noreferrer"&gt;What this means&lt;/a&gt; is that your Pod is now accessible through port 80 from any Kubernetes Node within your cluster. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding a MediaWiki Service
&lt;/h2&gt;

&lt;p&gt;But what happens if a Node dies? The Pods running on that Node die with it. The Deployment will create new Pods, but they will have different IP addresses. And that's exactly the problem solved by a Kubernetes Service. &lt;/p&gt;

&lt;p&gt;A Kubernetes Service defines a logical set of Pods that provide the same functionality and run somewhere in the Kubernetes cluster. Upon creation, each Service is assigned a unique IP address, the so-called &lt;code&gt;clusterIP&lt;/code&gt;.  This address will not change while the Service is alive. Communication to the Service will be automatically load-balanced to some Pod that is a member of that Service.&lt;/p&gt;

&lt;p&gt;Thus, we need to create a Service to expose the &lt;code&gt;mediawiki&lt;/code&gt; pods to the outside world (outside of the Kubernetes cluster, that is). As with our MediaWiki Deployment, we store the Service manifest in a new file &lt;code&gt;mediawiki-service.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-srv&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8883&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the Service label must match our &lt;code&gt;selector&lt;/code&gt; label in the Deployment &lt;code&gt;spec&lt;/code&gt; (in our case the label &lt;code&gt;app: mediawiki-app&lt;/code&gt;). By the way: for some odd reason, &lt;code&gt;microk8s.kubectl&lt;/code&gt; returns an error saying &lt;code&gt;Service&lt;/code&gt; is an unknown type if I use &lt;code&gt;apiVersion: apps/v1&lt;/code&gt; as in the Deployment (using just &lt;code&gt;v1&lt;/code&gt; instead works). &lt;/p&gt;

&lt;p&gt;To start the Service, we issue the &lt;code&gt;microk8s.kubectl apply -f mediawiki-service.yml&lt;/code&gt; command. Let's now inspect out Kubernetes cluster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/mediawiki-app-5494668f87-5xldp   1/1     Running   0          19m
pod/mediawiki-app-5494668f87-p7p4s   1/1     Running   0          19m

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
service/kubernetes      ClusterIP   10.152.183.1    &amp;lt;none&amp;gt;        443/TCP        75d
service/mediawiki-srv   ClusterIP   10.152.183.17   &amp;lt;none&amp;gt;        8883/TCP       3m51s

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mediawiki-app   2/2     2            2           49m

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/mediawiki-app-5494668f87   2         2         2       19m
replicaset.apps/mediawiki-app-656f6f8d64   0         0         0       49m
ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From the above output, you can see that the Service was created. However, the Service has the type &lt;code&gt;ClusterIP&lt;/code&gt; meaning that it cannot be reached from outside of the Kubernetes cluster. To fix this, we need to change the &lt;code&gt;type&lt;/code&gt; of the &lt;code&gt;mediawiki-srv&lt;/code&gt; service into &lt;code&gt;NodePort&lt;/code&gt;. In addition, the port should be 80 (as this is the port exposed by the &lt;code&gt;mediawiki&lt;/code&gt; Pod):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-srv&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We &lt;code&gt;microk8s.kubectl apply&lt;/code&gt; the Service manifest again and check the result:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl apply &lt;span class="nt"&gt;-f&lt;/span&gt; mediawiki-service.yml 
service/mediawiki-srv configured
ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/mediawiki-app-5494668f87-5xldp   1/1     Running   0          30m
pod/mediawiki-app-5494668f87-p7p4s   1/1     Running   0          30m

NAME                    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
service/kubernetes      ClusterIP   10.152.183.1    &amp;lt;none&amp;gt;        443/TCP        76d
service/mediawiki-srv   NodePort    10.152.183.17   &amp;lt;none&amp;gt;        80:32681/TCP   14m

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mediawiki-app   2/2     2            2           59m

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/mediawiki-app-5494668f87   2         2         2       30m
replicaset.apps/mediawiki-app-656f6f8d64   0         0         0       59m
ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Verifying the Intermediate Result
&lt;/h2&gt;

&lt;p&gt;We can now check our MediaWiki Service and Deployment using the &lt;code&gt;curl&lt;/code&gt; command line tool directly on the Raspberry Pi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;curl http://127.0.0.1:32681
&amp;lt;&lt;span class="o"&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;html &lt;span class="nv"&gt;lang&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"en"&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"ltr"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
    &amp;lt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
        &amp;lt;meta &lt;span class="nv"&gt;charset&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"UTF-8"&lt;/span&gt; /&amp;gt;
        &amp;lt;title&amp;gt;MediaWiki 1.34.2&amp;lt;/title&amp;gt;
        &amp;lt;style &lt;span class="nv"&gt;media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"screen"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
            body &lt;span class="o"&gt;{&lt;/span&gt;
                color: &lt;span class="c"&gt;#000;&lt;/span&gt;
                background-color: &lt;span class="c"&gt;#fff;&lt;/span&gt;
                font-family: sans-serif&lt;span class="p"&gt;;&lt;/span&gt;
                text-align: center&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;

            h1 &lt;span class="o"&gt;{&lt;/span&gt;
                font-size: 150%&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="o"&gt;}&lt;/span&gt;
        &amp;lt;/style&amp;gt;
    &amp;lt;/head&amp;gt;
    &amp;lt;body&amp;gt;
        &amp;lt;img &lt;span class="nv"&gt;src&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/resources/assets/mediawiki.png"&lt;/span&gt; &lt;span class="nv"&gt;alt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"The MediaWiki logo"&lt;/span&gt; /&amp;gt;

        &amp;lt;h1&amp;gt;MediaWiki 1.34.2&amp;lt;/h1&amp;gt;
        &amp;lt;div &lt;span class="nv"&gt;class&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"error"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;


            &amp;lt;p&amp;gt;LocalSettings.php not found.&amp;lt;/p&amp;gt;


                &amp;lt;p&amp;gt;Please &amp;lt;a &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/mw-config/index.php"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="nb"&gt;set &lt;/span&gt;up the wiki&amp;lt;/a&amp;gt; first.&amp;lt;/p&amp;gt;


        &amp;lt;/div&amp;gt;
    &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that it works locally, we can try to access the &lt;code&gt;mediawiki&lt;/code&gt; service from our host system, i.e., outside the Raspberry Pi. If you open your web browser and type &lt;code&gt;http://&amp;lt;ip address of you RPi&amp;gt;:32681&lt;/code&gt;, you should see this:&lt;/p&gt;

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

&lt;h1&gt;
  
  
  The Database Application
&lt;/h1&gt;

&lt;p&gt;Next, we need to create a Deployment and Service for the database (MariaDB in our case). In the &lt;code&gt;docker-compose.yml&lt;/code&gt; file, the MariaDB Service looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

  &lt;span class="c1"&gt;# -- snip --&lt;/span&gt;

  &lt;span class="na"&gt;database&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mariadb&lt;/span&gt;
    &lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;always&lt;/span&gt;
    &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="c1"&gt;# @see https://phabricator.wikimedia.org/source/mediawiki/browse/master/includes/DefaultSettings.php&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_DATABASE&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_wiki&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_USER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wikiuser&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
      &lt;span class="na"&gt;MYSQL_RANDOM_ROOT_PASSWORD&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yes'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The MariaDB Deployment
&lt;/h2&gt;

&lt;p&gt;We already saw that we don't need to do anything explicitly for the &lt;code&gt;always&lt;/code&gt; restart policy since it's the default in Kubernetes. However, we have to use a different Docker image for MariaDB because that Docker container will run on our Raspberry Pi. &lt;/p&gt;

&lt;p&gt;Why do we need a different Docker image? Well, the Raspberry Pi CPU is based on the ARM architecture which is a different from that of your standard PC or laptop (which nowadays mostly happens to be x86_64). A Docker image is essentially a collection of layers (packaged in &lt;code&gt;*.tar&lt;/code&gt; files) containing, among other things, binaries needed to run your application, e.g., command utilities like &lt;code&gt;cp&lt;/code&gt; or &lt;code&gt;ls&lt;/code&gt;, compilers like &lt;code&gt;gcc&lt;/code&gt;, scripting languages like &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;ruby&lt;/code&gt;, and your application itself, say, &lt;code&gt;nginx&lt;/code&gt; (if you're curious, you can just run &lt;code&gt;docker image save &amp;lt;image-name&amp;gt; -o &amp;lt;some-name&amp;gt;.tar&lt;/code&gt; and look into that &lt;code&gt;.tar&lt;/code&gt; archive to see the layers and the binaries in each layer).&lt;/p&gt;

&lt;p&gt;If the Docker image was built on an x86_64 architecture, the resulting binaries cannot be executed on ARM architecture. Thus, many of the official Docker images from DockerHub — an the official MariaDb image happens to be one of those — will not run on a Raspberry Pi. To deal with this, you can either build your own MariaDB image for RPi or &lt;a href="https://hub.docker.com/r/jsurf/rpi-mariadb/" rel="noopener noreferrer"&gt;use e.g., the &lt;code&gt;rpi-mariadb&lt;/code&gt; image&lt;/a&gt;, which is a port of the official &lt;a href="https://hub.docker.com/_/mariadb/" rel="noopener noreferrer"&gt;&lt;code&gt;mariadb&lt;/code&gt; image&lt;/a&gt; for Raspberry Pi.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A word of caution:&lt;/strong&gt; For security reasons, you should always be very careful when using unofficial Docker images from DockerHub! Since Docker itself runs with &lt;code&gt;root&lt;/code&gt; permissions on your system, every Docker container can essentially also access your system with &lt;code&gt;root&lt;/code&gt; permissions (unless you have configured your Docker installation to avoid this particular threat). If you want to learn more about this, a very accessible source is the "Docker in Practice" book by Ian Miell and Aidan Hobson Sayers, &lt;a href="https://livebook.manning.com/book/docker-in-practice-second-edition/chapter-14/92" rel="noopener noreferrer"&gt;Chapter 14 "Docker and Security"&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, we will need to define the 4 environment variables for the MariaDB container. In Kubernetes, you &lt;a href="https://kubernetes.io/docs/tasks/inject-data-application/define-environment-variable-container/" rel="noopener noreferrer"&gt;set environment variables&lt;/a&gt; using the &lt;code&gt;spec.containers.env&lt;/code&gt; field. So the Deployment manifest for MariaDB looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jsurf/rpi-mariadb&lt;/span&gt;
          &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MYSQL_DATABASE&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my_wiki&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MYSQL_USER&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;wikiuser&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MYSQL_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MYSQL_RANDOM_ROOT_PASSWORD&lt;/span&gt;
              &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;yes'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the &lt;code&gt;microk8s.kubectl apply -f &amp;lt;mariadb-deployment-manifest.yml&amp;gt;&lt;/code&gt; to create the Deployment. You can also check &lt;a href="https://phabricator.wikimedia.org/source/mediawiki/browse/master/includes/DefaultSettings.php" rel="noopener noreferrer"&gt;the DefaultSettings.php file&lt;/a&gt; to see why we need to set these environment variables in the MariaDB container for it to work. &lt;/p&gt;

&lt;h2&gt;
  
  
  Adding MariaDB Service
&lt;/h2&gt;

&lt;p&gt;Like with MediaWiki itself, we need to add a Kubernetes Service for MariaDB so it is exposed on the local host and thus can be accessed by the MediaWiki application. The default port number for MariaDB is 3306 and this is also the port MediaWiki will try to connect to. Hence, the manifest for the MariaDB service looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db-srv&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NodePort&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;3306&lt;/span&gt;
      &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-db&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the &lt;code&gt;microk8s.kubectl apply -f &amp;lt;mariadb-service-manifest.yml&amp;gt;&lt;/code&gt; to create the Service. You now should see the following Kubernetes objects running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                                 READY   STATUS    RESTARTS   AGE
pod/mediawiki-app-55f45cf568-gmpzv   1/1     Running   0          9h
pod/mediawiki-db-5cb8db589f-r6q8k    1/1     Running   0          9h

NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;          AGE
service/kubernetes         ClusterIP   10.152.183.1     &amp;lt;none&amp;gt;        443/TCP          98d
service/mediawiki-db-srv   NodePort    10.152.183.195   &amp;lt;none&amp;gt;        3306:31501/TCP   9h
service/mediawiki-srv      NodePort    10.152.183.17    &amp;lt;none&amp;gt;        80:32681/TCP     9h

NAME                            READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mediawiki-app   1/1     1            1           9h
deployment.apps/mediawiki-db    1/1     1            1           9h

NAME                                       DESIRED   CURRENT   READY   AGE
replicaset.apps/mediawiki-app-55f45cf568   1         1         1       9h
replicaset.apps/mediawiki-db-5cb8db589f    1         1         1       9h
ubuntu@ubuntu:~/mediawiki&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  Setting Up MediaWiki
&lt;/h1&gt;

&lt;p&gt;From your host computer, you can now point your browser to &lt;code&gt;http://&amp;lt;ip address of you RPi&amp;gt;:32681&lt;/code&gt; and click on the link to start setting up the MediaWiki application. The installation procedure is self-explanatory. The only thing you need to adjust during the setup is to set the IP address of the database to &lt;code&gt;127.0.0.1&lt;/code&gt; (instead of the default &lt;code&gt;localhost&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;At the end of the installation, you will be prompted to download the &lt;code&gt;LocalSettings.php&lt;/code&gt; file. You will need this setting file to run MediaWiki (otherwise, you will always end up in the setup mode). First, you need to &lt;code&gt;scp&lt;/code&gt; this file to your Raspberry Pi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;paulduplys@Pauls-MBP Downloads % paulduplys@Pauls-MBP Downloads % scp LocalSettings.php ubuntu@192.168.178.66:/home/ubuntu/mediawiki/
ubuntu@192.168.178.66&lt;span class="s1"&gt;'s password: 
LocalSettings.php                                                                                                                                                                                           100% 4233   858.3KB/s   00:00    
paulduplys@Pauls-MBP Downloads %
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To mount this file into the &lt;code&gt;mediawiki&lt;/code&gt; container, we use the &lt;a href="https://kubernetes.io/docs/concepts/storage/volumes/" rel="noopener noreferrer"&gt;&lt;code&gt;hostPath&lt;/code&gt; volume&lt;/a&gt; which mounts a file or directory from the host node's filesystem into your Pod. So, we need to add the following to our Deployment manifest:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="c1"&gt;# -- snip --&lt;/span&gt;

    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;

            &lt;span class="c1"&gt;# -- snip --&lt;/span&gt;

            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/LocalSettings.php&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-localsettings&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;

      &lt;span class="c1"&gt;# -- snip --&lt;/span&gt;

      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-localsettings&lt;/span&gt;
          &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/ubuntu/mediawiki/LocalSettings.php&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The final MediaWiki Deployment manifest looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
  &lt;span class="na"&gt;replicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-container&lt;/span&gt;
          &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki&lt;/span&gt;
          &lt;span class="na"&gt;volumeMounts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/images&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/www/html/LocalSettings.php&lt;/span&gt;
              &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-localsettings&lt;/span&gt;
          &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
      &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-volume&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mediawiki-localsettings&lt;/span&gt;
          &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/home/ubuntu/mediawiki/LocalSettings.php&lt;/span&gt;
            &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;File&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Issue &lt;code&gt;microk8s.kubectl apply -f mediawiki.yml&lt;/code&gt; one last time and you should have a Kubernetes-managed MediaWiki instance. Enjoy!&lt;/p&gt;

</description>
      <category>microk8s</category>
      <category>kubernetes</category>
      <category>wiki</category>
    </item>
    <item>
      <title>À la Carte for Devs: microk8s on Raspberry Pi 4</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Wed, 24 Jun 2020 20:58:14 +0000</pubDate>
      <link>https://dev.to/duplys/a-la-carte-for-devs-microk8s-on-raspberry-pi-4-9j9</link>
      <guid>https://dev.to/duplys/a-la-carte-for-devs-microk8s-on-raspberry-pi-4-9j9</guid>
      <description>&lt;p&gt;After finally getting my hands on a gorgeous &lt;a href="https://www.raspberrypi.org/products/raspberry-pi-4-model-b/" rel="noopener noreferrer"&gt;Raspberry Pi 4&lt;/a&gt; (the version with 4 GB of RAM), I decided to test-drive it by installing Canonical's &lt;a href="https://microk8s.io" rel="noopener noreferrer"&gt;&lt;code&gt;microk8s&lt;/code&gt;&lt;/a&gt;, a small, single-package, CNCF certified fully-conformant upstream Kubernetes deployment for offline development, prototyping, and testing. &lt;/p&gt;

&lt;p&gt;It came in handy that I had watched the superb &lt;a href="https://www.bretfisher.com/courses" rel="noopener noreferrer"&gt;"Docker Mastery: with Kubernetes + Swarm"&lt;/a&gt; course by Bret Fisher and so could apply the things I learned there. I can definitely recommend this course if you are interested in learning Docker, Swarm, and Kubernetes from the ground up. Bret is a great instructor and, besides the videos and slides, the course contains a lot of good exercises and handy reference material. All in all, it took me about 2 hours to install everything and maybe another 1 hour to play around a bit and set things up.&lt;/p&gt;

&lt;p&gt;In this post, I'll show you step by step how you can replicate that and create a &lt;code&gt;microk8s&lt;/code&gt; Kubernetes deployment on your Raspberry Pi 4. This will give you a nice environment where you can quickly deploy any containerized applications to experiment, prototype, test, and play around at home.&lt;/p&gt;

&lt;h1&gt;
  
  
  The Hardware
&lt;/h1&gt;

&lt;p&gt;In addition to the Raspberry Pi 4 microcomputer itself, you'll need 3 extra hardware items.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A power supply for the Raspberry. I use &lt;a href="https://www.raspberrypi.org/products/type-c-power-supply/" rel="noopener noreferrer"&gt;Raspberry Pi 15.3W USB-C&lt;/a&gt;, the officially recommended USB-C power supply for Raspberry Pi 4.
&lt;/li&gt;
&lt;li&gt;An microSD card for storing the operating system, Docker + Kubernetes and your applications on your Raspberry. I use a 128GB microSD card from SanDisk.&lt;/li&gt;
&lt;li&gt;A network cable to connect the Raspberry to your router. Any Ethernet cable will do. &lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Installing Ubuntu 18.04 on Raspberry Pi 4
&lt;/h1&gt;

&lt;p&gt;First of all, you need to install an operating system on the Raspberry. I chose Ubuntu Server because of its comprehensive package repositories and long-term support (LTS). Shortly after I was preparing this post, Ubuntu switched from 18.04 to 20.04 LTS release, but the instructions given here should work on 20.04 as well.&lt;/p&gt;

&lt;p&gt;To get the Ubuntu image for Raspberry Pi, visit the &lt;a href="https://ubuntu.com/download/raspberry-pi" rel="noopener noreferrer"&gt;official Ubuntu download website&lt;/a&gt; and download the 64-bit version of the Ubuntu Pi image. Follow the &lt;a href="https://ubuntu.com/download/raspberry-pi/thank-you?version=20.04&amp;amp;architecture=arm64+raspi" rel="noopener noreferrer"&gt;installation instructions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Once the download is complete you need to extract the Ubuntu Server 20.04 image (the &lt;code&gt;.xz&lt;/code&gt; file is an archive) and copy it onto your microSD card. The installation instructions contain links to short tutorials on how to do this under Linux, Windows and Macs. If you happen to be on a Windows machine, you can use the &lt;code&gt;win32disk&lt;/code&gt; tool to write the Ubuntu image onto the microSD card (remember that you first need unpack the &lt;code&gt;.xz&lt;/code&gt; archive).&lt;/p&gt;

&lt;p&gt;Because I'm too lazy to use an extra screen for the installation, I rather chose to &lt;code&gt;ssh&lt;/code&gt; into the Raspberry Pi once it has booted. To enable this, you can follow the instructions described &lt;a href="https://www.raspberrypi.org/documentation/remote-access/ssh/" rel="noopener noreferrer"&gt;here&lt;/a&gt; and simply create a new file called &lt;code&gt;ssh&lt;/code&gt; in the root directory of your microSD card (it's the same directory where files like config.txt are located). This will enable the &lt;code&gt;ssh&lt;/code&gt; daemon at the first boot of your Rapsberry so you will be able to immediately &lt;code&gt;ssh&lt;/code&gt; into it. &lt;/p&gt;

&lt;p&gt;Next, you need connect the Raspberry to your router using a network cable, insert the microSD card into the SD card slot and power up the Raspberry Pi.&lt;/p&gt;

&lt;p&gt;Connect to your router - most of them like e.g., the AVM FritzBox have a web admin frontend where you can login; If you happen to have a FritzBox, just fire up your browser and enter &lt;code&gt;http://fritz.box&lt;/code&gt;. This will give you a login mask. Once you logged in, look up the IP address of the newly connected Raspberry Pi. Typically, it will have &lt;code&gt;ubuntu&lt;/code&gt; as its device name.&lt;/p&gt;

&lt;p&gt;Now, from your host (which obviously needs to be connected to the same router, e.g., via WiFi), do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh ubuntu@&amp;lt;IP address of your pi&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and enter &lt;code&gt;ubuntu&lt;/code&gt; when asked for password. When logging in for the first time, you will be asked to choose a new password. From then on, you login using the user &lt;code&gt;ubuntu&lt;/code&gt; and your new password.&lt;/p&gt;

&lt;h1&gt;
  
  
  Installing &lt;code&gt;microk8s&lt;/code&gt;
&lt;/h1&gt;

&lt;p&gt;To install and use &lt;code&gt;microk8s&lt;/code&gt;, you need &lt;code&gt;cgroups&lt;/code&gt;. By default, &lt;code&gt;cgroups&lt;/code&gt; are not enabled on the Ubuntu server image for Raspberry Pi. As described in this &lt;a href="https://microk8s.io/docs/install-alternatives#arm" rel="noopener noreferrer"&gt;&lt;code&gt;microk8s&lt;/code&gt; documentation&lt;/a&gt;, you need to edit the boot parameters in &lt;code&gt;nobtcmd.txt&lt;/code&gt; (attention: in the older Pi versions before Pi 4, this file was called &lt;code&gt;cmdline.txt&lt;/code&gt;; if you google for how to enable &lt;code&gt;cgroups&lt;/code&gt; on a Raspberry Pi, you might hit a tutorial that uses this outdated name). Thus, fire up the &lt;code&gt;vi&lt;/code&gt; editor as &lt;code&gt;root&lt;/code&gt; using &lt;code&gt;sudo&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;vi /boot/firmware/nobtcmd.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and add the following line:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;cgroup_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;memory &lt;span class="nv"&gt;cgroup_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the change, you need to reboot the Raspberry Pi for it to take effect. To do this, issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, follow the &lt;a href="https://microk8s.io" rel="noopener noreferrer"&gt;instructions on the &lt;code&gt;microk8s&lt;/code&gt; website&lt;/a&gt; on how to install &lt;code&gt;microk8s&lt;/code&gt; using snap. As I'm writing this post, the current channel version is 1.18. So on your Raspberry Pi, issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;snap &lt;span class="nb"&gt;install &lt;/span&gt;microk8s &lt;span class="nt"&gt;--classic&lt;/span&gt; &lt;span class="nt"&gt;--channel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.18/stable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the installation is complete, you need to add your current user &lt;code&gt;ubuntu&lt;/code&gt; to the &lt;code&gt;microk8s&lt;/code&gt; group and make some other changes. See &lt;a href="https://microk8s.io/docs" rel="noopener noreferrer"&gt;the &lt;code&gt;microk8s&lt;/code&gt; documentation&lt;/a&gt; for details. You need to do the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;usermod &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nt"&gt;-G&lt;/span&gt; microk8s &lt;span class="nv"&gt;$USER&lt;/span&gt;
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo chown&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="nt"&gt;-R&lt;/span&gt; &lt;span class="nv"&gt;$USER&lt;/span&gt; ~/.kube
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;su - &lt;span class="nv"&gt;$USER&lt;/span&gt;
Password: 
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s status &lt;span class="nt"&gt;--wait-ready&lt;/span&gt;
microk8s is running
addons:
dashboard: disabled
dns: disabled
helm: disabled
helm3: disabled
ingress: disabled
kubeflow: disabled
metallb: disabled
metrics-server: disabled
rbac: disabled
registry: disabled
storage: disabled
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s kubectl get nodes
NAME     STATUS   ROLES    AGE   VERSION
ubuntu   Ready    &amp;lt;none&amp;gt;   10m   v1.18.0
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s kubectl get services
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;   AGE
kubernetes   ClusterIP   10.152.183.1   &amp;lt;none&amp;gt;        443/TCP   10m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congrats! You now have a working &lt;code&gt;microk8s&lt;/code&gt; installation on your Raspberry Pi 4!&lt;/p&gt;

&lt;h1&gt;
  
  
  Deploying nginx with microk8s
&lt;/h1&gt;

&lt;p&gt;To play around with your new shiny &lt;code&gt;microk8s&lt;/code&gt; installation, you can create a deployment for &lt;code&gt;nginx&lt;/code&gt;, a popular HTTP and reverse proxy server. If you want to learn more about &lt;code&gt;nginx&lt;/code&gt;, visit &lt;a href="https://nginx.org/en/" rel="noopener noreferrer"&gt;their website&lt;/a&gt;. Start by creating a Kubernetes deployment using the &lt;code&gt;nginx&lt;/code&gt; Docker image and verify that the deployment has been indeed created:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl create deployment my-nginx &lt;span class="nt"&gt;--image&lt;/span&gt; nginx
deployment.apps/my-nginx created
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/my-nginx-9b596c8c4-ngd2q   1/1     Running   0          12m

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;   AGE
service/kubernetes   ClusterIP   10.152.183.1   &amp;lt;none&amp;gt;        443/TCP   47h

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   1/1     1            1           12m

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/my-nginx-9b596c8c4   1         1         1       12m
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get services
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;   AGE
kubernetes   ClusterIP   10.152.183.1   &amp;lt;none&amp;gt;        443/TCP   47h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, you can optionally increase the number of your replicas (and again verify that it worked):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl scale deployment my-nginx &lt;span class="nt"&gt;--replicas&lt;/span&gt; 3
deployment.apps/my-nginx scaled
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/my-nginx-9b596c8c4-4jp7d   1/1     Running   0          7s
pod/my-nginx-9b596c8c4-7ql2q   1/1     Running   0          7s
pod/my-nginx-9b596c8c4-ngd2q   1/1     Running   0          14m

NAME                 TYPE        CLUSTER-IP     EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;   AGE
service/kubernetes   ClusterIP   10.152.183.1   &amp;lt;none&amp;gt;        443/TCP   47h

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   3/3     3            3           14m

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/my-nginx-9b596c8c4   3         3         3       14m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you need to expose a so-called NodePort so that the &lt;code&gt;nginx&lt;/code&gt; deployment can be accessed from anywhere within your home network using the IP address of your Raspberry Pi. Issue the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl expose deployment my-nginx &lt;span class="nt"&gt;--port&lt;/span&gt; 80 &lt;span class="nt"&gt;--name&lt;/span&gt; my-nginx-np &lt;span class="nt"&gt;--type&lt;/span&gt; NodePort
service/my-nginx-np exposed
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get services
NAME          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
kubernetes    ClusterIP   10.152.183.1    &amp;lt;none&amp;gt;        443/TCP        47h
my-nginx-np   NodePort    10.152.183.73   &amp;lt;none&amp;gt;        80:30178/TCP   13s
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get all
NAME                           READY   STATUS    RESTARTS   AGE
pod/my-nginx-9b596c8c4-4jp7d   1/1     Running   0          3m34s
pod/my-nginx-9b596c8c4-7ql2q   1/1     Running   0          3m34s
pod/my-nginx-9b596c8c4-ngd2q   1/1     Running   0          17m

NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT&lt;span class="o"&gt;(&lt;/span&gt;S&lt;span class="o"&gt;)&lt;/span&gt;        AGE
service/kubernetes    ClusterIP   10.152.183.1    &amp;lt;none&amp;gt;        443/TCP        47h
service/my-nginx-np   NodePort    10.152.183.73   &amp;lt;none&amp;gt;        80:30178/TCP   20s

NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-nginx   3/3     3            3           17m

NAME                                 DESIRED   CURRENT   READY   AGE
replicaset.apps/my-nginx-9b596c8c4   3         3         3       17m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's verify that &lt;code&gt;nginx&lt;/code&gt; is working and can be accessed using the host IP address, from outside of the Raspberry Pi. To do this, exit your &lt;code&gt;ssh&lt;/code&gt; session and from your host machine, do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;curl &amp;lt;IP address of your Raspberry Pi&amp;gt;:30178
&amp;lt;&lt;span class="o"&gt;!&lt;/span&gt;DOCTYPE html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;&lt;span class="nb"&gt;head&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&amp;lt;title&amp;gt;Welcome to nginx!&amp;lt;/title&amp;gt;
&amp;lt;style&amp;gt;
    body &lt;span class="o"&gt;{&lt;/span&gt;
        width: 35em&lt;span class="p"&gt;;&lt;/span&gt;
        margin: 0 auto&lt;span class="p"&gt;;&lt;/span&gt;
        font-family: Tahoma, Verdana, Arial, sans-serif&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&amp;lt;/style&amp;gt;
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;h1&amp;gt;Welcome to nginx!&amp;lt;/h1&amp;gt;
&amp;lt;p&amp;gt;If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;For online documentation and support please refer to
&amp;lt;a &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://nginx.org/"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;nginx.org&amp;lt;/a&amp;gt;.&amp;lt;br/&amp;gt;
Commercial support is available at
&amp;lt;a &lt;span class="nv"&gt;href&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://nginx.com/"&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;nginx.com&amp;lt;/a&amp;gt;.&amp;lt;/p&amp;gt;

&amp;lt;p&amp;gt;&amp;lt;em&amp;gt;Thank you &lt;span class="k"&gt;for &lt;/span&gt;using nginx.&amp;lt;/em&amp;gt;&amp;lt;/p&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Congratulations!! You now have a working &lt;code&gt;microk8s&lt;/code&gt; deployment and the &lt;code&gt;nginx&lt;/code&gt; application up and running on Kubernetes.&lt;/p&gt;

&lt;h1&gt;
  
  
  Further References
&lt;/h1&gt;

&lt;p&gt;You can also check out these references:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ubuntu.com/tutorials/install-a-local-kubernetes-with-microk8s#2-deploying-microk8s" rel="noopener noreferrer"&gt;https://ubuntu.com/tutorials/install-a-local-kubernetes-with-microk8s#2-deploying-microk8s&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s/" rel="noopener noreferrer"&gt;https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://microk8s.io/docs/" rel="noopener noreferrer"&gt;https://microk8s.io/docs/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>raspberrypi</category>
      <category>microk8s</category>
    </item>
    <item>
      <title>Growing Your Applications: Adding Nodes to a microk8s Kubernetes Cluster</title>
      <dc:creator>Paul Duplys</dc:creator>
      <pubDate>Sat, 20 Jun 2020 21:01:10 +0000</pubDate>
      <link>https://dev.to/duplys/growing-your-applications-adding-nodes-to-a-microk8s-kubernetes-cluster-20jl</link>
      <guid>https://dev.to/duplys/growing-your-applications-adding-nodes-to-a-microk8s-kubernetes-cluster-20jl</guid>
      <description>&lt;p&gt;With &lt;code&gt;microk8s&lt;/code&gt; up and running, the next step is to add new nodes to the Kubernetes cluster. Adding nodes not only increases the cluster's computing resources, but also adds &lt;em&gt;redundancy&lt;/em&gt;. Redundancy, in turn, increases the availability and reliability of the cluster because the Kubernetes Pods, e.g., the replicas of the same service, can be distributed over multiple hardware devices. Multiple hardware instances mean that failures can be tolerated. That's exactly the reason why the Kubernetes scheduler tries to ensure that Pods from the same application are distributed onto different machines.&lt;/p&gt;

&lt;p&gt;This post is a step-by-step guide for adding new nodes to a &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster running on Raspberry Pi (RPi) microcomputers. In particular, we will discuss how to add RPi nodes running the Ubuntu 20.04 release. As you will see, Ubuntu 20.04 nodes require a little manual tweaking related to &lt;code&gt;cgroups&lt;/code&gt; before you can actually use them in an &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Note: In the remainder of this post, all shell commands are executed on the RPi minicomputers. For me, the most convenient way to manage these microcomputers is to &lt;code&gt;ssh&lt;/code&gt; into them from my development laptop. In case you'd like to know how to enable &lt;code&gt;ssh&lt;/code&gt;, &lt;a href="https://www.raspberrypi.org/documentation/remote-access/ssh/" rel="noopener noreferrer"&gt;the official Raspberry Pi documentation on remote access via &lt;code&gt;ssh&lt;/code&gt;&lt;/a&gt; is a good place to start.&lt;/p&gt;

&lt;p&gt;Luckily for us, Kubernetes makes adding new nodes really easy. In case of &lt;code&gt;microk8s&lt;/code&gt;, the shell command &lt;code&gt;microk8s add-node&lt;/code&gt; generates a connection string and outputs a list of suggested &lt;code&gt;microk8s join&lt;/code&gt; commands for adding new nodes to the current &lt;code&gt;microk8s&lt;/code&gt; cluster. The cluster node on which this command is executed becomes the main node of the Kubernetes cluster and will host its control plane.&lt;/p&gt;

&lt;p&gt;So, to get started, connect to the main Raspberry Pi|s (i.e., the RPi you have designated to become the main node of your Kubernetes cluster) and issue:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s add-node
Join node with: microk8s &lt;span class="nb"&gt;join &lt;/span&gt;192.168.178.66:25000/ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk

If the node you are adding is not reachable through the default interface you can use one of the following:
 microk8s &lt;span class="nb"&gt;join &lt;/span&gt;192.168.178.66:25000/ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk
 microk8s &lt;span class="nb"&gt;join &lt;/span&gt;10.1.79.0:25000/ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk
 microk8s &lt;span class="nb"&gt;join &lt;/span&gt;172.17.0.1:25000/ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the above output of the &lt;code&gt;microk8s add-node&lt;/code&gt; command you see the string &lt;code&gt;ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk&lt;/code&gt; that is required for a new node to join the Kubernetes cluster. The output also gives multiple suggestions for the parameters of the &lt;code&gt;microk8s join&lt;/code&gt; command, which is great if you should have any issues with your setup. (Note: the value of your token will be different since Kubernetes generates it individually for every cluster).&lt;/p&gt;

&lt;p&gt;Connect to the RPi minicomputer that you want to add to the &lt;code&gt;microk8s&lt;/code&gt; cluster. The &lt;code&gt;microk8s&lt;/code&gt; software must be installed and configured on this RPi before you proceed. If that's not the case, check the &lt;a href="http://duplys.github.io/linux/rpi/raspberry-pi/microk8s/nginx/kubernetes/2020/04/21/installing-edge-on-raspberry-pi-4.blog.html" rel="noopener noreferrer"&gt;"Installing microk8s and nginx on Raspberry Pi 4" post&lt;/a&gt; for a step-by-step installation guide.&lt;/p&gt;

&lt;p&gt;If you installed the operating system on your Raspberry Pi from a standard Linux image (in my setup, I use the Ubuntu 20.04 image for my Raspberry Pi), you might want to change the hostname of your second Raspberry Pi device to something unique (otherwise, you will see multiple Kubernetes nodes all having the same name &lt;code&gt;ubuntu&lt;/code&gt; later on in your Kubernetes dashboard/command line). Under Linux, you can change the hostname like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;edit &lt;code&gt;/etc/hostname&lt;/code&gt; and replace the current hostname with the new hostname&lt;/li&gt;
&lt;li&gt;edit &lt;code&gt;/etc/hosts&lt;/code&gt; and replace every occurrence of the current hostname with the new hostname&lt;/li&gt;
&lt;li&gt;reboot the system by issuing &lt;code&gt;sudo reboot&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In my case, the hostname of the second RPi is &lt;code&gt;ubuntu-2&lt;/code&gt;. To add the second RPi to the &lt;code&gt;microk8s&lt;/code&gt; cluster, I need to &lt;code&gt;ssh&lt;/code&gt; into &lt;code&gt;ubuntu-2&lt;/code&gt; and issue the command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s &lt;span class="nb"&gt;join &lt;/span&gt;192.168.178.66:25000/ipySurdsIOGAlYyYQzzOOSlpUTEDNGfk
ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can verify that the new node has been successfully added to your Kubernetes cluster by logging into the Raspberry Pi running the main cluster node (in my setup, the RPi with the hostname &lt;code&gt;ubuntu&lt;/code&gt;) and issuing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get nodes
NAME       STATUS     ROLES    AGE   VERSION
ubuntu     Ready      &amp;lt;none&amp;gt;   59d   v1.18.3
ubuntu-2   NotReady   &amp;lt;none&amp;gt;   98s   v1.18.3
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see from the output above, we now have two nodes - &lt;code&gt;ubuntu&lt;/code&gt; and &lt;code&gt;ubuntu-2&lt;/code&gt; - in our &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster. However, we have an issue that needs to be fix: The newly added node is in the state &lt;code&gt;NotReady&lt;/code&gt;. It turns out, this is not due to a timing issue - even after more than an hour the node remains in this state. &lt;/p&gt;

&lt;p&gt;After searching a bit, I stumbled upon the &lt;a href="https://github.com/ubuntu/microk8s/issues/728" rel="noopener noreferrer"&gt;Konstantinos Tsakalozos comment&lt;/a&gt; on this issue in the &lt;code&gt;microk8s&lt;/code&gt; GitHub project.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;K8s was complaining that the cgroup memory was not enabled. Looking at /proc/cgroups was showing memeory disabled. Changes in config.txt are not enough, what actually worked is editing /boot/firmware/nobtcmd.txt, appending cgroup_enable=memory cgroup_memory=1. and rebooting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Inspecting the &lt;code&gt;/proc/cgroups&lt;/code&gt; file on my second RPi (the one I want to add to the &lt;code&gt;microk8s&lt;/code&gt; cluster) confirmed this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /proc/cgroups 
&lt;span class="c"&gt;#subsys_name    hierarchy   num_cgroups enabled&lt;/span&gt;
cpuset  8   1   1
cpu 7   1   1
cpuacct 7   1   1
blkio   3   1   1
memory  0   56  0
devices 5   47  1
freezer 6   2   1
net_cls 10  1   1
perf_event  9   1   1
net_prio    10  1   1
pids    2   52  1
rdma    4   1   1
ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt;
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 20.04 LTS
Release:    20.04
Codename:   focal
ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
Linux ubuntu-2 5.4.0-1012-raspi &lt;span class="c"&gt;#12-Ubuntu SMP Wed May 27 04:08:35 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://man7.org/linux/man-pages/man7/cgroups.7.html" rel="noopener noreferrer"&gt;&lt;code&gt;cgroups&lt;/code&gt; man page&lt;/a&gt; explains that Linux control groups, usually referred to as &lt;code&gt;cgroups&lt;/code&gt;, are a Linux kernel feature which allow processes to be organized into hierarchical groups whose resource usage can then be limited and monitored. In a nutshell, as &lt;a href="https://downey.io/blog/exploring-cgroups-raspberry-pi/" rel="noopener noreferrer"&gt;Tim Downey puts it in his post&lt;/a&gt;, &lt;code&gt;cgroups&lt;/code&gt; allow you to do things like limit the amount of CPU usage of a process, limit the amount of memory that a process can consume, control how many additional processes a process can fork, and even “freeze” a process in place. In other words, &lt;code&gt;cgroups&lt;/code&gt; are a &lt;a href="https://wiki.archlinux.org/index.php/cgroups" rel="noopener noreferrer"&gt;feature provided by the Linux kernel to manage, restrict, and audit groups of processes&lt;/a&gt;. Compared to other approaches like the &lt;code&gt;nice&lt;/code&gt; command or &lt;code&gt;/etc/security/limits.conf&lt;/code&gt;, &lt;code&gt;cgroups&lt;/code&gt; are more flexible as they can operate on (sub)sets of processes (possibly with different system users). &lt;code&gt;cgroups&lt;/code&gt; provide a mechanism for aggregating/partitioning sets of tasks, and all their future children, into hierarchical groups with specialized behavior. A container orchestrator like Kubernetes uses &lt;code&gt;cgroups&lt;/code&gt; to place resource limits on the containers it creates. In case you want to learn more about &lt;code&gt;cgroups&lt;/code&gt;, check out &lt;a href="https://www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt" rel="noopener noreferrer"&gt;this documentation on kernel.org&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Coming back to our issue, in contrast to the RPi running Ubuntu 20.04, the &lt;code&gt;cgroups&lt;/code&gt; settings on my main RPi (the main Kubernetes node) look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo cat&lt;/span&gt; /proc/cgroups 
&lt;span class="c"&gt;#subsys_name    hierarchy   num_cgroups enabled&lt;/span&gt;
cpuset  9   33  1
cpu 4   111 1
cpuacct 4   111 1
blkio   2   111 1
memory  3   125 1
devices 7   111 1
freezer 11  34  1
net_cls 6   33  1
perf_event  8   33  1
net_prio    6   33  1
pids    5   117 1
rdma    10  1   1
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;uname&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
Linux ubuntu 5.3.0-1017-raspi2 &lt;span class="c"&gt;#19~18.04.1-Ubuntu SMP Fri Jan 17 11:14:07 UTC 2020 aarch64 aarch64 aarch64 GNU/Linux&lt;/span&gt;
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;lsb_release &lt;span class="nt"&gt;-a&lt;/span&gt;
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 18.04.4 LTS
Release:    18.04
Codename:   bionic
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thus, as &lt;a href="https://askubuntu.com/questions/1189480/raspberry-pi-4-ubuntu-19-10-cannot-enable-cgroup-memory-at-boostrap" rel="noopener noreferrer"&gt;described in this post&lt;/a&gt;, you have to edit the &lt;code&gt;/boot/firmware/cmdline.txt&lt;/code&gt; file to look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;net.ifnames&lt;span class="o"&gt;=&lt;/span&gt;0 dwc_otg.lpm_enable&lt;span class="o"&gt;=&lt;/span&gt;0 &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;serial0,115200 &lt;span class="nv"&gt;cgroup_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cpuset &lt;span class="nv"&gt;cgroup_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;memory &lt;span class="nv"&gt;cgroup_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1 &lt;span class="nv"&gt;console&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;tty1 &lt;span class="nv"&gt;root&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;LABEL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;writable &lt;span class="nv"&gt;rootfstype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ext4 &lt;span class="nv"&gt;elevator&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;deadline rootwait fixrtc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;by adding the &lt;code&gt;cgroup&lt;/code&gt; options:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;cgroup_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;cpuset &lt;span class="nv"&gt;cgroup_enable&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;memory &lt;span class="nv"&gt;cgroup_memory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, you need to reboot the RPi for the changes to take effect. Probably the simplest way to do this is by issuing the following command :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;reboot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the reboot is complete and you have logged in into the second RPi, you can check the &lt;code&gt;cgroups&lt;/code&gt; settings and should see the &lt;code&gt;memory&lt;/code&gt; cgroup enabled:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; /proc/cgroups 
&lt;span class="c"&gt;#subsys_name    hierarchy   num_cgroups enabled&lt;/span&gt;
cpuset  11  6   1
cpu 4   6   1
cpuacct 4   6   1
blkio   3   6   1
memory  9   101 1
devices 5   52  1
freezer 6   7   1
net_cls 7   6   1
perf_event  2   6   1
net_prio    7   6   1
pids    8   57  1
rdma    10  1   1
ubuntu@ubuntu-2:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As a result, if you now &lt;code&gt;ssh&lt;/code&gt; into the Raspberry Pi running the main &lt;code&gt;microk8s&lt;/code&gt; cluster node and check the node status in the cluster, you should see something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;microk8s.kubectl get nodes
NAME       STATUS   ROLES    AGE     VERSION
ubuntu     Ready    &amp;lt;none&amp;gt;   59d     v1.18.3
ubuntu-2   Ready    &amp;lt;none&amp;gt;   4h12m   v1.18.3
ubuntu@ubuntu:~&lt;span class="nv"&gt;$ &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Voilà - as you can see, our &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster is now composed of two nodes, i.e., two RPi minicomputers. Both Kubernetes nodes are now in the &lt;code&gt;Ready&lt;/code&gt; state. You can verify this in the Kubernetes dashboard (in case you haven't setup the dashboard yet, take a look &lt;a href="http://duplys.github.io/kubernetes/microk8s/dashboard/raspberry-pi-cluster/2020/06/04/experimenting-with-microk8s-dashboard.blog.html" rel="noopener noreferrer"&gt;at this post&lt;/a&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk492bb1xr928oovm037t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fk492bb1xr928oovm037t.png" alt="kubernetes-dashboard-two-nodes" width="800" height="165"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To recap, the three steps needed to add a RPi running Ubuntu 20.04 to your &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster look like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;connect to the RPi you want to add to your &lt;code&gt;microk8s&lt;/code&gt; Kubernetes cluster and edit the &lt;code&gt;/boot/firmware/cmdline.txt&lt;/code&gt; file by adding &lt;code&gt;cgroup_enable=cpuset cgroup_enable=memory cgroup_memory=1&lt;/code&gt; to enable &lt;code&gt;cgroup memory&lt;/code&gt;. Restart that RPi (e.g., by issuing &lt;code&gt;sudo reboot&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;generate the connection token on your main &lt;code&gt;microk8s&lt;/code&gt; node using the &lt;code&gt;microk8s add-node&lt;/code&gt; command&lt;/li&gt;
&lt;li&gt;add the second RPi to the &lt;code&gt;microk8s&lt;/code&gt; cluster using the &lt;code&gt;microk8s join&lt;/code&gt; command with the token obtained in step 2&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffa1t1583p4klo0d2o8p6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Ffa1t1583p4klo0d2o8p6.png" alt="thats-all-folks" width="800" height="603"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
