<?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>The Ops Community ⚙️: Javier Marasco</title>
    <description>The latest articles on The Ops Community ⚙️ by Javier Marasco (@javi_labs).</description>
    <link>https://community.ops.io/javi_labs</link>
    <image>
      <url>https://community.ops.io/images/eL82GlEy6y8V18ZExd1bvrFKZs_Lk31FGAoiNET92yE/rs:fill:90:90/g:sm/mb:500000/ar:1/aHR0cHM6Ly9jb21t/dW5pdHkub3BzLmlv/L3JlbW90ZWltYWdl/cy91cGxvYWRzL3Vz/ZXIvcHJvZmlsZV9p/bWFnZS8xMjY3LzAz/NGRmMmJkLTMzNGQt/NDY3OS04NTBkLWY3/NjFhNzgzMWMxYy5w/bmc</url>
      <title>The Ops Community ⚙️: Javier Marasco</title>
      <link>https://community.ops.io/javi_labs</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://community.ops.io/feed/javi_labs"/>
    <language>en</language>
    <item>
      <title>Kubernetes probes easily explained</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Sun, 24 Dec 2023 11:30:39 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/kubernetes-probes-easily-explained-mld</link>
      <guid>https://community.ops.io/javi_labs/kubernetes-probes-easily-explained-mld</guid>
      <description>&lt;p&gt;Recently I needed to work with kubernetes probes (to implement them actually) and came across some questions about them, I think it is a good idea to write a quick explanation without being too deep in the technical aspects of it but more on the why they exist and what are they used for, let's take a look.&lt;/p&gt;

&lt;p&gt;In Kubernetes you deploy your applications in forms of images which are a single file that contains your application code and all needed dependencies for it to run.&lt;/p&gt;

&lt;p&gt;Idealy your application will start right away and put in place to receive requests (from other containers in your cluster or from the outside world) but this some times is not so quick, your application might need some extra time to start and during that time it can't respond properly to requests, let's see an example.&lt;/p&gt;

&lt;p&gt;Imagine your application is exposing and enpoint to the world and other applications will connect to it and send a certain payload in a POST request to that endpoint. Your application before receiving or even be able to respond to a request, it needs to do some preparations, it needs to connect to certain service to download a configuration file, then it needs to connect to another system to retrieve some secrets to connect to other services (SQL server for example) and open some connections to a database. While doing all this your application can't do much, any request you send to it will simply fail as your application is not running yet, it is initializing, so if Kubernetes tries to route traffic to it, your consummers will simply see an error. Now what?&lt;/p&gt;

&lt;h2&gt;
  
  
  How does traffic reach your application?
&lt;/h2&gt;

&lt;p&gt;When you create a pod (wrong, you should run deployments and not pods) it has it's own IP inside the cluster, you don't send traffic directly to your pods as they can be destroyed at any time and the new ones can have a different IP, so it is not reliable to expect a pod to get traffic, instead we have services which are another Kubernetes resource which has a name (fqdn in the form ) which your other pods or ingresses should use to reach your pods, this approaches ensures you always reach your application using a reliable approach (an fqdn). Each service contains a kind of "backend pool" where your pods are assigned to receive traffic, they are assigned to this pool as soon as they are ready, and this is the tricky part, how does Kubernetes knows your applications is ready?&lt;/p&gt;

&lt;h2&gt;
  
  
  Startup, liveness and readiness probes
&lt;/h2&gt;

&lt;p&gt;Kubernetes defines three types of probes to check the status of your pods, let's view what each one of them does and when they are executed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Startup probes&lt;/strong&gt;&lt;br&gt;
This is the first probe to be executed (if defined) and it only runs once when the pod is started, the intention of this probe is to wait until the pod is ready to start working, but watch out, this doesn't means the application is ready to receive traffic, it means the code inside the image is ready to start working, the main intention of this probe is to wait before start running other probes (liveness and/or readiness) ensuring kubelet doesn't kill the pod before it even start operating (this is specially true in slow starting applications).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;liveness probes&lt;/strong&gt;&lt;br&gt;
This is a probe that (again, if defined) will run constantly during the whole life of the pod, this is intended to allow kubelet to know when to destroy a pod and create a new one, when defining a liveness probe keep in mind it should return a success code constantly while your application is running, do not configure it for something that is only "true" while starting or a short period of time as that will cause kubelet to kill your pod when that test if failed but your application might be totally healty.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Readiness probes&lt;/strong&gt;&lt;br&gt;
This probe is probably the most important one for the topic we are covering (knowing when your application can accept traffic). The readiness probe will be executed only once just after your startup probe (if defined) and once it completes it will let Kubernetes determine what to do with your pod. If the probe is a success the pod IP will be added in the backed of the apropriate service to start receiving traffic, if the probe is failed Kubrenetes will kill your pod and start a new one (which will do all the probes defined again), this will prevent an unhealty pod to receive requests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Wrapping up&lt;/strong&gt;&lt;br&gt;
So, basically our deployment should specify the three probes, the "startup" will fire as soon as the pod is scheduled into a node, upon completion a "readiness" probe will be fires which can return a success code which will add the IP of the pod into the service backend but if it fails kubelet will kill the pod and restart the process again. If the readiness probe completes then your application is healthy and will start receiving traffic but then during it's whole life kubelet will keep executing periodically a "liveness" probe on the pod to determine if it is needed to kill the pod or not, on the first failure from the liveness probe, your pod will be restarted.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reference material&lt;/strong&gt;&lt;br&gt;
This is a short article to summarize the concept of probes in Kubernetes, there are a lot of technical details on how to properly implement and configure them, if you need more details on it, you can find it in the official documentation &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/"&gt;here&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>docker</category>
      <category>devops</category>
      <category>o11y</category>
    </item>
    <item>
      <title>Managed cluster vs unmanaged clusters in Kubernetes</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Tue, 27 Jun 2023 08:30:00 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/managed-cluster-vs-unmanaged-clusters-in-kubernetes-23ba</link>
      <guid>https://community.ops.io/javi_labs/managed-cluster-vs-unmanaged-clusters-in-kubernetes-23ba</guid>
      <description>&lt;p&gt;As we see in the latest article &lt;a href="https://community.ops.io/javi_labs/what-is-kubernetes-how-does-it-works-and-why-do-we-need-it-jhe"&gt;here&lt;/a&gt; a Kubernetes cluster can contain a lot of components just to function properly and as you can imagine, install, maintain, update, upgrade and operate such an infrastructure might be haunting.&lt;/p&gt;

&lt;h2&gt;
  
  
  What does it look like to build a Kubernetes cluster?
&lt;/h2&gt;

&lt;p&gt;As discussed there are two kinds of nodes for our cluster, the &lt;code&gt;master node&lt;/code&gt; also known as &lt;code&gt;control nodes&lt;/code&gt;, and the &lt;code&gt;worker nodes&lt;/code&gt;. Each of those nodes contains different components that will be supporting our cluster, a minimum configuration will have one &lt;code&gt;control node&lt;/code&gt; and one &lt;code&gt;worker node&lt;/code&gt;, which can be physical machines or virtual machines connected by a network.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;control nodes&lt;/code&gt; will run the API server, an etcd database, the scheduler, and some controllers while the &lt;code&gt;worker nodes&lt;/code&gt; will run only tree components being them the kubelet, kube-proxy, and the container runtime.&lt;/p&gt;

&lt;p&gt;This might sound simple, right? but then you need to configure all those components, you also need to do all the networking needed for those nodes to communicate, and in the networking part there is one additional thing, you will (eventually) need to expose your applications to the internet using an ingress controller which will require to have an external IP exposed to the internet from where all the incoming traffic to your applications will &lt;code&gt;ingress&lt;/code&gt; your cluster, this means you will need to administer the creation (and maintenance) of those public IPs.&lt;/p&gt;

&lt;p&gt;This becomes a complex task pretty quick but if this is so complicated, why would anyone want to build a cluster for themselves? well, the answer is pretty easy, &lt;em&gt;security&lt;/em&gt; when you build your cluster, you keep control of everything, patching, kernel versions, os versions, packages in the nodes, everything is possible to customize to your needs. It might sound like overkill but think in heavily regulated industries like the financial sector, there you would like to have control over every piece of your infrastructure, you don't want to have a bug in a library running in the OS of your worker node that allows malicious actors to exploit it and gain root in one of your pods.&lt;/p&gt;

&lt;h2&gt;
  
  
  Managed clusters and the cloud providers offerings
&lt;/h2&gt;

&lt;p&gt;So, we are not working in a highly regulated industry but we do need one or more clusters and we don't want to spend months learning how to build a cluster and then need to maintain them manually, what can we do?&lt;/p&gt;

&lt;p&gt;Luckily each cloud provider (at least the most popular ones) does offer automated ways to build a cluster for you and give you access to it to simply deploy resources into them. This is very convenient for most of the scenarios where a Kubernetes cluster is needed, even for production workloads, those cloud providers have (normally) multiple levels of SLAs for those clusters meaning that if you are running a development cluster you can opt for a lower SLA but also a cheaper billing while for production workloads you can opt for a higher SLA which includes highly available resources but that will also cost you more, but you can choose what is best for your case and environment.&lt;/p&gt;

&lt;p&gt;Those managed clusters have a clear downside, and that is the fact that the cloud providers will retain the control of the &lt;code&gt;control plane&lt;/code&gt; and their &lt;code&gt;control nodes&lt;/code&gt; meaning you can't decide on how to configure it and even when it is possible to adjust and tune your &lt;code&gt;worker nodes&lt;/code&gt; (even apps, libraries and kernel configurations) as soon as you modify them, you loose support from the cloud provider. This might sound like a bad move from the providers but think what would it mean for them to support every possible change made in any cluster in the world? it would be impossible for them to provide proper support so they stick to a proven configuration and they support it, if you move away from that you are on your own.&lt;/p&gt;

&lt;h2&gt;
  
  
  Patching and upgrading your cluster
&lt;/h2&gt;

&lt;p&gt;We discussed a lot about building and configuring your cluster, but what about version upgrades? what happens when Kubernetes releases a new version? well, here is again a big difference, cloud providers tend to be a bit delayed with the latest version being released because they need to test it first, pass certain internal validations, and then expose them for you to consume in a trustable manner, this takes time meaning if you are wanting to implement a super shiny new feature of the latest release of Kubernetes and you are running a managed cluster you will need to wait a bit before this version is available for you to pick it up, while in a managed cluster you can upgrade whenever you want.&lt;/p&gt;

&lt;p&gt;But what happens if something goes wrong with my upgrade? I moved to a newer version and now everything is broken (see the note after this paragraph for a good example). In a managed cluster it is very complicated to revert to an older Kubernetes version I would say it is impossible but I actually know cases where the cloud provider managed to revert the version in VERY specific cases but I would take this as "something you can't do" but in your unmanaged cluster it is as simple as just reinstall the older version or revert to a backup snapshot from before your upgrade (because of course you do backups before upgrading your cluster version) and you are again in a case like nothing happened, very easy.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;A note about this, recently with the introduction of version 1.25 there was a change in a configuration called cgroups in the kernel of the OS being used by Kubernetes, this caused some applications using an old (but still functional) version of their frameworks to start consuming a lot of memory and making the kubelet constantly kill those pods, a simple solution is to upgrade your framework in your app, but if that is not possible you can also revert the change in the worker node kernel, but this last option will make you &lt;code&gt;unsupported&lt;/code&gt; for your cloud provider.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  So, what is the takeaway of this article?
&lt;/h2&gt;

&lt;p&gt;Essentially you now know what a managed and unmanaged cluster, you know what are the advantages and disadvantages of each one, and also you know what it means to have a managed cluster and what can you do with it.&lt;/p&gt;

&lt;p&gt;The main point here is to know your situation, know your use case and know what you expect from Kubernetes and your cluster/s. Will you need to meet very strict standards and provide proof that you have certain configurations? then you are tied to an unmanaged cluster, you will need to learn and understand the details of Kubernetes and manage all by yourself, which might sound haunting but with time and training, you will be perfectly fine.&lt;br&gt;
If on the other hand, you will be deploying applications that you know are secure, you don't need to provide configurations to audits and you can rely on a cloud provider having control over your control plane, you are more than fine with a managed cluster and you can skip the deep part of Kubernetes for now (it is always advisable to learn the concepts anyway, but you will not be rushing to learn and understand everything because you need to build you cluster quickly).&lt;/p&gt;

&lt;p&gt;So what are your thoughts? would you prefer a managed cluster? an unmanaged cluster? would you feel comfortable knowing your control plane is not under your control? Let me know in the comments box 👍&lt;/p&gt;

&lt;p&gt;Thank you for reading 📚 !&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>tutorials</category>
      <category>devops</category>
    </item>
    <item>
      <title>What is Kubernetes, how does it works and why do we need it</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Mon, 19 Jun 2023 15:15:42 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/what-is-kubernetes-how-does-it-works-and-why-do-we-need-it-jhe</link>
      <guid>https://community.ops.io/javi_labs/what-is-kubernetes-how-does-it-works-and-why-do-we-need-it-jhe</guid>
      <description>&lt;p&gt;A lot of people are getting into Kubernetes and the first thing they normally do is google "How to deploy an application to Kubernetes" which could make sense but then you will find thousands of articles (including the official documentation) explaining how to deploy an application, but then you see "deployment", "replica set", "pods", "resource quotas", "secrets" and everything starts getting confusing, complex and not making sense and then you get overwhelmed by the amount of information.&lt;/p&gt;

&lt;p&gt;I believe the first thing to start with a technology that is new to you is to understand why such technology exists, what problems it solves, and how it works (conceptually), this will let you have a clear picture of the technology and will ultimately let you decide if it is the best fit for your particular case, so let's start with that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A little of the history and background&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Previous to Kubernetes applications used to be deployed in virtual machines (and previous to that into physical machines) but those machines needed to have libraries, dependencies, networking configurations, etc. You can imagine managing that was very complex and changes needed a long time and coordination between multiple teams. Then in 2013, there was a presentation that changed everything, a person called "Solomon Hykes" presented a new project his company (with two other persons) was working on, it was Docker.&lt;/p&gt;

&lt;p&gt;The key difference with Docker was nothing new actually, was a set of capabilities that already existed in the Linux kernel for a long time but now it was being exposed to the application level in a more comprehensive way, with this "Docker" tool, it was possible to pack your code with all dependencies in a "container" which you could take to any other system where Docker was running and it will behave the same, that was exactly what was needed!&lt;/p&gt;

&lt;p&gt;So now a few months/years had passed and you have containers everywhere, everyone is happy with the approach but the more containers you have it becomes a bit more complicated to manage, so we started using "docker-compose" which was a way to group containers into "logical units" to deploy them together and have some sense of integration between them, quickly it was obvious that there was a need for some way to manage large amounts of containers, then "Docker Swarm" appeared which was a tool to orchestrate the deployment of complex applications with multiple containers. This worked fine as an orchestrator but then in 2014 Google releases Kubernetes as an alternative to Docker Swarm but with more functionalities, more features were added with time and the community adopted Kubernetes as the de facto orchestrator, Kubernetes used Docker under the hood for a quite long time to execute the containers the same as Docker Swarm but later the community of Kubernetes decided to make it possible to use any container engine you want so they adjusted Kubernetes to support virtually any container engine removing the need to have Docker as the only container engine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But... how does it work?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Kubernetes at the very top level is very simple in concept, you have your image (your application code and dependencies packed into a single file made of multiple "layers") and you will deploy it into a set of machines running "something" that will take your image and make it run this "something" will take care of checking your image is running in a healthy machine, will restart the image when something bad happens to it, will kill it and restart it if it starts consuming too many resources, it will handle communication in/out from the world to your image, etc.&lt;/p&gt;

&lt;p&gt;To do this, Kubernetes has its own internal components, being them:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Server&lt;/li&gt;
&lt;li&gt;Scheduler&lt;/li&gt;
&lt;li&gt;Kubelet&lt;/li&gt;
&lt;li&gt;Kube proxy&lt;/li&gt;
&lt;li&gt;etcd&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The API Server is the interface between Kubernetes and the rest of the universe, every time you run a command using kubectl (the CLI to manage any Kubernetes cluster) it will make a REST call to the API server of your cluster and provide the parameters and files you pass to kubectl as the payload of the command.&lt;/p&gt;

&lt;p&gt;The Scheduler is a process that will take the container (note I am not talking about an image anymore, more on this later) and will validate which node in the cluster meets the needs to have that container running (resources, exclusions, affinity, etc.)&lt;/p&gt;

&lt;p&gt;Kubelet is a process running in each "worker" node that will do the actual work of making your container run, it will do all the needed tasks to ensure your code runs in the node, resource allocation, resource monitoring, process handling, etc. this is a key part as it also is responsible to support different container engines.&lt;/p&gt;

&lt;p&gt;Kube proxy is a component also running on each worker node that will take care of all the networking work for our containers, one important piece of information about kube proxy that many people get confused about is that it will not route or be in the middle of the traffic at all, kube proxy does the needed networking configuration in the worker node for the traffic to reach the correct container (adding and removing rules from iptables for example) which means that it can crash and the applications running in your node will continue to work.&lt;/p&gt;

&lt;p&gt;And of course, we have a lot of components in the control nodes and the worker nodes, we have container running, network routes defined and a lot of information about our infrastructure but how does the API server "remembers" all this? well, there is where etcd enters into the scene, etcd is a highly available and scalable key/value data store (what a lot of words, no worries, is not that complex) which takes the role to store all the cluster state constantly, it contains information about your cluster and the API server is the one updating it constantly.&lt;/p&gt;

&lt;p&gt;Now you have a basic understanding of how is it possible for Kubernetes to run a container,  by knowing this you can decide if Kubernetes is the right tool for your application.&lt;br&gt;
I often tend to not recommend Kubernetes for small applications that are just in the initial stages of being deployed or if your application is already running in another infrastructure and the migration to Kubernetes will only bring you more work to do without much gain.&lt;/p&gt;

&lt;p&gt;Just remember Kubernetes is a tool and like any other tool it serves a purpose and that should drive your intentions to move into it or not, adopting Kubernetes is a task that will require a lot of investigation, learning, and effort (translated into time), be mindful to move to Kubernetes only if it makes sense for your use case.&lt;/p&gt;

&lt;p&gt;I hope this first article of the series helped you to understand the basics of Kubernetes and decide if Kubernetes is the right choice for your next steps.&lt;/p&gt;

&lt;p&gt;If you enjoyed this article please consider following me to get alerted on every new article and consider leaving a message with your ideas and/or feedback.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>devops</category>
      <category>kubernetes</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>The ever-growing kubernetes manual</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Mon, 19 Jun 2023 07:17:14 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/the-ever-growing-kubernetes-manual-level-1-14g2</link>
      <guid>https://community.ops.io/javi_labs/the-ever-growing-kubernetes-manual-level-1-14g2</guid>
      <description>&lt;p&gt;After several months not posting and trying to think a way to articulate this idea I had in mind, I think I got how to present this to the community.&lt;/p&gt;

&lt;p&gt;This post will serve as an index for a series of other articles about kubernetes from scratch, I am planing on writing a more elavorated version of it (possibly in a ebook format) but I also wanted to give a free version to the community with the basics of Kubernetes in an easy to follow format clarifying the basic concepts you will need to go from 0 to a degree where you will feel confortable enough to deploy apps to Kubernetes and do some basic troubleshooting by using a easy to follow narrative to not only read technical information but also to nuderstand the reasoning behind it.&lt;/p&gt;

&lt;p&gt;I will try to add more articles into the list so if you feel there are items not being presented please let me know and I will add them into the index.&lt;/p&gt;

&lt;p&gt;So, let's jump into it:&lt;/p&gt;

&lt;p&gt;1 - &lt;a href="https://community.ops.io/javi_labs/what-is-kubernetes-how-does-it-works-and-why-do-we-need-it-jhe"&gt;What is Kubernetes, how does it works and why do we need it&lt;/a&gt;&lt;br&gt;
2 - Managed cluster vs unmanaged clusters &lt;br&gt;
3 - Basic elements of an application running in Kubernetes&lt;br&gt;
4 - Deployments, replicasets and pods&lt;br&gt;
5 - Services, networking and ingress&lt;br&gt;
6 - Storage classes, volumes and how to store your app data&lt;br&gt;
7 - Secrets, config maps and how they are used&lt;/p&gt;

&lt;p&gt;I will be adding those articles soon and update this index with the links to the corresponding articles, follow me to get notified every time I upload one of them and don't miss a single post :)&lt;/p&gt;

&lt;p&gt;As always, comments and feedback is always welcome and appreciated.&lt;/p&gt;

&lt;p&gt;See you later!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>automation</category>
      <category>devops</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>Terraform providerless (or how to work with non terraform resources)</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Sat, 03 Dec 2022 18:21:28 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/terraform-providerless-or-how-to-work-with-non-terraform-resources-2aeg</link>
      <guid>https://community.ops.io/javi_labs/terraform-providerless-or-how-to-work-with-non-terraform-resources-2aeg</guid>
      <description>&lt;h1&gt;
  
  
  Intro
&lt;/h1&gt;

&lt;p&gt;A few days ago I needed to integrate a service with Terraform in a way that allows my team to create infrastructure (with Terraform) while storing some information of that infrastructure in a remote service which doesn't have a Terraform provider, I also wanted to include the possibility to extract some information from this remote service to use in the resources managed by Terraform.&lt;/p&gt;

&lt;p&gt;It is not important which service I was working with but the fact that you can interact with any external service that exposes an API or that you have access to query (and write) to it.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial you will be able to write a Terraform module that will allow you to read and write to an external service that doesn't have a terraform provider.&lt;/p&gt;

&lt;h1&gt;
  
  
  Initial investigation
&lt;/h1&gt;

&lt;p&gt;While thinking on how to integrate this provider-less service I found that Terraform does have a way to interact with external services, it is by no means the same as a fully Terraform provider, the main difference is that with a provider you can manage the complete lifecycle of the resources managed with Terraform very easily while with this approach I will explain it is a bit limited, functional but limited.&lt;/p&gt;

&lt;p&gt;We will be using two scenarios in this tutorial, how to read information from an external resource and how to write to that external resource. Both approaches use a different implementation that we will be covering here.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's read!
&lt;/h1&gt;

&lt;p&gt;For this tutorial I will use the &lt;a href="https://pokeapi.co/"&gt;pokemon api&lt;/a&gt; as an external resource to retrieve the name of the first ability of a pokemon and use it as name of my terraform resource.&lt;/p&gt;

&lt;p&gt;The first thing we need to know is that Terraform has something called &lt;a href="https://registry.terraform.io/providers/hashicorp/external/latest/docs/data-sources/data_source"&gt;External Data Source&lt;/a&gt; which allows you to execute an script and send back to terraform the output of this script, we will need to write an script that connects to the API and returns something that out data source will expose later, here is an example:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/module/poke.ps1&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$stdio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Console&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;In.ReadLine&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$stdio&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$stdio&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://pokeapi.co/api/v2/pokemon/&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nv"&gt;$stdio&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$url&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ability_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abilities&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ability&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;name&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;Write-Output&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{""value"" : ""&lt;/span&gt;&lt;span class="nv"&gt;$ability_name&lt;/span&gt;&lt;span class="s2"&gt;""}"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This simple script will read from the stdio and parse it as a Powershell object to extract the name of the pokemon and then access our external API, then will retrieve the name of the first ability and write a json object to stdio containing this value.&lt;/p&gt;

&lt;p&gt;This is basically what the terraform documentation details on how this works. Let's write now a terraform module to use this:&lt;br&gt;
&lt;code&gt;/module/poke.tf&lt;/code&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="k"&gt;variable&lt;/span&gt; &lt;span class="s2"&gt;"name"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;type&lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt;&lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;string&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;default&lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Name of the pokemon"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;data&lt;/span&gt; &lt;span class="s2"&gt;"external"&lt;/span&gt; &lt;span class="s2"&gt;"read_ability"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;program&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"powershell.exe"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"./main.ps1"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="err"&gt; &lt;/span&gt; &lt;span class="err"&gt; &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;name&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;output&lt;/span&gt; &lt;span class="s2"&gt;"content"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;value&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;external&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read_ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whit this terraform code we can pack this .tf and the .ps1 files into a single folder and use them as a module from other .tf files as follow:&lt;br&gt;
&lt;code&gt;/main.tf&lt;/code&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="k"&gt;module&lt;/span&gt; &lt;span class="s2"&gt;"read_ability"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&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;"./module"&lt;/span&gt;
&lt;span class="err"&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;"ditto"&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;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"example"&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="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read_ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"West Europe"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;keep in mind the folder structure and where each file needs to be to make this work.&lt;/p&gt;

&lt;p&gt;When you run a Terraform plan on the main.tf file you will see the name of the resource group will be &lt;code&gt;limber&lt;/code&gt;, in this way you can pass other names to the &lt;code&gt;poke&lt;/code&gt; module and obtain different names, of course this is a very limited example just to demonstrate how this works but you are encouraged to do your own implementation with more functionality.&lt;/p&gt;

&lt;h1&gt;
  
  
  Let's write
&lt;/h1&gt;

&lt;p&gt;Now that we can read from our external service, we can create resources in Terraform using that information as input, but what if we want to store something we created with terraform in an external service. Let's suppose we have an external store and we want to keep track of the ID of some resources created in Azure in that store. For this example we will be using kind of the same approach as for &lt;code&gt;read&lt;/code&gt; , this means we will create a terraform module that will take an input and write it to some external service.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;/module/write.ps1&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Implement here your code to write to the external service&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# here we only do a write-host to demonstrate the functionality&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Value&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;/module/main.tf&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;variable&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"name"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"null_resource"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"write_to_external_service"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;triggers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${timestamp()}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="n"&gt;provisioner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"local-exec"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;command&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"powershell -file write.ps1 -Value &lt;/span&gt;&lt;span class="nv"&gt;${var.name}&lt;/span&gt;&lt;span class="s2"&gt; "&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;working_dir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;${path.module}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt; &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;main.tf&lt;/code&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="k"&gt;resource&lt;/span&gt; &lt;span class="s2"&gt;"azurerm_resource_group"&lt;/span&gt; &lt;span class="s2"&gt;"example"&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="k"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;read_ability&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;
  &lt;span class="nx"&gt;location&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"West Europe"&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;"write_value"&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="err"&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;"./module"&lt;/span&gt;
&lt;span class="err"&gt; &lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;azurerm_resource_group&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;example&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;/module/main.tf&lt;/code&gt; is using a &lt;a href="https://registry.terraform.io/providers/hashicorp/null/latest/docs/resources/resource"&gt;null_resource&lt;/a&gt; to execute a command and passing a single argument to the script, in this case "name" and the &lt;code&gt;trigger&lt;/code&gt; will force to run every time this null resource is called, be aware you need to implement in your script a mechanism to not write multiple times the same outputs to your external service, but that is up to you and the way your external service works.&lt;/p&gt;

&lt;h1&gt;
  
  
  Wrapping up
&lt;/h1&gt;

&lt;p&gt;So in this short tutorial you ended up with two modules, one to read from the PokeAPI and another to write to an external service, in both cases using a Powershell script to interact with the external services, this is an easy way to integrate a service without a terraform provider into a terraform deployment without the extra effort that means build a fully functional terraform provider (which of course is the prefered way to deal with external sources).&lt;/p&gt;

&lt;p&gt;As a workaround to quickly get it to work, this is a very good alternative that Hashicorp provides us, I hope this helps you and in case you find this useful, please consider to share it with others so we all benefit from others contributions!.&lt;/p&gt;

&lt;p&gt;Follow me in my &lt;a href="https://linktr.ee/javi_labs"&gt;other networks&lt;/a&gt; and get in touch if you have any question or need help 👍&lt;/p&gt;

</description>
      <category>terraform</category>
      <category>powershell</category>
      <category>devops</category>
      <category>iac</category>
    </item>
    <item>
      <title>Prometheus en AKS (en español)</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Fri, 16 Sep 2022 07:24:29 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/prometheus-en-aks-en-espanol-4dd5</link>
      <guid>https://community.ops.io/javi_labs/prometheus-en-aks-en-espanol-4dd5</guid>
      <description>&lt;h1&gt;
  
  
  Introducción
&lt;/h1&gt;

&lt;p&gt;La intención principal de esta publicación (así como de las demás) no es tener un tutorial muy profundo en todos los detalles de cada tecnología/aplicación que se describe, sino una guía breve y concisa de algo en particular que puede ayudar a las personas que están comenzando a conocer el tema descrito en este artículo, por supuesto, si tenés alguna recomendación para mejorar esto, no dudes en enviarme un mensaje/comentario.&lt;/p&gt;

&lt;h1&gt;
  
  
  ¿Qué es Prometheus?
&lt;/h1&gt;

&lt;p&gt;Arranquemos, vayamos directamente al tema que estaremos hablando hoy acá, &lt;a href="https://prometheus.io/"&gt;Prometheus&lt;/a&gt; es una herramienta de monitoreo que es muy popular en el mundo de Kubernetes al igual que una de los proyectos de la CNCF (Cloud Native Computing Foundation) y esto significa que es un producto muy maduro con un gran apoyo de la comunidad.&lt;/p&gt;

&lt;p&gt;Hay varias características que hacen de Prometheus una de las herramientas favoritas para monitorear entornos, algunas se enumeran aquí:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Metodología "pull" (significa que el servidor de Prometheus extrae métricas en lugar de esperar a que la aplicación envíe las métricas al servidor de Prometheus)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Muy rápido al recopilar métricas y realizar agregaciones&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Una arquitectura que permite monitorear no solo los entornos de Kubernetes sino otras aplicaciones como bases de datos o servidores web (usando "exporters")&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Soporta cientos de aplicaciones, hardware, plataformas, etc. para monitorear &lt;a href="https://prometheus.io/docs/instrumenting/exporters/"&gt;aquí está la lista&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Con esto en mente, continuemos instalándolo y configurándolo para un primer intento.&lt;/p&gt;

&lt;h1&gt;
  
  
  Cómo instalarlo
&lt;/h1&gt;

&lt;p&gt;Para nuestro ejemplo, instalaremos Prometheus en un &lt;a href="https://azure.microsoft.com/en-us/services/kubernetes-service/"&gt;clúster de AKS&lt;/a&gt; (clúster de Kubernetes que se ejecuta como PaaS en Azure).&lt;/p&gt;

&lt;p&gt;Hay varias formas de implementar Prometheus en un clúster de Kubernetes, pero la más simple y la que tiene más sentido es utilizar algo llamado Helm charts, imagina a un Helm chart como un conjunto de archivos YAML vinculados como un único recurso para implementar, esto significa que lo instala como un elemento "único" (un chart) pero, en su lugar, implementará los recursos necesarios en tu clúster para que la solución funcione correctamente (como pods, replica sets, deployments,  services,  secrets, etc.)&lt;/p&gt;

&lt;h1&gt;
  
  
  Qué es Helm
&lt;/h1&gt;

&lt;p&gt;Helm es una tecnología que nos permite empaquetar un montón de archivos YAML que se usarán como un todo para hacer que una solución funcione (en este caso, la solución es Prometheus), al usar Helm estás eliminando la complejidad de tener que administrar de forma independiente todos los recursos para que la solución funcione, en su lugar, proporcionas un archivo de configuración al chart y lo implementás, que creará todos los recursos y los configurará correctamente.&lt;/p&gt;

&lt;p&gt;Una de las ventajas de usar Helm es que se puede confiar en los repositorios donde se mantienen esos charts y usarlos, pero también si querés podés mantener tu propia versión del chart localmente o en un repositorio privado, para que puedas ajustarlo a tus necesidades (como usar una imagen de container personalizada para los deployments del chart en lugar de usar la predeterminada, esto podría ser un requerimiento de seguridad, por ejemplo)&lt;/p&gt;

&lt;p&gt;Otra característica es que se puede almacenar esos charts en los mismos repositorios donde se almacenan sus imágenes de contenedor y versionarlos de la misma manera que lo haces con una imagen de contenedor.&lt;/p&gt;

&lt;p&gt;Cada chart tiene su propio conjunto de archivos porque representan un grupo de recursos que funcionan para hacer que una aplicación funcione, por lo que los recursos necesarios para un chart  para Prometheus no son los mismos recursos necesarios para cert-manager, por ejemplo, pero la idea es la misma, un conjunto de archivos YAML que una vez implementados trabajarán juntos para hacer que la aplicación se ejecute.&lt;/p&gt;

&lt;p&gt;Para utilizar los charts de helm, tenés que tener helm instalado en tu sistema y agregar los repositorios que utilizará, cada chart de helm vive en un repositorio que se debe agregar para descargar el chart y sus archivos.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/fKupiNvG04tRq5CUxV2Bqme1pMLDI1Vuk1EiFAYyD8k/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLXJl/cG8tYWRkLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/fKupiNvG04tRq5CUxV2Bqme1pMLDI1Vuk1EiFAYyD8k/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLXJl/cG8tYWRkLnBuZw" alt="" width="880" height="186"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;agregando un repositorio *&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para este artículo usaremos los charts de Prometheus estándar.&lt;/p&gt;

&lt;h1&gt;
  
  
  Requisitos previos
&lt;/h1&gt;

&lt;p&gt;Bueno, para este artículo necesitaremos:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Un clúster de AKS en funcionamiento, nada especial, solo la implementación base está bien, podés seguir este &lt;a href="https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal"&gt;tutorial&lt;/a&gt; (Haré un tutorial en el futuro)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Una terminal con kubectl y helm instalados&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Instala el repositorio de helm de prometheus-community&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Instalación
&lt;/h1&gt;

&lt;p&gt;Lo primero que haremos es agregar el repositorio de helm de prometheus-community y actualizar nuestra lista de repositorios locales ejecutando el siguiente comando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ahora podemos revisar todos los gráficos que podemos instalar ahora que agregamos el repositorio, veamos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm search repo prometheus-community
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/gGLj52eAm7P3bM-aJKi1af-Pk71zhZ2km_f_AstGWf0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLXNl/YXJjaC1yZXBvLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/gGLj52eAm7P3bM-aJKi1af-Pk71zhZ2km_f_AstGWf0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLXNl/YXJjaC1yZXBvLnBu/Zw" alt="" width="880" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;¡Perfecto! Vemos muchos charts allí, esos son elementos diferentes que podemos instalar, pero hoy nos centraremos en el llamado "prometheus".&lt;/p&gt;

&lt;p&gt;Hay un nombre de columna ** CHART VERSION **, esta es la versión del gráfico en sí y no de Prometheus, esto se debe a que se pueden realizar modificaciones en la forma en que está compuesto el gráfico y lo que hay dentro, pero aun así usar la misma versión de Prometheus que la versión de gráfico anterior. Se pueden ver todas las versiones de charts ejecutando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm search repo prometheus-community/prometheus &lt;span class="nt"&gt;-l&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="s2"&gt;"community-prometheus/prometheus-"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;El comando grep es para eliminar todos los demás charts de la lista *&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/hkIqPas5u3nSMWcCS269YibgGg4wrkeJCMGfqVIy9RU/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9jaGFydC12/ZXJzaW9ucy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/hkIqPas5u3nSMWcCS269YibgGg4wrkeJCMGfqVIy9RU/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9jaGFydC12/ZXJzaW9ucy5wbmc" alt="" width="880" height="636"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Si instalas Prometheus sin decir la versión del gráfico que deseas, se instalará la última (14.11.1 en el momento en que escribo este artículo).&lt;/p&gt;

&lt;p&gt;Vamos a instalarlo ahora:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;prometheus prometheus-community/prometheus
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;el "prometheus" antes del nombre del repositorio/gráfico es el nombre que queremos darle a esta implementación, podés elegir otro nombre.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/YnNZOkJWnOxBMc97N_ZP6jFXFDSWb4fEQkEgeGVPf1M/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWlu/c3RhbGwucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/YnNZOkJWnOxBMc97N_ZP6jFXFDSWb4fEQkEgeGVPf1M/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWlu/c3RhbGwucG5n" alt="" width="880" height="559"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ahora que tenemos nuestro servidor Prometheus instalado y listo, verifiquemos en Kubernetes lo que implementamos (tenía el espacio de nombres "default" seleccionado al instalar el gráfico, por lo que mi gráfico se implementó en el espacio de nombres "default")&lt;/p&gt;

&lt;p&gt;** Una cosa importante es que cuando instala un gráfico de helm, se instala en un namespace en Kubernetes, si cambia a otro namespace e intentas ver los gráficos instalados, no verás el que instalaste en el otro namespace. Dejame poner esto en una imagen para explicarlo mejor: **&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Esto es un &lt;code&gt;helm list&lt;/code&gt; en mi namespace" default "&lt;br&gt;
&lt;a href="https://community.ops.io/images/JBM2uE84VveHjpIlhkjKmuhJkN5ycK63cWdFXuiJpGE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWxp/c3QtZGVmYXVsdC5w/bmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/JBM2uE84VveHjpIlhkjKmuhJkN5ycK63cWdFXuiJpGE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWxp/c3QtZGVmYXVsdC5w/bmc" alt="" width="880" height="43"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Esto es un &lt;code&gt;helm list&lt;/code&gt; en otro namespace&lt;br&gt;
&lt;a href="https://community.ops.io/images/McnPd-zlbKqVtOdqMjlPfHLyel3rqR-ssWRv5tpzhF0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWxp/c3Qtb3RoZXIucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/McnPd-zlbKqVtOdqMjlPfHLyel3rqR-ssWRv5tpzhF0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9oZWxtLWxp/c3Qtb3RoZXIucG5n" alt="" width="880" height="60"&gt;&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;En Kubernetes podemos ver todos los recursos creados automáticamente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get all
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/W7vyiEgVAg2Lwyf9HSufofZ5CSha2hl1CKAO_hpdv3I/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9rdWJlY3Rs/LWdldC1hbGwucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/W7vyiEgVAg2Lwyf9HSufofZ5CSha2hl1CKAO_hpdv3I/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9rdWJlY3Rs/LWdldC1hbGwucG5n" alt="" width="880" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Siguiendo los pasos descritos después de la instalación del gráfico de helm, debemos reenviar un puerto desde nuestra máquina al pod donde se ejecuta el servidor Prometheus con:&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;export &lt;/span&gt;&lt;span class="nv"&gt;POD_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;kubectl get pods &lt;span class="nt"&gt;--namespace&lt;/span&gt; default &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="s2"&gt;"app=prometheus, component=server"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{. items [0].metadata.name}"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

kubectl &lt;span class="nt"&gt;--namespace&lt;/span&gt; default port-forward &lt;span class="nv"&gt;$POD_NAME&lt;/span&gt; 9090
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;** Si estás utilizando WSL para ejecutar tus comandos de Kubernetes, debes realizar algunos pasos adicionales para que esto funcione **&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Primero verifica cuál es la IP de tu WSL ejecutando &lt;code&gt;wsl hostname -I&lt;/code&gt;, ya que no se puede acceder a los puertos en tu máquina host (Windows) ejecutando localhost: port si está exponiendo los puertos dentro de WSL.&lt;/li&gt;
&lt;li&gt;En segundo lugar, el comando port-forward debe incluir --address 0.0.0.0 como &lt;code&gt;kubectl --namespace default port-forward --address 0.0.0.0 $POD_NAME 9090&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;En tercer lugar, debe usar la IP de WSL (la del paso uno) en lugar de &lt;code&gt;localhost&lt;/code&gt; para acceder a Prometheus&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Con esto ahora podemos ir a nuestro navegador y acceder a &lt;code&gt;localhost: 9090&lt;/code&gt; para ver este dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/9HgT9QJ4bDvMQMCdTh3aUHCKaMdXDs9Ka5Lz0S0asDE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9wcm9tZXRo/ZXVzLWRhc2hib2Fy/ZC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/9HgT9QJ4bDvMQMCdTh3aUHCKaMdXDs9Ka5Lz0S0asDE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9wcm9tZXRo/ZXVzLWRhc2hib2Fy/ZC5wbmc" alt="" width="880" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Prometheus tiene muchas métricas predeterminadas que se recopilan de forma predeterminada, para verlas, podes comenzar a escribir algo en el cuadro de búsqueda y se completará automáticamente con las métricas disponibles.&lt;/p&gt;

&lt;p&gt;Como ejemplo:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/847r--BXNUpnE_BRX8xX5VBWU2FzkkoNfNAxGynudgk/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9wcm9tZXRo/ZXVzLW1ldHJpYzEu/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/847r--BXNUpnE_BRX8xX5VBWU2FzkkoNfNAxGynudgk/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvcHJvbWV0/aGV1cy9wcm9tZXRo/ZXVzLW1ldHJpYzEu/cG5n" alt="" width="880" height="407"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Y ahora, lo único que queda es profundizar en las métricas que Prometheus está recopilando, tal vez agregando algunos exportadores, ¿configurar el administrador de alertas tal vez? (este es un tema para otra publicación), ¿consumir esas métricas desde Grafana? (El artículo sobre esto ya está en camino : guiño :)&lt;/p&gt;

&lt;h1&gt;
  
  
  Últimas palabras
&lt;/h1&gt;

&lt;p&gt;Espero que esto te ayude a comenzar con Prometheus, ya que es muy simple de implementar y al mismo tiempo muy poderoso, si tenés algún problema siguiendo esta guía o alguna recomendación, hacémelo saber en la sección de comentarios.&lt;br&gt;
Aprovecho y les dejo un link a  &lt;a href="https://linktr.ee/javi_labs"&gt;mis redes sociales&lt;/a&gt; donde tambien publico algunos tips.&lt;/p&gt;

&lt;p&gt;Saludos!&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>devops</category>
      <category>azure</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Let's test our configurations with Powershell and Pester</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Tue, 13 Sep 2022 13:25:55 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/lets-test-our-configurations-with-powershell-and-pester-5aj9</link>
      <guid>https://community.ops.io/javi_labs/lets-test-our-configurations-with-powershell-and-pester-5aj9</guid>
      <description>&lt;p&gt;I tend to automate everything, it makes sense that if there is something you are requested to do more than once and the time you need to invest to automate it is not huge, you will spend some time automating it. But I often found myself having a configuration file to not need to deal with modifying my scripts, I simply create a script that does the job and provide the script with configuration files as input.&lt;br&gt;
This is a very nice approach when you don't want to have parameters in your scripts as well.&lt;/p&gt;

&lt;p&gt;But then, you communicate this in your company and more people start using your automation, you of course know how the config file should look like, but what if others are not aware? what if that automation ends up in a pipeline? you have the usage documented (of course you do!) but probably others are not aware of it.&lt;/p&gt;

&lt;p&gt;So.... how do you ensure your script is used in a safe way and your configuration file is honored? well, keep reading and I will show you how I do it :)&lt;/p&gt;
&lt;h2&gt;
  
  
  General idea
&lt;/h2&gt;

&lt;p&gt;Let's think that we have a terraform file that will build some resources in the cloud, and you are providing this terraform plan a JSON file, inside the terraform plan you decode the JSON and use the contents to provide of values to you resources.&lt;/p&gt;

&lt;p&gt;Since this JSON is something you create, the format is what ever it makes sense for your needs, it can have any format and any amount of fields, but you need to be aware that depending on the structure you give to it the tests might change a bit.&lt;/p&gt;
&lt;h2&gt;
  
  
  Example configuration file
&lt;/h2&gt;

&lt;p&gt;For this example I will go with a general JSON file that I made for this example, if contains arrays, nested objects, booleans and arrays inside nested objects.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vnet_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-vnet-name-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"resource_group_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource_groupname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"address_space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/23"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dns_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"10.0.1.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.128"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vnet_location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Subnets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"misubnet"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"address_prefixes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"10.0.0.128/24"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Enabled"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vnet_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"demo-vneT-name"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"resource_group_name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"resource_groupname"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"address_space"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"dns_servers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"10.0.1.12"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"vnet_location"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eastus3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Subnets"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"misubnet2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"address_prefixes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.0/24"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"10.0.2.128/24"&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Enabled"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;

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

&lt;/div&gt;



&lt;p&gt;This configuration file is an array that contains 2 definitions to build two hypothetical network resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thinking process
&lt;/h2&gt;

&lt;p&gt;The first thing we want to do is to sit, relax and watch our configuration file, think on what make sense to validate and what doesn't, we don't want to write kilometers of tests when we don't need to validate all, maybe there are fields that are ok to have any value on them (like tags, we probably don't care if the tag is correct), while others are very important to validate, like a naming convention.&lt;/p&gt;

&lt;p&gt;Once we saw what we want to write a test for, our next step is to write down a list of test we want to address, let's do that.&lt;/p&gt;

&lt;p&gt;I want to test:&lt;/p&gt;

&lt;p&gt;1) My vnet_name is not empty&lt;br&gt;
2) I want to check the vnet_name is following my naming convention (very simple needs to have 4 sections)&lt;br&gt;
3) My vnet_name is composed of only lower case letters&lt;br&gt;
4) The location for the resources needs to be one that I "approve" to build on&lt;/p&gt;

&lt;p&gt;With this list, we are ready to start writing our tests.&lt;/p&gt;
&lt;h2&gt;
  
  
  Pester
&lt;/h2&gt;
&lt;h3&gt;
  
  
  What it is
&lt;/h3&gt;

&lt;p&gt;Pester is a test framework for powershell, is very easy to use, contains a lot of methods/keywords to run our assertions and it's installation and configuration can't be simpler.&lt;br&gt;
Using Pester we will be writing blocks called "Descriptions" defined by the word "Describe" which are logical ways to split our assertions, inside each "Describe" block we will be creating one or more "It" asserts, each one of those will run a validation and will be reported as a "Passed" or "Failed" assertion.&lt;/p&gt;

&lt;p&gt;In the output, when you run the "Invoke-Pester" command with the "-Output Detailed" parameter, the "Describe" block will be grouping the "It" asserts, so it will be easier to read as an output.&lt;/p&gt;
&lt;h3&gt;
  
  
  How to install (Windows)
&lt;/h3&gt;

&lt;p&gt;To install Pester is as simple as install it from the PSGallery following &lt;a href="https://pester-docs.netlify.app/docs/introduction/installation"&gt;this&lt;/a&gt; guide.&lt;/p&gt;

&lt;p&gt;The key steps are:&lt;/p&gt;

&lt;p&gt;1) Open a powershell terminal as administrator&lt;br&gt;
2) Run &lt;code&gt;Install-Module -Name Pester -Force -SkipPublisherCheck&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;No big mystery here, it will install pester as a module in your host and let it ready to use.&lt;/p&gt;
&lt;h3&gt;
  
  
  How it works
&lt;/h3&gt;

&lt;p&gt;As mentioned before, writing a test is simply create &lt;code&gt;Describe&lt;/code&gt; blocks to group similar assertions and inside those blocks, write one &lt;code&gt;It&lt;/code&gt; block for each assertion.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;Describe&lt;/code&gt; blocks, there is not much to say, you place a name on them and nothing more.&lt;/p&gt;

&lt;p&gt;For the &lt;code&gt;It&lt;/code&gt; asserts is different, in them you can pass a &lt;code&gt;TestCase&lt;/code&gt; which is an array of elements that the assert will evaluate one by one or you can simply skip that and inside the &lt;code&gt;It&lt;/code&gt; block write the code to make the validation.&lt;/p&gt;

&lt;p&gt;When you write an assertion you want to do an operation and then pipe it to the &lt;code&gt;should&lt;/code&gt; operator (which is part of what you install with Pester). This &lt;code&gt;should&lt;/code&gt; operator has some parameters that you can use to describe what are you expecting the evaluation will return.&lt;/p&gt;

&lt;p&gt;Some parameters for &lt;code&gt;should&lt;/code&gt; are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Be: Compares the evaluation result to a desired value&lt;/li&gt;
&lt;li&gt;Not: Inverts the boolean of the evaluation&lt;/li&gt;
&lt;li&gt;BeNullOrEmpty: Checks if the evaluation is an empty string or not defined at all&lt;/li&gt;
&lt;li&gt;BeGreaterThan: Checks if the evaluation is greater than a defined value&lt;/li&gt;
&lt;li&gt;BeLessThan: Checks if the evaluation is less than a defined value&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://pester-docs.netlify.app/docs/commands/Should"&gt;Here&lt;/a&gt; you can find the complete list&lt;/p&gt;

&lt;p&gt;Once you have your test written, you can call it by running &lt;code&gt;Invoke-Pester -Path &amp;lt;file.ps1&amp;gt;&lt;/code&gt; and if you like the verbose output (like me) add &lt;code&gt;-Output Detailed&lt;/code&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Example Pester test
&lt;/h2&gt;

&lt;p&gt;We are going to be using Pester 5.3.1 in this guide, next I will split my test in sections to explain them next:&lt;/p&gt;
&lt;h3&gt;
  
  
  Pre tests data
&lt;/h3&gt;

&lt;p&gt;We will need some elements before we can start testing stuff, this is to define certain values for the tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# We retrieve the configuration to test&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$configfile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Content&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"./configuration.json"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertFrom-Json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Depth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Define the list of approved regions to deploy resources&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$Regions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="s1"&gt;'eastus2'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'eastus'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# We create an empty array to pass to our tests&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# We populate our test cases creating elements named "Instance" for each entry in our config file&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;foreach&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$configfile&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="nx"&gt;Instance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$item&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  "My vnet_name is not empty"
&lt;/h3&gt;

&lt;p&gt;Let's define the test for this&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example to verify if the value was defined, this prevents missing important fields.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check vnet_name is defined."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify the name is set in &amp;lt;Instance.vnet_name&amp;gt;."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vnet_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-not&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Benullorempty&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In here we are using the "should", "not" and "benullorempty" to compare with the value we got from the configuration, Pester already have all this functions ready for use to use.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check naming convention
&lt;/h3&gt;

&lt;p&gt;In this one we are going to assume our naming convention is something that needs to have 4 segments separated by a "-". This is a very simple check, think that you can even validate each of those sections and see if their values are correct.&lt;br&gt;
Another useful check is to validate if there are no other resources already created with this name.&lt;br&gt;
Keep in mind you can have multiple "It" commands inside a "Describe" section.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example to verify namingconvention, this helps to enforce we don't create resources wrongly named.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check naming convention for vnet_name."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify the vnet_name for &amp;lt;Instance.vnet_name&amp;gt; matches naming convention length."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vnet_name&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;split&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="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;4&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;    
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Only lower letters are allowed on the vnet_name
&lt;/h3&gt;

&lt;p&gt;This one is very interesting, we are going to use regular expressions to check if the name we are providing is composed of lower case letters, regular expressions are very powerfull and we can write really good tests by using them to define what we are expecting.&lt;/p&gt;

&lt;p&gt;In this example cmath is for case sensitive matches and imatch is used fo case insensitive matches.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example to validate our names are all lowercase, useful for resources that doesn't support uppercase&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# here the cmatch uses a regular expression, this can be adjusted to match any patter we need.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check name for vnet_name should be all lowercase."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify &amp;lt;Instance.vnet_name&amp;gt; is all lowercase."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vnet_name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-cmatch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^[^A-Z]*$"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Validate we are only deploying to approved locations
&lt;/h3&gt;

&lt;p&gt;At the beginning of this section we defined a list of approved locations, we will use that to validate the location in our configuration is in that list.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Example on how to validate a value in an array of values, like in this case where an &lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# approved list of regions is given to the test to validate we build in the approved locations/regions.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Describe&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Check location/region to deploy."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;It&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Verify if region for &amp;lt;Instance.vnet_name&amp;gt; is approved."&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TestCases&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Verbose&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="kr"&gt;Param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$Regions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Instance&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;vnet_location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;should&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$true&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Final notes
&lt;/h2&gt;

&lt;p&gt;As usual, you can find my other networks in &lt;a href="https://linktr.ee/javi_labs"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find this useful or have any recommendation, please let me know in the comments, follow me for future posts so I know which content is more desired by the community and I can focus on produce more of it and I hope you enjoyed it.&lt;/p&gt;

&lt;p&gt;Thanks for reading!!&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>devops</category>
      <category>tutorials</category>
    </item>
    <item>
      <title>Counting message in Azure ServiceBus at subscription level</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Wed, 07 Sep 2022 08:20:49 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/counting-message-in-azure-servicebus-at-subscription-level-3lnj</link>
      <guid>https://community.ops.io/javi_labs/counting-message-in-azure-servicebus-at-subscription-level-3lnj</guid>
      <description>&lt;h1&gt;
  
  
  A bit of background
&lt;/h1&gt;

&lt;p&gt;In some applications the usage of messaging systems is a must, you need to communicate different components in your application in an asynchronous way, this means putting a message in some place hoping another piece of your application pick up those messages and process them. This is fairly simple and doesn't present a problem by itself but sometimes a piece of your application (the one that should be picking up those messages) fails and you only notice when you see the implication of those messages not being processed (which could be too late and your customers are noticing the bad effect).&lt;/p&gt;

&lt;h1&gt;
  
  
  Presenting the scenario
&lt;/h1&gt;

&lt;p&gt;Let's suppose we have a single service bus (a messaging/queue PaaS component you can consume in Azure) with a single topic and 2 subscriptions in that topic. This is a very simple scenario but the idea is to present the way to monitor the amount of messages in each subscription to detect if our application is not processing those messages. We will have an application that will be publishing messages into one of the subscription at a regular basis and will be running always, another application will process those messages but will be doing it a bit slower than the one pushing messages, this will show the messages piling up a little bit (just to have a nice graph :) ) and then, for some wild reason, this application stops working, we should notice in the graph that the messages in that subscription starts piling up noticing that they are not being processed.&lt;br&gt;
This example doesn't include an alert, but in Azure Monitor it is possible to set an alert to trigger when we detect a certain amount of messages in the subscription&lt;/p&gt;
&lt;h1&gt;
  
  
  The limitations currently present in Azure
&lt;/h1&gt;

&lt;p&gt;At this moment, we do have a metric in Service Bus that presents the count of messages, but it is at a topic level and not at subscription level, even when the information is accessible thru the Service Bus API it is not possible to set alerts or create graphs based on that information (yet), if you are only interested in the messages at a topic level, those metrics (still in preview) are enough but if you are interested in knowing which subscription is not being processed, this article is definitely for you.&lt;/p&gt;
&lt;h1&gt;
  
  
  What we will need
&lt;/h1&gt;

&lt;p&gt;We will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;A Service Principal Name, which is called "App Registration" in the Azure portal. This SPN will be what we will use to interact with our Service Bus to read the message counters and to push the counter to the Log Analytics Workspace.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Log Analytics Workspace which is a really nice resource where we can store metrics we extract from azure or even push our own metrics there (like we are about to do with the counters of the amount of messages per subscription).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A Service Bus with one Topic and 2 subscriptions in the topic, this is an over simplified scenario but will help present the case and then you can adjust to your needs.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Configuring the SPN
&lt;/h1&gt;

&lt;p&gt;The first item we are going to need is a Service Principal, this is created in the Azure Active Directory under the "App Registration" section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/Kxa4qNSapPj0mGziaLbb3tcqY0btdLwfJZ9dLBzyeLw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL05ld0Fw/cFJlZ2lzdHJhdGlv/bi5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/Kxa4qNSapPj0mGziaLbb3tcqY0btdLwfJZ9dLBzyeLw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL05ld0Fw/cFJlZ2lzdHJhdGlv/bi5wbmc" alt="alt text" title="Configuration example" width="432" height="260"&gt;&lt;/a&gt;&lt;/p&gt;
Configuration of new app registration



&lt;p&gt;The configuration is fair simple, you need to provide a Name and later when it is created setup a password of it and you are done.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/pmo00JCWjs9-X2DMORB3gqOJzj2l1qp_1wlwY0lJPlM/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL2FwcHJl/Z2lzdHJhdGlvbl9u/YW1lLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/pmo00JCWjs9-X2DMORB3gqOJzj2l1qp_1wlwY0lJPlM/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL2FwcHJl/Z2lzdHJhdGlvbl9u/YW1lLnBuZw" alt="Defining a name for the SPN" title="Defining a name for the SPN" width="870" height="555"&gt;&lt;/a&gt;&lt;/p&gt;
Setting a name for our SPN



&lt;p&gt;&lt;a href="https://community.ops.io/images/r4Wpxs1vgyo7rWYms5WxBj7vpp3lOmoj0LfaD9tk9OE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL2FwcHJl/Z2lzdHJhdGlvbl9z/ZXRfc2VjcmV0LnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/r4Wpxs1vgyo7rWYms5WxBj7vpp3lOmoj0LfaD9tk9OE/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL2FwcHJl/Z2lzdHJhdGlvbl9z/ZXRfc2VjcmV0LnBu/Zw" alt="Setting a secret for the SPN" title="Setting a secret for the SPN" width="880" height="595"&gt;&lt;/a&gt;&lt;/p&gt;
Setting a secret for our SPN



&lt;p&gt;At this point take note of the secret created (it is automatically created by Azure) because you will not be able to retrieve it again in the future, if for some reason you lost it or it gets compromised, you can always create a new secret and remove this one.&lt;/p&gt;

&lt;p&gt;Also very important to note the Application ID from this SPN as you will need it in the script to identify to Azure as this SPN.&lt;/p&gt;

&lt;p&gt;After this is completed there is an extra step that is very important, we need to provide permissions to this Service Principal to operate with our resources otherwise it will not be able to even read anything, for this example I will simplify this by giving "Contributor" in the subscription where the resources are, this is not ideal at all but for this example will be ok, please setup yours with less permissions to limit the actions this SPN can do.&lt;/p&gt;

&lt;p&gt;For this we simply go to the Azure Subscription pane and click on "Access control (IAM)" and then add a role assignment as follows.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/vitswO0W5BHDJHQzE19CqXTULedI0IxGpeCOpM3S9qg/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL0Fzc2ln/bmVkX3Blcm1pc2lv/bl9zcG4ucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/vitswO0W5BHDJHQzE19CqXTULedI0IxGpeCOpM3S9qg/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL0Fzc2ln/bmVkX3Blcm1pc2lv/bl9zcG4ucG5n" alt="Setting a permissions for the SPN" title="Setting a permissions for the SPN" width="880" height="392"&gt;&lt;/a&gt;&lt;/p&gt;
Define permissions for our SPN



&lt;p&gt;&lt;a href="https://community.ops.io/images/A5W-4UgkQOBrVD3vTLYjHXbiT1_7ZkHy7l27gega3Lw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL0Fzc2ln/bmluZ19jb250cmli/dXRvcl9zcG4ucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/A5W-4UgkQOBrVD3vTLYjHXbiT1_7ZkHy7l27gega3Lw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL0Fzc2ln/bmluZ19jb250cmli/dXRvcl9zcG4ucG5n" alt="Setting contributor for the SPN" title="Setting contributor for the SPN" width="880" height="260"&gt;&lt;/a&gt;&lt;/p&gt;
We setup our SPN as Contributor for simplicity of this example


&lt;h1&gt;
  
  
  Setup the Log Analytics Workspace
&lt;/h1&gt;

&lt;p&gt;We will need a Log Analytics Workspace to store the results of our script and later build alerts on top of it.&lt;/p&gt;

&lt;p&gt;Once you have the Log Analytics Workspace deployed you will need to obtain it's:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WorkspaceID&lt;/li&gt;
&lt;li&gt;Primary Key (or Secondary, it is the same)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This two items will be used to tell the script which one is the Log Analytics to store the information and to give permissions to write in it.&lt;/p&gt;

&lt;p&gt;You will find this information in the "Advanced settings" blade of the Log Analytics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/c0H3cG5BGLE44MKLaUVkkGBY_ubrGc3ofaWFPpdrams/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL3dvcmtz/cGFjZV9pZF9rZXku/cG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/c0H3cG5BGLE44MKLaUVkkGBY_ubrGc3ofaWFPpdrams/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL3dvcmtz/cGFjZV9pZF9rZXku/cG5n" alt="Details of Workspace" title="Details of Workspace" width="880" height="457"&gt;&lt;/a&gt;&lt;/p&gt;
Details of our Log Analytics workspace



&lt;p&gt;Then we can go to the Logs in our Log Analytics and check what are the stored tables in there, this ones are the defaults, you will see later that we will add a new one to store our informations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/T0wHSY-c9ZerSoeOpv6RvSCwqF_2qqCI0LsWouxUhQw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wcmVfZmly/c3Rfc2VuZC5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/T0wHSY-c9ZerSoeOpv6RvSCwqF_2qqCI0LsWouxUhQw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wcmVfZmly/c3Rfc2VuZC5wbmc" alt="Default tables of Workspace" title="Default tables of Workspace" width="859" height="591"&gt;&lt;/a&gt;&lt;/p&gt;
Default tables of our Log Analytics


&lt;h1&gt;
  
  
  Lets start the coding
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Obtain a valid header for our calls to Azure API
&lt;/h2&gt;

&lt;p&gt;Ok, now that we have the SPN setup and the Log Analytics configured we can start with the needed code to work with them.&lt;br&gt;
Our first piece of code will create the needed header to interact with the Azure API to perform calls.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;GetAzureAuthHeader&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://login.microsoftonline.com/&lt;/span&gt;&lt;span class="nv"&gt;$TenantId&lt;/span&gt;&lt;span class="s2"&gt;/oauth2/token?api-version=1.0"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Post&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;             &lt;/span&gt;&lt;span class="nt"&gt;-Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="s2"&gt;"grant_type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"client_credentials"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="s2"&gt;"resource"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://management.core.windows.net/"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="s2"&gt;"client_id"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$client_id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
                        &lt;/span&gt;&lt;span class="s2"&gt;"client_secret"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$client_secret&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
                    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s1"&gt;'Authorization'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$(&lt;/span&gt;&lt;span class="s2"&gt;"Bearer "&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$token&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;write-host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Logging to Azure finished."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$header&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Posting information to Log Analytics
&lt;/h2&gt;

&lt;p&gt;In order to post information to your Log Analytics you will need a signature, this signature is build based on the information of your workspace and also with the information you are posting, here is a simple function that will calculate the signature for you.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;Function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;BuildSignature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sharedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentLength&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$xHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"x-ms-date:"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$date&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$stringToHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;`n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;`n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;`n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$xHeaders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;`n&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$bytesToHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8.GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$stringToHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$keyBytes&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;FromBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$sharedKey&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$sha256&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;System.Security.Cryptography.HMACSHA256&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Key&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$keyBytes&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$calculatedHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ComputeHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$bytesToHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$encodedHash&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Convert&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;ToBase64String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$calculatedHash&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$authorization&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;'SharedKey {0}:{1}'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-f&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nv"&gt;$encodedHash&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$authorization&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you have your signature and you will need to post the information to Log Analytics, we will encapsulate that in a function to make it easier.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="kr"&gt;Function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;PostLogAnalyticsData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sharedKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$logType&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"application/json"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/api/logs"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$rfc1123date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UtcNow.ToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"r"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$contentLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Length&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;BuildSignature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-WorkspaceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-sharedKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sharedKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$rfc1123date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-contentLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentLength&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-contentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="se"&gt;`
&lt;/span&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nt"&gt;-resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;".ods.opinsights.azure.com"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$resource&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"?api-version=2016-04-01"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$TimeStampField&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"TimeGenerated"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Authorization"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$signature&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"Log-Type"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$logType&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"x-ms-date"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$rfc1123date&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"time-generated-field"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$TimeStampField&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ContentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$contentType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$body&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$response&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;StatusCode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this two functions you have everything that is neede to post your information to Log Analytics, now we need to obtain the information and simply push it.&lt;/p&gt;

&lt;h1&gt;
  
  
  Populate the subscriptions with some messages for our example
&lt;/h1&gt;

&lt;p&gt;In this example we will simply add a few messages to a few subscriptions to show the functionality of the script, this example is not intended to be a real life scenario.&lt;/p&gt;

&lt;p&gt;In our Service Bus we will have a single topic called "simpletopic" and two subscriptions in it called "Subscription1" and "Subscription2" (how creative), we will have 2 messages in "Subscription1" and 3 messages in "Subscription2"&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/bN33E4ZNnH45jnV4sHWmnTLRqrTrtjGi7AAmITiuAd4/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1RvcGlj/d2l0aFN1YnNjcmlw/dGlvbnMucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/bN33E4ZNnH45jnV4sHWmnTLRqrTrtjGi7AAmITiuAd4/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1RvcGlj/d2l0aFN1YnNjcmlw/dGlvbnMucG5n" alt="Alt text of image" width="880" height="524"&gt;&lt;/a&gt;&lt;/p&gt;
Our demo topic with it's subscriptions



&lt;p&gt;Allright, all the setup is done and we have the tree basic functions we need to retrieve information and post it to Log Analytics, let's put it all together!&lt;/p&gt;

&lt;h1&gt;
  
  
  Querying the Log Analytics Workspace to see the messages
&lt;/h1&gt;

&lt;p&gt;Let's first get the topics of our Service Bus&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$Header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;GetAzureAuthHeader&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusTopicQueryURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://management.azure.com//subscriptions/&lt;/span&gt;&lt;span class="nv"&gt;$Subscription&lt;/span&gt;&lt;span class="s2"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="nv"&gt;$ResourceGroupName&lt;/span&gt;&lt;span class="s2"&gt;/providers/Microsoft.ServiceBus/namespaces/&lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusName&lt;/span&gt;&lt;span class="s2"&gt;/topics?api-version=2017-04-01"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusTopics&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusTopicQueryURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In $ServiceBusTopics.value we will have all the founded topic names, in our case is only "simpletopic"&lt;/p&gt;

&lt;p&gt;Then we will do our next call to retrieve the subscriptions for that topic. In a real world example you will want to do this in a foreach loop to look in all the topics returned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ServiceBusSubscriptionQueryURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://management.azure.com//subscriptions/&lt;/span&gt;&lt;span class="nv"&gt;$Subscription&lt;/span&gt;&lt;span class="s2"&gt;/resourceGroups/&lt;/span&gt;&lt;span class="nv"&gt;$ResourceGroupName&lt;/span&gt;&lt;span class="s2"&gt;/providers/Microsoft.ServiceBus/namespaces/&lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusName&lt;/span&gt;&lt;span class="s2"&gt;/topics/simpletopic/subscriptions?api-version=2017-04-01"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusSubscriptions&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Invoke-RestMethod&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Uri&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusSubscriptionQueryURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Headers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Header&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Method&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will return the two subscriptions, here is a picture of the output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/dDByIPV36Bb-SCk1fSkAPjjtHths-qTGTlBBFQ_cYfw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1NlcnZp/Y2VCdXNTdWJzY3Jp/cHRpb25zLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/dDByIPV36Bb-SCk1fSkAPjjtHths-qTGTlBBFQ_cYfw/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1NlcnZp/Y2VCdXNTdWJzY3Jp/cHRpb25zLnBuZw" alt="Details of subscription" title="Details of subscription" width="880" height="145"&gt;&lt;/a&gt;&lt;/p&gt;
Subscriptions defined



&lt;p&gt;you can see in the "properties" the counts of messages already, they will look like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/GpGMN0grVedSXtMu1UC9eM4rQMXhly--be9KGjDCFf8/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1NlcnZp/Y2VCdXNTdWJzY3Jp/cHRpb25zUHJvcGVy/dGllcy5wbmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/GpGMN0grVedSXtMu1UC9eM4rQMXhly--be9KGjDCFf8/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1NlcnZp/Y2VCdXNTdWJzY3Jp/cHRpb25zUHJvcGVy/dGllcy5wbmc" alt="Details of subscription properties" title="Details of subscription properties" width="880" height="360"&gt;&lt;/a&gt;&lt;/p&gt;
Properties of our subscriptions



&lt;p&gt;And there you have the count of messages as "messageCount" which is the sum of all messages in the subscription no matter if they are active, deadletter, scheduled, transfer or transferdeadletter, but in "countDetails" we DO have them split by message status and this is what we are looking for!&lt;/p&gt;

&lt;p&gt;Now we will want to do something for every message that we found, yes you are right, store them in Log Analytics!. Let's do that using the function "PostLogAnalyticsData" that we created earlier.&lt;/p&gt;

&lt;p&gt;For ActiveMessages&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$jsonActive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"ServiceBusTopicName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$Topic&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"ServiceBusSubscriptionName"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusSubscription&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"ServiceBusSubscriptionActiveMessageCount"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$ServiceBusSubscription&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;properties&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;countDetails&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;activeMessageCount&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$jsonActive&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;ConvertTo-Json&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="c"&gt;# Submit the data to the API endpoint for active messages&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;PostLogAnalyticsData&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-WorkspaceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$WorkspaceId&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-sharedKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$sharedKey&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-body&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;System.Text.Encoding&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;UTF8.GetBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$json&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-logType&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$logType&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The tricky part in here is the "logType" this is the name we will give to our table in Log Analytics, it is any name we want it to have and then we need to provide a json as the body with the information that we want to store in Log Analytics, keep in mind that this is whatever you want to put in there, I am choosing to store the name of the topic, the name of the subscription and the amount of active messages but you can put whatever you want.&lt;/p&gt;

&lt;h1&gt;
  
  
  Executing all together
&lt;/h1&gt;

&lt;p&gt;When you put all this together you will want to run it in a fixed schedule to constantly count the messages, keep in mind that the information you push to Log Analytics can take up to 5 minutes to show there, so is not instant when you push it.&lt;/p&gt;

&lt;p&gt;After your first push to Log Analytics you will see a "Custom Log" appear and below a table with the name you gave to this ("ServiceBusMessageCount" in our example)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/eOur9Fmooq_M_LsefY1K8lEektdBaG0F-oimrnqNbyA/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wb3N0X2Zp/cnN0X3NlbmQucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/eOur9Fmooq_M_LsefY1K8lEektdBaG0F-oimrnqNbyA/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wb3N0X2Zp/cnN0X3NlbmQucG5n" alt="new table in log analytics" title="new table in log analytics" width="720" height="600"&gt;&lt;/a&gt;&lt;/p&gt;
Our custom table appears



&lt;p&gt;After a minutes of our first execution we see the numbers there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/Q3J6jCR-KoTtyfXWVWng6MwJuzNehEBDAe1y3vhWMZA/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wb3N0X2Zp/cnN0X3NlbmRfd2l0/aF9yZXN1bHRzLnBu/Zw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/Q3J6jCR-KoTtyfXWVWng6MwJuzNehEBDAe1y3vhWMZA/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/ZGV2b3Bzam91cm5l/eS9tYXN0ZXIvTWVz/c2FnZUNvdW50ZXIv/SW1hZ2VzL1dvcmtz/cGFjZV9wb3N0X2Zp/cnN0X3NlbmRfd2l0/aF9yZXN1bHRzLnBu/Zw" alt="workspace results" title="workspace results" width="880" height="536"&gt;&lt;/a&gt;&lt;/p&gt;
We start getting the numbers in our Log Analytics



&lt;p&gt;OK! the information is in there from here on you can build a graph and place it in a dashboard, an Azure Monitor alert or consume this information with another third party application (like grafana).&lt;/p&gt;

&lt;h1&gt;
  
  
  Final notes
&lt;/h1&gt;

&lt;p&gt;I am putting this in a github repository in a single file so is easy for you to clone and tune on top of it.&lt;/p&gt;

&lt;p&gt;Github: &lt;a href="https://github.com/javiermarasco/devopsjourney/tree/master/MessageCounter"&gt;https://github.com/javiermarasco/devopsjourney/tree/master/MessageCounter&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you liked this article, please consider letting me know by liking this article, leaving a comment or sharing this article with others, you can also follow me in &lt;a href="https://linktr.ee/javi_labs"&gt;my networks&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>azure</category>
      <category>automation</category>
      <category>cloudops</category>
      <category>powershell</category>
    </item>
    <item>
      <title>PowerGrafana, what is it and how to use it</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Tue, 06 Sep 2022 10:19:36 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/powergrafana-what-is-it-and-how-to-use-it-39hj</link>
      <guid>https://community.ops.io/javi_labs/powergrafana-what-is-it-and-how-to-use-it-39hj</guid>
      <description>&lt;h3&gt;
  
  
  Lets begin with a bit of context and how it all started
&lt;/h3&gt;

&lt;p&gt;Sometimes it happens that we have to monitor some application, service or component (among other things) and the tool that we use or our company uses is &lt;a href="https://grafana.com"&gt;Grafana&lt;/a&gt;, while we have few things to monitor it is not much of a problem but as we add more elements to the panels it becomes more complex to maintain and / or configure.&lt;/p&gt;

&lt;p&gt;Imagine that you have to deploy 20 applications, each one running in an &lt;a href="https://azure.microsoft.com/en-us/services/app-service"&gt;app service&lt;/a&gt; so we would surely want to monitor your CPU and Memory usage (to begin with) and surely a few more things.&lt;br&gt;
In this simplistic scenario we have to configure some panels in a dashboard and in each panel put the metrics that we want to show (CPU and Memory) in our case.&lt;/p&gt;

&lt;p&gt;This sounds simple but in a short time we will surely need to add another metric or deploy a new version of our application, even worse, we could deploy a new version of some components and not others while we have to keep all the versions (the initial one plus the new one) of each component, as you can see this becomes more and more complex to show in Grafana, it is a lot of time clicking on the interface or (the alternative) editing json files to paste them in the Grafana web interface and generate the dashboards or panels ( believe me that it is very easy to make mistakes when editing those files).&lt;/p&gt;
&lt;h3&gt;
  
  
  The alternative
&lt;/h3&gt;

&lt;p&gt;PowerGrafana was created to solve this problem (or try to make it easier to handle) by extracting all the complexity of dealing with the web interface, json files, or even entering the names of the resources to monitor by hand.&lt;br&gt;
Using a simple PowerShell module we can quickly iterate through our resources and for each of them create a panel that shows CPU and Memory usage.&lt;/p&gt;

&lt;p&gt;Each command has it's own help, which can be accessed by executing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;PS&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Get-Help&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;New-GrafanaDashboard&lt;/span&gt;&lt;span class="w"&gt; 

&lt;/span&gt;&lt;span class="n"&gt;NAME&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;New-GrafanaDashboard&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;SYNOPSIS&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;Creates&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Grafana&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;SYNTAX&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;New-GrafanaDashboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;-DashboardName&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Object&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="nt"&gt;-Tags&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;CommonParameters&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;


&lt;/span&gt;&lt;span class="n"&gt;DESCRIPTION&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cmdlet&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;an&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;empty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dashboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Grafana&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;can&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;used&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;starting&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;point&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;create&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;grafana&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;monitoring.&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;EXAMPLE&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;New-GrafanaDashboard&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-DashboardName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My new dashboard"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Tags&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@(&lt;/span&gt;&lt;span class="s1"&gt;'Web'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Azure'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'Production'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;RELATED&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;LINKS&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="n"&gt;REMARKS&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;To&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;see&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;examples&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get-Help New-GrafanaDashboard -Examples"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;more&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get-Help New-GrafanaDashboard -Detailed"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;technical&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;information&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get-Help New-GrafanaDashboard -Full"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;For&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;help&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;type:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get-Help New-GrafanaDashboard -Online"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/javiermarasco/PowerGrafana"&gt;Link to PowerGrafana at GitHub&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.powershellgallery.com/packages/PowerGrafana/0.1.0"&gt;Link to PowerGrafana at the PowerShell gallery&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>powershell</category>
      <category>automation</category>
      <category>cloudops</category>
    </item>
    <item>
      <title>One day I woke up to a crashed AKS cluster and this is what I did to get it back to life</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Mon, 05 Sep 2022 18:57:35 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/one-day-wake-up-to-a-crashed-aks-cluster-and-this-is-what-i-did-to-get-it-back-to-life-1592</link>
      <guid>https://community.ops.io/javi_labs/one-day-wake-up-to-a-crashed-aks-cluster-and-this-is-what-i-did-to-get-it-back-to-life-1592</guid>
      <description>&lt;p&gt;[Article updated]&lt;/p&gt;

&lt;p&gt;One day I just found a crashed AKS cluster where I used to have a healthy cluster, yes, you read it right, a cluster with 3 nodes simply stopped working in the tree nodes at the same time.&lt;/p&gt;

&lt;p&gt;At first for some reason I thought it was a problem with the portal and that the pods were still working fine but I only lost proper response from the control plane (the side of Kubernetes you don't manage in a managed cluster), to my surprise, no, the complete cluster crashed with all the pods in "Terminating" as the control plane was desperately trying to reschedule them in a healthy node (sadly there were no healthy nodes).&lt;/p&gt;

&lt;h2&gt;
  
  
  And now what?
&lt;/h2&gt;

&lt;p&gt;Well, my first attempt was check what the portal was reporting, so I went to the portal, selected my cluster and then went directly to "node pools", to my (not) surprise, the nodepool was in "not ready" status, indicating 3/3 nodes were in "Failed" status... nice :)&lt;/p&gt;

&lt;p&gt;So my second attempt was "simply" spin up a new node_pool, super simple.... well, I was in an unsupported version, so you can't do that&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lesson learned #1&lt;/p&gt;

&lt;p&gt;If your control plane is in a version that is not&lt;br&gt;
available in azure to create new clusters, it &lt;br&gt;
means that you can't create node_pools with that &lt;br&gt;
version, you need to update your control plane &lt;br&gt;
first in order to create node_pools&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Ok, no new node_pool for me (bad Javi, you should keep your clusters updated!!). Next idea? yes, add another node to the same/existing node_pool, that will create another node with the same image of the others, this is something that DOES work even when you are in outdated version of kubernetes.&lt;/p&gt;

&lt;p&gt;Well... when you create your cluster you can choose two kinds of network plugins in AKS, the regular "kubenet" and the "Azure CNI", there are slights differences between them, the most notorious one is that Azure CNI pre-allocates an IP in the subnet of your cluster when you create a new node in the node_pool, this is because it allocates an IP of the subnet to each pod that could be created in the node, in this case my subnet was too small to fit another node, so no, I couldn't create another node because of the ip starvation in my subnet...&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lesson learned #2&lt;/p&gt;

&lt;p&gt;When you plan your kubernetes environment, think&lt;br&gt;
on the future growth, maybe make extra room for&lt;br&gt;
a few more nodes, if your don't create them&lt;br&gt;
you don't pay for them, but you still have plenty&lt;br&gt;
of space in your subnet.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Not all is darkness and desolation
&lt;/h2&gt;

&lt;p&gt;Al this point nothing was working, so I was not loosing anything by cordoning my nodes and trying to delete one of them. From the portal was not possible, any scaling operation was taking around 20 minutes to simply fail.&lt;br&gt;
I ran &lt;code&gt;kubectl cordon &amp;lt;node_name&amp;gt;&lt;/code&gt; on my tree nodes and then tried a &lt;code&gt;kubectl drain &amp;lt;node_name&lt;/code&gt; on one of them, it complained about the daemonsets and the stateful pods, force them to be removed anyway and then..... it failed to drain the node, if you think a bit about it it actually makes sense, who is gonna drain the nods (a simple delete pod in the background) if the kubelet was dead... absolutely no one, so I simple deleted the node &lt;code&gt;kubectl delete node&lt;/code&gt; what can I lose?&lt;/p&gt;

&lt;p&gt;IT WORKED! the node was gone, I checked the Azure portal and it was actually deleted (I have the suspicion that the nodes were not even there, they crashed and the Portal never updated the status).&lt;/p&gt;

&lt;p&gt;The next thing I noticed is that my node count in the Portal was still 3... despite the fact I had only 2 visible in there.That was weird, but I decided to wait 20 minutes and see it a new node spawned by it's own.... no, it never happened, so I simple scaled up to 4 nodes, my idea was that AKS will try to match the new &lt;code&gt;max nodes&lt;/code&gt; by creating a new node, and that's exactly what happened!!!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Lesson learned #3&lt;/p&gt;

&lt;p&gt;The Azure Portal some times is a bit buggy, &lt;br&gt;
take what it shows with careful, trust you&lt;br&gt;
tool kit and try to obtain as much information&lt;br&gt;
as you can in different ways.&lt;/p&gt;

&lt;p&gt;Lesson learned #4&lt;/p&gt;

&lt;p&gt;Think outside of the box, if I was in 3 nodes&lt;br&gt;
already and I was seeing only 2, to create&lt;br&gt;
a new one I decided to scale up to 4, this&lt;br&gt;
makes no sense, but it actually worked.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The (slow) resurrection of the cluster
&lt;/h2&gt;

&lt;p&gt;So now I had a new node, WITH the same version my previous one had AND without needing to update my control plane.&lt;br&gt;
As soon as this node was ready all my deployments tried desperately to schedule their pods in it (good luck fitting all of them there), I repeated the step for the second node, this time scaling to 5, and it worked again!&lt;/p&gt;

&lt;p&gt;So after a few minutes I had 3 nodes (1 crashed, 2 healthy and all my pods running fine)&lt;/p&gt;

&lt;h2&gt;
  
  
  Recommendations
&lt;/h2&gt;

&lt;p&gt;Here is what I learned at the end and the knowledge I can transfer to you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;ALWAYS send our kubernetes logs to some other place, in my case log analytics, once the node crashed, you can't check the logs anymore.&lt;/li&gt;
&lt;li&gt;Setup a nice set of alerts to know when something doesn't goes nice, even for those cases you might think you are covered when running a managed cluster&lt;/li&gt;
&lt;li&gt;Remember, if everything is broken, you can't broke it even more, don't be afraid of deleting or breaking a little bit more something in the pursue of fixing the problem, after all the cluster was already not working&lt;/li&gt;
&lt;li&gt;If you can, document every step you do, it is helpful for when you have a similar problem or just to share with others (or your team)&lt;/li&gt;
&lt;li&gt;Have your kubernetes cheat sheet handy, you never know when you will need some command that you rarely use (I will be posting something about this soon)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Update (30-Sept-2022)
&lt;/h2&gt;

&lt;p&gt;Some things to add in this case that I found useful to share are:&lt;/p&gt;

&lt;p&gt;1) The whole problem was caused initially because the service principal used by AKS to interact with Azure resources had it secret/password expired, so part of the solution was to update the secret in AAD, update AKS to use the new one and then follow the steps described here.&lt;/p&gt;

&lt;p&gt;2) If this service principal expires, AKS gets into a "failed" status, which block any kubernetes version update in the future (but the workloads will keep working), to resolve this you need to use the azcli to upgrade the version of the AKS control plane &lt;code&gt;to the same version you are currently running&lt;/code&gt; &lt;a href="https://learn.microsoft.com/en-us/answers/questions/893384/azure-aks-cluster-is-in-failed-state.html"&gt;as described here&lt;/a&gt;, this seems to do kind of a restart of the control plane, but remember to do this &lt;code&gt;after&lt;/code&gt; you do the service principal password reset, update AKS and do the other steps in this article.&lt;/p&gt;

&lt;p&gt;Up to here the update on this case, if I get more information in the future, I will update this article.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;I am still investigating what was the root cause of this, it is not common at all that suddenly all the nodes in your cluster crash, but I don't think I can get much information, the node is completely crashed, I can't even debug it and my only hope are the node logs (which I store in another place)&lt;/p&gt;

&lt;p&gt;I apologize for not including pictures in the post, I was trying to fix the problem while trying to extract something educative about the incident and totally forget about capturing some screenshots.&lt;/p&gt;

&lt;p&gt;If you enjoyed this post/history please let me know in the comments or with a hearth, and if you are interested in knowing more about my adventures on the wild, remember to follow me in &lt;a href="https://linktr.ee/javi_labs"&gt;my networks&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See you in another post, thank you for reading!&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>azure</category>
      <category>tutorials</category>
      <category>devops</category>
    </item>
    <item>
      <title>Install and run Grafana in AKS cluster</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Mon, 05 Sep 2022 07:19:16 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/install-and-run-grafana-in-aks-cluster-39cl</link>
      <guid>https://community.ops.io/javi_labs/install-and-run-grafana-in-aks-cluster-39cl</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;In this post I will go thru the steps needed to install &lt;a href="https://grafana.com/"&gt;Grafana&lt;/a&gt; in AKS as well as explaining what is Grafana used for and how it looks after installed.&lt;/p&gt;

&lt;h1&gt;
  
  
  What is Grafana
&lt;/h1&gt;

&lt;p&gt;To start this post let's first focus on what is Grafana and what is it used for. Grafana is a monitoring tool very widely used because of it's simplicity and power, you can monitor basically anything because of it's many datasources and it comes with an alert engine to send out notifications when a certain threshold is met.&lt;br&gt;&lt;br&gt;
It's configuration is not complicated and you can even version the dashboards you make in Grafana, but we will also cover in this post a powershell module that can help automate the creation of dashboards and panels in Grafana (At this moment it only supports Azure resources but I will work in add other datasources like Prometheus) this module is called &lt;a href="https://www.powershellgallery.com/packages/PowerGrafana/0.1.0"&gt;PowerGrafana&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;
  
  
  How to install it
&lt;/h1&gt;

&lt;p&gt;To install Grafana you have multiple options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run a VM with Grafana installed inside&lt;/li&gt;
&lt;li&gt;Run Grafana as a container in a VM&lt;/li&gt;
&lt;li&gt;Deploy a Grafana container inside a Kubernetes cluster deploying the resources by yourself&lt;/li&gt;
&lt;li&gt;Deploy a Grafana container inside a Kubernetes cluster using Helm Charts (we are going to use this one)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This post will cover the deployment of Grafana as a Helm Chart as we did in the post about Prometheus, I will link an article about a deeper explanation on Helm Charts in the near future to go thru the benefits of Helm Charts and specially how to adjust them to cover your particular needs.&lt;/p&gt;

&lt;p&gt;To quickly start with this let's go and add the repository and update our repository list.&lt;br&gt;
&lt;a href="https://community.ops.io/images/ZDvStM80TW7UA_f8pnYObAzjByhDW99Cqz3BUSw8UZ8/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXJlcG8t/YWRkLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ZDvStM80TW7UA_f8pnYObAzjByhDW99Cqz3BUSw8UZ8/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXJlcG8t/YWRkLnBuZw" alt="helm_repo_add" width="880" height="271"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And now we can check the Charts we have in the repository we just added, let's take a look.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://community.ops.io/images/ZllbTmRkN1OmqTVq60e8u1jeBuBi0OmodCYO1nl3JJI/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXNlYXJj/aC1yZXBvLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ZllbTmRkN1OmqTVq60e8u1jeBuBi0OmodCYO1nl3JJI/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXNlYXJj/aC1yZXBvLnBuZw" alt="helm_search_repo" width="880" height="242"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Perfect! we are ready to start working!&lt;/p&gt;
&lt;h1&gt;
  
  
  Prerequisites
&lt;/h1&gt;

&lt;p&gt;Well for this article we will need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;An AKS cluster up and running, nothing special, just the base deployment is fine, you can follow this &lt;a href="https://docs.microsoft.com/en-us/azure/aks/kubernetes-walkthrough-portal"&gt;tutorial&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A terminal with kubectl and helm installed &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Install the grafana helm repository&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;
  
  
  Installation
&lt;/h1&gt;

&lt;p&gt;Since we want to keep our grafana resources in a separate namespace to have a better view of what was created, we will start by creating a namespace called &lt;code&gt;monitoring&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl create namespace monitoring
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/ecWohIpXohctdAbSyZ1pOXveYdQAH5ZpmlOpCc3vnm0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9jcmVhdGUtbmFt/ZXNwYWNlLnBuZw" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/ecWohIpXohctdAbSyZ1pOXveYdQAH5ZpmlOpCc3vnm0/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9jcmVhdGUtbmFt/ZXNwYWNlLnBuZw" alt="create_namespace" width="880" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can see here I use &lt;code&gt;kubens&lt;/code&gt; to switch to the monitoring namespace, this is a very cool tool that makes your work with kubernetes easy by changing your namespace to the one you define so your next commands doesn't need to include the namespace on them&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm search repo grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/vGMwYjzrNKyCl-Dgm5lRHAvn3ZjdzJqyu_WgTBUSCLc/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXNlYXJj/aC1yZXBvLWdyYWZh/bmEucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/vGMwYjzrNKyCl-Dgm5lRHAvn3ZjdzJqyu_WgTBUSCLc/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLXNlYXJj/aC1yZXBvLWdyYWZh/bmEucG5n" alt="helm_search_repo_grafana" width="880" height="245"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nice!, lot's of charts there, you can see there are charts for multiple solutions, including Grafana Enterprise, Loki (focused on logs more than metrics) and the base &lt;code&gt;grafana&lt;/code&gt; one, that's the one we will be using, let's install it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;helm &lt;span class="nb"&gt;install &lt;/span&gt;grafana grafana/grafana
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/xNea1uMLah9COogo3lWvLn2BqwJ7UJJ0ZkgvEnNAfcQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLWluc3Rh/bGwtZ3JhZmFuYS5w/bmc" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/xNea1uMLah9COogo3lWvLn2BqwJ7UJJ0ZkgvEnNAfcQ/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9oZWxtLWluc3Rh/bGwtZ3JhZmFuYS5w/bmc" alt="helm_install_grafana" width="880" height="328"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here we can see the helm command completed and we can notice several things, let's go over them one by one:&lt;/p&gt;

&lt;h3&gt;
  
  
  Warnings about deprecations:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;W1115 09:12:11.499698    9800 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W1115 09:12:11.529960    9800 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W1115 09:12:12.040639    9800 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
W1115 09:12:12.040639    9800 warnings.go:70] policy/v1beta1 PodSecurityPolicy is deprecated in v1.21+, unavailable in v1.25+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is because my kubernetes cluster is running version v1.21.2 which means the &lt;code&gt;PodSecurityPolicy kind of resource for the apiVersion v1beta1&lt;/code&gt; is deprecated in my version and will be completely removed in the version v1.25 of kubernetes, this is something to cover in the post about helm, I will link it at the end of this article as soon as it is finished, but for now we don't need to worry about this.&lt;/p&gt;

&lt;h3&gt;
  
  
  Grafana admin password:
&lt;/h3&gt;

&lt;p&gt;Of course, the initial setup needs to configure a default password for the admin account, after your first login you must change this password but to get what this initial password is you need to retrieve it from a &lt;code&gt;secret&lt;/code&gt;, and then decode it (secrets are stored encoded as base64)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secret &lt;span class="nt"&gt;--namespace&lt;/span&gt; monitoring grafana &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.data.admin-password}"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;--decode&lt;/span&gt; &lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will first retrieve the secret called &lt;code&gt;grafana&lt;/code&gt; and from it will take the &lt;code&gt;admin-password&lt;/code&gt; data, decode it and output to the terminal, let's see what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;If you don't have base64 installed in your system you can use brew (linux, macos) or chocolatey (windows)&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;I am running the commands in Windows so I should run:&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl get secret &lt;span class="nt"&gt;--namespace&lt;/span&gt; monitoring grafana &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;jsonpath&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"{.data.admin-password}"&lt;/span&gt; | &lt;span class="nb"&gt;base64&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and my output is &lt;code&gt;xzUvc2esXl2aSivqhQQ5X3ZZ01TZ2HMCEPdpWVSJ&lt;/code&gt; so that's the password of my admin account in the Grafana deployment I just installed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Accessing our Grafana!
&lt;/h3&gt;

&lt;p&gt;The last step is how to reach our Grafana instance, and it states that we can access it reaching the service called &lt;code&gt;grafana.monitoring.svc.cluster.local&lt;/code&gt; but that will work if you are running kubernetes locally, in my case I am running an AKS cluster in the cloud, which meands I need to port forward the port 3000 of the Grafana server pod to my localmachine port 3000 (or any other port that is not in use), to do this port-forward I need the &lt;code&gt;pod name&lt;/code&gt; which we can get by running this:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;kubectl get pods --namespace monitoring -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana" -o jsonpath="{.items[0].metadata.name}"&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why do I use port 3000 and not 80 as stated in the output of the helm install command?, that is because the &lt;code&gt;service&lt;/code&gt; will listen in port 80 as the snippet states, but the pods listes in port 3000 which is the default port for Grafana and since I am accessing the pod directly I need to use the pod port (3000) instead of the service port (80)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This will get the pods in namespace &lt;code&gt;monitoring&lt;/code&gt; that matches the labels &lt;code&gt;name=grafana&lt;/code&gt; and &lt;code&gt;instance=grafana&lt;/code&gt; then it will apply a jsonpath query to extract the name of the pod, in my case that is &lt;code&gt;grafana-5c999c4fd5-czxdw&lt;/code&gt;.  &lt;/p&gt;

&lt;p&gt;Now we can simply do a:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;kubectl port-forward grafana-5c999c4fd5-czxdw 3000:3000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://community.ops.io/images/2g2Lb-D4Yxd5uh2px13-uj9hl_nU-BIhAEAh4P5r0sU/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9ncmFmYW5hLWxv/Z2luLTEucG5n" class="article-body-image-wrapper"&gt;&lt;img src="https://community.ops.io/images/2g2Lb-D4Yxd5uh2px13-uj9hl_nU-BIhAEAh4P5r0sU/w:880/mb:500000/ar:1/aHR0cHM6Ly9yYXcu/Z2l0aHVidXNlcmNv/bnRlbnQuY29tL2ph/dmllcm1hcmFzY28v/YXJ0aWNsZXMvbWFp/bi9BcnRpY2xlcy9J/bWFnZXMvZ3JhZmFu/YS9ncmFmYW5hLWxv/Z2luLTEucG5n" alt="grafana_login" width="880" height="476"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Use the username &lt;code&gt;admin&lt;/code&gt; and the password you got in the step 1 of the instructions of the helm chart.&lt;/p&gt;

&lt;p&gt;To this point Grafana is fully configured but keep in mind that his installation is very basic, it will not persist (keep) your dashboards or any other configuration if the pod is killed, and this only has one single pod.&lt;/p&gt;

&lt;p&gt;All this can changed by downloading the Chart locally with &lt;code&gt;helm pull grafana/grafana --untar&lt;/code&gt; and then adjusting our needs in there. If you do this you will notice the fact that there is no persistence once the pod is killed is because the storage for the &lt;code&gt;/var/lib/&lt;/code&gt; directory (where all grafana config is stored) is an &lt;code&gt;EmptyDir&lt;/code&gt; which is a very quick way to give the pod some place where to store data but it will be removed when the pod dies.&lt;/p&gt;

&lt;p&gt;I will cover more on how to customize Helm Charts locally in the post about that in the near future.&lt;/p&gt;

&lt;h1&gt;
  
  
  Final words
&lt;/h1&gt;

&lt;p&gt;I hope the explanation was clear and you have now the basics to start playing around with Grafana, if you liked this post, please feel free to leave a comment and/or share it!. Thank you for reading 😊&lt;/p&gt;

&lt;p&gt;If you liked this article, please consider letting me know by liking this article, leaving a comment or sharing this article with others, you can also follow me in &lt;a href="https://linktr.ee/javi_labs"&gt;my networks&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>kubernetes</category>
      <category>grafana</category>
      <category>azure</category>
      <category>docker</category>
    </item>
    <item>
      <title>How to auto-document your deployments</title>
      <dc:creator>Javier Marasco</dc:creator>
      <pubDate>Wed, 31 Aug 2022 17:00:19 +0000</pubDate>
      <link>https://community.ops.io/javi_labs/how-to-auto-document-your-deployments-1cp6</link>
      <guid>https://community.ops.io/javi_labs/how-to-auto-document-your-deployments-1cp6</guid>
      <description>&lt;h2&gt;
  
  
  A bit of background
&lt;/h2&gt;

&lt;p&gt;For most of my infrastructure deployments (if not all) I use a combination of terraform and terragrunt (I could write an article on it if you like, let me know in the comments) which relies on a configuration file to know what the automation should build.&lt;br&gt;
You can think of it like there is a pipeline that reads a config file (json), extracts the information from it and passes it to terraform/terragrunt to run the build.&lt;/p&gt;

&lt;p&gt;After each automation I create, there is a documentation on how to use it, what does what and where every piece of code exists and how to extend functionality if needed, this is very important for my team (so everyone can use it without me even present) but also as a guideline for another automations I will be doing in the future so I can re-use some patterns.&lt;/p&gt;
&lt;h2&gt;
  
  
  Functional documentation vs build documentation
&lt;/h2&gt;

&lt;p&gt;After I create the documentation on how the solution works (functional documentation) there is another kind of documentation that is not so common to have, I am talking about a place where you can store details of what my automation built, and you can imagine this is kind of hard to track as (remember anyone in my team can run the pipeline now reading my documentation) more runs are done on the pipeline.&lt;/p&gt;
&lt;h2&gt;
  
  
  Approach (what if.....)
&lt;/h2&gt;

&lt;p&gt;A few days ago I woke up having an idea and I started coding it right away (yeah... about 6am in the morning on a Monday...), I was excited to test if the idea was actually possible and how would it work out in the reality.&lt;br&gt;
The idea was simple apparently: "Every time the pipeline runs, it will read the config files (json files) and extract information from them and format them nicely in html and publish that into some place". That sounds easy, so let's see what else can we add.&lt;/p&gt;
&lt;h2&gt;
  
  
  Folders, files and configs
&lt;/h2&gt;

&lt;p&gt;Ok, I wrote a very simple folder structure to simulate an imaginary repository, this is like this.&lt;/p&gt;

&lt;p&gt;/folder/app1/config.json&lt;br&gt;
/folder/app2/config.json&lt;br&gt;
/folder/app3/config.json&lt;/p&gt;

&lt;p&gt;Each one of the config.json contains this structure with different values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"Name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My resource in one"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"UUID"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"232131232-13eqwesd2-12321w-dsdasda"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ConnectionString"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"My-connection-one.com:2020"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"IPs"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"10.1.1.2"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"23.2.2.12"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"192.233.123.3"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is a very simple configuration file, imagine this will be sent as input to a terraform module that would build something for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code approach
&lt;/h2&gt;

&lt;p&gt;I will be writing this in Python. First we will define a basic html structure and a variable that will contain our dynamic content.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;json&lt;/span&gt;

&lt;span class="n"&gt;base_template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'''
&amp;lt;body&amp;gt;
    &amp;lt;table style="border: 1px solid black;"&amp;gt;
        &amp;lt;th style="border: 1px solid black;"&amp;gt;Name&amp;lt;/th&amp;gt;
        &amp;lt;th style="border: 1px solid black;"&amp;gt;Connection string&amp;lt;/th&amp;gt;
        &amp;lt;th style="border: 1px solid black;"&amp;gt;IPs&amp;lt;/th&amp;gt;
        {tr}
    &amp;lt;/table&amp;gt;
&amp;lt;/body&amp;gt;
'''&lt;/span&gt;

&lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;''&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;base_template&lt;/code&gt; contains an html table with a template place holder &lt;code&gt;{tr}&lt;/code&gt; which will be replaced later by the content of &lt;code&gt;temporal_tr&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Then, we need to iterate over any .json file in our folder structure where we have our configuration files.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;subdirectories&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;walk&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="s"&gt;'/configs'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;    
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; 
        &lt;span class="k"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;__contains__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'.json'&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;tr&amp;gt;'&lt;/span&gt;
            &lt;span class="n"&gt;config_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;config_file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'r'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;content_json&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;read_json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;td style="border: 1px solid black;"&amp;gt;'&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;content_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'Name'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/td&amp;gt;'&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;td style="border: 1px solid black;"&amp;gt;'&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;content_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'ConnectionString'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/td&amp;gt;'&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;td style="border: 1px solid black;"&amp;gt;'&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;' '&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_json&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'IPs'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
                &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/td&amp;gt;'&lt;/span&gt;
            &lt;span class="n"&gt;temporal_tr&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'&amp;lt;/tr&amp;gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep in mind this is VERY hardcoded to match the config files I am using in this test, this definitely needs adjusts to match your case or even be more performant.&lt;/p&gt;

&lt;p&gt;Lastly we will need to grab an &lt;code&gt;index.html&lt;/code&gt; file, write our table inside, replace the &lt;code&gt;{tr}&lt;/code&gt; place holder and save the &lt;code&gt;index.html&lt;/code&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;path_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dirname&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__file__&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;'index.html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;write_base&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;write_base&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;base_template&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;tmpl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'rt'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tmpl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;format&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;temporal_tr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path_html&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'w'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, if we execute this file, if will update an &lt;code&gt;index.html&lt;/code&gt; file with the table format and the dynamic content extracted from our files.&lt;/p&gt;

&lt;p&gt;You can try adding more config files (following the folder structure) and run the script again to see the changes in the file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Just one more thing
&lt;/h2&gt;

&lt;p&gt;Why stop here and have an index.html file with our content? why not do something more? ... since confluence is so used and they have a free tier, I opened an space there and I added a functionality to update a page in a particular space in confluence with the content of the &lt;code&gt;index.html&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;Before we can start this section you need to do some steps.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Go to &lt;a href="https://www.atlassian.com/software/confluence/free"&gt;Confluence&lt;/a&gt; and open a free cloud account&lt;/li&gt;
&lt;li&gt;Log in into &lt;a href="https://id.atlassian.com/manage-profile/security/api-tokens"&gt;here&lt;/a&gt; and create a token for your access&lt;/li&gt;
&lt;li&gt;Create an empty page and retrieve the id of the page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How does that works? I wrote a simple function that does this for you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Get's the current version of your page&lt;/li&gt;
&lt;li&gt;Increases the version by 1&lt;/li&gt;
&lt;li&gt;Creates the content of the page based on our previous work&lt;/li&gt;
&lt;li&gt;Publish the content to the confluence page
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_to_confluence&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
    &lt;span class="n"&gt;confluence_page_version_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'%s?expand=version'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page_current_version&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;confluence_page_version_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'version'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s"&gt;'number'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'"'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"'"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'''
    {"id":"%s","type":"page", "title":"%s","body":{"storage":{"value": "%s","representation":"storage"}}, "version":{"number":%i}}
    '''&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;page_current_version&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;update_page_content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s"&gt;''&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At this point, your confluence page is updated and every time your pipeline runs you get this page updated.&lt;/p&gt;

&lt;h3&gt;
  
  
  We have a small problem here, and that is you will eventually end with a lot of versions there, so we can do some plean up and remove any older version and retain only the last X amount of versions. For that I have this other function:
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;clean_old_versions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;requests&lt;/span&gt;
    &lt;span class="n"&gt;versions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'%s/version'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nb"&gt;max&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;versions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="s"&gt;'results'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;amount_to_delete&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;max&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;threshold&lt;/span&gt;
        &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;amount_to_delete&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'%s/version/%s'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;amount_to_delete&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;'content-type'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;'application/json'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
            &lt;span class="n"&gt;amount_to_delete&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What if I don't want to use confluence but I still want to show this to others?
&lt;/h2&gt;

&lt;p&gt;Well.... there is another possibility, Azure storage accounts have a functionality called &lt;code&gt;static pages&lt;/code&gt;, basically there is a repository in them that you can upload an &lt;code&gt;index.html&lt;/code&gt; file and expose it to the world using a well known azure dns FQDN (you can later configure your DNSs to use a more friendly name if you want).&lt;/p&gt;

&lt;p&gt;I just created an azure storage account in Azure and enabled "Static websites" this gives us an url &lt;code&gt;https://javilabsstaticpage.z6.web.core.windows.net/&lt;/code&gt; (in my case)&lt;br&gt;
and also creates a &lt;code&gt;$web&lt;/code&gt; container so we store there our static page (you need to specify the page you will be serving and also optionally an error page, I called mine index.html) so you should upload your page into this container.&lt;/p&gt;

&lt;p&gt;I will use the python SDK for this so we keep the same language and integrate all this in a single big script.&lt;/p&gt;

&lt;p&gt;You will need to install the Azure SDK (mainly the authorization module and the storage one, nothing else).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt; &lt;span class="n"&gt;install&lt;/span&gt; &lt;span class="n"&gt;azure&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;storage&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you installed this, you can execute this code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;publish_to_storageaccount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_conn_str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;content_file_location&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;azure.storage.blob&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BlobServiceClient&lt;/span&gt;
    &lt;span class="n"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;'$web'&lt;/span&gt;
    &lt;span class="n"&gt;blob_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BlobServiceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;from_connection_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;account_conn_str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;my_blob&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blob_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_blob_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'index.html'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;my_blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delete_snapshots&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s"&gt;'include'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nb"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;content_file_location&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"rb"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;my_blob&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upload_blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this you are uploading into the $web container, the html file you created previously (the one you are using to update your confluence page).&lt;/p&gt;

&lt;p&gt;In this way, you also stored this information in a cheap storage that can be accessed from internet.&lt;/p&gt;

&lt;h2&gt;
  
  
  And how does all this works with a pipeline?
&lt;/h2&gt;

&lt;p&gt;Well, at this point you have all the code needed to use any CI/CD tool you know/use and execute this script pointing to the place where your configuration files are. &lt;br&gt;
Every time the pipeline runs, you will be updating your confluence page and/or your storage account static page, no more manual updates and a real reflection of your deployed resources in a handy place.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap up
&lt;/h2&gt;

&lt;p&gt;So in this article you saw how to make your pipeline update a document on each run, so basically on every run, you will be updating the documentation with the list of what was created by the pipeline while you don't need to do anything for it to keep current.&lt;/p&gt;

&lt;p&gt;I hope you find this articule useful and let me know if you have any question, if you find this interesting I will appreciate you share if with others, leave a heath, a comment or follow me in any of my &lt;a href="https://linktr.ee/javi_labs"&gt;networks&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Thank you for reading!&lt;/p&gt;

</description>
      <category>python</category>
      <category>tutorials</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
