<?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: Marcel Kirschner</title>
    <description>The latest articles on DEV Community by Marcel Kirschner (@marcelki).</description>
    <link>https://dev.to/marcelki</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%2F2975809%2Fad1b3917-1899-4044-92aa-a2f8002a4010.png</url>
      <title>DEV Community: Marcel Kirschner</title>
      <link>https://dev.to/marcelki</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/marcelki"/>
    <language>en</language>
    <item>
      <title>Designing a Configuration Management System</title>
      <dc:creator>Marcel Kirschner</dc:creator>
      <pubDate>Thu, 27 Mar 2025 12:45:31 +0000</pubDate>
      <link>https://dev.to/marcelki/designing-a-configuration-management-system-3g2k</link>
      <guid>https://dev.to/marcelki/designing-a-configuration-management-system-3g2k</guid>
      <description>&lt;h2&gt;
  
  
  Starting small
&lt;/h2&gt;

&lt;p&gt;I want to build this system from the bottom up.&lt;/p&gt;

&lt;p&gt;In the past, I’ve fallen into the trap of trying to design for every possible use case from day one, aiming for the perfect architecture and eventually getting stuck before real progress is made.&lt;/p&gt;

&lt;p&gt;Sticking to the fundamentals. Move upward step by step. Stay pragmatic and let the system grow based on real needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Identifying Core Challenges
&lt;/h2&gt;

&lt;p&gt;The overall goals of system configuration management are well-known and well-documented. I’m not going to repeat what you can already find online, instead I’ll focus on a few specific areas that I care about and want to emphasize.&lt;/p&gt;

&lt;p&gt;Take something as simple as configuring NGINX on a linux server.&lt;/p&gt;

&lt;p&gt;We’ve probably all been in a situation where we pushed broken configuration to a server. Maybe it was a syntax error caught by &lt;code&gt;nginx -t.&lt;/code&gt; But sometimes, the syntax is perfect, yet the configuration is logically flawed, perhaps pointing to a non-existent backend, causing errors &lt;em&gt;after&lt;/em&gt; the service restarts. These functional issues are harder to catch upfront.&lt;/p&gt;

&lt;p&gt;If the system is set up properly, your configuration management tool should ideally handle both types of failures gracefully. Let’s look at how we might build robust handling for this in Ansible, including a basic health check after the restart:&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="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;Deploy, validate and health-check nginx configuration&lt;/span&gt;
  &lt;span class="na"&gt;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webservers&lt;/span&gt;
  &lt;span class="na"&gt;vars&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;nginx_conf_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/nginx.conf&lt;/span&gt;
    &lt;span class="na"&gt;nginx_conf_backup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/etc/nginx/nginx.conf.bak&lt;/span&gt;
    &lt;span class="na"&gt;new_nginx_template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;templates/nginx.conf.j2&lt;/span&gt;
    &lt;span class="na"&gt;nginx_health_check_url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://localhost/&lt;/span&gt;
  &lt;span class="na"&gt;tasks&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;Backup current nginx config&lt;/span&gt;
      &lt;span class="na"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_conf_path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_conf_backup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
        &lt;span class="na"&gt;remote_src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
        &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;preserve&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;block&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;Deploy new nginx config&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;new_nginx_template&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_conf_path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&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;Validate nginx configuration syntax BEFORE restart&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx -t&lt;/span&gt;
          &lt;span class="na"&gt;changed_when&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&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;Restart nginx to apply configuration&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.service&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;nginx&lt;/span&gt;
            &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&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;Wait briefly for nginx to stabilise&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.pause&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&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;Perform health check AFTER restart&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.uri&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_health_check_url&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;status_code&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt;
          &lt;span class="na"&gt;register&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;health_check_result&lt;/span&gt;
      &lt;span class="na"&gt;rescue&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;Roll back nginx config due to failure&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.copy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_conf_backup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;dest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;nginx_conf_path&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}"&lt;/span&gt;
            &lt;span class="na"&gt;remote_src&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;yes&lt;/span&gt;
            &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;preserve&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;Restart nginx after rollback&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.service&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;nginx&lt;/span&gt;
            &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;restarted&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;Fail the playbook after rollback&lt;/span&gt;
          &lt;span class="na"&gt;ansible.builtin.fail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;msg&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Nginx&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deployment&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;failed&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;(syntax&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;or&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;health&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;check)&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;and&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;rollback&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;was&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;triggered."&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While technically valid, it’s a lot of boilerplate for a fairly basic operation.&lt;/p&gt;

&lt;p&gt;How can we tackle those essential challenges? Do we even need to express them in the code and if so, how do we do it without blowing up the code and adding complexity?&lt;/p&gt;

&lt;p&gt;Code for declaring resources has to be written. So we need to think about what kind of format makes sense, something like YAML, JSON or maybe a DSL?&lt;/p&gt;

&lt;p&gt;Each option has its trade-offs.&lt;/p&gt;

&lt;p&gt;YAML is easy to write, but can get messy. JSON is stricter, but not great for humans. A DSL gives you more power and flexibility, but isn’t as user-friendly for non-programmers.&lt;/p&gt;

&lt;p&gt;No matter which format we choose, humans make mistakes. Typos, wrong types missing fields. We need to assist the user and help with the validation of the code.&lt;/p&gt;

&lt;p&gt;If we can declare the desired state of resources, we usually don’t want to just hand it off and hope for the best. Before anything runs, we want to understand what’s about to happen.&lt;/p&gt;

&lt;p&gt;What’s going to change? What stays the same? Are there any surprises?&lt;/p&gt;

&lt;p&gt;Being able to plan the resource, see a diff between the current state and the desired one, gives the user a chance to stay in control.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Pragmatic Architecture
&lt;/h2&gt;

&lt;p&gt;To build this system pragmatically, let's think about a client-server architecture. But instead of a monolithic server doing everything, we'll split the responsibilities.&lt;/p&gt;

&lt;p&gt;The client will be the &lt;em&gt;brain&lt;/em&gt;, responsible for the orchestration of the overall process, deciding what resources to manage and in what order.&lt;/p&gt;

&lt;p&gt;The server will be the &lt;em&gt;hands&lt;/em&gt;, performing specific actions on resources via a simple REST API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Server
&lt;/h3&gt;

&lt;p&gt;The server exposes a granular, idempotent API for managing single resources. It doesn't need to understand the bigger picture, just how to handle its specific tasks reliably.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Responsibilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Exposing a REST API focused on CRUD-like idempotent operations for individual resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;GET: Retrieve the current state and properties of a resource&lt;/li&gt;
&lt;li&gt;PUT: Create or update properties for a resource&lt;/li&gt;
&lt;li&gt;DELETE: Delete an existing resource&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;&lt;p&gt;Validating the schema of the incoming resource definition&lt;/p&gt;&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client
&lt;/h3&gt;

&lt;p&gt;The client is responsible for the end-to-end configuration process, acting as the orchestrator.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key Responsibilities&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Provide the user interface&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Load, parse, and validate the overall declarative configuration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Understand dependencies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Determine the execution plan&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Execute the plan&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Features
&lt;/h2&gt;

&lt;p&gt;This architecture gives me a solid foundation to build on. One that's simple, flexible and designed to grow with the project.&lt;/p&gt;

&lt;p&gt;Looking ahead, there are a few features I’m especially interested in exploring:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Execution planning&lt;/strong&gt; – visualize and control what happens before it happens&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clear diffs&lt;/strong&gt; – make changes visible and traceable before they’re applied&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Safe failure handling&lt;/strong&gt; – support recovery and rollback without complex boilerplate&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Backup functionality&lt;/strong&gt; – local and remote backups built into the workflow&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Graph-based orchestration&lt;/strong&gt; – use a dependency graph with topological sorting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Parallel execution&lt;/strong&gt; – speed things up by running independent tasks in parallel&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Hybrid configuration&lt;/strong&gt; – support both human-friendly formats and a programmable layer for more advanced use cases&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strong security support&lt;/strong&gt; – mTLS, certificate-based auth&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s the plan for now. We’ll see where it goes.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Starting the Journey</title>
      <dc:creator>Marcel Kirschner</dc:creator>
      <pubDate>Tue, 25 Mar 2025 17:38:01 +0000</pubDate>
      <link>https://dev.to/marcelki/memories-2fh5</link>
      <guid>https://dev.to/marcelki/memories-2fh5</guid>
      <description>&lt;h1&gt;
  
  
  Memories
&lt;/h1&gt;

&lt;p&gt;I still remember the first time I used Ansible. It felt like magic. Writing a few lines of YAML and watching systems fall into place gave me a sense of efficiency I had never experienced before. It was like getting a superpower, suddenly I could automate tasks across dozens of servers, enforce consistency and reduce human errors.&lt;/p&gt;

&lt;p&gt;It was one of those moments in tech where something just clicks. That experience stuck with me. It shaped the way I think about managing infrastructure. Since then, I’ve worked with a range of tools. Each one brought something unique to the table. I learned something from all of them.&lt;/p&gt;

&lt;p&gt;But after a while, patterns emerged. Certain things kept repeating - workarounds, orchestration logic or glue code. I started to feel like I was bending the tools to fit how I wanted to work, instead of the other way around.&lt;/p&gt;

&lt;p&gt;That’s when the idea of building my own tool started to form. Not because the existing ones were bad, far from it. But because I wanted something that brought together the pieces I valued most.&lt;/p&gt;

&lt;p&gt;This post is the first in a series where I’ll talk a bit about what I’m building and why. Just sharing my thoughts and the ideas behind it.&lt;/p&gt;

&lt;p&gt;More soon.&lt;/p&gt;

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