<?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: Daniel German Rivera</title>
    <description>The latest articles on DEV Community by Daniel German Rivera (@danielrive).</description>
    <link>https://dev.to/danielrive</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%2F573185%2F5d258f98-fbba-4ac9-9815-1be8120d9082.jpeg</url>
      <title>DEV Community: Daniel German Rivera</title>
      <link>https://dev.to/danielrive</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/danielrive"/>
    <language>en</language>
    <item>
      <title>Exploring Istio Ambient Mode: Understanding the Role of Istio-CNI</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Wed, 29 Oct 2025 03:33:15 +0000</pubDate>
      <link>https://dev.to/aws-builders/exploring-istio-ambient-mode-understanding-the-role-of-istio-cni-4del</link>
      <guid>https://dev.to/aws-builders/exploring-istio-ambient-mode-understanding-the-role-of-istio-cni-4del</guid>
      <description>&lt;p&gt;This article is part of my personal project, &lt;strong&gt;Smart-cash&lt;/strong&gt;. Previous posts covered topics such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3"&gt;Deploying AWS and Kubernetes resources with Terraform and GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/aws-builders/configuring-logging-in-aws-eks-using-fluent-bit-and-cloudwatch-1fpb"&gt;Configuring logging with Fluent Bit and CloudWatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/aws-builders/traces-for-go-application-with-opentelemetry-on-aws-eks-2e3e"&gt;Distributed tracing with Jaeger and OpenTelemetry&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;h2&gt;
  
  
  The Basics
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Service Mesh&lt;/strong&gt;: A dedicated infrastructure layer that manages service-to-service communication. It provides traffic routing, observability (metrics, logs, traces), security (mTLS), and resilience,all transparently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why Use a Service Mesh?&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
It simplifies microservices complexity by centralizing traffic control, enforcing security using mTLS, and enabling good observability. In my experience, it is good for environments with many interdependent services or strict traffic policies. For simple applications, it can add unnecessary overhead.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Istio&lt;/strong&gt;: An open-source service mesh that integrates seamlessly with Kubernetes. It offers two deployment models:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;sidecar mode&lt;/strong&gt;, adds an Envoy proxy to every pod.
&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Ambient mode&lt;/strong&gt;, eliminates sidecars and uses daemons running in nodes for traffic interception.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Istio Sidecar vs. Ambient Mode
&lt;/h2&gt;

&lt;p&gt;In &lt;strong&gt;sidecar mode&lt;/strong&gt;, Istio injects an &lt;strong&gt;Envoy proxy container&lt;/strong&gt; into every pod that joins the mesh. You can configure this at the namespace or pod level. This proxy intercepts all inbound and outbound traffic. 3 main CRD can be highlighted:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;VirtualService&lt;/code&gt;&lt;/strong&gt;: Defines routing rules (path-based routing, retries, timeouts).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;Gateway&lt;/code&gt;&lt;/strong&gt;: Exposes services externally.
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;DestinationRule&lt;/code&gt;&lt;/strong&gt;: Controls how to serve the traffic to the services (load balancing, circuit breaking).&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;In &lt;strong&gt;Ambient mode&lt;/strong&gt;, Istio removes sidecars. Instead, it uses two daemons running in each node to intercept and secure traffic. This is an innovative way that uses kernel-level features.&lt;/p&gt;

&lt;p&gt;Here the main components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;ztunnel&lt;/code&gt;&lt;/strong&gt; Secures traffic and authenticates workloads within the mesh, manages mTLS, authentication, L4 authorization, and telemetry. This is only for L3 and L4 traffic; this doesn't terminate HTTP traffic. Ztunnel runs on every node.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;IstioCNI&lt;/code&gt;&lt;/strong&gt; Detects when a new pod is created and configures iptables rules to redirect traffic to ztunnel.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;&lt;code&gt;waypoint&lt;/code&gt;&lt;/strong&gt; Provides L7 traffic management, HTTP routing, retries, etc. This is an optional component that is deployed per namespace or service.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Installing Istio
&lt;/h2&gt;

&lt;p&gt;Installation will be done using Helm and Flux. For the detailed YAML manifest used, you can check these links: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/bootstrap/flux-sources/helm-istio.yaml" rel="noopener noreferrer"&gt;Flux-Source Istio Helm repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/core/helm-istio.yaml#L1" rel="noopener noreferrer"&gt;Flux-Helm-Release Istio Base (CRDs)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/core/helm-istio.yaml#L19" rel="noopener noreferrer"&gt;Flux-Helm-Release Istiod (Control Plane)&lt;/a&gt;. Here, we need to set the profile to ambient, check the values&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/core/helm-istio.yaml#L39" rel="noopener noreferrer"&gt;Flux-Helm-Release Istio-cni&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/core/helm-istio.yaml#L59" rel="noopener noreferrer"&gt;Flux-Helm-Release Istio-ztunnel&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You should see the pods in the istio-system namespace. Notice that only the control plane runs as a deployment; other components are daemon-sets.&lt;/p&gt;

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

&lt;p&gt;Kiali is a useful tool for checking and visualizing the mesh. You can install using &lt;a href="https://kiali.io/docs/installation/installation-guide/install-with-helm/" rel="noopener noreferrer"&gt;the documentation&lt;/a&gt;. For this case, the services graph looks like:&lt;/p&gt;

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

&lt;p&gt;Add the following label to the namespace you want to include in the mesh. Here, we added it to the develop namespace:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;istio.io/dataplane-mode&lt;span class="o"&gt;=&lt;/span&gt;ambient
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  How does it work internally?
&lt;/h3&gt;

&lt;p&gt;When a pod is created istio-cni automatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detects the pod via Kubernetes CNI events&lt;/li&gt;
&lt;li&gt;Enters the pod’s network namespace&lt;/li&gt;
&lt;li&gt;Injects iptables rules to redirect inbound and outbound traffic to ztunnel&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's check this in detail&lt;/p&gt;

&lt;h4&gt;
  
  
  WITHOUT Istio enabled
&lt;/h4&gt;

&lt;p&gt;Inside one node,list the containers that are running:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;crictl pods &lt;span class="nt"&gt;--namespace&lt;/span&gt; develop
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;We need to go inside the pod's network namespace and check the iptables rules. For that, we need to get the &lt;strong&gt;Pause&lt;/strong&gt; container associated&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;crictl inspectp 07689efcedf1f | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.info.pid'&lt;/span&gt;
&lt;span class="nb"&gt;sudo &lt;/span&gt;lsns &lt;span class="nt"&gt;-t&lt;/span&gt; net
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;With the PID of the Pause container, you can enter the network namespace or run a command directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;nsenter &lt;span class="nt"&gt;-t&lt;/span&gt; 18757 &lt;span class="nt"&gt;-n&lt;/span&gt; iptables &lt;span class="nt"&gt;-t&lt;/span&gt; nat &lt;span class="nt"&gt;-L&lt;/span&gt; | &lt;span class="nb"&gt;grep &lt;/span&gt;REDIRECT
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output should not show any redirection rules.&lt;/p&gt;

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

&lt;p&gt;We can confirm that there are no redirection rules.&lt;/p&gt;

&lt;h4&gt;
  
  
  WITH Istio enabled
&lt;/h4&gt;

&lt;p&gt;We can run the same commands to go inside pod network namespace, but when we list the IP tables rules, we will see these redirection rules:&lt;/p&gt;

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

&lt;p&gt;Those ports are related to Istio Ztunnel, which are listed in &lt;a href="https://github.com/istio/ztunnel/blob/master/ARCHITECTURE.md#ports" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;With this, all traffic inside the pod is redirected to a ztunnel proxy, which manages it. This eliminates the need for a sidecar container.&lt;/p&gt;

&lt;p&gt;IstioCNI plays a key role in Ambient Mode. It replaces the sidecar injection step by configuring traffic redirection at the network namespace.&lt;/p&gt;

&lt;p&gt;In the next post, we will check mTLS and custom configurations.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>istio</category>
      <category>aws</category>
      <category>linux</category>
    </item>
    <item>
      <title>Configuring Tracing with OpenTelemetry on AWS EKS and Go application</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Fri, 07 Mar 2025 01:57:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/traces-for-go-application-with-opentelemetry-on-aws-eks-2e3e</link>
      <guid>https://dev.to/aws-builders/traces-for-go-application-with-opentelemetry-on-aws-eks-2e3e</guid>
      <description>&lt;p&gt;This article is part of a personal project called Smart-cash. Previous posts covered topics such as &lt;a href="https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3"&gt;The deployment of AWS and Kubernetes resources&lt;/a&gt; and &lt;a href="https://dev.to/aws-builders/configuring-logging-in-aws-eks-using-fluent-bit-and-cloudwatch-1fpb"&gt;Configuring logging with FluentBit&lt;/a&gt;, among others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Source Code
&lt;/h2&gt;

&lt;p&gt;The full project code can be found &lt;a href="https://github.com/danielrive/smart-cash/tree/develop" rel="noopener noreferrer"&gt;here&lt;/a&gt;, the project is still under development, but you can find the terraform code to create AWS resources and also the Kubernetes manifests.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key concepts
&lt;/h2&gt;

&lt;p&gt;Two key concepts that often come up in modern system monitoring are observability and telemetry.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Observability helps us understand what is happening in the system. &lt;/li&gt;
&lt;li&gt;Telemetry refers to the data generated by applications, which includes logs, metrics, and traces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post focuses on traces&lt;/p&gt;

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

&lt;h3&gt;
  
  
  What is distributed tracing?
&lt;/h3&gt;

&lt;p&gt;Distributed tracing provides visibility into the path that requests follow through an application, helping to identify which parts of the system are experiencing errors and how much time each operation takes.&lt;/p&gt;

&lt;p&gt;Imagine an application that generates two random numbers and stores them in a database, two complex functions handle the numbers calculation. To gain visibility into the system’s behavior, we can introduce tracing.&lt;/p&gt;

&lt;p&gt;Here we can introduce another important concept: &lt;strong&gt;The Span&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A span represents a single operation within a trace, in the example, 3 spans can be defined:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;One for each function that calculates a random number.&lt;/li&gt;
&lt;li&gt;One for the database call.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;By structuring the trace with these spans, we can better understand what happens at each step, identify bottlenecks, and debug issues more effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction to OpenTelemetry
&lt;/h2&gt;

&lt;p&gt;OpenTelemetry(OTel) is an open-source, vendor agnostic tool for generating and managing telemetry data, such as traces, metrics, and logs. However, storage and visualization of this data must be handled with other tools. &lt;/p&gt;

&lt;p&gt;OTel helps us instrument our applications by providing APIs to define how telemetry data is generated, as well as components that can receive and export this data to external endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  OpenTelemetry collector
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry collector&lt;/a&gt; is a key component that works as a proxy to receive, process, and export telemetry data.&lt;/p&gt;

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

&lt;p&gt;Detailed information about receivers, processors, and exporters can be found in &lt;a href="https://opentelemetry.io/docs/collector/" rel="noopener noreferrer"&gt;OpenTelemetry Documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The collector is not a mandatory component; data can be exported directly to the backend using libraries. However, doing so adds processing overhead to the application.&lt;/p&gt;

&lt;p&gt;In this scenario, a collector will be installed, and the data will be sent to Jaeger.&lt;/p&gt;

&lt;h3&gt;
  
  
  Installing OpenTelemetry on an AWS EKS cluster
&lt;/h3&gt;

&lt;p&gt;We will use &lt;a href="https://github.com/open-telemetry/opentelemetry-helm-charts/tree/main/charts" rel="noopener noreferrer"&gt;OpenTelemetry Helm chart&lt;/a&gt;, installation is managed by FluxCD. All the files can be found in &lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/2-eks-cluster-stage/k8-manifests/core/helm-otel-collector.yaml" rel="noopener noreferrer"&gt;the repo&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Helm chart values
&lt;/h4&gt;

&lt;p&gt;Let's start with some general values for the Helm chart&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt; &lt;span class="na"&gt;mode&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;deployment"&lt;/span&gt;
   &lt;span class="na"&gt;namespaceOverride&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;observability"&lt;/span&gt;
   &lt;span class="na"&gt;presets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;kubernetesAttributes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
       &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
       &lt;span class="na"&gt;extractAllPodLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
       &lt;span class="na"&gt;extractAllPodAnnotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; 
   &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;repository&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;otel/opentelemetry-collector-k8s&lt;/span&gt;
     &lt;span class="na"&gt;pullPolicy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;IfNotPresent&lt;/span&gt;
   &lt;span class="na"&gt;command&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="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;otelcol-k8s"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's focus in the  &lt;a href="https://opentelemetry.io/docs/platforms/kubernetes/helm/collector/#presets" rel="noopener noreferrer"&gt;presets&lt;/a&gt; section, which allows predefined configurations for specific scenarios. In this case, the &lt;strong&gt;kubernetesAttributes&lt;/strong&gt; preset:&lt;/p&gt;

&lt;p&gt;✔ Extracts all pod labels and annotations to enrich traces with Kubernetes metadata.&lt;br&gt;
✔ Uses the Kubernetes Attributes processor to automatically add pod-related information to telemetry data.&lt;/p&gt;

&lt;p&gt;This additional metadata helps correlate telemetry data with the Kubernetes environment.&lt;/p&gt;
&lt;h5&gt;
  
  
  Collector configurations
&lt;/h5&gt;

&lt;p&gt;OpenTelemetry collector configuration is passed in the chart values, the configuration defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Receivers ---&amp;gt;  Where telemetry data is received.&lt;/li&gt;
&lt;li&gt;Processors ---&amp;gt; How the data is modified or filtered.&lt;/li&gt;
&lt;li&gt;Exporters ---&amp;gt; Where the data is sent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's break down the configuration&lt;/p&gt;
&lt;h6&gt;
  
  
  Receivers
&lt;/h6&gt;

&lt;p&gt;The collector listens for telemetry data on port 4318 (HTTP). Applications should send telemetry data to this endpoint.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;otlp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;protocols&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;http&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;$${env:MY_POD_IP}:4318&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Processors
&lt;/h6&gt;

&lt;p&gt;The processor manages data in batches using a default configuration ({}). This groups data before exporting it, helping to reduce network load.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;processors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;batch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{}&lt;/span&gt;
  &lt;span class="na"&gt;memory_limiter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;check_interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;
    &lt;span class="na"&gt;limit_percentage&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;80&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;memory_limiter&lt;/strong&gt; processor prevents high memory consumption by monitoring usage at intervals defined by &lt;strong&gt;check_interval&lt;/strong&gt; and limiting usage based on &lt;strong&gt;limit_percentage&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In this case, the collector checks memory usage every 5 seconds.&lt;/li&gt;
&lt;li&gt;If usage exceeds 80%, the collector drops data to prevent a crash.&lt;/li&gt;
&lt;/ul&gt;

&lt;h6&gt;
  
  
  Exporters
&lt;/h6&gt;

&lt;p&gt;The collector will send the data to a K8 service using the internal DNS &lt;em&gt;&lt;strong&gt;jaeger-traces-collector.observability.svc.cluster.local&lt;/strong&gt;&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;otlphttp&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
     &lt;span class="na"&gt;endpoint&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://jaeger-traces-collector.observability.svc.cluster.local:4318"&lt;/span&gt;
     &lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;insecure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h6&gt;
  
  
  Services section
&lt;/h6&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;pipelines&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
   &lt;span class="na"&gt;traces&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;receivers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otlp&lt;/span&gt;
    &lt;span class="na"&gt;exporters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;otlphttp&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;strong&gt;service&lt;/strong&gt; section defines the enabled components(receiver, exporters, processors) and specifies how the data(traces, metrics, or logs)  flows through &lt;strong&gt;pipelines&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;For this scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only traces are processed in the pipeline.&lt;/li&gt;
&lt;li&gt;The exporters and receivers defined above are used.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Instrumenting a Golang microservice
&lt;/h2&gt;

&lt;p&gt;At a high level, the code is structured as shown in the following diagram:&lt;/p&gt;

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

&lt;p&gt;The Gin Web Framework is used to create the API. Incoming requests pass through different layers (handler, service, and repository), each responsible for specific logic.&lt;/p&gt;

&lt;p&gt;OTel provides &lt;a href="https://opentelemetry.io/docs/languages/" rel="noopener noreferrer"&gt;APIs and SDKs&lt;/a&gt; to generate and collect telemetry data. Additionally, there are several  &lt;a href="https://opentelemetry.io/docs/languages/go/libraries/" rel="noopener noreferrer"&gt;instrumentation libraries&lt;/a&gt;  that simplify these tasks.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/open-telemetry/opentelemetry-go-contrib/tree/main/instrumentation/github.com/gin-gonic/gin/otelgin" rel="noopener noreferrer"&gt;Gin instrumentation library&lt;/a&gt; will be used. &lt;/p&gt;

&lt;h3&gt;
  
  
  Init the OTel SDK
&lt;/h3&gt;

&lt;p&gt;To begin, we need to create an &lt;a href="https://opentelemetry.io/docs/concepts/resources/" rel="noopener noreferrer"&gt;OTel resource&lt;/a&gt;, which represents an entity producing telemetry data—in this case, the microservice.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;resource&lt;/strong&gt; can have attributes that are configured during its creation. These attributes help in discovering telemetry data, particularly the traces generated by the application.&lt;/p&gt;

&lt;p&gt;Let's see part of the code used here&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
   &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
   &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFromEnv&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;   &lt;span class="c"&gt;// Discover and provide attributes from OTEL_RESOURCE_ATTRIBUTES and OTEL_SERVICE_NAME environment variables.&lt;/span&gt;
   &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTelemetrySDK&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c"&gt;// Discover and provide information about the OpenTelemetry SDK used.&lt;/span&gt;
   &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithContainer&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;    &lt;span class="c"&gt;// Discover and provide container information.&lt;/span&gt;
 &lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;semconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServiceNameKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ExpenseService"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="c"&gt;// Add custom resource attributes.&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Next, we need to create an &lt;strong&gt;exporter&lt;/strong&gt; to send the data to the previously created collector:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithEndpoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;otelUrl&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;":4318"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;otlptracehttp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithInsecure&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To start generating traces, we need to define a &lt;strong&gt;Tracer Provider&lt;/strong&gt; responsible for generating and managing traces. Here we set some configurations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;tp&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewTracerProvider&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBatcher&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;exporter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMaxExportBatchSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultMaxExportBatchSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithBatchTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultScheduleDelay&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithMaxExportBatchSize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DefaultMaxExportBatchSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithResource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The complete code for initializing the collector can be found &lt;a href="https://github.com/danielrive/smart-cash/blob/main/app/utils/otelInit.go" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Configuring Gin middleware
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;router&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;router&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;otelgin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Middleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ExpenseService"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;otelgin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filterTraces&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Recovery&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;gin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Recovery&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key point in this code is the &lt;strong&gt;service_name(ExpenseService)&lt;/strong&gt; passed to the middleware. It must remain consistent across all spans generated to ensure accurate and unified trace data.&lt;/p&gt;

&lt;p&gt;Gin library will manage internally the instrumentation for this part of the code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Creating spans
&lt;/h3&gt;

&lt;p&gt;A trace is composed of multiple spans that can be nested using &lt;a href="https://pkg.go.dev/context" rel="noopener noreferrer"&gt;context&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Here’s how to create a span in one of our functions (service layer) used to create an expense:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;createExpense&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

&lt;span class="n"&gt;tr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;otel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ExpenseService"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="err"&gt;#&lt;/span&gt; &lt;span class="n"&gt;Trace&lt;/span&gt; &lt;span class="n"&gt;Creation&lt;/span&gt; &lt;span class="n"&gt;Microservice&lt;/span&gt; &lt;span class="n"&gt;Name&lt;/span&gt; 

&lt;span class="n"&gt;trContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;childSpan&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;tr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"CreateExpense"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
 &lt;span class="n"&gt;childSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SetAttributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"component"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;"serviceLevel"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;childSpan&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;End&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expensesRepository&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CreateExpense&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expense&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the first line, a tracer is created with the name  &lt;strong&gt;ExpenseService&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;Then, a new span named &lt;strong&gt;CreateExpense&lt;/strong&gt; is created and associated with this tracer. This span includes an attribute called &lt;strong&gt;component&lt;/strong&gt; with the value serviceLevel.&lt;/p&gt;

&lt;p&gt;The span creation returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;trContext&lt;/strong&gt;: A new context that carries span information for passing to other functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;childSpan&lt;/strong&gt;: The span object itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This allows you to track the execution and timing of different parts of the function.&lt;/p&gt;

&lt;p&gt;Now, let’s proceed to the Jaeger installation to visualize the traces created.&lt;/p&gt;

&lt;h2&gt;
  
  
  Visualize traces with jaeger
&lt;/h2&gt;

&lt;p&gt;Jaeger is an end-to-end distributed tracing system designed for monitoring and troubleshooting microservices-based architectures.&lt;/p&gt;

&lt;p&gt;Commonly integrated as an OpenTelemetry backend, Jaeger stores and visualizes trace data, helping developers understand the flow and performance of their applications.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/jaegertracing/jaeger-operator#jager-v2-operator" rel="noopener noreferrer"&gt;Jaeger operator&lt;/a&gt; simplifies managing Jaeger resources on Kubernetes, the creation of the Jaeger instance is made by this yaml file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaegertracing.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Jaeger&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jaeger-traces&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;observability&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;strategy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;production&lt;/span&gt;
  &lt;span class="na"&gt;ingress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;enabled&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;kubernetes.io/ingress.class&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx&lt;/span&gt; &lt;span class="c1"&gt;# Ingress annotations here&lt;/span&gt;
    &lt;span class="na"&gt;ingressClassName&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;hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;jaeger.smartcash.rootkit.site&lt;/span&gt; &lt;span class="c1"&gt;#your domain name.&lt;/span&gt;
  &lt;span class="na"&gt;collector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;maxReplicas&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;limits&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;cpu&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;100m&lt;/span&gt;
        &lt;span class="na"&gt;memory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;128Mi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Through K8 ingress we can access the Jaeger UI and visualize the traces.&lt;/p&gt;

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

&lt;p&gt;In the Jaeger UI, traces can be filtered based on the service name defined during OpenTelemetry (OTel) setup — in this case, expenses.&lt;/p&gt;

&lt;p&gt;Jaeger displays the trace along with its spans, which represent individual operations within the trace. For this example, we have three spans:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Handler Span&lt;/strong&gt;: Captures the execution of the HTTP handler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service Span&lt;/strong&gt;: Covers the business logic executed in the service layer.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Repository Span&lt;/strong&gt;: Represents the database connection and related operations.
This structure helps in understanding the flow of requests and identifying potential bottlenecks or failures in the microservice.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Each span includes the component tag added in the code, along with metadata about the Kubernetes environment (such as pod labels and annotations). This enriched information helps in tracing requests across distributed components effectively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Visualizing span created by Gin Library
&lt;/h3&gt;

&lt;p&gt;The following image shows the span generated by the Gin Instrumentation library, all the data related to the incoming request is aggregated by this library. &lt;/p&gt;

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

</description>
      <category>opentelemetry</category>
      <category>go</category>
      <category>aws</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Configuring Logging in AWS EKS Using Fluent Bit and CloudWatch</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Sun, 06 Oct 2024 15:27:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/configuring-logging-in-aws-eks-using-fluent-bit-and-cloudwatch-1fpb</link>
      <guid>https://dev.to/aws-builders/configuring-logging-in-aws-eks-using-fluent-bit-and-cloudwatch-1fpb</guid>
      <description>&lt;p&gt;This article is part of a personal project called Smart-cash. Previous posts covered the deployment of &lt;a href="https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3"&gt;AWS and Kubernetes resources&lt;/a&gt; and &lt;a href="https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep"&gt;how to install FluxCD&lt;/a&gt; to implement GitOps practices.&lt;/p&gt;

&lt;p&gt;Observability is essential for any application, and the Smart-cash project is no exception. Previously &lt;a href="https://dev.to/aws-builders/adding-monitoring-to-eks-using-prometheus-operator-3ke1"&gt;Prometheus&lt;/a&gt; was integrated for monitoring. &lt;/p&gt;

&lt;h2&gt;
  
  
  Project Source Code
&lt;/h2&gt;

&lt;p&gt;The full project code can be found &lt;a href="https://github.com/danielrive/smart-cash/tree/develop" rel="noopener noreferrer"&gt;here&lt;/a&gt;, the project is still under development but you can find the terraform code to create AWS resources and also the Kubernetes manifest.&lt;/p&gt;

&lt;p&gt;In this &lt;a href="https://github.com/danielrive/blog-posts/tree/main/Logging-EKS-FluentBit-CloudWatch" rel="noopener noreferrer"&gt;link&lt;/a&gt; you can find the files used in this post&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1. Export logs directly to Cloudwatch Logs(No Cloudwatch add-on)
&lt;/h2&gt;

&lt;p&gt;The simplest configuration involves using Fluent-Bit's Tail Input, which reads the logs in the host &lt;strong&gt;&lt;em&gt;/var/log/containers/*.log&lt;/em&gt;&lt;/strong&gt; and sends them to Cloudwatch. This approach could be enough if you want to centralize the logs in CloudWatch or maybe another platform.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fluent-Bit installation
&lt;/h3&gt;

&lt;p&gt;The Fluent-Bit Helm chart will be used in combination with FluxCD.&lt;/p&gt;

&lt;p&gt;Adding the FluxCD source:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m0s&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://fluent.github.io/helm-charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Adding the Helm chart
&lt;/h4&gt;

&lt;p&gt;The supported values for the Fluent-Bit Helm chart can be found &lt;a href="https://github.com/fluent/helm-charts/blob/main/charts/fluent-bit/values.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use Fluent Bit with AWS, the following requirements must be met:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;IAM Roles for Service Accounts (IRSA)&lt;/strong&gt;: You must set up an IAM role with permissions to create CloudWatch Logs streams and write logs. This role should be associated with the service account that Fluent Bit uses. AWS EKS Pod identity is also an option.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudWatch Log Group&lt;/strong&gt;: You can either create the CloudWatch Log group in advance or allow Fluent Bit to handle the log group creation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The main configuration is shown below (some lines have been omitted for brevity). The full configuration file can be found &lt;a href="https://github.com/danielrive/blog-posts/tree/main/Logging-EKS-FluentBit-CloudWatch" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fluent-Bit will run as a DaemonSet. The Helm chart will create the RBAC and the Service account, named fluent-bit, which will be annotated with the AWS IAM role with the appropriate CloudWatch permissions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm.toolkit.fluxcd.io/v2beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRelease&lt;/span&gt;
&lt;span class="c1"&gt;###ommited-lines&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.47.9&lt;/span&gt;
      &lt;span class="c1"&gt;###ommited-lines&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DaemonSet&lt;/span&gt;
    &lt;span class="c1"&gt;###ommited-lines&lt;/span&gt;
    &lt;span class="na"&gt;serviceAccount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
        &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::${ACCOUNT_NUMBER}:role/role-fluent-bit-${ENVIRONMENT}&lt;/span&gt;
    &lt;span class="na"&gt;rbac&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="c1"&gt;###ommited-lines&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A volume is needed for Filesystem buffering, which helps to manage &lt;a href="https://docs.fluentbit.io/manual/administration/buffering-and-storage#filesystem-buffering-to-the-rescue" rel="noopener noreferrer"&gt;backpressure and overall memory control&lt;/a&gt;, also this is used to store the position (or offsets) of the log files being read, allowing Fluent Bit to track its progress and resume from the correct position if needed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;extraVolumes&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;fluentbit-status&lt;/span&gt;
        &lt;span class="na"&gt;hostPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/fluent-bit/state&lt;/span&gt;
    &lt;span class="na"&gt;extraVolumeMounts&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;fluentbit-status&lt;/span&gt;
        &lt;span class="na"&gt;mountPath&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/fluent-bit/state&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Configs section defines the &lt;a href="https://docs.fluentbit.io/manual/pipeline/inputs" rel="noopener noreferrer"&gt;Inputs&lt;/a&gt;, &lt;a href="https://docs.fluentbit.io/manual/pipeline/filters" rel="noopener noreferrer"&gt;Filters&lt;/a&gt;, and &lt;a href="https://docs.fluentbit.io/manual/pipeline/outputs" rel="noopener noreferrer"&gt;Outputs&lt;/a&gt; for collecting and processing data. For this scenario an Input of Tail type is configured to read the content of the files located at &lt;strong&gt;&lt;em&gt;/var/log/containers/*.log&lt;/em&gt;&lt;/strong&gt;. Let's break down the configuration details:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; AWS has some advanced configurations for inputs, filters and output, you can check this &lt;a href="https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/main/k8s-quickstart/cwagent-operator-rendered.yaml" rel="noopener noreferrer"&gt;link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The service section defines the global properties for Fluent-Bit, for this case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Flush Interval&lt;/strong&gt;: Set to 1 second, meaning Fluent Bit will send the collected logs to the configured output destinations every second.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log Level&lt;/strong&gt;: Set to Info, which includes informational messages as well as warnings and errors.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The storage path for Filesystem buffering is the volume mounted in previous configurations with a backlog memory limit of 5M, which means that if Fluent-bit service reaches this limit, it stops loading any more backlog chunks from the storage path into memory.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;service&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[SERVICE]&lt;/span&gt;
      &lt;span class="s"&gt;Daemon Off&lt;/span&gt;
      &lt;span class="s"&gt;Flush  1&lt;/span&gt;
      &lt;span class="s"&gt;Log_Level  info&lt;/span&gt;
      &lt;span class="s"&gt;Parsers_File /fluent-bit/etc/parsers.conf&lt;/span&gt;
      &lt;span class="s"&gt;Parsers_File /fluent-bit/etc/conf/custom_parsers.conf&lt;/span&gt;
      &lt;span class="s"&gt;HTTP_Server On&lt;/span&gt;
      &lt;span class="s"&gt;HTTP_Listen 0.0.0.0&lt;/span&gt;
      &lt;span class="s"&gt;HTTP_Port  \{\{ .Values.metricsPort \}\}&lt;/span&gt;
      &lt;span class="s"&gt;Health_Check On&lt;/span&gt;
      &lt;span class="s"&gt;storage.path  /var/fluent-bit/state/flb-storage/&lt;/span&gt;
      &lt;span class="s"&gt;storage.sync              normal&lt;/span&gt;
      &lt;span class="s"&gt;storage.checksum          off&lt;/span&gt;
      &lt;span class="s"&gt;storage.backlog.mem_limit 5M&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inputs define the data sources that Fluent Bit will collect logs from. In this scenario, the Tail input is used, which allows Fluent Bit to monitor one or more text files. Key points for this configuration include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In common Kubernetes environments, container runtimes store logs in the &lt;strong&gt;&lt;em&gt;/var/log/pod/&lt;/em&gt;&lt;/strong&gt; and &lt;strong&gt;&lt;em&gt;/var/log/containers/&lt;/em&gt;&lt;/strong&gt; (containers directory has symlinks to pod directory). Each log file follows a naming convention that includes key information like the pod name, namespace, container name, and container ID, for this case entries for fluent-bit, cloudwath-agent,kube-proxy, and aws-node will be ignored&lt;/li&gt;
&lt;li&gt;Some log entries may span multiple lines. Fluent Bit handles multi-line logs with built-in modes, and for this scenario, the Docker or CRI modes are used to process them correctly.&lt;/li&gt;
&lt;li&gt;To track the last line read from each log file, Fluent Bit uses a database to store this position. The database is saved in the previously mounted volume, ensuring that Fluent Bit can resume reading from the correct location.
&lt;/li&gt;
&lt;/ul&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="nv"&gt;INPUT&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
   &lt;span class="s"&gt;Name                tail&lt;/span&gt;
   &lt;span class="s"&gt;Tag                 applications.*&lt;/span&gt;
   &lt;span class="s"&gt;Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*&lt;/span&gt;
   &lt;span class="s"&gt;Path                /var/log/containers/*.log&lt;/span&gt;
   &lt;span class="s"&gt;multiline.parser    docker, cri&lt;/span&gt;
   &lt;span class="s"&gt;DB                  /var/fluent-bit/state/flb_container.db&lt;/span&gt;
   &lt;span class="s"&gt;Mem_Buf_Limit       50MB&lt;/span&gt;
   &lt;span class="s"&gt;Skip_Long_Lines     On&lt;/span&gt;
   &lt;span class="s"&gt;Refresh_Interval    &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;
   &lt;span class="s"&gt;storage.type        filesystem&lt;/span&gt;
   &lt;span class="s"&gt;Rotate_Wait         &lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Outputs define where the collected data is sent, and Fluent-Bit provides a plugin to send logs to CloudWatch.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you check the Input configurations there is a tag defined, &lt;strong&gt;&lt;em&gt;applications.*&lt;/em&gt;&lt;/strong&gt;. this helps to assign a label to the logs collected for that Input, in this case, it ensures that logs with this tag are routed to the specified output destination.&lt;/li&gt;
&lt;li&gt;CloudWatch log groups can be created by Fluent Bit, but in this scenario, the creation is disabled (set to off) since Terraform is used to manage log groups.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;log_stream_prefix&lt;/em&gt;&lt;/strong&gt; sets a prefix for the log streams created in CloudWatch, helping organize and identify the log entries within the stream.
&lt;/li&gt;
&lt;/ul&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="nv"&gt;OUTPUT&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
   &lt;span class="s"&gt;Name cloudwatch_logs&lt;/span&gt;
   &lt;span class="s"&gt;Match applications.*&lt;/span&gt;
   &lt;span class="s"&gt;region ${AWS_REGION}&lt;/span&gt; 
   &lt;span class="s"&gt;log_group_name /aws/eks/${CLUSTER_NAME}/workloads&lt;/span&gt;
   &lt;span class="s"&gt;log_stream_prefix from-k8-fluent-bit-&lt;/span&gt;
   &lt;span class="s"&gt;auto_create_group off&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you deploy the Helm chart you can check the CloudWatch service, if everything is working you should see some stream created, in this case out prefix is from-k8-fluent-bit-&lt;/p&gt;

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

&lt;p&gt;and the log entry&lt;/p&gt;

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

&lt;h4&gt;
  
  
  Adding a filter
&lt;/h4&gt;

&lt;p&gt;Filters in Fluent Bit allow you to enrich the data being collected. For instance, the Kubernetes Filter adds valuable metadata to log entries, such as namespace, pod_name, host, and more.&lt;/p&gt;

&lt;p&gt;Here are some key points about the filter configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The Tag from the input configuration is reused here to extract information like pod_name, namespace, and other relevant metadata.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The Kube_URL points to the Kubernetes API server, which Fluent Bit queries to obtain metadata about the pods involved in the logs. The path for the token and certificate is specified in Kube_CA_File and Kube_Token_File.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You can configure the filter to include annotations and labels from the pods in the log entries.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Be cautious about Fluent Bit querying the API server for metadata. In clusters with a high number of resources, &lt;a href="https://aws.amazon.com/blogs/containers/capturing-logs-at-scale-with-fluent-bit-and-amazon-eks/" rel="noopener noreferrer"&gt;this can put an additional&lt;/a&gt; load on the API server. &lt;a href="https://aws.amazon.com/blogs/containers/capturing-logs-at-scale-with-fluent-bit-and-amazon-eks/" rel="noopener noreferrer"&gt;One optimization&lt;/a&gt; is to retrieve pod metadata from the node’s kubelet instead of the kube-apiserver, but this requires enabling hostNetwork in the DaemonSet&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="nv"&gt;FILTER&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
   &lt;span class="s"&gt;Name   kubernetes&lt;/span&gt;
   &lt;span class="s"&gt;Match  applications.*&lt;/span&gt;
   &lt;span class="s"&gt;Kube_URL      https://kubernetes.default.svc:443&lt;/span&gt;
   &lt;span class="s"&gt;Kube_CA_File       /var/run/secrets/kubernetes.io/serviceaccount/ca.crt&lt;/span&gt;
   &lt;span class="s"&gt;Kube_Token_File&lt;/span&gt; 
     &lt;span class="s"&gt;/var/run/secrets/kubernetes.io/serviceaccount/token&lt;/span&gt;
   &lt;span class="s"&gt;Kube_Tag_Prefix     application.var.log.containers.&lt;/span&gt;
   &lt;span class="s"&gt;Merge_Log           On&lt;/span&gt;
   &lt;span class="s"&gt;Merge_Log_Key       log_processed&lt;/span&gt;
   &lt;span class="s"&gt;K8S-Logging.Parser  On&lt;/span&gt;
   &lt;span class="s"&gt;K8S-Logging.Exclude Off&lt;/span&gt;
   &lt;span class="s"&gt;Labels              On&lt;/span&gt;
   &lt;span class="s"&gt;Annotations         Off&lt;/span&gt;
   &lt;span class="s"&gt;Buffer_Size         &lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After applying this filter the logs should have pods metadata&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Option 2. Use the amazon-cloudwatch-observability add-on
&lt;/h2&gt;

&lt;p&gt;Container Insights can be used to collect, aggregate, and summarize both metrics and logs. If you plan to enable this in your EKS cluster, the Amazon CloudWatch Observability add-on installs the necessary resources to achieve this.&lt;/p&gt;

&lt;p&gt;At a high level, the add-on installs two key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A CloudWatch agent to collect metrics.&lt;/li&gt;
&lt;li&gt;Fluent-Bit to collect logs, using the &lt;a href="https://github.com/aws/aws-for-fluent-bit" rel="noopener noreferrer"&gt;AWS for Fluent-bit container image&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both components are deployed as DaemonSets.&lt;/p&gt;

&lt;p&gt;The add-on can be installed via Terraform or by using a &lt;a href="https://github.com/aws-observability/helm-charts/blob/main/charts/amazon-cloudwatch-observability/values.yaml" rel="noopener noreferrer"&gt;Helm-chart&lt;/a&gt;, Regardless of the method, you'll need to create an IAM role for the service account &lt;strong&gt;&lt;em&gt;cloudwatch-agent&lt;/em&gt;&lt;/strong&gt; in the &lt;strong&gt;&lt;em&gt;amazon-cloudwatch&lt;/em&gt;&lt;/strong&gt; namespace.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eks_addon"&lt;/span&gt; &lt;span class="s2"&gt;"cloudwatch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;addon_name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"amazon-cloudwatch-observability"&lt;/span&gt;
  &lt;span class="nx"&gt;addon_version&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="nx"&gt;-eksbuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nx"&gt;service_account_role_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudwatch_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt; 
  &lt;span class="nx"&gt;resolve_conflicts_on_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"OVERWRITE"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The add-on creates several resources in the cluster, some of which you may not need. For example, if you list the DaemonSets in the amazon-cloudwatch namespace, you'll notice seven DaemonSets, some of which might have 0 replicas. While these resources may not be actively used, they still exist in your cluster and can create some noise.&lt;/p&gt;

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

&lt;p&gt;You can customize the add-on configurations to suit your needs. For example, you can disable Fluent Bit logs for Accelerated Compute monitoring or skip collecting NVIDIA GPU metrics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_eks_addon"&lt;/span&gt; &lt;span class="s2"&gt;"cloudwatch"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;cluster_name&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;addon_name&lt;/span&gt;                  &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"amazon-cloudwatch-observability"&lt;/span&gt;
  &lt;span class="nx"&gt;addon_version&lt;/span&gt;               &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="nx"&gt;v2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="nx"&gt;-eksbuild&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
  &lt;span class="nx"&gt;service_account_role_arn&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cloudwatch_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt; 
  &lt;span class="nx"&gt;resolve_conflicts_on_update&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"OVERWRITE"&lt;/span&gt;
  &lt;span class="nx"&gt;configuration_values&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jsonencode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;containerLogs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;enabled&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;    
    &lt;span class="nx"&gt;agent&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;config&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="nx"&gt;metrics_collected&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nx"&gt;application_signals&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{},&lt;/span&gt;
            &lt;span class="nx"&gt;kubernetes&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
              &lt;span class="s2"&gt;"enhanced_container_insights"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
              &lt;span class="s2"&gt;"accelerated_compute_metrics"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
            &lt;span class="p"&gt;}}}}}&lt;/span&gt;
  &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By default, the add-on creates four CloudWatch log groups. The following image, taken from the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-logs-FluentBit.html#Container-Insights-FluentBit-setup" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt;, explains the naming structure of the log groups and the type of data each group stores.&lt;/p&gt;

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

&lt;p&gt;To change expiration days and names for the groups is better to use the Helm chart instead of the Terraform code to install the add-on. You can do this by modifying the fluent-bit outputs.&lt;/p&gt;

&lt;p&gt;The last log group is named performance and stores metrics collected by the CloudWatch agent, such as the number of running pods, CPU usage, and memory metrics.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bonus: Cluster dashboard
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, the CloudWatch add-on collects, aggregates, and summarizes metrics. Once the add-on is installed, AWS automatically generates a dashboard that provides useful insights and metrics for your cluster.&lt;/p&gt;

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

&lt;p&gt;You can watch metric per pod&lt;/p&gt;

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

&lt;p&gt;It also generates a visual map that organizes Kubernetes resources by namespace.&lt;/p&gt;

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

</description>
      <category>kubernetes</category>
      <category>observability</category>
      <category>aws</category>
      <category>fluentbit</category>
    </item>
    <item>
      <title>Using Terraform to push files to Git Repo for GitOps</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Thu, 11 Jul 2024 21:32:30 +0000</pubDate>
      <link>https://dev.to/aws-builders/pros-and-cons-of-using-terraform-with-fluxcd-for-gitops-4k9h</link>
      <guid>https://dev.to/aws-builders/pros-and-cons-of-using-terraform-with-fluxcd-for-gitops-4k9h</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Note: This post was updated because I incorrectly stated that the GitHub Terraform provider didn't delete files in the remote repository when they were removed from the Terraform code. The issue arose because I was using GitHub Actions to run the plan and apply steps, but the GitHub token was not propagating correctly during the plan step. This caused Terraform to fail to delete the files in the remote repository and resulted in multiple commits with each execution. I apologize for any confusion this may have caused and have edited the article to provide accurate information.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I have been working on a personal project named &lt;a href="https://github.com/danielrive/smart-cash" rel="noopener noreferrer"&gt;Smart-cash&lt;/a&gt; to improve some skills and learn new ones.&lt;/p&gt;

&lt;p&gt;In this article, I will share my thoughts about using Terraform in the GitOps process, specifically to create the manifest and push it to the Git repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  The basics
&lt;/h2&gt;

&lt;p&gt;GitOps relies on a Git repository as the single source of truth. New commits imply infrastructure and application updates.&lt;/p&gt;

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

&lt;p&gt;Imagine a Git repository where you push all the manifests of the Kubernetes resources you want to create in your cluster. These are pulled by a tool or script that runs a "kubectl apply", creates the resources, and checks the Git repo for new changes to apply. This, at a high level, is GitOps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the scenario
&lt;/h2&gt;

&lt;p&gt;For this case, the K8 cluster will run in AWS EKS, and Terraform is being used as an IaC tool. &lt;/p&gt;

&lt;p&gt;A basic cluster can be created using Terraform. You can check an example &lt;a href="https://github.com/danielrive/smart-cash/blob/main/infra/terraform/modules/eks/main.tf" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;FluxCD installation can be done using &lt;a href="https://fluxcd.io/flux/installation/bootstrap/github/" rel="noopener noreferrer"&gt;the official documentation&lt;/a&gt; or you can check &lt;a href="https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep"&gt;this&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I will not explain some Flux concepts like sources and Kustomizations; you can check that in the links shared previously.&lt;/p&gt;

&lt;h2&gt;
  
  
  Creating the YAML files
&lt;/h2&gt;

&lt;p&gt;Let's say that we want to create a namespace for the development environment, we can use the following YAML:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;develop&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can push this file to GitHub and wait for FluxCD to do the magic.&lt;/p&gt;

&lt;p&gt;Now let's say that we want to create a service account and associate it with an AWS IAM role, the YAML can be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa-test-develop&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::12345678910:role/TEST&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This looks easy but what happens if we have multiple environments or if We don't yet know the ARN of the role because this is part of our IaC?&lt;/p&gt;

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

&lt;p&gt;Here is where Terraform gives us a hand.&lt;/p&gt;

&lt;p&gt;You can create something like a template for the manifest and some variables that you can specify with Terraform. The two manifests would look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ENVIRONMENT}&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;test&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sa-test-${ENVIRONMENT}&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
    &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${ROLE_ARN}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the ${ENVIRONMENT} and ${ROLE_ARN} variables added.&lt;/p&gt;

&lt;p&gt;We can use the &lt;a href="https://registry.terraform.io/providers/integrations/github/latest/docs" rel="noopener noreferrer"&gt;Terraform GitHub provider&lt;/a&gt; to push the file to the repository. Let's check the following code to push the service account:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"github_repository_file"&lt;/span&gt; &lt;span class="s2"&gt;"sa-test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;repository&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;github_repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;flux-gitops&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;
  &lt;span class="nx"&gt;branch&lt;/span&gt;              &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;main&lt;/span&gt;
  &lt;span class="nx"&gt;file&lt;/span&gt;                &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./manifest/sa-manifest.yaml"&lt;/span&gt;
  &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;templatefile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s2"&gt;"sa-manifest.yaml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;ENVIRONMENT&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;
      &lt;span class="nx"&gt;ROLE_ARN&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;aws_iam_role&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;commit_message&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Terraform"&lt;/span&gt;
  &lt;span class="nx"&gt;commit_author&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"terraform"&lt;/span&gt;
  &lt;span class="nx"&gt;commit_email&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"example@example"&lt;/span&gt;
  &lt;span class="nx"&gt;overwrite_on_create&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The arguments &lt;strong&gt;repository&lt;/strong&gt; and &lt;strong&gt;branch&lt;/strong&gt; allow us to specify the remote repo and the branch where we want to push the file. The &lt;strong&gt;file&lt;/strong&gt; argument is the location &lt;strong&gt;in the remote repository&lt;/strong&gt; where we want to put the file.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;content&lt;/strong&gt; argument is where we pass the values to the variables created in the template, in this case ENVIRONMENT and ROLE_ARN, the values are a terraform variable and the reference to a Terraform resource that creates the role.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;overwrite_on_create&lt;/strong&gt; argument is needed because if you run Terraform again, it will show an error because the file already exists in the repo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pros
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Pushing the manifests using Terraform avoids the manual tasks of committing and pushing them, allowing us to automate more steps.&lt;/li&gt;
&lt;li&gt;We can integrate this process into our pipeline, so a full environment can be ready when the pipeline finishes.&lt;/li&gt;
&lt;li&gt;Terraform count can be used when there are many manifests to push, avoiding repetitive code.&lt;/li&gt;
&lt;/ol&gt;

</description>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>terraform</category>
      <category>fluxcd</category>
    </item>
    <item>
      <title>Smart-Cash project -Adding monitoring to EKS using Prometheus operator</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Thu, 30 Nov 2023 15:54:00 +0000</pubDate>
      <link>https://dev.to/aws-builders/adding-monitoring-to-eks-using-prometheus-operator-3ke1</link>
      <guid>https://dev.to/aws-builders/adding-monitoring-to-eks-using-prometheus-operator-3ke1</guid>
      <description>&lt;p&gt;Previous articles showed how to &lt;a href=""&gt;build the EKS Infrastructure in AWS&lt;/a&gt; and &lt;a href="https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep"&gt;how to install FluxCD&lt;/a&gt; to implement GitOps practices, This article is focused on explaining the steps taken to install the Prometheus Operator(using Helm) and Grafana for monitoring.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Source Code
&lt;/h2&gt;

&lt;p&gt;The source code for this project can be found &lt;a href="https://github.com/danielrive/smart-cash/releases/tag/v1.3.0" rel="noopener noreferrer"&gt;here&lt;/a&gt;, also a &lt;a href="https://github.com/danielrive/smart-cash-gitops-flux" rel="noopener noreferrer"&gt;GitOps repository&lt;/a&gt; has been created to store the Yaml files that FluxCD uses and apply to the EKS cluster. &lt;/p&gt;

&lt;h2&gt;
  
  
  Prometheus operator
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;The Prometheus Operator provides Kubernetes native deployment and management of Prometheus and related monitoring components.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://prometheus-operator.dev/docs/operator/design/" rel="noopener noreferrer"&gt;Prometheus operator&lt;/a&gt; defines Kubernetes Custom Resources and controllers that facilitate installing Prometheus. The community has developed alternative options such as &lt;em&gt;kube-Prometheus&lt;/em&gt; and &lt;em&gt;kube-prometheus-stack&lt;/em&gt; to install the components to monitor Kubernetes. &lt;/p&gt;

&lt;h3&gt;
  
  
  Prometheus Operator, kube-prometheus and kube-prometheus-stack
&lt;/h3&gt;

&lt;p&gt;The project repository for Prometheus-operator can be found &lt;a href="https://github.com/prometheus-operator/prometheus-operator" rel="noopener noreferrer"&gt;here&lt;/a&gt;, The repo defines the CRDs and the controller. You can follow this &lt;a href="https://prometheus-operator.dev/docs/user-guides/getting-started/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for the installation. which will require the creation of metrics exporters, node exporters, scrape configurations, etc. &lt;/p&gt;

&lt;p&gt;On the other hand, the &lt;a href="https://github.com/prometheus-operator/kube-prometheus" rel="noopener noreferrer"&gt;Kube-prometheus&lt;/a&gt; project provides documentation and scripts to operate end-to-end Kubernetes cluster monitoring using the Prometheus Operator, making easier the process of monitoring the Kubernetes cluster. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/prometheus-community/helm-charts" rel="noopener noreferrer"&gt;kube-prometheus-stack&lt;/a&gt; is a Helm chart that contains several components to monitor the Kubernetes cluster, along with Grafana dashboards to visualize the data. This option will be used in this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing kube-prometheus-stack Helm chart
&lt;/h2&gt;

&lt;p&gt;In previous articles, FluxCD was installed in EKS cluster to implement GitOps, the following flux source will now be added.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 YAML
apiVersion: source.toolkit.fluxcd.io/v1beta2
kind: HelmRepository
metadata:
  name: helm-repo-prometheus
  namespace: flux-system
spec:
  interval: 10m0s
  url: https://prometheus-community.github.io/helm-charts


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

&lt;/div&gt;

&lt;p&gt;Also, a Flux Helm release is added&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 YAML
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: prometheus
  namespace: monitoring
spec:
  interval: 10m0s
  chart:
    spec:
      chart: kube-prometheus-stack
      sourceRef:
        kind: HelmRepository
        name: helm-repo-prometheus
        namespace: flux-system
  values:
    defaultRules:
      rules:
        etcd: false
        kubeSchedulerAlerting: false
        kubeSchedulerRecording: false
        windows: false
    prometheus:
      prometheusSpec:
        storageSpec:
            volumeClaimTemplate:
              spec:
                storageClassName: aws-ebs-gp2
                accessModes: ["ReadWriteOnce"]
                resources:
                  requests:
                    storage: 40Gi


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

&lt;/div&gt;

&lt;p&gt;If you examine the values for the chart, by default, it installs rules to monitor etcd and some other control plane components. However, in this case, that is not necessary due to EKS limiting access to certain control-plane components.&lt;/p&gt;

&lt;p&gt;By default, Prometheus uses local storage to store data. To enable persistent storage, an EBS volume can be added. In this scenario, the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/ebs-csi.html" rel="noopener noreferrer"&gt;EBS CSI&lt;/a&gt; driver is employed, and a storage class is defined to manage the integration with Prometheus.&lt;/p&gt;

&lt;p&gt;Once the manifests are ready, you can push them to the GitOps repo( &lt;a href="https://github.com/danielrive/smart-cash-gitops-flux/blob/main/common/helm-prometheus.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt; for this case), and wait for Flux to handle the installation process in the cluster.&lt;/p&gt;

&lt;p&gt;You can check the cluster by looking for the resources created, notice that for this case, everything will be placed in the &lt;strong&gt;monitoring&lt;/strong&gt; namespace.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get crd&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Additionally, certain deployments and services should have been created in the monitoring namespace.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get pods -n monitoring&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Let's look at the Prometheus server console, you can expose the service by an ingress or using port-forward.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl port-forward service/prometheus-kube-prometheus-prometheus 3001:9090 -n monitoring&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The previous command will expose the Prometheus service in localhost:3001, you can go directly to the targets and you should see some targets created automatically, as well as the metrics and the services discovered by the server.&lt;/p&gt;

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

&lt;p&gt;This is useful because you don't need to configure the targets to monitor K8 and node metrics, the Helm chart does this for you. For instance, a simple example is just to check the number of pods created in the default namespace, you can run this PromQL query.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;count(kube_pod_created{namespace="default"})&lt;/code&gt;&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Grafana Dashboards
&lt;/h3&gt;

&lt;p&gt;Helm chart also installs Grafana and configures some useful dashboards that you can use, if you list the services and pods you will see some resources related to Grafana. You can expose the Grafana service or create an ingress for it.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating nginx ingress for Grafana
&lt;/h4&gt;

&lt;p&gt;Nginx-ingress is utilized and installed using Helm. You can add the following Flux source and the Helm release to the GitOps repo. Check the GitOps repo for this project &lt;a href="https://github.com/danielrive/smart-cash-gitops-flux/blob/main/common/helm-nginx-ingress.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt; and use it as a model.&lt;/p&gt;

&lt;p&gt;Helm source&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;source.toolkit.fluxcd.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm-repo-nginx-ingress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m0s&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci://ghcr.io/nginxinc/charts&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Use this yaml to install the chart, in this case AWS Network Load Balancer is used, this is done through the annotation specified in the values for the chart.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Yaml
apiVersion: helm.toolkit.fluxcd.io/v2beta1
kind: HelmRelease
metadata:
  name: nginx-ingress
  namespace: nginx-ingress
spec:
  interval: 10m0s
  chart:
    spec:
      chart: nginx-ingress
      version: 1.0.2
      sourceRef:
        kind: HelmRepository
        name: helm-repo-nginx-ingress
        namespace: flux-system
  values:
    controller:
      service:
        annotations: 
          service.beta.kubernetes.io/aws-load-balancer-type: "nlb"


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

&lt;/div&gt;
&lt;h4&gt;
  
  
  Installing Cert-managet to support SSL
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;This article will not dig into details about cert-manager concepts.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;In order to support SSL in the EKS cluster cert-manager will be used, cert-manager adds certificates and certificate issuers as resource types in Kubernetes clusters, and simplifies the process of obtaining, renewing and using those certificates. &lt;/p&gt;

&lt;p&gt;Cert-manager uses Kubernetes CRD, to install them you can run:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.2/cert-manager.crds.yaml&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;This also can be added to the GitOps repo(check &lt;a href="https://github.com/danielrive/smart-cash-gitops-flux/blob/main/common/crd-cert-manager.yaml" rel="noopener noreferrer"&gt;here&lt;/a&gt;) and given to Flux to handle it.&lt;/p&gt;

&lt;p&gt;When the CRDs are ready, you can install cert-manager, in this case a Helm chart will be used, this also will be added in GitOps repo.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm.toolkit.fluxcd.io/v2beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRelease&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m0s&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1.13.2&lt;/span&gt;
      &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&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;helm-cert-manager&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
  &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;serviceAccount&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;arn:aws:iam::123456789:role/cert-manager-us-west-2&lt;/span&gt;
    &lt;span class="na"&gt;securityContext&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;fsGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1001&lt;/span&gt;
    &lt;span class="na"&gt;extraArgs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--issuer-ambient-credentials&lt;/span&gt;



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

&lt;/div&gt;

&lt;p&gt;Finally and cert-manager ClusterIssuer is added, in this case the domain will be validated in AWS Route53, this is done through the IAM role passed in the previous YAML file for the Helm chart.&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cert-manager.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIssuer&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;example-letsencrypt2&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;acme&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;notreply@example.info&lt;/span&gt;
    &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://acme-staging-v02.api.letsencrypt.org/directory&lt;/span&gt;
    &lt;span class="na"&gt;privateKeySecretRef&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;example-issuer-account-key&lt;/span&gt;
    &lt;span class="na"&gt;solvers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;dnsZones&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.info"&lt;/span&gt;
      &lt;span class="na"&gt;dns01&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;route53&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;us-west-2&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Once cert-manager and nginx-ingress are installed you can create an ingress for Grafana. The following manifest has been added to the GitOps repo.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: grafana-ingress
  namespace: monitoring
  annotations:
    cert-manager.io/cluster-issuer: example-letsencrypt2
spec:
  ingressClassName: nginx
  rules:
  - host: monitoring.example.info
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: prometheus-grafana
            port:
              number: 80
  tls:
   - hosts:
     - monitoring.example.info
     secretName: example-issuer-account-ingress-key2


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

&lt;/div&gt;

&lt;p&gt;With this installed you can browser and access Grafana, you should see some dashboards already created.&lt;/p&gt;

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

&lt;p&gt;For instance, the &lt;em&gt;Kubernetes/API server&lt;/em&gt; dashboard is pre-configured, also you can also use third-party dashboards.&lt;/p&gt;

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

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>monitoring</category>
      <category>prometheus</category>
    </item>
    <item>
      <title>Smart-Cash Project - GitOps with FluxCD</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Sat, 04 Nov 2023 21:54:01 +0000</pubDate>
      <link>https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep</link>
      <guid>https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep</guid>
      <description>&lt;p&gt;In a &lt;a href="https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3"&gt;previous article&lt;/a&gt; I mentioned the idea behind this project that I named SmartCash. I began building the terraform code for the infrastructure in AWS and the pipeline to deploy it.&lt;/p&gt;

&lt;p&gt;In this article, I will introduce FluxCD as a GitOps tool and demonstrate its usage.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft18cxoi3v9353gucvs2p.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ft18cxoi3v9353gucvs2p.png" alt="GitOps meme, source https://blog.kubesimplify.com/gitops-demystified" width="521" height="417"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Source code
&lt;/h2&gt;

&lt;p&gt;A new release has been created in the smart-cash repository for the project. v1.1.0 version will be used, you can check the repository &lt;a href="https://github.com/danielrive/smart-cash/tree/v1.1.0"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Additionally, a new repository will be created to store the K8 manifest that will be synced with the EKS cluster using FluxCD, you can view the repo &lt;a href="https://github.com/danielrive/smart-cash-gitops-flux"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A quick introduction to GitOps
&lt;/h2&gt;

&lt;p&gt;GitOps is an operational model for cloud-native architectures,  it relies on a Git repository as the single source of truth. New commits imply infrastructure and application updates.&lt;/p&gt;

&lt;p&gt;OpenGitOps group has defined 5 principles, and while I won't delve into them, &lt;a href="https://opengitops.dev/"&gt;here&lt;/a&gt;, you can read more. If you take a look at those principles you will see that they are, in some sense related to some Kubernetes concepts. &lt;/p&gt;

&lt;p&gt;A great book to gain a better understanding of GitOps history and concepts is &lt;strong&gt;&lt;a href="https://developers.redhat.com/e-books/path-gitops"&gt;The Path to GitOps&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In summary, GitOps is centered around using a Git repository for defining and managing both infrastructure and application configurations through a Git-based workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is FluxCD
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://fluxcd.io/"&gt;FluxCD&lt;/a&gt; is an open-source GitOps operator for Kubernetes, you can declaratively define the desired state of your infrastructure and configurations in a Git repository. Flux monitors the repository and applies updates to the Kubernetes cluster when new changes arrive.&lt;/p&gt;

&lt;p&gt;Flux started as a monolith but in v2 it was broken up into individual components called GitOps Toolkit, this refers collection of specialized tools, Flux Controllers, composable APIs, and reusable Go packages available under the fluxcd GitHub organization. &lt;/p&gt;

&lt;p&gt;Core concepts and toolkit components are described &lt;a href="https://www.weave.works/technologies/what-is-flux-cd/"&gt;here&lt;/a&gt;. &lt;/p&gt;

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

&lt;h2&gt;
  
  
  Installing FluxCD in the cluster
&lt;/h2&gt;

&lt;p&gt;FluxCD &lt;a href="https://fluxcd.io/flux/installation/"&gt;installation&lt;/a&gt; can be done by Flux CLI, the most straightforward method can be done by the &lt;a href="https://fluxcd.io/flux/installation/bootstrap/"&gt;&lt;strong&gt;&lt;em&gt;flux bootstrap&lt;/em&gt;&lt;/strong&gt; command&lt;/a&gt;, this deploys the Flux controllers on the K8 cluster and configures them to synchronize the cluster to the Git repository, if the Git Repo doesn't exist, the bootstrap command will create it.&lt;/p&gt;

&lt;p&gt;To incorporate FluxCD installation into this project a new bash script has been added into the repository that contains the terraform code, this bash script will be execute by terraform as a null resource.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#/bin/bash&lt;/span&gt;

&lt;span class="c"&gt;## Configure Cluster Credentials&lt;/span&gt;

&lt;span class="c"&gt;# $1 = CLUSTER_NAME&lt;/span&gt;
&lt;span class="c"&gt;# $2 = AWS_REGION&lt;/span&gt;
&lt;span class="c"&gt;# $3 = GH_USER_NAME&lt;/span&gt;
&lt;span class="c"&gt;# $4 = FLUX_REPO_NAME&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------&amp;gt;  get eks credentials"&lt;/span&gt;
aws eks update-kubeconfig &lt;span class="nt"&gt;--name&lt;/span&gt; &lt;span class="nv"&gt;$1&lt;/span&gt;  &lt;span class="nt"&gt;--region&lt;/span&gt; &lt;span class="nv"&gt;$2&lt;/span&gt;

&lt;span class="c"&gt;## validate if flux is installed&lt;/span&gt;

&lt;span class="nv"&gt;flux_installed&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl api-resources | &lt;span class="nb"&gt;grep &lt;/span&gt;flux&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$flux_installed&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------&amp;gt;  flux is not installed"&lt;/span&gt;

  &lt;span class="c"&gt;### install flux&lt;/span&gt;

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------&amp;gt;  installing flux cli"&lt;/span&gt;

  curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://fluxcd.io/install.sh | &lt;span class="nb"&gt;sudo &lt;/span&gt;bash

  &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------&amp;gt;  run flux bootstrap"&lt;/span&gt;
  flux bootstrap github &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--owner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--repository&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"clusters/&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;/bootstrap"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--branch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;main &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--personal&lt;/span&gt;
&lt;span class="k"&gt;else
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"----------&amp;gt;  flux is installed"&lt;/span&gt;
&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;em&gt;flux bootstrap github&lt;/em&gt; command deploys the Flux controllers on the K8 cluster and configures the controllers to synchronize the Git repo with the cluster. This is done by some K8 manifests that are created and pushed to the repo in the path passed in the command.&lt;/p&gt;

&lt;p&gt;It's worth noting that some env variables like FLUX_REPO_NAME, and GH_USER_NAME are used by the bash script, these variables are passed as an argument in the bash script execution.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding FluxCD bootstrap script to terraform code
&lt;/h3&gt;

&lt;p&gt;The bash script will be executed in the GH workflow template created to deploy the infrastructure, the following job is added to the Workflow template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#### bash script arguments&lt;/span&gt;
  &lt;span class="c1"&gt;# $1 = CLUSTER_NAME&lt;/span&gt;
  &lt;span class="c1"&gt;# $2 = AWS_REGION&lt;/span&gt;
  &lt;span class="c1"&gt;# $3 = GH_USER_NAME&lt;/span&gt;
  &lt;span class="c1"&gt;# $4 = FLUX_REPO_NAME&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"bootstrap-flux"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt;          &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
    ./scripts/bootstrap-flux.sh ${local.cluster_name}  ${var.region} ${local.gh_username} ${data.github_repository.flux-gitops.name}
&lt;/span&gt;&lt;span class="no"&gt;    EOF
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;triggers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;cluster_oidc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cluster_oidc&lt;/span&gt;
    &lt;span class="nx"&gt;created_at&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created_at&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the &lt;em&gt;GITHUB_TOKEN&lt;/em&gt; variable is passed directly in the Github job.&lt;/p&gt;

&lt;p&gt;Once the workflow is ready you can push it to the repo and see how terraform will create all the infra and after EKS cluster creation will execute the bash script.&lt;/p&gt;

&lt;p&gt;You can run &lt;strong&gt;flux check&lt;/strong&gt; command locally to validate the status of the installation(you should have access to the cluster in your local env)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczt08fmy5h10p2gq0na3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fczt08fmy5h10p2gq0na3.png" alt="flux-check" width="575" height="460"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you take a look at the above image you will see that the Source Controller is deployed, &lt;a href="https://fluxcd.io/flux/components/source/"&gt;Source Controller&lt;/a&gt; enables seamless integration of various Git repositories with your Kubernetes cluster. Think of the Source Controller as an interface to connect with GitRepositories, OCIRepository, HelmRepository, and Bucket resources.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;✔ source-controller: deployment ready&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The bootstrap command will create a flux source and associate it to the repo passed in the command, to validate this you can list the git sources created and you will see the one, for now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get sources git
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjtan9rnhp0qc9mb6l02.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fkjtan9rnhp0qc9mb6l02.png" alt="Flux-git-source" width="800" height="32"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;and you can see the K8 CDRs created &lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get crds | grep flux&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Structuring the Git repository
&lt;/h2&gt;

&lt;p&gt;There are different strategies to structure the GitOps repository, for this scenario, a mono-repo strategy is used and &lt;a href="https://kustomize.io/"&gt;kustomize&lt;/a&gt; will be used to manage the K8 manifest for the application.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;./clusters&lt;/strong&gt;: contains all the cluster associated with the project, cluster for each environment or region should be placed here.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;./clusters/smart-cash-develop/bootstrap:&lt;/strong&gt; Yaml files created by fluxcd installation, also there is a file name &lt;strong&gt;core-kustomization.yaml&lt;/strong&gt; that points to a core folder that manages the manifests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;./clusters/smart-cash-develop/core:&lt;/strong&gt; Contains the main manifest for the project, manifest like FluxSources, and also kustomization files. Here will be placed the kustomization file for each microservice that will be created.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;./clusters/smart-cash-develop/core:&lt;/strong&gt; Manifests that create common resources for the cluster like namespaces, ingress, storage-classes, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Manifests:&lt;/strong&gt; This contains subfolders that contain the YAML files for each microservices.&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;├── clusters
    └── smart-cash-develop
        |── bootstrap
        |── common
        |   |── ingress-namespace.yaml
        |   └── namespaces.yaml
        |── core
        |   |── common-kustomize.yaml
        |   └── helm-cert-manager.yaml
        └── manifests
            └── app1
                |── base
                |   |── kustomization.yaml
                |   └── deployment.yaml
                └── overlays
                    |── develop
                    |   └── kustomization.yaml
                    └── production
                        └── kustomization.yaml


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

&lt;/div&gt;



&lt;h2&gt;
  
  
  Adding resources to the cluster
&lt;/h2&gt;

&lt;p&gt;Let's create a K8 namespace to be used for an nginx-ingress. The manifest for this can be placed in the &lt;em&gt;common&lt;/em&gt; folder. A FluxCD Kustomization can be added to synchronize the contents of this folder with the K8 cluster.&lt;/p&gt;

&lt;p&gt;The following is the Flux Kustomization that reconciles the Kubernetes manifests located at the path &lt;em&gt;./common&lt;/em&gt; in the Git repository .&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Note:&lt;/strong&gt; This file can be added in &lt;em&gt;clusters/smart-cash-develop&lt;/em&gt; folder, FluxCD will automatically create the Kustomization resource because this path was specified in the bootstrap command, and Flux created a Kustomization to synchronize it.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.toolkit.fluxcd.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;smartcash-common&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
  &lt;span class="na"&gt;targetNamespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
  &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitRepository&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;flux-system&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./kustomize"&lt;/span&gt;
  &lt;span class="na"&gt;prune&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;interval:&lt;/strong&gt; The period at which the Kustomization is reconciled.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;sourceRef:&lt;/strong&gt; refers to the Source object with the required Artifacts, in this case, our GitOps repository.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;prune:&lt;/strong&gt;: When is true, if previously applied objects are missing from the current revision, these objects are deleted from the cluster&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you push the Yaml file to the GitOps repo, Flux will create the resources in the cluster. You can validate running:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get kustomization -n flux-system&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0aqez3paizw42s38imz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs0aqez3paizw42s38imz.png" alt="common-kustomization" width="733" height="66"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The previous steps have created the FluxCD Kustomization to sync the &lt;em&gt;common&lt;/em&gt; folder with the cluster. Now, a Kustomize file needs to be added to specify which resource to create.&lt;/p&gt;

&lt;p&gt;Don't confuse the &lt;a href="https://fluxcd.io/flux/components/kustomize/kustomizations/#path"&gt;FluxCD Kustomization&lt;/a&gt; file with the K8 configuration management &lt;a href="https://kustomize.io/"&gt;Kustomize&lt;/a&gt;. FluxCD will look for the Kustomize file in the &lt;em&gt;common&lt;/em&gt; folder.&lt;/p&gt;

&lt;p&gt;Let's create and push the following files in the &lt;em&gt;common&lt;/em&gt; folder.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ns-nginx-ingress.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-ingress&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can wait for the flux reconciliation or force it using the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux reconcile kustomization smartcash-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If the process was successful you should see the nginx-ingress namespace.&lt;/p&gt;

&lt;h3&gt;
  
  
  Troubleshooting
&lt;/h3&gt;

&lt;p&gt;To validate the status of the reconciliation you can use the following command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;flux get kustomization smartcash-common
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For instance, a mistake in the name of the YAML files caused this error, which was visible in the output of the flux command.&lt;/p&gt;

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

&lt;p&gt;If you want more details you can check the K8 CDRs using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl describe kustomization smartcash-common &lt;span class="nt"&gt;-n&lt;/span&gt; flux-system 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Creating a Helm release for nginx-ingress
&lt;/h2&gt;

&lt;p&gt;The Flux Helm Controller will be used to install the ingress. &lt;a href="https://fluxcd.io/flux/components/helm/"&gt;The Helm Controller&lt;/a&gt; is a Kubernetes operator that enables the management of Helm chart releases.&lt;/p&gt;

&lt;p&gt;A FluxCD source for Helm needs to be added. This can be accomplished by using the following manifest, which should be placed in &lt;em&gt;clusters/smart-cash-develop&lt;/em&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;source.toolkit.fluxcd.io/v1beta2&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm-repo-nginx-ingress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m0s&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci&lt;/span&gt;
  &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;oci://ghcr.io/nginxinc/charts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This source fetches the Helm OCI repository oci://ghcr.io/nginxinc/charts every 5 minutes, and the artifact is stored and updated each time new updates are done to the repository.&lt;/p&gt;

&lt;p&gt;After creating the Helm source, you can proceed to create the Helm release. This release specifies the chart to install in the cluster, with the chart being fetched from the source already created. The following manifest can be used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;helm.toolkit.fluxcd.io/v2beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRelease&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-ingress&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-ingress&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;interval&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;10m0s&lt;/span&gt;
  &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;chart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nginx-ingress&lt;/span&gt;
      &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;0.17.1&lt;/span&gt;
      &lt;span class="na"&gt;sourceRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;HelmRepository&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;helm-repo-nginx-ingress&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;flux-system&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;To delegate the creation of the HelmRelease task to flux, this file can be added to the common folder and in the Kustomize file as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kustomize.config.k8s.io/v1beta1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Kustomization&lt;/span&gt;

&lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;ns-nginx-ingress.yaml&lt;/span&gt;
&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nginx-ingress-helm.yaml&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After updating and pushing the files, you can validate the creation of the Helm Release and nginx-ingress resources.&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="s"&gt;flux get helmreleases -n nginx-ingress&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Up to this point, we've covered the second phase of this project. In the upcoming articles, you'll delve into the implementation of various other tools and continue building the project.&lt;/p&gt;

&lt;p&gt;If you have any feedback or suggestions, please feel free to reach out to me on &lt;a href="https://www.linkedin.com/in/danielrive/"&gt;LinkedIn&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>gitops</category>
      <category>aws</category>
      <category>fluxcd</category>
    </item>
    <item>
      <title>Smart-Cash Project - AWS Infrastructure - Terraform and GitHub Actions</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Wed, 25 Oct 2023 22:12:36 +0000</pubDate>
      <link>https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3</link>
      <guid>https://dev.to/aws-builders/smartcash-project-infrastructure-terraform-and-github-actions-2bo3</guid>
      <description>&lt;p&gt;The journey to learn a new tool can be a little tricky, watching videos and reading some blogs can be an option, but watching and reading can not be enough for everyone, personally I need a hands-on approach for effective learning.&lt;/p&gt;

&lt;p&gt;That motivation drove me to embark on a personal project where I could implement the tools I had been using and those I wanted to explore. to initiate this journey I decided to create an application that helped me to follow my expenses, it could be something trivial but I needed a "business case" to begin building a solution. &lt;/p&gt;

&lt;p&gt;The general idea is to have an application that helps you track the monthly expenses and filter for a particular category to see where the money is going.&lt;/p&gt;

&lt;p&gt;Let's start building the initial infrastructure needed for the application.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/aws-builders/smartcash-project-gitops-with-fluxcd-3aep"&gt;Here&lt;/a&gt; you can find the GitOps implementation(Part 2 of the project). &lt;/p&gt;

&lt;h3&gt;
  
  
  Source Code
&lt;/h3&gt;

&lt;p&gt;The version of the code used in this article can be found &lt;a href="https://github.com/danielrive/smart-cash/releases/tag/v1.0.0"&gt;here&lt;/a&gt;. only specific sections of the code are included here to provide explanations, as this approach avoids the need to paste the entire code.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;

&lt;p&gt;The image below shows the first version of the architecture to use, I chose Kubernetes for this project as it allows me to explore some tools for K8 and improve some skills to present a K8 certification. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1b6f3mgm9voy16u0lp8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fx1b6f3mgm9voy16u0lp8.jpeg" alt="Diagram Arch v1" width="800" height="543"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  IaC
&lt;/h3&gt;

&lt;p&gt;Terraform is the tool for IaC, while I won't delve into a detailed terraform code explanation, I will provide some key highlights:&lt;/p&gt;

&lt;h4&gt;
  
  
  Networking
&lt;/h4&gt;

&lt;p&gt;A &lt;a href="https://registry.terraform.io/modules/terraform-aws-modules/vpc/aws/latest"&gt;third-party&lt;/a&gt; TF module is used. to save some aws costs the NAT gateways have been disabled. &lt;/p&gt;

&lt;h4&gt;
  
  
  EKS
&lt;/h4&gt;

&lt;p&gt;A TF module has been developed to deploy the resources needed for an EKS cluster. I created manually an IAM user and passed it as a variable(&lt;em&gt;var.userRoleARN&lt;/em&gt;) to the EKS module. This internally runs an &lt;em&gt;eksctl&lt;/em&gt; command to add it to the RBAC configs. This user will be used to operate the cluster locally.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"null_resource"&lt;/span&gt; &lt;span class="s2"&gt;"iam-role-cluster-access"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;provisioner&lt;/span&gt; &lt;span class="s2"&gt;"local-exec"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;command&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;
      curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp
      /tmp/eksctl version
      /tmp/eksctl create iamidentitymapping --cluster ${local.eksClusterName} --region=${var.region} --arn ${var.userRoleARN} --group system:masters --username "AWSAdministratorAccess:{{SessionName}}"
&lt;/span&gt;&lt;span class="no"&gt;    EOF
&lt;/span&gt;  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;depends_on&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_eks_cluster&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kube_cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;aws_eks_node_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;worker-node-group&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;EKS worker nodes will run in public subnets, this is because I have disabled the NAT GW to save some costs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure Pipeline
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Branch strategy
&lt;/h3&gt;

&lt;p&gt;I will follow a common branch strategy as the following image shows. The main branch is associated with the production environment.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;

&lt;p&gt;GitHub actions will be used to implement the pipeline to deploy the infrastructure in AWS, if you are not familiar with GitHub actions terminology you can check the &lt;a href="https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions"&gt;documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You can find the YAML files in the &lt;em&gt;.github&lt;/em&gt; folder, which also contains other 3 subfolders, let's explore them in detail.&lt;/p&gt;

&lt;h4&gt;
  
  
  actions folder
&lt;/h4&gt;

&lt;p&gt;This folder contains the &lt;a href="https://docs.github.com/en/actions/creating-actions/creating-a-composite-action"&gt;GitHub composite Actions&lt;/a&gt; to use in the workflows, you can think of an action as a template that defines the task to execute(jobs). Now let's review the &lt;em&gt;terraform-plan&lt;/em&gt; action.&lt;/p&gt;

&lt;p&gt;The first part defines the name and the inputs for the action, in this case, I am just defining the working directory where TF is placed as an input.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Plan'&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Running&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plan'&lt;/span&gt;

&lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;WORKING_DIRECTORY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;directory&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;where&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;tf&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;is'&lt;/span&gt;
    &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;default&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/infra/terraform'&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The second part defines the tasks to execute, this action will start installing Terraform.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform install&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;install-terraform'&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;hashicorp/setup-terraform@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
         &lt;span class="na"&gt;terraform_version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.TERRAFORM_VERSION&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 terraform version&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;validate-tf-version&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform version&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice that the step &lt;em&gt;Terraform install&lt;/em&gt; is using an external GH action, &lt;a href="https://github.com/marketplace/actions/hashicorp-setup-terraform"&gt;hashicorp/setup-terraform@v2&lt;/a&gt;, the version of this action is specified after the @. This action is available in the &lt;a href="https://github.com/marketplace?type=actions"&gt;GH actions marketplace&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The next step is to run terraform init but I won't delve into detail, therefore, let's proceed to the final two steps.&lt;/p&gt;

&lt;p&gt;The step &lt;em&gt;Run terraform plan&lt;/em&gt; runs the TF plan command passing some variables that are defined in the workflow definition, the plan generated is saved in a file named with the GH actions run id.&lt;/p&gt;

&lt;p&gt;Finally, the plan generated in the previous step is published as an artifact, that is used for the action created for the TF apply process.&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;Run terraform plan&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
            &lt;span class="s"&gt;terraform plan \&lt;/span&gt;
            &lt;span class="s"&gt;-input=false \&lt;/span&gt;
            &lt;span class="s"&gt;-var 'region=${{ env.AWS_REGION }}' \&lt;/span&gt;
            &lt;span class="s"&gt;-var 'environment=${{ env.ENVIRONMENT }}' \&lt;/span&gt;
            &lt;span class="s"&gt;-var 'project_name=${{ env.PROJECT_NAME }}' \&lt;/span&gt;
            &lt;span class="s"&gt;-out ${{ github.run_id }}.tfplan&lt;/span&gt;
        &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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;inputs.WORKING_DIRECTORY&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;Publish Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&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;tf-plan&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&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;github.workspace&lt;/span&gt;&lt;span class="nv"&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;inputs.WORKING_DIRECTORY&lt;/span&gt;&lt;span class="nv"&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;github.run_id&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}.tfplan'&lt;/span&gt;

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

&lt;/div&gt;



&lt;h4&gt;
  
  
  jobs
&lt;/h4&gt;

&lt;p&gt;This folder stores some bash scripts used in the pipelines to perform specific tasks, currently just one script is stored here, and its purpose is to create the S3 bucket and the DynamoDB tables used for the TF state.&lt;/p&gt;

&lt;p&gt;The script runs some AWS CLI commands to validate if the S3 bucket and DynamoDB table exist, if not the resources are created.&lt;/p&gt;

&lt;p&gt;A composite action has been created to execute this script, you can find it with the name terraform-backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;backend&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;set-up'&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;set-up&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;terraform&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;plan'&lt;/span&gt;
&lt;span class="na"&gt;runs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;using&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;composite"&lt;/span&gt;
  &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Config tf backend&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tf-backend&lt;/span&gt;
      &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./terraform-backend.sh&lt;/span&gt;
      &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
      &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.github/workflows&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Workflows
&lt;/h4&gt;

&lt;p&gt;Workflows define the triggers(commits, tags, branches...) for the pipeline and also specify the process to execute(jobs), inside the workflow you can use the composite actions already defined. &lt;/p&gt;

&lt;h5&gt;
  
  
  Workflow Template
&lt;/h5&gt;

&lt;p&gt;I have created a workflow template that defines the common tasks for all the environments, but this does not define the triggers. let's take a look at the template.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform deploy template&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;inputs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;aws&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;region&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;where&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;the&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;resources&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;will&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;be&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;deployed'&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
        &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;string&lt;/span&gt;
     &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;AWS_ACCOUNT_NUMBER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The initial section defines the inputs and secrets for the workflow, the main difference between inputs and secrets is that Github hides the value of the secret in the workflow logs.&lt;/p&gt;

&lt;p&gt;The second part of the template defines some common environment variables.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.ENVIRONMENT }}&lt;/span&gt;
  &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.AWS_REGION }}&lt;/span&gt;
  &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.PROJECT_NAME }}&lt;/span&gt;
  &lt;span class="na"&gt;TERRAFORM_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ inputs.TERRAFORM_VERSION }}&lt;/span&gt;
  &lt;span class="na"&gt;AWS_IAM_ROLE_GH&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GitHubAction-AssumeRoleWithAction'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can observe that the value for the env variable AWS_REGION is set to the value passed by the input inputs.AWS_REGION defined earlier. Why it is done? let's review one snippet of code from the composite action for the terraform plan to gain a better understanding.&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;Run terraform plan&lt;/span&gt;
        &lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-plan&lt;/span&gt;
        &lt;span class="s"&gt;run&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
            &lt;span class="s"&gt;terraform plan \&lt;/span&gt;
            &lt;span class="s"&gt;-input=false \&lt;/span&gt;
            &lt;span class="s"&gt;-var 'project_name=${{ env.PROJECT_NAME }}' \&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As you can see I'm using the env variable PROJECT_NAME and passing it as a TF variable, this is possible because in the workflow I defined the value for the variable, and this is passed down in the runner. You can use inputs here but you would need to define the same input in the composite action.&lt;/p&gt;

&lt;p&gt;The last part of the template defines the jobs to execute&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;
&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
&lt;span class="c1"&gt;## Execute bash script that  create s3 bucket and dynamodb table for Terraform backend&lt;/span&gt;
  &lt;span class="na"&gt;set-up-terraform-backend&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;checkout-repo&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&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;configure aws credentials&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;aws-actions/configure-aws-credentials@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;role-to-assume&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;arn:aws:iam::${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;secrets.AWS_ACCOUNT_NUMBER&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}:role/${{&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;env.AWS_IAM_ROLE_GH&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;}}'&lt;/span&gt; 
          &lt;span class="na"&gt;role-session-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;GitHub_to_AWS_via_FederatedOIDC&lt;/span&gt;
          &lt;span class="na"&gt;aws-region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ env.AWS_REGION }}&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;config tf backend&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;tf-backend&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./terraform-backend.sh&lt;/span&gt;
        &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.github/jobs/&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The provided code shows the job to set up the Terraform backend, this job is executed in a Ubuntu runner that is defined by runs-on.&lt;/p&gt;

&lt;p&gt;Let's check the steps executed for the job.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The first step is to make a checkout of the repo into the runner, this is done by an &lt;a href="https://github.com/marketplace/actions/checkout"&gt;external composite action&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;To execute Terraform the job needs access to AWS, this is done by an &lt;a href="https://github.com/marketplace/actions/configure-aws-credentials-action-for-github-actions"&gt;external composite action&lt;/a&gt;, to avoid pass Access and Secret keys OpenID Connect (OIDC) will be used, which allows GitHub Actions workflows to access AWS. You need to create an IAM IdP in your AWS account and associate it with an IAM role, this role must contain the permissions that Terraform needs to run properly.
The details for the configuration can be checked &lt;a href="https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Once the AWS credentials have been configured you can call the composite action created to set up the Teraform backend.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The other jobs follow a similar pattern but they execute the composite actions for the Terraform plan and apply.&lt;/p&gt;

&lt;h5&gt;
  
  
  Using the template
&lt;/h5&gt;

&lt;p&gt;Once the template is ready you can create the workflows for each environment. let's review the workflow for the develop environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Terraform infra workflow DEVELOP&lt;/span&gt;
&lt;span class="na"&gt;run-name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;terraform-deploy-DEVELOP&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;develop&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;infra/**"&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;develop&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;id-token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt; &lt;span class="c1"&gt;# This is required for requesting the JWT&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;  &lt;span class="c1"&gt;# This is required for actions/checkout&lt;/span&gt;

&lt;span class="na"&gt;defaults&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;shell&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bash&lt;/span&gt;
    &lt;span class="na"&gt;working-directory&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./infra/terraform&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;### Makes a call to the workflow template defined to execute terraform, in this case, the variables define the develop environment&lt;/span&gt;
  &lt;span class="na"&gt;terraform-deploy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;danielrive/smart-cash/.github/workflows/run-terraform-template.yaml@develop&lt;/span&gt;
    &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; 
      &lt;span class="na"&gt;AWS_REGION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;us-west-2'&lt;/span&gt;
      &lt;span class="na"&gt;ENVIRONMENT&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;develop'&lt;/span&gt;
      &lt;span class="na"&gt;PROJECT_NAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;smart-cash'&lt;/span&gt;
      &lt;span class="na"&gt;TERRAFORM_VERSION&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;1.4.6'&lt;/span&gt;
    &lt;span class="na"&gt;secrets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;AWS_ACCOUNT_NUMBER&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AWS_ACCOUNT_NUMBER_DEVELOP }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I have defined two triggers for the workflow, The first trigger is when new updates are pushed to &lt;em&gt;infra&lt;/em&gt; folder within the develop branch, and the second trigger is when a new Pull request is open with the develop branch as a base. &lt;/p&gt;

&lt;p&gt;The permissions section is necessary to generate a token used to establish the connection with AWS IAM IdP.&lt;/p&gt;

&lt;p&gt;In the job definition, you can is where you can utilize the previously created template, you need to specify the path where the template is located and the values for the inputs defined in the template.&lt;/p&gt;

&lt;p&gt;You can create other workflows for other environments and pass the respective values for inputs.&lt;/p&gt;

&lt;p&gt;Up to this point, you've covered the first phase of this project. In the upcoming articles, you'll delve into the implementation of various other tools and continue building the project.&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>githubactions</category>
      <category>kubernetes</category>
      <category>aws</category>
    </item>
    <item>
      <title>Containers - entre historia y runtimes</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Wed, 26 Apr 2023 22:50:09 +0000</pubDate>
      <link>https://dev.to/aws-builders/containers-entre-historia-y-runtimes-16ee</link>
      <guid>https://dev.to/aws-builders/containers-entre-historia-y-runtimes-16ee</guid>
      <description>&lt;p&gt;Estudiando kubernetes gasté un tiempo considerable intentando entender muchos conceptos, por ejemplo, por todo lado se habla de &lt;em&gt;OCI compliant&lt;/em&gt;, buscas &lt;em&gt;OCI&lt;/em&gt; y te lleva a &lt;em&gt;runtime-spec&lt;/em&gt;, buscas &lt;em&gt;runtimes&lt;/em&gt; y te lleva a &lt;em&gt;containerd&lt;/em&gt;, &lt;em&gt;runc&lt;/em&gt;, &lt;em&gt;image-spec&lt;/em&gt;, &lt;em&gt;cgroups&lt;/em&gt;, &lt;em&gt;namespaces&lt;/em&gt;, etc; puedes pasar días buscando, y mucho más cuando eres del tipo de persona que quiere entender a fondo cómo funcionan las cosas.&lt;/p&gt;

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

&lt;p&gt;Motivado por lo anterior, me decidí a escribir este post con la idea de compartir los conceptos que logré adquirir y que me han servido para entender varias cosas del gran mundo de los containers, en algunas cosas no voy a tan bajo nivel ya que hay muchos conceptos que todavía desconozco y puedo decir cosas equiviocadas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lo básico
&lt;/h2&gt;

&lt;p&gt;Iniciemos entendiendo un poco la idea detrás de los containers.&lt;/p&gt;

&lt;p&gt;Containers tienen como objetivo crear un ambiente virtual &lt;strong&gt;&lt;em&gt;aislado&lt;/em&gt;&lt;/strong&gt; el cual se pueda distribuir y desplegar fácilmente. Dentro del container pueden correr diferentes procesos los cuales deben estar aislados de otros corriendo en el host. El kernel de linux ofrece distintas funcionalidades que permiten la creación de estos ambientes. Hay dos componentes principales que quizás son el core de todos los containers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Linux namespaces
&lt;/h3&gt;

&lt;p&gt;Linux namespaces nos permite crear ambientes virtuales y aislados, estos particionan recursos del kernel y hacen que  sean visibles solo para los procesos que corren dentro del namespace, pero no para procesos externos. En otras palabras, namespaces nos facilitan el aislamiento entre procesos.&lt;/p&gt;

&lt;p&gt;¿Qué recursos se pueden particionar?, bueno esto va a depender del &lt;a href="https://www.redhat.com/sysadmin/7-linux-namespaces" rel="noopener noreferrer"&gt;tipo de namespace&lt;/a&gt; que se este usando, por ejemplo, network namespaces nos permite encapsular los recursos relacionados con networking, como interfaces, tablas de rutas, etc. De esta forma podemos crear una red virtual dentro de nuestro namespace.&lt;/p&gt;

&lt;p&gt;Este &lt;a href="https://www.redhat.com/sysadmin/7-linux-namespaces" rel="noopener noreferrer"&gt;post&lt;/a&gt; explica un poco más en detalle los namespaces.&lt;/p&gt;

&lt;h3&gt;
  
  
  cgroups
&lt;/h3&gt;

&lt;p&gt;Recordemos que el Kernel de Linux es la interfaz principal entre el hardware y los procesos, permitiendo la comunicación entre estos dos y ayudando a la gestión de recursos, por ejemplo, puede terminar procesos que consuman demasiada memoria para evitar afectar el sistema operativo. Adicionalmente pueden controlar qué procesos pueden consumir cierta cantidad de recursos.&lt;/p&gt;

&lt;p&gt;cgroups es una funcionalidad del Kernel de Linux que permite organizar jerárquicamente procesos y distribuir recursos(cpu, memoria, networking, storage) dentro de dicha jerarquía.&lt;/p&gt;

&lt;p&gt;Configurar cgroups puede ser un poco complejo, en mi caso estuve leyendo varios post acerca del tema y requiere cierto tiempo para entender por completo su funcionamiento. En esta &lt;a href="https://www.redhat.com/sysadmin/cgroups-part-one" rel="noopener noreferrer"&gt;serie de posts&lt;/a&gt; creados por RedHat se habla sobe cgroups y su configuración a través de systemd, pero si se desea entrar en detalle la &lt;a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html" rel="noopener noreferrer"&gt;documentación de Linux&lt;/a&gt; puede ser de ayuda.&lt;/p&gt;

&lt;p&gt;cgroups y namespaces se convierten en los ingredientes secretos en la creación de containers, namespaces permiten aislamiento a nivel de recursos y cgroups permiten controlar los limites para dichos recursos.&lt;/p&gt;

&lt;p&gt;Por suerte hoy en día con una sola linea podemos crear un container, no tenemos que entrar a configurar namespaces ni cgroups.&lt;/p&gt;

&lt;p&gt;Veamos un poco de la evolución de los containers y así vamos aclarando ciertas cosas.&lt;/p&gt;

&lt;h3&gt;
  
  
  Un poco de historia
&lt;/h3&gt;

&lt;p&gt;Docker fue el primero que popularizó los containers, era(o es) común asociar containers directamente con Docker, pero antes ya existía algo llamado LXC(Linux containers), el cual puede entenderse como un proveedor de ambientes virtuales en Linux que usa ciertos componentes del Kernel de Linux para crear ambientes aislados(containers). &lt;/p&gt;

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

&lt;p&gt;LXC se encuentra dentro del user-space, es decir, nosotros interactuamos con LXC y este se encarga de interactuar con los componentes del kernel para permitir la creación de containers. Aqui un &lt;a href="https://www.youtube.com/watch?v=aIwgPKkVj8s" rel="noopener noreferrer"&gt;video&lt;/a&gt; en donde se puede ver LXC en acción. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Nota:&lt;/strong&gt; Antes de LXC ya se habían desarrollado otros alternativas para la creación de containers como OpenVZ y Linux Vserver. LXC es mencionado inicialmente ya que es lo más cercano a Docker que es el software con el que muchos iniciamos interactuando con containers.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  La llegada de Docker
&lt;/h4&gt;

&lt;p&gt;Docker empaquetó LXC en una herramienta que facilitaba más la creación de containers. Al ganar popularidad se crearon mejoras y unos meses después Docker lanzó &lt;a href="https://github.com/opencontainers/runc/tree/main/libcontainer" rel="noopener noreferrer"&gt;libcontainer&lt;/a&gt; el cual está escrito en &lt;a href="https://github.com/opencontainers/runc/tree/main/libcontainer" rel="noopener noreferrer"&gt;Golang&lt;/a&gt; y básicamente reemplazaba LXC. &lt;/p&gt;

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

&lt;p&gt;Docker se enfocó más en la creación de containers optimizados para el despliegue de aplicaciones mejorando la portabilidad. Este &lt;a href="https://earthly.dev/blog/lxc-vs-docker/" rel="noopener noreferrer"&gt;post&lt;/a&gt; explica más detalladamente las diferencias entre LXC y Docker.&lt;/p&gt;

&lt;h4&gt;
  
  
  Definiendo un estándar para containers
&lt;/h4&gt;

&lt;p&gt;Como alternativa a Docker, empezaron a surgir otras opciones,CoreOS por su parte lanzó &lt;a href="https://www.redhat.com/en/topics/containers/what-is-rkt" rel="noopener noreferrer"&gt;rkt(2014)&lt;/a&gt; proponiendo mejores de seguridad, CoreOS &lt;a href="https://lwn.net/Articles/623875/" rel="noopener noreferrer"&gt;argumentaba&lt;/a&gt; que Docker había sido construido como un monolito el cual corría como root en el host, abriendo posibilidades a comprometer todo el host en el caso de un ataque.&lt;/p&gt;

&lt;p&gt;rkt usa &lt;a href="https://github.com/appc" rel="noopener noreferrer"&gt;appc(open source container)&lt;/a&gt; con el fin de mejorar la operabilidad, appc tiene como propósito crear un estándar general para crear containers buscando ser vendor-independent y OS-independent.&lt;/p&gt;

&lt;p&gt;Otras iniciativas empezaron a surgir debido a la alta popularidad de los containers y debido a esto, en 2015 se crea &lt;a href="https://opencontainers.org/about/overview/" rel="noopener noreferrer"&gt;OCI(Open Container Initiative)&lt;/a&gt; para definir un estandar para containers(&lt;a href="https://github.com/opencontainers/runtime-spec/blob/main/spec.md" rel="noopener noreferrer"&gt;runtimes&lt;/a&gt; e &lt;a href="https://github.com/opencontainers/image-spec/blob/main/spec.md" rel="noopener noreferrer"&gt;imagenes&lt;/a&gt;).&lt;/p&gt;

&lt;h4&gt;
  
  
  OCI Runtime spec
&lt;/h4&gt;

&lt;p&gt;&lt;em&gt;Runtime spec&lt;/em&gt; define la configuración(archivo JSON), ambiente y ciclo de vida de un container. Las configuraciones son definidas en un archivo llamado config.json, el cual contiene la metadata necesaria para la ejecución del container, este archivo es definido de acuerdo a plataforma a usar(windows, linux, solaris, etc).&lt;/p&gt;

&lt;p&gt;otro concepto a destacar es el &lt;em&gt;filesystem bundle&lt;/em&gt;, este es un grupo de archivos con la data y metadata para correr un container. Los principales archivos que deben contener son, el config.json mencionado anteriormente y el &lt;a href="https://www.baeldung.com/linux/rootfs" rel="noopener noreferrer"&gt;rootfs(linux file system)&lt;/a&gt;, este  &lt;em&gt;filesystem bundle&lt;/em&gt; se genera a través del container image.&lt;/p&gt;

&lt;p&gt;Todas las especificaciones para el container runtime son descritas &lt;a href="https://github.com/opencontainers/runtime-spec/blob/main/spec.md" rel="noopener noreferrer"&gt;aqui&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  OCI Image spec
&lt;/h4&gt;

&lt;p&gt;Docker en sus inicios ya había definido las especificaciones para la creación de imágenes&lt;a href="https://docs.docker.com/registry/spec/manifest-v2-2/" rel="noopener noreferrer"&gt;Image Manifest 2 Schema Version 2&lt;/a&gt;, al ser el más popular, OCI partió de este para crear un estándar más general, que no estuviera asociado a un vendor en específico. &lt;em&gt;Image spec&lt;/em&gt; define como construir y empaquetar container images, personalmente no he entendido del todo el funcionamiento pero aquí está la url del &lt;a href="https://github.com/opencontainers/image-spec" rel="noopener noreferrer"&gt;repo&lt;/a&gt; y un &lt;a href="https://blog.quarkslab.com/digging-into-the-oci-image-specification.html" rel="noopener noreferrer"&gt;blog-post&lt;/a&gt; que contienen mayor información.&lt;/p&gt;

&lt;p&gt;Haciendo uso del &lt;em&gt;Image spec&lt;/em&gt;, se puede crear un container image que puede ser ejecutada por cualquier &lt;em&gt;OCI Runtime&lt;/em&gt;, esto quiere decir que a través del &lt;em&gt;Image spec&lt;/em&gt; se puede generar el &lt;em&gt;filesystem bundle&lt;/em&gt;, el cual es usado por el runtime para la creación y ejecución del container.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;The Runtime Specification outlines how to run a "filesystem bundle" that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h4&gt;
  
  
  Container runtimes y Kubernetes
&lt;/h4&gt;

&lt;p&gt;En el 2015 se lanza el primer release de kubernetes, el cual usaba Docker como runtime.&lt;/p&gt;

&lt;p&gt;Docker decide dividir el monolito creado. libcontainer es donado a OCI y Docker empieza a trabajar en un proyecto llamado runC, este se puede ver como una herramienta que lee OCI specifications e interactúa con libcontainer para la creación de containers. runC es independiente del Docker Engine y es donado a OCI.&lt;/p&gt;

&lt;p&gt;runC es una low-level runtime por lo que también se desarrolla &lt;em&gt;containerd&lt;/em&gt; el cual es como una interfaz entre el cliente y runC.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzi3mkr4s6u5bbmj3y9ls.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzi3mkr4s6u5bbmj3y9ls.png" title="fuente: https://images.techhive.com/images/article/2016/04/docker-runc-100656060-large.idge.png" alt="docker-runc-containerd"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Hasta el momento solo se ha cubierto parte del origen de los container y el origen de algunas herramientas que seguimos viendo hoy en día como runC y conteinerd. En lo que sigue del post trataré de exponer un poco más a fondo las &lt;em&gt;container images&lt;/em&gt; al igual que algunas &lt;em&gt;containers runtimes&lt;/em&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Container Images
&lt;/h3&gt;

&lt;p&gt;Antes de entrar a ver las &lt;em&gt;containers runtimes&lt;/em&gt;, es importante entender qué es lo que contienen las &lt;em&gt;containers images&lt;/em&gt;, para ello vamos a usar &lt;a href="https://www.redhat.com/en/topics/containers/what-is-skopeo" rel="noopener noreferrer"&gt;Skopeo&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Skopeo permite manipular e inspeccionar &lt;em&gt;container images&lt;/em&gt; ya sea para Windows, Linux o MacOs. En este caso vamos a usar Skopeo para obtener "el contenido" de una imagen que se encuentra en DockerHub, esto es muy similar al comando &lt;a href="https://docs.docker.com/engine/reference/commandline/export/" rel="noopener noreferrer"&gt;docker export&lt;/a&gt;,pero en este caso no vamos a instalar Docker.&lt;/p&gt;

&lt;h4&gt;
  
  
  copiando images con skopeo
&lt;/h4&gt;

&lt;p&gt;Para instalar skopeo se puede usar snap en ubuntu&lt;/p&gt;

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

sudo snap install skopeo --edge


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

&lt;/div&gt;

&lt;p&gt;una vez que finalice la instalación podemos copiar una imagen que se encuentra en DockerHub a nuestro local. En este caso se va a usar la imagen de golang.&lt;/p&gt;

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

sudo skopeo --insecure-policy copy docker://golang:latest  oci://home/ubuntu/example-dev-to/golang-image-v2


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

&lt;/div&gt;

&lt;p&gt;Skopeo copia el contenido de la imagen en el destino especificado, en este caso &lt;code&gt;oci:/home/ubuntu/example-dev-to/golang-image-v2&lt;/code&gt;. En la imagen se puede ver que se tiene un archivo index.json, oci-layout y un directorio llamado blobs. Esto corresponde a la estructura de archivos definidos por &lt;a href="https://github.com/opencontainers/image-spec/blob/main/spec.md" rel="noopener noreferrer"&gt;OCI&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;el &lt;em&gt;index.json&lt;/em&gt; se puede entender como un resumen de la imagen, en donde se ve el sistema operativo y la arquitectura, además se especifica la ubicación del &lt;em&gt;image manifest&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;El &lt;em&gt;image manifest&lt;/em&gt; contiene metadata de la imagen al igual que las especificaciones de cada layer creada.&lt;/p&gt;

&lt;p&gt;Revisando el index.json vamos a encontrar lo siguiente:&lt;/p&gt;

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

&lt;p&gt;Se puede ver información acerca del sistema operativo y arquitectura soportados por la imagen. El digest(linea 6) nos indica en que archivo se encuentra el manifest.json.&lt;/p&gt;

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

&lt;p&gt;En el manifest(imagen anterior) se puede ver el digest para el config file y para cada una de las layers que se tienen. El &lt;a href="https://github.com/opencontainers/image-spec/blob/main/media-types.md" rel="noopener noreferrer"&gt;mediaType&lt;/a&gt; puede entenderse como el formato de cada archivo, por ejemplo la linea 4 nos dice que el archivo config de formato json se puede identificar con el digest &lt;code&gt;bdba673e96d6e9707e2a724103e8835dbdd11dc81ad0c76c4453066ed8db29fd&lt;/code&gt;. Este se puede encontrar en la carpeta blobs y va a lucir como la siguiente imagen.&lt;/p&gt;

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

&lt;p&gt;Este archivo ya contiene más información de la imagen, por ejemplo podemos ver el workdir y algunas variables de entorno.&lt;/p&gt;

&lt;p&gt;pasemos ahora a las layers, en el manifest podemos identificar los digest para cada layers, si vemos el media type nos indica que es &lt;code&gt;v1.tar+gzip&lt;/code&gt;, en este caso tenemos que descomprimir el contenido de dicho digest, para ello vamos a usar &lt;code&gt;tar&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Una vez termine el proceso podemos analizar el resultado, en este caso vamos a tener una serie de directorios que representan el rootfs de la imagen, estos archivos van a hacer parte de un layer en específico. Si observamos la siguente imagen podemos ver que tenemos /home, /etc y /bin, etc, los cuales representan el sistema de archivos de linux(rootfs).&lt;/p&gt;

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

&lt;p&gt;Con esto vemos a alto nivel el contenido de un &lt;em&gt;container image&lt;/em&gt;, al final el &lt;em&gt;container runtime&lt;/em&gt; es el que se encarga de descomprimir y leer todos estos archivos, el cual va a ser usado para correr el container.&lt;/p&gt;

&lt;p&gt;Hasta aquí va la primera parte de este post, en la siguiente veremos un poco m'as los container runtimes.&lt;/p&gt;

</description>
      <category>docker</category>
      <category>containers</category>
      <category>spanish</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>AWS Event-Bridge and Lambda to copy RDS snapshots to another Region</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Sun, 02 Apr 2023 19:22:31 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-event-bridge-and-lambda-to-copy-rds-snapshots-to-another-region-kl3</link>
      <guid>https://dev.to/aws-builders/aws-event-bridge-and-lambda-to-copy-rds-snapshots-to-another-region-kl3</guid>
      <description>&lt;p&gt;A few months ago I was asked to design the DRP process(Multi-Region) for a project that used RDS(PostgreSQL). RDS instances were critical components, these stored PII information. RDS automatically takes snapshots of the instances and you can use them to recreate the instances in case of failure, these snapshots just can be used in the same region but you can share or copy them between accounts and Regions, here some &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_WorkingWithAutomatedBackups.html" rel="noopener noreferrer"&gt;AWS Docs related RDS automatic snapshots&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;My initial idea was to create a K8 job to run pg_dump for each RDS instance and then, upload the file to an S3 bucket created in a different region, not too bad but this requires more work to backup and restore. so I decided to copy snapshots between regions, a new challenge appeared here, how to automate that copy? In this post I will show the approach that I follow to solve this, one crucial point to mention here is that for a big number of snapshots, this solution could not be the best due to some limitations that AWS RDS to copy snapshots.&lt;/p&gt;

&lt;p&gt;AWS Documentation says:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"You can have up to 20 snapshot copy requests in progress to a single destination Region per account."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This approach uses AWS event-bridge and Lambda to automate the copy process, at summary, even-bridge detects that a new RDS snapshot has been created and triggers a lambda function to copy the snapshot to the other region.&lt;/p&gt;

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

&lt;blockquote&gt;
&lt;p&gt;A terraform code was created for this pods and you can check it &lt;a href="https://github.com/danielrive/blog-posts/blob/main/copy-rds-snapshots" rel="noopener noreferrer"&gt;here&lt;/a&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  RDS Snapshot
&lt;/h2&gt;

&lt;p&gt;You can configure automated RDS snapshots for your instance, this occurs daily during the backup window that you define in the instance creation. &lt;br&gt;
In this case, the automated RDS snapshot was configured in each instance, this just creates the snapshot in the same account and region where the RDS instance was created.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Event-Bridge
&lt;/h2&gt;

&lt;p&gt;EventBridge is a serverless service that uses events to connect application components together, making it easier for you to build scalable event-driven applications. You can use it to route events from sources such as home-grown applications, AWS services, and third-party software to consumer applications across your organization.&lt;/p&gt;

&lt;p&gt;In this case, AWS generates a significant number of events for some services, for RDS you can find the events divided into categories, the following table shows the events for RDS snapshots. when RDS starts an automated snapshot, AWS registers that event. You can find all the events in the &lt;a href="https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_Events.Messages.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt;.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  How to use the events?
&lt;/h3&gt;

&lt;p&gt;Let's start with an important concept in the EventBridge world, An event bus. This a pipeline that receives events, you can configure rules to manipulate the events and specify actions when these came. Events are represented as JSON objects and they all have a similar structure and the same top-level fields. By default, the AWS accounts have a default event bus that receives events from AWS services.&lt;/p&gt;

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

&lt;p&gt;In this case, we can create a rule using the default event-bus.&lt;/p&gt;

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

&lt;p&gt;You can choose the AWS service to use and AWS will show you and JSON with an example of how the event will look.&lt;/p&gt;

&lt;p&gt;For our case we can use a simpler event using the EventID for automated snapshots, RDS-EVENT-0091, you can refer to the image shown at the top of the post for more information.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 json
{
  "source": ["aws.rds"],
  "detail-type": ["RDS DB Snapshot Event"],
  "account": ["1234567890"],
  "region": ["us-east-1"],
  "detail": {
     "SourceType": ["SNAPSHOT"],
      "EventID": ["RDS-EVENT-0091"]   
    }
}


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

&lt;/div&gt;

&lt;p&gt;With the event-pattern defined, we can specify the lambda function to execute when this event comes to the default event bus.&lt;/p&gt;

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

&lt;p&gt;This means that if an event is received in the default event bus and matches with the pattern specified, that will trigger the lambda and pass a JSON with the event generated, this looks like this:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&lt;br&gt;
 json&lt;br&gt;
{&lt;br&gt;
  "version": "0",&lt;br&gt;
  "id": "844e2571-85d4-695f-b930-0153b71dcb42",&lt;br&gt;
  "detail-type": "RDS DB Snapshot Event",&lt;br&gt;
  "source": "aws.rds",&lt;br&gt;
  "account": "123456789012",&lt;br&gt;
  "time": "2018-10-06T12:26:13Z",&lt;br&gt;
  "region": "us-east-1",&lt;br&gt;
  "resources": ["arn:aws:rds:us-east-1:123456789012:db:mysql-instance-2018-10-06-12-24"],&lt;br&gt;
  "detail": {&lt;br&gt;
    "EventCategories": ["creation"],&lt;br&gt;
    "SourceType": "SNAPSHOT",&lt;br&gt;
    "SourceArn": "arn:aws:rds:us-east-1:123456789012:db:mysql-instance-2018-10-06-12-24",&lt;br&gt;
    "Date": "2018-10-06T12:26:13.882Z",&lt;br&gt;
    "SourceIdentifier": "rds:mysql-instance-2018-10-06-12-24",&lt;br&gt;
    "Message": "Automated snapshot created"&lt;br&gt;
  }&lt;br&gt;
}

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

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Lambda Function&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;The lambda function is a python code that gets the events and extracts the useful information and starts a copy in another region.&lt;/p&gt;

&lt;p&gt;The lambda functions just start the copy, the function doesn't wait to be completed the task.&lt;/p&gt;

&lt;p&gt;You can see the python code &lt;a href="https://github.com/danielrive/blog-posts/blob/main/copy-rds-snapshots/modules/python_code/copy-snapshots.py" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>eventdriven</category>
      <category>python</category>
      <category>aws</category>
      <category>devops</category>
    </item>
    <item>
      <title>Enabling logs and alerting in AWS EKS cluster - CloudWatch Log Insights and Metric filters</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Tue, 10 Jan 2023 00:04:53 +0000</pubDate>
      <link>https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-2-log-insights-and-metric-filters-3ped</link>
      <guid>https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-2-log-insights-and-metric-filters-3ped</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-1-2gnb"&gt;first part&lt;/a&gt; of this post, I described the steps to enable logs in an EKS cluster, control plane logs, and container logs using Fluent-bit and CloudWatch, in this post I will show how to get helpful information from logs and create alerts for specific events.&lt;/p&gt;

&lt;p&gt;AWS CloudWatch Logs could be used to store logs generated by resources created in AWS or external resources, once the logs are in CloudWatch you can run some queries to get specific information and create alerts for specific events.&lt;/p&gt;

&lt;h2&gt;
  
  
  CloudWatch Log Insights
&lt;/h2&gt;

&lt;p&gt;CloudWatch Logs Insights allows search and analysis of log data stored in Amazon CloudWatch Logs, queries can be run to identify potential causes and validate fixes, an advantage of Logs Insights is the ability to discover fields, doing more easy the process to run queries. Automatically Logs Insights define 5 fields:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;message: This field contains the original log message sent to CloudWatch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;timestamp: contains the event timestamp registered in the original event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;ingestionTime: contains the time when CloudWatch Logs received the log event.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;logStream: contains the name of the log stream where the event was added.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;log: it is an identifier in the form of account-id:log-group-name. When querying multiple log groups, this can be useful to identify which log group a particular event belongs to.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those fields are discovered by CloudWatch and depending on the log type that we are using CloudWatch will discover more fields, for instance, for EKS control plane logs you can see the field shown in the following image:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Running queries in AWS Log Groups
&lt;/h2&gt;

&lt;p&gt;Queries can be run to search specific events, the field discovery is really helpful in designing the query to run, in some cases when you don't know the structure of the logs you can run a simple query to get the fields that CloudWatch discovered, and use them to design the query based on the use case.&lt;/p&gt;

&lt;p&gt;An important thing to mention here is if the CloudWatch log groups have been encrypted by KMS you must have permission to use the key.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to run queries?
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Go to AWS CloudWatch service, in the left panel select Logs Insights.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Select the logs groups to run queries, up to 20 log groups can be selected, Logs Insights will search in the groups specified.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;By default CloudWatch shows a simple query, you can run it and validate the fields discovered by CloudWatch. The following image shows a query that gets up to 10 results, you can check it and validate the fields.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/CWL_QuerySyntax.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt; describes the query sintax that you can use.&lt;/p&gt;

&lt;h4&gt;
  
  
  Queries examples for EKS
&lt;/h4&gt;

&lt;h5&gt;
  
  
  Search API calls made by kubectl user-agent
&lt;/h5&gt;

&lt;p&gt;The following example searches the calls made to Kube API using the kubectl command and with the GET action. In this case, the log group is the one that EKS has created when you enable logging in the cluster, in the &lt;a href="https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-1-2gnb"&gt;previous&lt;/a&gt; post I mentioned the name-format and how to enable it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @logStream, @timestamp, @message
| filter @logStream like /kube-apiserver-audit/
| filter userAgent like /kubectl/
| sort @timestamp desc
| filter verb like /(get)/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first line is used to specify the fields that you want to show in the results, the query in the example will show the logStream name, the timestamp, and the message, you can add the fields that you want.&lt;/p&gt;

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

&lt;h5&gt;
  
  
  Search events filtering by namespace
&lt;/h5&gt;

&lt;p&gt;You can use the fields discovered by CloudWatch to create your queries, in this case for EKS control logs, one field discovered is objectRef.namespace, and the following query uses it to get the events where the kube-system namespace is used.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, @message
| sort @timestamp desc
| filter objectRef.namespace like 'kube-system'
| limit 2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of the previous query could look like:&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Creating alerts for specific events
&lt;/h2&gt;

&lt;p&gt;CloudWatch logs can look for specific patterns inside the events that are sent, this allows the creation of metrics to monitor if a particular event happened and create alerts for this, for that we need to use AWS CloudWatch metric filters, this is configured directly in the Log group created and you must specify a pattern.&lt;/p&gt;

&lt;p&gt;To create a metric filter you must select the Log Group to use, then in actions, you will see the option to create a metric filter.&lt;/p&gt;

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

&lt;h3&gt;
  
  
  Defining a pattern for the filter
&lt;/h3&gt;

&lt;p&gt;When you are creating the filter you need to define a pattern to specify what to look for in the log file. When the logs are in JSON format is more easily define the pattern because you just need to specify the name of the key that you want to evaluate, for this case you can use the following format:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{ PropertySelector Operator Value }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;For more details about the pattern syntax you can check the &lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html" rel="noopener noreferrer"&gt;AWS documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;When the logs are not in JSON format is more tricky define the pattern , in this case you need to take in mind that each space is taken as a word in the filter, for instance, suppose that you have the following log event:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;time="2022-10-09T20:37:25Z" level=info msg="STS response" accesskeyid=ABCD1234 accountid=123456789 arn="arn:aws:sts::123456789:assumed-role/test" client="127.0.0.1:1234" method=POST path=/authenticate&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;In this case, you have different words separated by space, if you want to look for some word specific you need to know the exact position of the element to compare, let's see this with an example, in this case, i want to match the logs with a level equal to info, if you see the previous log event you can validate that leve=info is the word number 2 in the whole event, in this case, the pattern could be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[word1,word2="level=info",word3]&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Remember that you need to include the whole word that you want to compare in this case you can use leve=info or you put the word between * which means any match with the word specified. let's see the result of the previous pattern.&lt;/p&gt;

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

&lt;p&gt;If you see, CloudWatch is showing each word defined in the pattern and the events that match.&lt;/p&gt;

&lt;p&gt;Let's see more examples to be more clear&lt;/p&gt;

&lt;h4&gt;
  
  
  Metric Filter to alert when actions are made in AWS-AUTH configmap
&lt;/h4&gt;

&lt;p&gt;AWS-AUTH configmap is used to authenticate the user by IAM RBAC, and part of this kind of event looks like the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kind:Event
level: Metadata
objectRef.apiVersion: v1
objectRef.name: aws-auth
objectRef.namespace: kube-system
objectRef.resource: configmaps
verb: get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unauthorized modifications in this configmap could be a security risk, a metric filter can be created to alert when this configmap is edited. The pattern could be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{( $.objectRef.name = "aws-auth" &amp;amp;&amp;amp; $.objectRef.resource = "configmaps" ) &amp;amp;&amp;amp;  ($.verb = "delete" || $.verb = "create" || $.verb = "patch"  ) }&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Metric filter for 403 code response in calls to K8 API-SERVER
&lt;/h4&gt;

&lt;p&gt;This is useful to detect several attempts to login or make calls to the cluster without valid credentials, part of this event looks like the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;requestURI: /
responseStatus.code:403
responseStatus.reason: Forbidden
responseStatus.status: Failure
sourceIPs.0 : 12.34.56.78
verb: get
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern could be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;{$.responseStatus.code = "403" }&lt;/code&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Metric filter to check Access Denied generated by AWS IAM-RBAC
&lt;/h4&gt;

&lt;p&gt;You can monitor the number access-denied in API calls, this is generated by the AWS IAM-RBAC, part of this event looks like the following message:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authenticator-8c7,2022-08-08 10:04:56,"time=""2020-08-04T28:43:44Z"" level=warning msg=""access denied"" client=""127.0.0.1:1234"" error=""sts getCallerIdentity failed: error from AWS (expected 200, got 403)"" method=POST path=/authenticate"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pattern could be:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;[time,level="*warning*",message1="*access*",message2="*denied*",more]&lt;/code&gt;&lt;/p&gt;

</description>
      <category>watercooler</category>
    </item>
    <item>
      <title>Enabling logs and alerting in AWS EKS cluster - CloudWatch and fluent-bit</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Thu, 10 Nov 2022 14:35:36 +0000</pubDate>
      <link>https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-1-2gnb</link>
      <guid>https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-1-2gnb</guid>
      <description>&lt;p&gt;Logs are a fundamental component in our environments, these provide useful information that helps to debug in the case of any issue or to identify events that can affect the security of the application. Logs should be enabled in each component, from the infrastructure level to the application level. This brings some challenges like where to store the logs, what kind of events log, how to search events, and what to do with the logs.&lt;/p&gt;

&lt;p&gt;In this post I will share my experience enabling and configuring logging in an EKS cluster, creating alerts to send a notification when a specific event appears in the logs.&lt;/p&gt;

&lt;p&gt;This is the part #1 in which I show how to enable control plane logging and container logging in an EKS cluster, in &lt;a href="https://dev.to/aws-builders/enabling-logs-and-alerting-in-eks-cluster-part-2-log-insights-and-metric-filters-3ped"&gt;the part #2&lt;/a&gt; I will show you how to enable some alerts using the logs groups created.&lt;/p&gt;

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

&lt;p&gt;Before to start is important to mention that logs can contain private data like user information, keys, passwords, etc. For this reason, logs should be encrypted at rest and enable restrictions to access them.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Control Plane logging
&lt;/h2&gt;

&lt;p&gt;Kubernetes architecture can be divided into a control plane and worker nodes, control plane contains the components that manage the cluster, components like etcd, API Server, Scheduler, and Controller Manager. Almost every action done in the cluster pass through the API Server that logs each event.&lt;/p&gt;

&lt;p&gt;AWS EKS manages the control plane for us, deploying and operating the necessary components. By default, EKS doesn't have logging enabled and actions from our side are required. Enabling EKS control plane logging is an easy task, you need to know what component log and enable it. You can enable logs for the API server, audit, authenticator, control manager, and scheduler. &lt;/p&gt;

&lt;p&gt;In my opinion, audit logs and authenticator are useful because records actions done in our cluster and help us to understand the origin of the actions and requests generated by the IAM authenticator.&lt;/p&gt;

&lt;p&gt;By terraform you can use the following code to create a simple cluster and enabling audit,api,authenticator, and scheduler logs.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Terraform
resource "aws_eks_cluster" "kube_cluster" {
  name                      = "test-cluster"
  role_arn                  = aws_iam_role.role-eks.arn
  version                   = "1.22"
  enabled_cluster_log_types = ["audit", "api", "authenticator","scheduler"]
  vpc_config {
    subnet_ids              = ["sub-1234","sub-5678"]
    endpoint_private_access = true
    endpoint_public_access  = true
  }
}


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

&lt;/div&gt;

&lt;p&gt;Logs are stored in AWS CloudWatch logs and the log group is created automatically following this name structure &lt;code&gt;/aws/eks/&amp;lt;cluster-name&amp;gt;/cluster&lt;/code&gt;, inside the group you can find the log stream for each component that you enabled&lt;/p&gt;

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

authenticator-123abcd
kube-apiserver-123abcd
kube-apiserver-123abcd


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

&lt;/div&gt;

&lt;p&gt;By default, the Log group created by AWS doesn't have encryption and retention days enabled, I recommend creating the logs group by yourself and specifying and KMS Key, and setting some time to expiry the logs, Kubernetes generates a considerable number of logs that will increase the size of the group that can impact the billing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Containers logging
&lt;/h2&gt;

&lt;p&gt;The steps mentioned above were to enable just logging in the control plane, to send logs generated by the applications running in the containers a log aggregator is necessary, in this case, I will use Fluent-Bit](&lt;a href="https://fluentbit.io/how-it-works/" rel="noopener noreferrer"&gt;https://fluentbit.io/how-it-works/&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;Fluent-Bit runs as a daemonSet in the cluster and sends logs to CloudWatch Logs. Fluent-Bit creates the log groups using the configuration specified in the kubernetes manifests. &lt;/p&gt;

&lt;p&gt;Here is important to mention that AWS has created a docker image for the daemonSet, this can be found in this &lt;a href="https://github.com/aws/aws-for-fluent-bit" rel="noopener noreferrer"&gt;link&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-logs-FluentBit.html" rel="noopener noreferrer"&gt;AWS describes the steps&lt;/a&gt; to run the daemonSet, this is done by some commands, but I will use a Kubernetes manifest that can be stored in our repository and then use Argo or Fluxcd to automate deployments.&lt;/p&gt;

&lt;p&gt;The following steps show the manifests to create the objects that Kubernetes needs to send containers logs to CloudWatch, you must have access to the cluster and by kubeclt command create the resources (&lt;code&gt;kubeckt apply -f manifest-name.yml&lt;/code&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Namespace creation
&lt;/h3&gt;

&lt;p&gt;A K8 namespace is necessary, &lt;em&gt;&lt;strong&gt;amazon-cloudwatch&lt;/strong&gt;&lt;/em&gt; name will use for this, you can change the name but make sure to use the same in the following steps.&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Namespace&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  2. ConfigMap for aws-fluent-bit general configs
&lt;/h3&gt;

&lt;p&gt;This configMap is necessary to specify some configurations for fluent-bit and for AWS, for instance, the cluster-name AWS use to create the logs group. In this case, I don't want to create an HTTP server for fluent-bit and I will read the logs from the tail, more information about this can be found }(&lt;a href="https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/classic-mode/configuration-file" rel="noopener noreferrer"&gt;https://docs.fluentbit.io/manual/administration/configuring-fluent-bit/classic-mode/configuration-file&lt;/a&gt;).&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit-general-configs&lt;/span&gt; &lt;span class="c1"&gt;## you can use a different name, make sure to use the same in the following steps&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;cluster.name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${CLUSTERNAME}&lt;/span&gt;
  &lt;span class="na"&gt;http.port&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="na"&gt;http.server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Off"&lt;/span&gt;
  &lt;span class="na"&gt;logs.region&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${AWS_REGION}&lt;/span&gt;
  &lt;span class="na"&gt;read.head&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Off"&lt;/span&gt;
  &lt;span class="na"&gt;read.tail&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;On"&lt;/span&gt; 


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  3.Service Account, Cluster Role and Role Binding
&lt;/h3&gt;

&lt;p&gt;Some permissions are required to send logs from daemonSet to Cloudwatch, you can attach a role to the worker-nodes or use a service account with an IAM role, in this case, I will create an IAM role and associate it with a service account.&lt;br&gt;
The following Terraform code creates a policy and the role.&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Terraform
resource "aws_iam_role" "iam-role-fluent-bit" {
  name                  = "role-fluent-bit-test"
  force_detach_policies = true
  max_session_duration  = 3600
  path                  = "/"
  assume_role_policy    = jsonencode({

{
    Version= "2012-10-17"
    Statement= [
      {
        Effect= "Allow"
        Principal= {
            Federated= "arn:aws:iam::${ACCOUNT_ID}:oidc-provider/oidc.eks.${REGION}.amazonaws.com/id/${EKS_OIDCID}"
        }
        Action= "sts:AssumeRoleWithWebIdentity"
        Condition= {
          StringEquals= {
            "oidc.eks.${REGION}.amazonaws.com/id/${EKS_OIDCID}:aud": "sts.amazonaws.com",
"oidc.eks.${REGION}.amazonaws.com/id/${EKS_OIDCID}:sub": "system:serviceaccount:${AWS_CLOUDWATCH_NAMESPACE}:${EKS-SERVICE_ACCOUNT-NAME}"
          }
        }
      }
    ]
  }

})

}



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

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EKS_OIDCID:&lt;/strong&gt; is the OpenID Connect for your cluster, you can get it in the cluster information or by terraform outputs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS_CLOUDWATCH_NAMESPACE:&lt;/strong&gt; is the namespace create in the step 1, in this case amazon-cloudwatch.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;ACCOUNT_ID:&lt;/strong&gt; is the AWS account number where the cluster was created.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The role needs a policy with permissions to create and put logs in cloudwatch, you can use the following code to create the policy and attach it to IAM Role created.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
 Terraform

resource "aws_iam_policy" "policy_sa_logs" {
  name        = "policy-sa-fluent-bit-logs"
  path        = "/"
  description = "policy for EKS Service Account fluent-bit "
  policy = &amp;lt;&amp;lt;EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "cloudwatch:PutMetricData",
                "ec2:DescribeVolumes",
                "ec2:DescribeTags",
                "logs:PutLogEvents",
                "logs:DescribeLogStreams",
                "logs:DescribeLogGroups",
                "logs:CreateLogStream",
                "logs:CreateLogGroup",
                "logs:PutRetentionPolicy"
            ],
            "Resource": "arn:aws:logs:${REGION}:${ACCOUNT_ID}:*:*"
        }
    ]
}
EOF
}

######## Policy attachment to IAM role ########

resource "aws_iam_role_policy_attachment" "policy-attach" {
  role       = aws_iam_role.iam-role-fluent-bit.name
  policy_arn = aws_iam_policy.policy_sa_logs.arn
}



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

&lt;/div&gt;

&lt;p&gt;Once the role has been created the Service account can be created, you can use the following k8 manifest for that, you should replace the IAM_ROLE variable for the ARN of the role created previously.&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;
  &lt;span class="na"&gt;annotations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;eks.amazonaws.com/role-arn&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;${IAM_ROLE}"&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;With the SA ready, you need to create a cluster role and associate that to the SA created, the following manifests can be used for that.&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit-role&lt;/span&gt;
&lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;nonResourceURLs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/metrics&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;get&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;apiGroups&lt;/span&gt;&lt;span class="pi"&gt;:&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="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;namespaces&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pods&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;pods/logs&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nodes&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;nodes/proxy&lt;/span&gt;
    &lt;span class="na"&gt;verbs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;get"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;list"&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;watch"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;

&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRoleBinding&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit-role-binding&lt;/span&gt;
&lt;span class="na"&gt;roleRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;apiGroup&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;rbac.authorization.k8s.io&lt;/span&gt;
  &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterRole&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;fluent-bit-role&lt;/span&gt;
&lt;span class="na"&gt;subjects&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceAccount&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;fluent-bit&lt;/span&gt;
    &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;


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

&lt;/div&gt;
&lt;h3&gt;
  
  
  4.ConfigMap for fluent-bit configurations
&lt;/h3&gt;

&lt;p&gt;A ConfigMap is used to specify a detailed configuration for Fluent-bit, AWS already defines a configuration, but you can add custom configs, The following &lt;a href="https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml" rel="noopener noreferrer"&gt;link&lt;/a&gt;, shows the configurations defined by AWS, if you see, the first objects created in the YAML are the manifest defined in previous steps, in this step you just need to define the ConfigMap with name &lt;em&gt;fluent-bit-config&lt;/em&gt;, I don't want to put here all the manifest because is a little long and can complicate the lecture of this post.&lt;/p&gt;

&lt;p&gt;With this ConfigMap, Fluent Bit will create the log groups in the below table, you also have the option to create by terraform and specify encryption and retention period (i recommend this way).&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CloudWatch Log Group Name&lt;/th&gt;
&lt;th&gt;Source of the logs(Path inside the Container)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;aws/containerinsights/Cluster_Name/application&lt;/td&gt;
&lt;td&gt;All log files in /var/log/containers&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/aws/containerinsights/Cluster_Name/host&lt;/td&gt;
&lt;td&gt;Logs from /var/log/dmesg, /var/log/secure, and /var/log/messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/aws/containerinsights/Cluster_Name/dataplane&lt;/td&gt;
&lt;td&gt;The logs in /var/log/journal for kubelet.service, kubeproxy.service, and docker.service.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If you analyze the ConfigMap you can see the INPUTS for each source mentioned in the table. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit-config&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;

&lt;span class="nn"&gt;...&lt;/span&gt; &lt;span class="s"&gt;OTHER CONFIGS&lt;/span&gt; 

&lt;span class="c1"&gt;### here is the INPUT configurations for application logs  &lt;/span&gt;
&lt;span class="na"&gt;application-log.conf&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;[INPUT]&lt;/span&gt;
        &lt;span class="s"&gt;Name                tail&lt;/span&gt;
        &lt;span class="s"&gt;Tag                 application.*&lt;/span&gt;
        &lt;span class="s"&gt;Exclude_Path        /var/log/containers/cloudwatch-agent*, /var/log/containers/fluent-bit*, /var/log/containers/aws-node*, /var/log/containers/kube-proxy*&lt;/span&gt;
        &lt;span class="s"&gt;Path                /var/log/containers/*.log&lt;/span&gt;

&lt;span class="s"&gt;... OTHER CONFIGS&lt;/span&gt; 

    &lt;span class="s"&gt;[OUTPUT]&lt;/span&gt;
        &lt;span class="s"&gt;Name                cloudwatch_logs&lt;/span&gt;
        &lt;span class="s"&gt;Match               application.*&lt;/span&gt;
        &lt;span class="s"&gt;region              $${AWS_REGION}&lt;/span&gt;
        &lt;span class="s"&gt;log_group_name      /aws/containerinsights/$${CLUSTER_NAME}/application&lt;/span&gt;
        &lt;span class="s"&gt;log_stream_prefix   $${HOST_NAME}-&lt;/span&gt;
        &lt;span class="s"&gt;auto_create_group   &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;
        &lt;span class="s"&gt;extra_user_agent    container-insights&lt;/span&gt;
        &lt;span class="s"&gt;log_retention_days  ${logs_retention_period}&lt;/span&gt; 



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

&lt;/div&gt;

&lt;p&gt;The OUTPUT in the previous manifest defines the CloudWatch log Group configuration that fluent bit will create, as you can see you can specify if the log groups should be created, the prefix for the stream, the name, and the retention period for the logs. If you are using Terraform you should set to false the option &lt;strong&gt;auto_create_group&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5.DaemonSet Creation
&lt;/h3&gt;

&lt;p&gt;This is the last step :) , AWS also provides the manifest to create the DaemonSet, in this &lt;a href="https://raw.githubusercontent.com/aws-samples/amazon-cloudwatch-container-insights/latest/k8s-deployment-manifest-templates/deployment-mode/daemonset/container-insights-monitoring/fluent-bit/fluent-bit.yaml" rel="noopener noreferrer"&gt;link&lt;/a&gt; you can find it in the bottom of the file. As I mentioned, I don't want to put the whole file here, you can copy and paste the content or edit the file if you have custom configurations.&lt;/p&gt;

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

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;DaemonSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;amazon-cloudwatch&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;k8s-app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;fluent-bit&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt; &lt;span class="s"&gt;OTHER CONFIGS&lt;/span&gt;


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

&lt;/div&gt;

&lt;p&gt;Once you have run the above steps, you can validate that the daemonSet is running well and if everything is ok you should be the Logs groups in the AWS console with some events passed by fluent-bit DaemonSet&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-logs-FluentBit.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/Container-Insights-setup-logs-FluentBit.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.fluentbit.io/manual/pipeline/outputs/cloudwatch" rel="noopener noreferrer"&gt;https://docs.fluentbit.io/manual/pipeline/outputs/cloudwatch&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/eks/latest/userguide/control-plane-logs.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>aws</category>
      <category>terraform</category>
      <category>cloudwatch</category>
    </item>
    <item>
      <title>Trusting in your IaC -Terraform-Compliance</title>
      <dc:creator>Daniel German Rivera</dc:creator>
      <pubDate>Sun, 30 Jan 2022 21:50:37 +0000</pubDate>
      <link>https://dev.to/aws-builders/trusting-in-your-iac-terraform-compliance-4cch</link>
      <guid>https://dev.to/aws-builders/trusting-in-your-iac-terraform-compliance-4cch</guid>
      <description>&lt;p&gt;Infrastructure as Code has started to be an important part of our cloud journey, giving us the ability to automate deploys and replicate our infra in multiple environments and accounts. Using code to define our infra allow us to implement some practices from developers world, like testing, version control, etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rz6ct19c0b7xr5ulfo5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rz6ct19c0b7xr5ulfo5.jpeg" alt="IaC power" width="300" height="225"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a lot of tools, IaC can introduce bugs or maybe miss configurations that then can be our headache when errors arise, or maybe audit companies come to review the security of our infra.&lt;br&gt;
IaC has enabled us to implement development practices like generating Pull Request with the new changes, this is very important when we want the revision from other coworkers, frameworks to do testing and maybe IaC tools that use common language programing help us to validate our infra-code, but this is more for technical teams and translate this to non-technical languages can be complicated, limiting the scope for the revision. Here is when terraform-compliance framework appears to help us to define rules to &lt;br&gt;
validate our infra and define if accomplish with the requirements defined from the technical side and business side.&lt;/p&gt;

&lt;p&gt;Terraform-compliance is a framework that validates the compliance in our infrastructure defined by terraform, this is based on negative testing and BDD that work together to validate our infra. This post will show how to implement this framework and add it to our DevOps pipeline used to deploy our IaC, AWS will be used as a cloud provider.&lt;/p&gt;
&lt;h3&gt;
  
  
  Requirements
&lt;/h3&gt;

&lt;p&gt;Terraform-compliance validates the code before it is deployed, we can install it using docker or via pip. In this post, we will use pip.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Python&lt;/li&gt;
&lt;li&gt;terraform 0.12+&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  How does this work?
&lt;/h3&gt;

&lt;p&gt;Terraform-compliance uses policies that define the features that the infrastructure must-have, like encryption enabled in the buckets, resources well tagged, etc. These policies are executed against the plan that Terraform generates and are defined using  &lt;strong&gt;Behaviour Driven Development(BDD)&lt;/strong&gt; Principles, that use English like the language to define the policies.&lt;/p&gt;

&lt;p&gt;To work with terraform we need to create a file in which the policies are defined, using BDD principles that file is named feature and contain the scenario that we want to evaluate.&lt;br&gt;
The structure of the file is the following:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Feature:&lt;/strong&gt; A summary about the things to validate&lt;br&gt;
&lt;strong&gt;Scenario/scenario Outline&lt;/strong&gt;: define the test to execute, this includes BDD directives like:&lt;br&gt;
&lt;strong&gt;GIVEN:&lt;/strong&gt; It is used to define a context that can be a list of resources or data that we want to check, we can see this as a filter. &lt;br&gt;
&lt;strong&gt;WHEN:&lt;/strong&gt; filter the context defined above, for instance, if the context defined says that will evaluate all the s3 buckets, with WHEN we can filter by tags for instance. If the condition doesn't pass this just skip to the next line instead of fail.&lt;br&gt;
&lt;strong&gt;THEN:&lt;/strong&gt; Has a similar function that WHEN but if the condition doesn't pass the scenario will fail.&lt;br&gt;
&lt;strong&gt;AND:&lt;/strong&gt; It is used to define an extra condition to our scenario, this is an optional statement.&lt;/p&gt;

&lt;p&gt;Here and example to evaluate if all the resources has tag defined.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;Scenario: Ensure all resources have tags&lt;br&gt;
      Given I have resource that supports tags defined&lt;br&gt;
      Then it must contain tags&lt;br&gt;
      And its value must not be null&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Each BDD directive has more capabilities and can be checked in &lt;a href="https://terraform-compliance.com/pages/bdd-references/"&gt;Terraform-compliance documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Feature file is executed against terraform plan, to do this terraform-compliance needs the TF plan as an input, for that we need to save our plan in an external file and pass it to terraform-compliance command that we will see later.&lt;/p&gt;

&lt;p&gt;With the basic explanation made above let's make and example.&lt;/p&gt;
&lt;h3&gt;
  
  
  Hands on !!!
&lt;/h3&gt;

&lt;p&gt;This post was part of a webinar made in my current company and a Github repository was created, &lt;a href="https://github.com/danielrive/epam-devops-webinar-2022"&gt;here is the link&lt;/a&gt;.This repo contains a few AWS resources defined by terraform and uses Github actions to execute with each push the terraform-compliance framework. All the stuff that we will execute is before the terraform apply so we don't spend money creating resources :)&lt;/p&gt;

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

&lt;p&gt;The project has been divided into two parts, the first one contains the terraform code to deploy the infrastructure and is located in the root of the repo, the second part is a folder named compliance that contains some files with .feature extension and defines the rules that we want to evaluate against our TF plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Terraform Code&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight terraform"&gt;&lt;code&gt;&lt;span class="c1"&gt;#### Data Sources&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_caller_identity"&lt;/span&gt; &lt;span class="s2"&gt;"ID_CURRENT_ACCOUNT"&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;


&lt;span class="c1"&gt;###  KMS Policy &lt;/span&gt;
&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"aws_iam_policy_document"&lt;/span&gt; &lt;span class="s2"&gt;"kms_policy"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;statement&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;sid&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Enable IAM User Permissions"&lt;/span&gt;
    &lt;span class="nx"&gt;effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
    &lt;span class="nx"&gt;principals&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;type&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"AWS"&lt;/span&gt;
      &lt;span class="nx"&gt;identifiers&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"arn:aws:iam::&lt;/span&gt;&lt;span class="k"&gt;${data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_caller_identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ID_CURRENT_ACCOUNT&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account_id&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;:root"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
      &lt;span class="s2"&gt;"*"&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="nx"&gt;resources&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"*"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="c1"&gt;##########################&lt;/span&gt;
&lt;span class="c1"&gt;## Secret manager&lt;/span&gt;

&lt;span class="c1"&gt;# KMS ky to encrypt at rest secret manager&lt;/span&gt;
&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"kms_secret_manager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/kms"&lt;/span&gt;
  &lt;span class="nx"&gt;NAME&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"KMS-SecretManager-&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;POLICY&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_iam_policy_document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kms_policy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;json&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"secret_manager"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/secret_manager"&lt;/span&gt;
  &lt;span class="nx"&gt;NAME&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secret_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;RETENTION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;KMS_KEY&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kms_secret_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN_KMS&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"secret_manager_k8"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;source&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"./modules/secret_manager"&lt;/span&gt;
  &lt;span class="nx"&gt;NAME&lt;/span&gt;      &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"secret_k8_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
  &lt;span class="nx"&gt;RETENTION&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;KMS_KEY&lt;/span&gt;   &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kms_secret_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN_KMS&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"bucket_test"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-bucket-forcompliance-test"&lt;/span&gt;

  &lt;span class="nx"&gt;server_side_encryption_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="cm"&gt;/*   
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
      */&lt;/span&gt;

      &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;kms_master_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kms_secret_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN_KMS&lt;/span&gt;
        &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:kms"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;


    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bucket_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"develop"&lt;/span&gt;
    &lt;span class="nx"&gt;Owner&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DanielR"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_s3_bucket"&lt;/span&gt; &lt;span class="s2"&gt;"bucket_test2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"my-bucket-forcompliance-test"&lt;/span&gt;

  &lt;span class="nx"&gt;server_side_encryption_configuration&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;rule&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

      &lt;span class="nx"&gt;apply_server_side_encryption_by_default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;kms_master_key_id&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kms_secret_manager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ARN_KMS&lt;/span&gt;
        &lt;span class="nx"&gt;sse_algorithm&lt;/span&gt;     &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"aws:kms"&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"bucket_&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"develop"&lt;/span&gt;
    &lt;span class="nx"&gt;Owner&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DanielR"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;


&lt;span class="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"aws_secretsmanager_secret"&lt;/span&gt; &lt;span class="s2"&gt;"secret_manager2"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;                    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
  &lt;span class="nx"&gt;recovery_window_in_days&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
  &lt;span class="nx"&gt;lifecycle&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;create_before_destroy&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="nx"&gt;tags&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;Name&lt;/span&gt;        &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"test"&lt;/span&gt;
    &lt;span class="nx"&gt;Environment&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"develop"&lt;/span&gt;
    &lt;span class="nx"&gt;Owner&lt;/span&gt;       &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"DanielR"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Terraform code creates KMS key, Secret managers that are encrypted by KMS and s3 buckets with SSE with KMS. &lt;/p&gt;

&lt;p&gt;Compliance folder has three files and each one define a scenario, let's move on with the &lt;em&gt;S3.feature&lt;/em&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Feature:  This validates if the s3 buckets has Encryption enabled using KMS
Scenario: Ensure that s3 buckets are encrypted by KMS
    Given I have aws_s3_bucket resource configured
    When it contain server_side_encryption_configuration
    Then it must have apply_server_side_encryption_by_default
    Then it must have sse_algorithm
    And its value must be "aws:kms"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With that file TF-Compliance framework will validate if the S3 buckets created with that TF code have Server-Side-Encryption enabled but using KMS key, here a quick explanation of the file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Given&lt;/strong&gt; statement define the context in this case, all the resources defined by &lt;strong&gt;&lt;em&gt;aws_s3_bucket&lt;/em&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;When&lt;/strong&gt; statement makes more small the context, in this case TF-compliance will check the buckets that have server_side_encryption_configuration in their definition.&lt;/li&gt;
&lt;li&gt;We have two &lt;strong&gt;Then&lt;/strong&gt; statement and are used to defined that the buckets must to have &lt;strong&gt;&lt;em&gt;apply_server_side_encryption_by_default&lt;/em&gt;&lt;/strong&gt; and the &lt;strong&gt;&lt;em&gt;sse_algorithm&lt;/em&gt;&lt;/strong&gt; must be &lt;strong&gt;&lt;em&gt;aws:kms&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Running the commands
&lt;/h3&gt;

&lt;p&gt;As we mentioned before, we are using Github actions to run the TF-compliance command, the idea is to validate automatically if the rules are accomplished before the plan if so, the pipe will allow the TF apply command and the infra will comply with the rules defined.&lt;/p&gt;

&lt;p&gt;To do that, we need to generate the terraform plan, this must be stored in a file in the same directory, then that will be an input for our TF-compliance command.&lt;/p&gt;

&lt;p&gt;To do that we can run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Terraform init

terraform plan -out=plan.out 

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

&lt;/div&gt;



&lt;p&gt;plan.out is the name of the file to create, we can put any another name.&lt;/p&gt;

&lt;p&gt;Once the plan is created we can go ahead with the execution of the TF-Compliance command, here is the command to do the magic&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;terraform-compliance -f compliance/ -p plan.out
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the -f flag we specify the folder in which we are storing the rules(.features files).&lt;/p&gt;

&lt;p&gt;The output will be&lt;/p&gt;

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

&lt;p&gt;Let's change the code to use the default algorithm to encrypt  the s3 buckets.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;resource "aws_s3_bucket" "bucket_test" {
  bucket = "my-bucket-forcompliance-test"

  server_side_encryption_configuration {
    rule {

      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }

    }

  }

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

&lt;/div&gt;



&lt;p&gt;Running again the TF-compliance command will be:&lt;/p&gt;

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

&lt;p&gt;With this we can validate how the framework works, we can define multiple rules and these can be checked for all the teams because are not 100% technical.&lt;/p&gt;

</description>
      <category>iac</category>
      <category>terraform</category>
      <category>aws</category>
      <category>security</category>
    </item>
  </channel>
</rss>
