<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Lattice]]></title><description><![CDATA[Real-world infrastructure engineering — Kubernetes, SRE, multi-cloud architecture, and AI-assisted development, written by a practitioner with 10+ years in the industry.]]></description><link>https://www.ltce.dev</link><image><url>https://substackcdn.com/image/fetch/$s_!F2lT!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa02beb84-cbe4-4942-8e5a-7db2967c6b60_1080x1080.png</url><title>Lattice</title><link>https://www.ltce.dev</link></image><generator>Substack</generator><lastBuildDate>Fri, 08 May 2026 11:22:18 GMT</lastBuildDate><atom:link href="https://www.ltce.dev/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Ian Miller]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[ltce@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[ltce@substack.com]]></itunes:email><itunes:name><![CDATA[Ian Miller]]></itunes:name></itunes:owner><itunes:author><![CDATA[Ian Miller]]></itunes:author><googleplay:owner><![CDATA[ltce@substack.com]]></googleplay:owner><googleplay:email><![CDATA[ltce@substack.com]]></googleplay:email><googleplay:author><![CDATA[Ian Miller]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Shifting Left]]></title><description><![CDATA[How micro-services, Kubernetes, SRE, and AI-assisted development have reshaped the software development lifecycle over the last 12 years.]]></description><link>https://www.ltce.dev/p/shifting-left</link><guid isPermaLink="false">https://www.ltce.dev/p/shifting-left</guid><dc:creator><![CDATA[Ian Miller]]></dc:creator><pubDate>Mon, 30 Mar 2026 18:32:06 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7cVU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Software engineering has undergone a <strong>massive </strong>transformation since I started my career in 2013. When I first started, companies were shifting from on-premise to cloud-based software, and rearchitected their software to take advantage of the new cloud paradigm. These days, I&#8217;m witnessing the rise of a new industry-shifting technology - Artificial Intelligence (AI). It&#8217;s safe to say that a lot has changed in the last 12 years. I&#8217;m taking stock of the major trends that I&#8217;ve witnessed so far during my career, and sharing my thoughts in today&#8217;s post.</p><p>There are 4 trends that have contributed to the transformation in the enterprise software industry, and have impacted how engineering organizations approach the software development life cycle (SDLC). From monoliths to micro-services, Kubernetes, and the rise of Site Reliability Engineering (SRE) to AI-assisted development, these trends have set the stage for shifting critical functions &#8212; and the ownership of those functions &#8212; to the left.</p><p>Before I dive in, I&#8217;d like to share my background. I started my software career in 2013 as a backend engineer after completing a bootcamp at Flatiron School. I spent several years at Percolate, where I witnessed the rise of micro-services and Kubernetes firsthand, and followed the team through its acquisition by Seismic in 2019. Today, I&#8217;m part of Seismic&#8217;s SRE team, where one of my responsibilities has been evaluating AI-assisted development tools and rolling them out across the engineering organization.</p><h2>Monoliths to Micro-services</h2><p>The shift from monoliths to micro-services is largely a by-product of the rise of cloud computing. Monolithic architectures provide distinct advantages over micro-services if the following bullet points describe your organization:</p><ul><li><p>Small engineering team / engineering organizations</p></li><li><p>Simple applications without complex logic / no need for breaking out specialized domains to meet scaling needs</p></li><li><p>Focus on product-market fit over revenue generation</p></li></ul><p>I&#8217;ve had a hand in several monolith-to-micro-service transition projects. While there were different reasons used to justify each of the projects, scaling was a common thread. <em><strong>Performance scaling</strong></em> is a major driver, driven by the rise of cloud computing and a corresponding decrease in compute costs. This driver allows engineering teams to have full control of horizontal and vertical scaling isolated to a specific domain.</p><p>For example, I worked on a Django monolith application, and during the course of trying to achieve product-market fit (PMF), the domain logic for managing content had become bloated. This meant that were no clearly defined boundaries between the content domain and other domains in the monolith application, and resulted in performance implications that ultimately impacted customers. When a monolith starts to encounter scaling challenges and low-cost optimization solutions have been exhausted, spinning out the problematic domain with scaling challenges into a micro-service becomes a realistic option.</p><p><em><strong>Organizational scaling</strong></em> is another driver. As an organization starts to scale beyond an extent where there is more collision of development changes in a monolith application code base, the organization typically will break up the engineering group into multiple teams where each team oversees a particular domain. This allows for teams to have more autonomy and oversight of their domain from a code management perspective.</p><p>The diagram below illustrates the main differences between monoliths and micro-services, and how they ultimately shape an engineering organization. The bottom half  illustrates how this architectural shift mirrors organizational structure &#8212; a startup with 5-20 people sharing a monolithic codebase versus an enterprise with dedicated teams owning their own domains.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7cVU!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7cVU!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7cVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png" width="518" height="518" data-attrs="{&quot;src&quot;:&quot;https://substackcdn.com/image/fetch/$s_!7cVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:518,&quot;width&quot;:518,&quot;resizeWidth&quot;:518,&quot;bytes&quot;:7353,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!7cVU!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 424w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 848w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 1272w, https://substackcdn.com/image/fetch/$s_!7cVU!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F115469c0-f13a-493a-aa76-970f1a6033fa_800x800.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Monolith vs Micro-service Architecture and Organizational Structure</figcaption></figure></div><div><hr></div><p>The diagram informs us of several key insights into organizational structure, and how they manifest downstream.</p><p>Monoliths have characteristics that are more favorable for startups:</p><ul><li><p>Tight coupling of business logic, data access layer, and database(s)</p></li><li><p>Engineering teams &#8220;share&#8221; the monolith application domains</p></li><li><p>Supporting infrastructure (CICD, observability, etc) coalesced around monolith</p></li><li><p>Fast feature development that requires less cross-functional coordination</p></li></ul><p>Enterprises, on the other hand, benefit from micro-services:</p><ul><li><p>Loose coupling of business logic, data access layer, and database(s)</p></li><li><p>Engineering teams oversee their own application domains</p></li><li><p>Supporting infrastructure has to scale to support increasing complexity of services</p></li><li><p>Slower feature development that requires cross-functional coordination</p></li></ul><p>Note that these are just a few of the many differences between monoliths and micro-services. An organization&#8217;s structure ultimately informs the path that the organization will take in the monolith-to-micro-service journey, and that has absolutely been true in my case. As organizations scale their teams and transition their services from monoliths to micro-services, critical functions such as service compute scaling, unit testing, and optimization of business logic within a domain get shifted left to a team that directly oversees that domain.</p><h2>Kubernetes</h2><p>Kubernetes (K8s) is the technology trend that has had the most significant impact on my career over the last 12 years. There was a Cloud Native Computing Foundation (CNCF) survey that said &#8220;96% of organizations [surveyed] are either using or evaluating Kubernetes&#8221;<em>. </em>The survey data is broken down below:</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!hrPq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!hrPq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 424w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 848w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 1272w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!hrPq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png" width="1456" height="515" data-attrs="{&quot;src&quot;:&quot;https://substackcdn.com/image/fetch/$s_!hrPq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:515,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:175840,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ltce.dev/i/154827382?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!hrPq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 424w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 848w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 1272w, https://substackcdn.com/image/fetch/$s_!hrPq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2fe66ce6-259b-44d0-8a07-c4fd9222ff15_2808x994.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">CNCF Annual Survey 2021</figcaption></figure></div><div><hr></div><p>K8s introduced an orchestration layer that provided engineering organizations with 3 key benefits:</p><ol><li><p>Resource management</p></li><li><p>Operational efficiency</p></li><li><p>Engineering productivity</p></li></ol><p>K8s&#8217; killer feature is a &#8220;scheduling&#8221; mechanism that promoted more efficient use of compute resources. The feature allows an engineering team to coordinate the deployment of services in such a way that the team&#8217;s services only utilize what they need by specifying the resources that they need at startup (&#8220;requests&#8221;) and the maximum that they can scale up to (&#8220;limits&#8221;).  By configuring K8s features such as <em>HorizontalPodAutoscaler </em>resources or <em>PodDisruptionBudget </em>resources<em>, </em>engineering teams have a lot of capabilities they can leverage for <em><strong>resource management</strong></em>. In short, K8s makes up for its steep learning curve by providing a lot of features that are necessary for managing compute infrastructure in a cost-effective manner.</p><p><em><strong>Operational efficiency</strong></em> has improved for engineering teams due to the leap in <em><strong>resource management</strong></em> capabilities that K8s introduced. The most significant impact has been in the resource optimization capabilities made possible by the K8s <a href="https://kubernetes.io/docs/tasks/debug/debug-cluster/resource-metrics-pipeline/">Metrics API</a>. The Metrics API provides an endpoint for CPU and memory resource usage at both the pod level and the node level. This makes it quite powerful for teams to understand how services are utilizing resources, frees up teams from building out bespoke monitoring solutions, and allows teams to execute on strategic priorities such as cost optimization and efficient resource utilization.</p><p>That visibility also enables automation. K8s makes it possible to leverage the Metrics API and dynamically adjust resource allocations through scaling operations. For instance, if a service is experiencing 80% average CPU utilization across all of its pods, the HorizontalPodAutoscaler can automatically provision new pods to scale up the service and meet additional resource needs. This combination of observability and automated response is what makes K8s&#8217; operational efficiency gains so durable &#8212; teams aren&#8217;t just seeing problems faster, they&#8217;re resolving them without manual intervention.</p><p><em><strong>Engineering productivity </strong></em>has also skyrocketed for engineering teams that leverage K8s largely due to the ecosystem that K8s introduced. From ArgoCD to Harness and other GitOps-related tools for CICD pipelines, K8s has helped provide a platform for shifting what has historically been DevOps functions to the left. With enterprise organizations adopting K8s, there are 3 critical benefits:</p><ul><li><p>ease of integration with CI/CD pipelines</p></li><li><p>faster time-to-market development time for new services</p></li><li><p>ability to provide multi-cloud strategies</p></li></ul><p>Prior to the introduction of K8s, lots of different engineering organizations used a variety of tools or internally developed patterns for provisioning infrastructure and deploying services. From bespoke deployment scripting tools to other virtualization technologies like VMWare, the ecosystem for service orchestration was quite fragmented, and grew even more so as products started to transition from monolith services to micro-services. In my personal experience, K8s helped to shift left the management of resources at the service level from specialized DevOps teams to backend engineering teams.</p><h2>Site Reliability Engineering</h2><p>The Site Reliability Engineering (&#8220;SRE&#8221;) function is where I&#8217;ve also seen a significant amount of change, particularly as engineering organizations shift performance and reliability initiatives away from core engineering teams. This is largely thanks to Google&#8217;s influence on engineering organizations via <a href="https://sre.google/">Google SRE</a>, which took traditional DevOps / operations teams, applied software approaches and best practices, and created the SRE discipline. The intent behind the SRE discipline was to bridge the gap between engineering development and operations teams, and it has since become a critical role within engineering teams. <strong>Full disclosure</strong>: SRE is also where my focus has been lately as part of the SRE team at <a href="https://seismic.com/">Seismic</a>.</p><p>SRE also has seen a profound impact on enterprise engineering organizations. In the DevOps Institute&#8217;s Global SRE Pulse 2022 report, 62% of organizations have adopted SRE in some shape or form. Within those organizations, 19% leveraged SRE across the entire organization, and 55% implemented SRE practices within specific teams, products, or services. Given that SRE is a relatively nascent discipline, this is a significant shift where engineering leaders see integrating SRE practices as a way to maintain competitive advantage.</p><p>At Seismic, the SRE team was built from scratch in response to scaling and reliability pain. As the platform grew &#8212; both organically and through several acquisitions &#8212; the operational burden on engineering teams became noticeable. There was no dedicated function focused on reliability as a discipline, and incidents were handled reactively rather than systematically. Standing up a dedicated SRE team was the answer to a question that had been building for a while: <em>who owns reliability?</em></p><p>The answer, it turns out, is <em>everyone</em> &#8212; but SRE&#8217;s role is to make that ownership practical. At Seismic, we&#8217;ve shifted reliability left in three meaningful ways.</p><p>First, engineering teams now define and own their own Service Level Objectives (SLOs) and Service Level Indicators (SLIs). Rather than SRE dictating what &#8220;reliable&#8221; means for a given service, the team that builds and operates the service sets those targets based on their understanding of customer expectations. Error budgets provide a shared language for balancing reliability investment against feature velocity &#8212; when you&#8217;re burning through your error budget, reliability work takes priority; when you have budget to spare, you ship features with confidence.</p><p>Second, on-call ownership has shifted to the teams closest to the code. This was a cultural shift as much as an operational one. Engineers who build a service are best positioned to diagnose and resolve issues with it, and owning on-call creates a natural feedback loop &#8212; if your service pages you at 2 AM, you&#8217;re motivated to fix the underlying issue rather than apply a band-aid.</p><p>Third, we invested in self-service observability. SRE built out shared platforms and tooling that engineering teams use to instrument, monitor, and alert on their own services. Instead of filing a ticket with an ops team to get a dashboard, engineers have the autonomy to build the visibility they need. This democratization of observability has been one of the highest-leverage investments we&#8217;ve made &#8212; it gives teams the data they need to make informed reliability decisions without creating a bottleneck.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rPMF!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rPMF!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 424w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 848w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 1272w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rPMF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png" width="1156" height="674" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:674,&quot;width&quot;:1156,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:82279,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ltce.dev/i/154827382?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!rPMF!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 424w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 848w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 1272w, https://substackcdn.com/image/fetch/$s_!rPMF!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1005b2a0-38c6-465c-9916-5a5ce14e8d87_1156x674.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Catchpoint SRE Report 2025 &#8212; Observability Gap</figcaption></figure></div><p> &#8212; Observability Gap</p><div><hr></div><p>This investment was far from unique &#8212; according to the Catchpoint SRE Report 2025, over half of organizations still report not having enough observability.</p><p>The common thread across these three shifts is a move from centralized gatekeeping to distributed ownership, enabled by shared platforms and practices. SRE didn&#8217;t shift responsibility onto engineering teams and walk away &#8212; it shifted <em>capability</em> left by building the tooling, frameworks, and culture that make reliability a first-class concern for every team. That&#8217;s a fundamentally different model from traditional operations, and it&#8217;s one that scales with the organization rather than against it.</p><h2>AI-Assisted Development</h2><p>AI-assisted development is the most recent, and arguably the most disruptive, trend I&#8217;ve witnessed. As part of Seismic&#8217;s SRE team, one of my responsibilities has been evaluating AI development tools and identifying use cases where they can meaningfully improve engineering workflows.</p><p>We took a structured approach to the evaluation, looking at multiple tools including GitHub Copilot, Claude, and ChatGPT across different use cases. Two criteria rose to the top: workflow integration and measurable ROI. A tool that produces impressive demos but doesn&#8217;t fit into an engineer&#8217;s existing IDE, CI/CD pipeline, or review process won&#8217;t see sustained adoption. Similarly, engineering leadership needed to see concrete productivity gains, and not just anecdotal enthusiasm, to justify the investment.</p><p>The initial reception among engineers was skeptical. Many viewed AI code assistants as glorified autocomplete, and there were legitimate concerns about code quality, security implications, and whether these tools would generate more technical debt than they resolved. Winning people over required a mix of approaches: data-driven metrics for leadership, live demos of real workflows, and enabling a handful of early adopters whose results could speak for themselves. Attitudes shifted once engineers saw where AI genuinely helped rather than just impressed.</p><p>At the individual developer level, the impact has been most visible in four areas: generating boilerplate and scaffolding code that nobody wants to write by hand, navigating unfamiliar codebases where the AI provides context-aware suggestions faster than reading through documentation, generating unit tests and edge cases that improve coverage, and producing documentation and inline comments from existing code. None of these are flashy use cases, but that&#8217;s the point. The cumulative time saved on routine work compounds into meaningful productivity gains across an engineering organization. The chart below from Catchpoint illustrates the impact of various AI use cases.</p><div><hr></div><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!MvAV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!MvAV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 424w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 848w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 1272w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!MvAV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png" width="1152" height="1074" data-attrs="{&quot;src&quot;:&quot;https://substackcdn.com/image/fetch/$s_!MvAV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1074,&quot;width&quot;:1152,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:173070,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:&quot;&quot;,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.ltce.dev/i/154827382?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!MvAV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 424w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 848w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 1272w, https://substackcdn.com/image/fetch/$s_!MvAV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9eaf8aae-5c47-4245-8eef-2c33850682b5_1152x1074.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Catchpoint SRE Report 2025 &#8212; AI Use Cases</figcaption></figure></div><div><hr></div><p>Where things get more interesting, and more relevant to the &#8220;shifting left&#8221; theme of this post, is in applying AI to SRE-specific workflows. I&#8217;ve been experimenting with connecting AI tools to observability and incident management platforms through integration protocols like MCP (Model Context Protocol). To appreciate why this matters, consider what incident triage typically looks like without it: an engineer gets paged, then scrambles across four or five tools &#8212; the alerting platform, observability dashboards, log search, deployment history, and a runbook &#8212; context-switching between tabs and mentally correlating signals. How long it takes to form a hypothesis depends on the engineer&#8217;s familiarity with the service, the clarity of the alert, and sometimes just luck. MCP collapses that workflow: rather than an on-call engineer manually jumping between dashboards, log viewers, and run-book documentation during an incident, the AI can pull from those sources and help synthesize what&#8217;s happening. The biggest win so far has been in the first few minutes of an incident - the triage phase where identifying the blast radius and narrowing down the likely root cause has the highest leverage. Having an AI assistant that can query your observability platform, cross-reference recent deployments, and surface relevant historical incidents in a single conversation meaningfully compresses the time from &#8220;something is broken&#8221; to &#8220;here&#8217;s where we should look.&#8221;</p><p>Outside of work, I&#8217;ve been using Claude and Claude Code for personal projects, and the experience has shifted how I think about AI tools as an engineer. Claude has become more of an architectural thinking partner &#8212; something I use to talk through design decisions, debug tricky problems interactively, and explore unfamiliar technologies before committing to an approach. The distinction matters because it reveals that &#8220;AI-assisted development&#8221; isn&#8217;t a single capability &#8212; it&#8217;s a spectrum that ranges from autocomplete to autonomous agents, and different tools occupy different positions on that spectrum depending on the use case.</p><p>The &#8220;shifting left&#8221; dimension of AI-assisted development is perhaps the most profound of the four trends in this post. AI tools are compressing the feedback loop between writing code and understanding its implications for testing, security, performance, and documentation. Tasks that previously required specialized knowledge or a separate review cycle can increasingly happen in the moment, at the point of development. When an engineer gets real-time feedback on a potential security vulnerability or a performance anti-pattern as they write the code, that&#8217;s shifting left in its purest form. And when an on-call engineer can triage an incident in minutes instead of the time it used to take to manually correlate data across half a dozen tools, that&#8217;s shifting left applied to operations, which brings the entire arc of this post full circle.</p><h2>Takeaways</h2><p>Looking back at the last 12 years, these four forces collectively shifted software engineering from monolithic craftsmanship toward distributed, automated, reliability-engineered systems. Infrastructure became code. Services became composable units. Operations became a discipline. And artificial intelligence became a collaborator. The cumulative effect has been to fundamentally raise the ceiling of what small teams can build and operate at scale by shifting complex organizational functions to the left.</p><p>The practical lesson I&#8217;d share with engineers navigating these shifts is that each trend didn&#8217;t eliminate complexity, but rather redistributed it. Micro-services gave teams autonomy but introduced coordination overhead. Kubernetes gave teams power over their infrastructure but demanded a steep investment in learning. SRE gave teams ownership of reliability but required cultural change around on-call and error budgets. AI tools are giving teams speed and rapid iteration but demand new judgment about when to trust the output and when to question it.</p><p>The ones who thrive through these transitions aren&#8217;t the ones who master every new tool first. They&#8217;re the ones who understand <em>why</em> the shift is happening and adapt their mental models accordingly. &#8220;Shifting left&#8221; isn&#8217;t just about moving tasks earlier in the development lifecycle - it&#8217;s about moving ownership, capability, and accountability closer to the people who are best positioned to act upon them.</p>]]></content:encoded></item><item><title><![CDATA[Migrating Data Across Kafka Clusters in Kubernetes]]></title><description><![CDATA[An overview of K8s' Strimzi Operator and how to manage data migrations with MirrorMaker 2]]></description><link>https://www.ltce.dev/p/migrating-data-across-kafka-clusters</link><guid isPermaLink="false">https://www.ltce.dev/p/migrating-data-across-kafka-clusters</guid><dc:creator><![CDATA[Ian Miller]]></dc:creator><pubDate>Mon, 01 Apr 2024 22:07:51 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p></p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080" width="5205" height="2928" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:2928,&quot;width&quot;:5205,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;a close up of a metal structure with a blue light in the background&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="a close up of a metal structure with a blue light in the background" title="a close up of a metal structure with a blue light in the background" srcset="https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1675970211113-d2be9e27b27a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwzMXx8bGF0dGljZXxlbnwwfHx8fDE3MTIwMDkxMzd8MA&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@chrislinnett">Chris Linnett</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>There are a handful of tasks that do a great job of instilling dread in a software engineer. You know the feeling - you're not quite sure how to approach it, or what the potential ramifications could be should the task implementation go wrong. Nothing does a better job of dredging up those feelings than Kafka migrations.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.ltce.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lattice! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div><p>In the first Technical Tuesdays post, I'll discuss how to migrate Kafka topics as well as consumer group offsets from a source cluster to a destination cluster using <a href="https://strimzi.io/?ref=lattice.ghost.io">Strimzi Operator</a> and MirrorMaker 2 on Kubernetes.</p><p>Generally, a simple producer-consumer cutover process is more than enough for most Kafka migration use cases, but this approach results in delayed message delivery during the cutover. There are some cases where delays are not an option during the cutover. This is where MirrorMaker 2 comes in - MirrorMaker 2 allows for a seamless migration and cutover process that mitigates the downtime risk.</p><p>Let's dive in.</p><div><hr></div><h2>What&#8217;s the goal?</h2><p>The goal is to migrate two topics from one cluster to another. A migration of Kafka topics across clusters is a unique challenge in that there are a lot of moving parts involved. For example, in a simple migration scenario we have to consider the migration of the following:</p><ul><li><p>Topics</p></li><li><p>Consumer group offsets</p></li></ul><p>The following diagram represents a high-level migration of 2 simple topics from an "origin cluster" to a "destination cluster":</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uH-d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uH-d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 424w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 848w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 1272w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uH-d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png" width="784" height="233" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:233,&quot;width&quot;:784,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!uH-d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 424w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 848w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 1272w, https://substackcdn.com/image/fetch/$s_!uH-d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8a87fe6e-63ad-479e-a17a-607fe75b1a17_784x233.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a><figcaption class="image-caption">MirrorMaker2 - Cluster Migration Path</figcaption></figure></div><p>Using the above diagram as an example, we'll leverage MirrorMaker 2 to sync the 2 topics and their consumer group offsets.</p><p>The rest of the post will explore the "MirrorMaker2" component in the middle of the diagram.</p><h2><strong>Time Out: What's MirrorMaker?</strong></h2><p>MirrorMaker 2 (MM2) is a tool provided by Apache Kafka for copying data between two Kafka clusters. MM2 supports functionality for replicating "topics, topic configurations, consumer groups and their offsets, and ACLs". MM2 leverages Kafka Connectors to consume data from a source cluster, and produce that data to the destination cluster.<br><br>From here on forth, I'll refer to MM2's replication functionality as mirroring. Apache makes that distinction because replication is a core feature set of Kafka, and the "mirroring" term is a way to distinguish that data is being replicated from one Kafka cluster to the other.</p><p>You can read more about MirrorMaker and its implementation at the official <a href="https://github.com/apache/kafka/blob/trunk/connect/mirror/README.md?ref=lattice.ghost.io">apache/kafka repo</a>.</p><div><hr></div><h2><strong>Let's set up MirrorMaker 2</strong></h2><p>This implementation will leverage <a href="https://strimzi.io/?ref=lattice.ghost.io">Strimzi Operator</a>'s MM2 configuration in order to allow for data mirroring between 2 clusters. We'll set up an active/passive MM2 mirror, which will allow for syncing data in a uni-directional fashion. It's also possible to set up an active/active mirror, but that's outside of the scope of this post.</p><p>Strimzi Operator allows for deploying Kafka infrastructure via Kubernetes. It also allows for deploying other Kafka-related technologies, such as Kafka Connect, MM2, &nbsp;and Kafka Bridge. Strimzi Operator provides Custom Resource Definitions (CRDs) &nbsp;that make it easier to manage and operate Kafka infrastructure as a first-class resource in Kubernetes. In order to get started with Strimzi Operator in minikube (or any K8S cluster), you can get started with the <a href="https://strimzi.io/install/latest?namespace=kafka&amp;ref=lattice.ghost.io">Strimzi Operator's bootstrap template</a>. An example implementation is available at <a href="https://github.com/irmiller22/mirrormaker2-poc/tree/main/workloads/kafka-operator?ref=lattice.ghost.io">irmiller22/mirrormaker2-poc</a>.<br><br>Now, on to the MM2 configuration.</p><p>The MM2 configuration is based off of the <a href="https://strimzi.io/docs/operators/latest/configuring.html?ref=lattice.ghost.io#type-KafkaMirrorMaker2-reference">KafkaMirrorMaker2 spec</a> of the Strimzi Kafka Operator API, and is implemented in Kubernetes as a <code>KafkaMirrorMaker2</code> CRD. Looking at the spec, there are a couple of key properties:</p><ul><li><p>connectCluster</p></li><li><p>clusters</p></li><li><p>mirrors</p></li></ul><p>Let's explore these by looking at an example YAML <code>KafkaMirrorMaker2</code> resource configuration, and we'll discuss the key properties further.</p><pre><code>apiVersion: kafka.strimzi.io/v1beta2
kind: KafkaMirrorMaker2
metadata:
  name: kafka-mirror
spec:
  version: 3.4.0
  replicas: 1
  connectCluster: "destination-cluster"
  clusters:
  - alias: "origin-cluster"
    bootstrapServers: origin-cluster-kafka-bootstrap:9093
    tls: 
      trustedCertificates:
      - secretName: origin-cluster-ca-cert
        certificate: ca.crt
    authentication: 
      type: tls
      certificateAndKey:
        secretName: admin-user-1
        certificate: user.crt
        key: user.key
    config:
      config.storage.replication.factor: -1
      offset.storage.replication.factor: -1
      status.storage.replication.factor: -1       
  - alias: "destination-cluster"
    bootstrapServers: destination-cluster-kafka-bootstrap:9093
    tls: 
      trustedCertificates:
      - secretName: destination-cluster-ca-cert
        certificate: ca.crt
    authentication: 
      type: tls
      certificateAndKey:
        secretName: admin-user-2
        certificate: user.crt
        key: user.key
    config:
      config.storage.replication.factor: 1
      config.storage.topic: mirrormaker2-cluster-configs
      offset.storage.replication.factor: 1
      offset.storage.topic: mirrormaker2-cluster-offsets
      status.storage.replication.factor: 1
      status.storage.topic: mirrormaker2-cluster-status
  mirrors:
  - sourceCluster: "origin-cluster"
    targetCluster: "destination-cluster"
    sourceConnector:
      tasksMax: 3
      config:
        replication.factor: 3
        offset-syncs.topic.replication.factor: 1
        sync.topic.acls.enabled: "false"
        replication.policy.separator: "."
        replication.policy.class: "io.strimzi.kafka.connect.mirror.IdentityReplicationPolicy"
        refresh.topics.interval.seconds: 60
    heartbeatConnector:
      config:
        heartbeats.topic.replication.factor: 3
    checkpointConnector:
      config:
        checkpoints.topic.replication.factor: 3      
        sync.group.offsets.enabled: true
        refresh.groups.interval.seconds: 60
    topicsPattern: ".*"
    groupsPattern: ".*"
  logging:
    type: inline
    loggers:
      connect.root.logger.level: "INFO"
  readinessProbe:
    initialDelaySeconds: 45
    timeoutSeconds: 5
  livenessProbe:
    initialDelaySeconds: 45
    timeoutSeconds: 5</code></pre><p>Let's break each key property down step by step.<br><br>The <code>connectCluster</code> property should be set to the alias that's specified for the <em>destination cluster</em>. In other words, it should be defined as the cluster that data is being mirrored to. In the example above, connectCluster would be set to destination-cluster:</p><pre><code><code>spec:
  ...
  connectCluster: "destination-cluster"
  ...
</code></code></pre><p>The <code>clusters</code> property represents a list of Kafka clusters that will be used in the replication process, one of which is the source cluster and one of which is the destination cluster. The <a href="https://strimzi.io/docs/operators/latest/configuring.html?ref=lattice.ghost.io#type-KafkaMirrorMaker2ClusterSpec-schema-reference">KafkaMirrorMaker2ClusterSpec</a> describes the properties that make up the spec. Let's look at the the spec specified in the example above:</p><pre><code><code>spec:
  ...
  clusters:
  - alias: "origin-cluster"
    bootstrapServers: origin-cluster-kafka-bootstrap:9093
    tls: 
      trustedCertificates:
      - secretName: origin-cluster-ca-cert
        certificate: ca.crt
    authentication: 
      type: tls
      certificateAndKey:
        secretName: admin-user-1
        certificate: user.crt
        key: user.key
    config:
      config.storage.replication.factor: -1
      offset.storage.replication.factor: -1
      status.storage.replication.factor: -1
  - alias: "destination-cluster"
    bootstrapServers: destination-cluster-kafka-bootstrap:9093
    tls: 
      trustedCertificates:
      - secretName: destination-cluster-ca-cert
        certificate: ca.crt
    authentication: 
      type: tls
      certificateAndKey:
        secretName: admin-user-2
        certificate: user.crt
        key: user.key
    config:
      config.storage.replication.factor: 1
      config.storage.topic: mirrormaker2-cluster-configs
      offset.storage.replication.factor: 1
      offset.storage.topic: mirrormaker2-cluster-offsets
      status.storage.replication.factor: 1
      status.storage.topic: mirrormaker2-cluster-status
</code></code></pre><p>For each cluster definition, there are several keys that need to be defined:</p><ul><li><p><code>alias</code></p></li><li><p><code>bootstrapServers</code></p></li><li><p><code>tls</code> (only if TLS is enabled)</p></li><li><p><code>authentication</code> (only if authentication is required for the respective cluster)</p></li><li><p><code>config</code> (only if there are additional Kafka configs that are required)</p></li></ul><p>These properties will look familiar to you if you've worked with Kafka before. These keys represent the configuration details needed in order for MirrorMaker2 to connect to each cluster.</p><p>The <code>mirrors</code> property represents the <code>KakfaMirrorMaker2</code> mirroring configuration, and is a list of mirroring configurations. In other words, this property specifies how the data should flow in between the clusters configured in the <code>clusters</code> property. The <a href="https://strimzi.io/docs/operators/latest/configuring.html?ref=lattice.ghost.io#type-KafkaMirrorMaker2MirrorSpec-reference">KafkaMirrorMaker2MirrorSpec</a> describes the properties that make up the spec. Let's look at the <code>mirrors</code> property that we defined earlier:</p><pre><code><code>spec:
  ...
  mirrors:
  - sourceCluster: "origin-cluster"
    targetCluster: "destination-cluster"
    sourceConnector:
      tasksMax: 3
      config:
        replication.factor: 3
        offset-syncs.topic.replication.factor: 1
        sync.topic.acls.enabled: "false"
        replication.policy.separator: "."
        replication.policy.class: "io.strimzi.kafka.connect.mirror.IdentityReplicationPolicy"
        refresh.topics.interval.seconds: 60
    heartbeatConnector:
      config:
        heartbeats.topic.replication.factor: 3
    checkpointConnector:
      config:
        checkpoints.topic.replication.factor: 3
        sync.group.offsets.enabled: true
        refresh.groups.interval.seconds: 60
    topicsPattern: ".*"
    groupsPattern: ".*"
  ...
</code></code></pre><p>There are a couple of key properties for each mirroring configuration:</p><ul><li><p><code>sourceCluster</code></p></li><li><p><code>targetCluster</code></p></li><li><p><code>sourceConnector</code></p></li><li><p><code>heartbeatConnector</code></p></li><li><p><code>checkpointConnector</code></p></li><li><p><code>topicsPattern</code></p></li><li><p><code>groupsPattern</code></p></li></ul><p>The <code>sourceCluster</code> and <code>targetCluster</code> properties are self-explanatory, but let's dig into the other properties one-by-one.</p><p>The <code>sourceConnector</code> represents the configuration for the Kafka Connector belonging to the source cluster. The source cluster's Kafka Connector is responsible for replicating topics, consumer groups, ACLs, and any other cluster-specific configurations from the source cluster to the destination cluster. Additionally, it's also responsible for emitting messages to the <code>offset-syncs</code> topic that keeps track of the latest consumer group offsets for the respective topics. In the configuration example above, the <code>sourceConnector</code> configuration has the following properties:</p><pre><code><code>spec:
  ...
  mirrors:
  - ...
    sourceConnector:
      tasksMax: 3
      config:
        replication.factor: 3
        offset-syncs.topic.replication.factor: 1
        sync.topic.acls.enabled: "false"
        replication.policy.separator: "."
        replication.policy.class: "io.strimzi.kafka.connect.mirror.IdentityReplicationPolicy"
        refresh.topics.interval.seconds: 60
  ...
</code></code></pre><p>For the above configuration, the number of tasks that the connector will run is set to 3 via the <code>tasksMax</code> setting. The <code>replication.factor</code> setting for replicated topics is 3. This means that the respective topic data will have three copies for each partition of the topic, and each copy will live on a different Kafka broker. For the <code>offset-syncs</code> topic, the <code>replication.factor</code> setting is 1, meaning that there are no copies of partitions in the <code>offset-syncs</code> topic. The <code>sync.topic.acls.enabled</code> settings is set to false, meaning that no ACLs are mirrored across clusters. The <code>replication.policy.class</code> setting configures how mirrored topics are named in the destination cluster. By default, mirrored topic names are prepended with the name of the destination cluster. However, the <code>io.strimzi.kafka.connect.mirror.IdentityReplicationPolicy</code> setting ensures that the topic retains the original name of the topic. More information is available via the <a href="https://strimzi.io/docs/operators/0.24.0/full/using.html?ref=lattice.ghost.io#cluster_configuration">Strimzi documentation on cluster configurations</a>.</p><p>The <code>heartbeatConnector</code> represents the configuration for the Kafka Connector that manages heartbeats. The heartbeats Kafka Connector emits heartbeat messages to the <code>heartbeats</code> topic, and allows for monitoring of the mirroring process between clusters.</p><p>The <code>checkpointConnector</code> represents the configuration for the Kafka Connector that manages the checkpoints. The checkpoints Kafka Connector consumes messages from the <code>offset-syncs</code> topic, and creates checkpoints for the mirroring process to allow MM2 to continue the mirroring process where it left off in the event of a failure. In the example above, the <code>checkpointConnector</code> has the following properties:</p><pre><code><code>spec:
  ...
  mirrors:
  - ...
    checkpointConnector:
      config:
        checkpoints.topic.replication.factor: 3
        sync.group.offsets.enabled: true
        refresh.groups.interval.seconds: 60
  ...
</code></code></pre><p>For the above configuration, the replication factor for the <code>checkpoints</code> topic is 3. This means that there are three copies of each partition in the <code>checkpoints</code> topic, and each copy lives on a different broker. The <code>sync.group.offsets.enabled</code> setting is set to true, meaning that consumer group offsets are mirrored from the source cluster to the destination cluster.</p><p>Finally, the <code>topicsPattern</code> and <code>groupsPattern</code> specify which topics and consumer groups are mirrored, respectively. Both of these parameters take a regex pattern. The <code>.*</code> value specified for both of these parameters means that all topics and all consumer groups are mirrored from the source cluster to the destination cluster.</p><div><hr></div><h2><strong>Let's visualize the MirrorMaker2 configuration</strong></h2><p>Let's start with the Kafka Connector for the source cluster.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!6SKQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!6SKQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 424w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 848w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 1272w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!6SKQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png" width="784" height="241" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:241,&quot;width&quot;:784,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!6SKQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 424w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 848w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 1272w, https://substackcdn.com/image/fetch/$s_!6SKQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F06b47ce9-7698-4e66-8653-ce314b9296a0_784x241.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">MirrorMaker2 - Source Connector Visualization</figcaption></figure></div><p>Above, we see the Kafka Connector for the source cluster and the data flows associated with it. The connector for the source cluster has 2 main responsibilities:</p><ul><li><p>replicate topics and consumer group data from the source cluster to the destination cluster</p></li><li><p>produce consumer group offset mappings to the <code>offset-syncs</code> topic</p></li></ul><p>MM2 leverages the <code>offset-syncs</code> topic so that the connector tasks can pick up where they left off in the event of a failure or a restart. In the diagram above, the dotted line represents MM2 keeping track of the consumer group offset mapping so that it can keep the consumer groups in sync across both the origin and destination clusters.</p><p>Let's take a look at the Kafka Connector for heartbeats:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!okOM!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!okOM!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 424w, https://substackcdn.com/image/fetch/$s_!okOM!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 848w, https://substackcdn.com/image/fetch/$s_!okOM!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 1272w, https://substackcdn.com/image/fetch/$s_!okOM!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!okOM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png" width="784" height="269" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:269,&quot;width&quot;:784,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!okOM!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 424w, https://substackcdn.com/image/fetch/$s_!okOM!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 848w, https://substackcdn.com/image/fetch/$s_!okOM!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 1272w, https://substackcdn.com/image/fetch/$s_!okOM!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcf97c90b-d76d-4622-819f-4feffd7859a2_784x269.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">MirrorMaker2 - Heartbeat Connector Visualization</figcaption></figure></div><p>The connector for heartbeats has 1 primary responsibility:</p><ul><li><p>produces a message from the origin cluster to the <code>heartbeats</code> topic on a specified interval (default is 1 second) to indicate liveness</p></li></ul><p>The heartbeats connector is intended for monitoring purposes to indicate that both the source and destination clusters are available and healthy. If there's no "heartbeat", then MirrorMaker2 takes that as a signal to interrupt the replication process.</p><div><hr></div><p>Finally, there's the Kafka Connector for checkpoints:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!m1F4!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!m1F4!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 424w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 848w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 1272w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!m1F4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png" width="784" height="538" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:538,&quot;width&quot;:784,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!m1F4!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 424w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 848w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 1272w, https://substackcdn.com/image/fetch/$s_!m1F4!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F412a4382-6d12-42f6-80e1-cd0be34b6272_784x538.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">MirrorMaker2 - Checkpoint Connector Visualization</figcaption></figure></div><p>The connector for checkpoints has one primary responsibility:</p><ul><li><p>produces messages containing consumer group offsets on the origin cluster to the <code>checkpoints</code> topic on the destination cluster</p></li></ul><p>MM2 uses the <code>checkpoints</code> topic to keep consumer groups that have been replicated to the destination cluster up to date. MM2 will periodically check the <code>checkpoints</code> topic, compare the messages in the topic to the specified offsets for the respective consumer group, and will update the consumer group offset if necessary.</p><div><hr></div><h2><strong>Migration Gotchas</strong></h2><p>MM2 does an exceptional job of simplifying migrations between Kafka clusters, but there are two gotchas to account for and keep in mind.</p><p><strong>Offset Translation</strong>. MM2 by default does a great job with offset translation in cases where Kafka topics have a retention period defined. However, if there are consumer applications that store offsets in a database, then those applications will need to account for the change in offsets.</p><p>Let's illustrate this with an example. Let's say that topic <code>animals</code> has 1,000 messages on the topic, and the latest offset is equal to 1,000. The retention period, defined via <code>retention.ms</code>, is set to 604,800,000 ms, which is the equivalent of 1 week. Of the 1,000 messages, let's say that 250 of those were created within the last week. So that means that 250 messages are still retained on the topic, and 750 messages have been cleaned up due to the topic retention policy. This means that when initiate the migration, only 250 messages for topic <code>animals</code> will get replicated from the origin cluster to the destination cluster, and the latest offset for the respective consumer group will be 1000 on the origin cluster and 250 on the destination cluster.</p><p><strong>Size of Consumer Groups / Topics</strong>. Be mindful of how many consumer groups and topics there are on the source cluster. If there's a significant number of consumer groups, you may need to adjust the number of tasks running on behalf of the source connector. Monitor the replication status during the migration, and adjust the number of tasks via the <code>tasks</code> parameter accordingly.</p><p><strong>Schema Registry Topic Schemas</strong>. When working with topics that leverage a schema, make sure that the destination cluster is working with the same schema definitions that the source cluster is utilizing. Schemas leverage something called a "magic byte", which is a unique identifier that a schema uses when serializing / deserializing messages. For example, with Confluent Schema Registry, I was bitten by the "magic byte" during the migration. The "magic byte" is a unique identifier that is randomly generated during the creation of a schema, and is unique per schema. &nbsp;It's not enough to recreate the schemas and utilize them with the new cluster, even if the schemas have the same schema IDs as the origin cluster. This is because while the schema IDs might match, the magic bytes won't have the same values. Fortunately, Confluent allows for exporting schemas across schema registries, which preserves the <code>&lt;magic_byte&gt;&lt;schema_id&gt;&lt;schema_bytes&gt;</code> format for each schema.</p><p><strong>Fine-tune with Minikube</strong>. I can't emphasize this one enough. MM2 is a wonderful technology, but it does take some time to get used to and understand, especially if you haven't worked with Kafka much. For example, I built out a POC at <a href="https://github.com/irmiller22/mirrormaker2-poc?ref=lattice.ghost.io">irmiller22/mirrormaker2-poc</a> with Minikube to better understand Strimzi Operator, &nbsp;MM2 configuration, and the data replication flow between clusters.</p><div><hr></div><h2><strong>Closing Thoughts</strong></h2><p>The Strimzi team has done an exceptional job building out their Kafka operator for Kubernetes, and has one of the more in-depth documentation resources that I've seen out there for open-source technology.</p><p>Even though I used Strimzi Kafka operator explicitly for the MM2 functionality, I'll consider using the operator for projects where a managed solution is prohibitively expensive.</p><p>Please reach out at <a href="mailto:ian@ltce.dev">ian@ltce.dev</a> or leave a message in the comments if there's more that you'd like to learn about MM2 or other topics. Thanks for reading!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.ltce.dev/p/migrating-data-across-kafka-clusters/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.ltce.dev/p/migrating-data-across-kafka-clusters/comments"><span>Leave a comment</span></a></p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://www.ltce.dev/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe&quot;,&quot;language&quot;:&quot;en&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">Thanks for reading Lattice! Subscribe for free to receive new posts and support my work.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Type your email&#8230;" tabindex="-1"><input type="submit" class="button primary" value="Subscribe"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>