<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Davide Bedin</title>
    <description>The latest articles on DEV Community by Davide Bedin (@bedindavide).</description>
    <link>https://dev.to/bedindavide</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F348348%2Fe5d6b5ca-0029-438c-ad95-2355582257b1.jpg</url>
      <title>DEV Community: Davide Bedin</title>
      <link>https://dev.to/bedindavide</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/bedindavide"/>
    <language>en</language>
    <item>
      <title>Unlocking the Power of Azure OpenAI with Azure API Management</title>
      <dc:creator>Davide Bedin</dc:creator>
      <pubDate>Wed, 06 Mar 2024 12:51:28 +0000</pubDate>
      <link>https://dev.to/bedindavide/unlocking-the-power-of-azure-openai-with-azure-api-management-5gb0</link>
      <guid>https://dev.to/bedindavide/unlocking-the-power-of-azure-openai-with-azure-api-management-5gb0</guid>
      <description>&lt;p&gt;In today’s world, artificial intelligence (AI) plays a pivotal role in transforming businesses. Azure OpenAI Service (AOAI in the article) combines the power of OpenAI’s advanced models with the security and enterprise capabilities of Azure. Harnessing the full potential of Azure OpenAI requires effective management, security, and scalability: this is where Azure API Management (APIM) steps in to help.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL; DR;
&lt;/h2&gt;

&lt;p&gt;As I engaged with customers on Azure API Management + Azure Open AI scenarios, I had the chance to combine &amp;amp; extend few excellent ready to use scenario &amp;amp; samples, specifically to track tokens used by application.&lt;/p&gt;

&lt;p&gt;In a nutshell I investigated:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;How to correlate the &lt;em&gt;diagnostics from APIM with the ones coming from AOAI&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;How to track &lt;em&gt;tokens used by each AOAI requests, therefore measuring AOAI token usage per APIM subscription = application&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This post describes how to do it.&lt;/p&gt;

&lt;p&gt;All code, queries and workbook is in this &lt;a href="https://github.com/dabedin/apim-aoai-smart-loadbalancing/tree/feature/tracingAOAI" rel="noopener noreferrer"&gt;feature branch dabedin/apim-aoai-smart-loadbalancing/tree/feature/tracingAOAI&lt;/a&gt; which is a fork of the great &lt;em&gt;Smart load balancing for OpenAI endpoints and Azure API Management&lt;/em&gt; described below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Azure API Management?
&lt;/h2&gt;

&lt;p&gt;Azure API Management is a robust solution that allows organizations to expose APIs securely, manage access, and monitor usage. It acts as a gateway, enabling controlled access to APIs while shielding sensitive keys. When combined with Azure OpenAI, it becomes a central capability that enhances the overall application and user experience.&lt;/p&gt;

&lt;h2&gt;
  
  
  Azure API Management for Azure OpenAI
&lt;/h2&gt;

&lt;p&gt;Here's a list of my favorite Azure OpenAI with Azure API Manangement architectures and samples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/azure/apim-aoai-smart-loadbalancing" rel="noopener noreferrer"&gt;Smart load balancing for OpenAI endpoints and Azure API Management&lt;/a&gt; which elegantly supports smart load balancing between AOAI endpoints managing tokens per minute (TPM) and requests per minute (RPM) constraints via APIM policy.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/dolevshor/Azure-OpenAI-Insights#how-to-use-it" rel="noopener noreferrer"&gt;Azure OpenAI Insights&lt;/a&gt; that offers a rich visualization on AOAI diagnostics persisted to a central Log Analytics.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/azure/architecture/ai-ml/openai/architecture/log-monitor-azure-openai" rel="noopener noreferrer"&gt;Implement logging and monitoring for Azure OpenAI models&lt;/a&gt; describe an approach to logging and monitoring for Azure OpenAI models. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is this post all about?
&lt;/h2&gt;

&lt;p&gt;Together with the customer, we built a scenario combining many of the previously described approaches. Let's start with a diagram:&lt;/p&gt;

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

&lt;p&gt;In the architecture presented above, APIM role is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Governing access&lt;/strong&gt; of external applications (via subscription) to AOAI, leveraging APIM Managed Identity authentication (&lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-azure-openai#authenticate-with-managed-identity" rel="noopener noreferrer"&gt;https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-azure-openai#authenticate-with-managed-identity&lt;/a&gt;) and preventing spread of AOAI access keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Smart load balancing&lt;/strong&gt; of request from external application to OpenAI LLMs between multiple AOAI resources, whether because you decided to purchase &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/provisioned-throughput" rel="noopener noreferrer"&gt;provisioned throughput&lt;/a&gt; for predictable performances and cost saving AND/OR you want to increase overall resilience distributing load among multiple regions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Measure usage&lt;/strong&gt; of AOAI resources by external applications considering the metrics that are mostly relevant: while the typical API usually favors RPM, the used tokens are the most relevant usage metric in AOAI.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  An important constraint
&lt;/h2&gt;

&lt;p&gt;As clearly described in the APIM documentation, &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-app-insights?tabs=rest#performance-implications-and-log-sampling" rel="noopener noreferrer"&gt;monitoring can have a significant impact on performances&lt;/a&gt;. That is the reason why we have a sampling rate of just 10% for detailed traces destined for Application Insights. The following image shows the configuration:&lt;/p&gt;

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

&lt;p&gt;Also, to avoid potential impact on performances, we chose to neither log the payload of frontend or backend request &amp;amp; response. On the contrary, we decided to precisely trace only the information needed --&amp;gt; more about this in the next section.&lt;/p&gt;

&lt;p&gt;While sampling is applied to Application Insights, we are leveraging &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-howto-use-azure-monitor#view-diagnostic-data-in-azure-monitor" rel="noopener noreferrer"&gt;APIM diagnostics&lt;/a&gt; to track all requests in APIM, as the main mechanism to measure application usage. APIM diagnostics is configured to be persisted in the Log analytics workspace also shared by the multiple AOAI resources. &lt;/p&gt;

&lt;p&gt;Furthermore, customer is heavily leveraging Azure Log Analytics workspaces for storing logs and metrics and building workbooks on top of it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Bringing it all together
&lt;/h2&gt;

&lt;p&gt;Distributed tracing is the cornerstone of any modern application. Azure API Management supports the &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C trace context&lt;/a&gt; on top of which &lt;a href="https://opentelemetry.io/docs/concepts/context-propagation/" rel="noopener noreferrer"&gt;OpenTelemetry&lt;/a&gt; is built, so a client initiated distributed trace can pass through APIM and include interactions with backends and other resources.&lt;/p&gt;

&lt;p&gt;APIM and AOAI support rich diagnostics, each on its own terms. Digging deeper on this part of the scenario, I found out that the W3C trace context passed by APIM in the request to the AOAI backend is not persisted in the AOAI diagnostic logs. I also noticed the AOAI response includes a &lt;em&gt;apim-request-id&lt;/em&gt; header and also returns back the &lt;em&gt;x-ms-client-request-id&lt;/em&gt; header with the same value passed by the client (APIM in this stance) or with the same value as &lt;em&gt;apim-request-id&lt;/em&gt;.&lt;br&gt;
As from Azure documentation (like &lt;a href="https://learn.microsoft.com/en-us/azure/machine-learning/how-to-troubleshoot-online-endpoints?view=azureml-api-2&amp;amp;tabs=cli#request-tracing" rel="noopener noreferrer"&gt;this one&lt;/a&gt;) the &lt;em&gt;x-ms-client-request-id&lt;/em&gt; is intended to be used as a 40-chars long client tracing string which . &lt;br&gt;
For the time being, I decided not to pass a segment of the traceparent I can access in the APIM policy, also because while the AOAI diagnostic logs does include the value of &lt;em&gt;apim-request-id&lt;/em&gt; header in the &lt;em&gt;CorrelationId&lt;/em&gt; column, it does not persist the &lt;em&gt;x-ms-client-request-id&lt;/em&gt; header.&lt;/p&gt;

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

&lt;p&gt;As depicted in the diagram above, I included a section the powerful &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/provisioned-throughput" rel="noopener noreferrer"&gt;smart load balancing policy for Azure API Management&lt;/a&gt; to &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/trace-policy" rel="noopener noreferrer"&gt;trace&lt;/a&gt; a tuple made of the the &lt;em&gt;traceparent&lt;/em&gt; header from the APIM request and the &lt;em&gt;apim-request-id&lt;/em&gt; from the AOAI response, this for each retry attempt performed by the logic.&lt;/p&gt;

&lt;p&gt;The following policy fragment shows how to trace the aforementioned correlation information AND the involved tokens.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="c"&gt;&amp;lt;!-- Prepare the tokens correlation info --&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;choose&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;when&lt;/span&gt; &lt;span class="na"&gt;condition=&lt;/span&gt;&lt;span class="s"&gt;"@(context.Response != null &amp;amp;&amp;amp; context.Response.StatusCode == 200)"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;set-variable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tokens"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"@{
         var responseBody = context.Response.Body.As&amp;lt;JObject&amp;gt;(preserveContent: true);

         return new JObject(
            new JProperty("&lt;/span&gt;&lt;span class="err"&gt;apim-traceparent",&lt;/span&gt; &lt;span class="err"&gt;context.Request.Headers.GetValueOrDefault("traceparent",string.Empty)),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("aoai-correlation",&lt;/span&gt; &lt;span class="err"&gt;context.Response.Headers.GetValueOrDefault("apim-request-id",string.Empty)),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("prompt_tokens",&lt;/span&gt; &lt;span class="err"&gt;responseBody["usage"]["prompt_tokens"]),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("completion_tokens",&lt;/span&gt; &lt;span class="err"&gt;responseBody["usage"]["completion_tokens"]),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("total_tokens",&lt;/span&gt; &lt;span class="err"&gt;responseBody["usage"]["total_tokens"]),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("aoai-statusCode",&lt;/span&gt; &lt;span class="err"&gt;context.Response.StatusCode)&lt;/span&gt;
          &lt;span class="err"&gt;).ToString();&lt;/span&gt;
        &lt;span class="err"&gt;}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/when&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;otherwise&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;set-variable&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"tokens"&lt;/span&gt; &lt;span class="na"&gt;value=&lt;/span&gt;&lt;span class="s"&gt;"@{
         return new JObject(
            new JProperty("&lt;/span&gt;&lt;span class="err"&gt;apim-traceparent",&lt;/span&gt; &lt;span class="err"&gt;context.Request.Headers.GetValueOrDefault("traceparent",string.Empty)),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("aoai-correlation",&lt;/span&gt; &lt;span class="err"&gt;context.Response&lt;/span&gt; &lt;span class="err"&gt;!=&lt;/span&gt; &lt;span class="err"&gt;null&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;context.Response.Headers.GetValueOrDefault("apim-request-id",string.Empty)&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;string.Empty),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("prompt_tokens",&lt;/span&gt; &lt;span class="err"&gt;0),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("completion_tokens",&lt;/span&gt; &lt;span class="err"&gt;0),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("total_tokens",&lt;/span&gt; &lt;span class="err"&gt;0),&lt;/span&gt;
            &lt;span class="err"&gt;new&lt;/span&gt; &lt;span class="err"&gt;JProperty("aoai-statusCode",&lt;/span&gt; &lt;span class="err"&gt;context.Response&lt;/span&gt; &lt;span class="err"&gt;!=&lt;/span&gt; &lt;span class="err"&gt;null&lt;/span&gt; &lt;span class="err"&gt;?&lt;/span&gt; &lt;span class="err"&gt;context.Response.StatusCode&lt;/span&gt; &lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="err"&gt;0)&lt;/span&gt;
         &lt;span class="err"&gt;).ToString();&lt;/span&gt;
        &lt;span class="err"&gt;}"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;/otherwise&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/choose&amp;gt;&lt;/span&gt;
&lt;span class="c"&gt;&amp;lt;!--Trace the tokens correlation--&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;trace&lt;/span&gt; &lt;span class="na"&gt;source=&lt;/span&gt;&lt;span class="s"&gt;"Global APIM Policy"&lt;/span&gt; &lt;span class="na"&gt;severity=&lt;/span&gt;&lt;span class="s"&gt;"information"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
   &lt;span class="nt"&gt;&amp;lt;message&amp;gt;&lt;/span&gt;@(context.Variables.GetValueOrDefault&lt;span class="nt"&gt;&amp;lt;string&amp;gt;&lt;/span&gt;("tokens", "none"))&lt;span class="nt"&gt;&amp;lt;/message&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/trace&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What is the outcome of this tracing in the APIM diagnostic logs? As an example, following the sequence described in the previous diagram, a client request which encountered a HTTP 429 failure from a higher priority AOAI resource, therefore retried with the lower priority AOAI resource receiving a HTTP 200 success, would have two elements in the &lt;em&gt;TraceRecords&lt;/em&gt; column in the  &lt;em&gt;ApiManagementGatewayLogs&lt;/em&gt; log anaytics table, on the record corresponding to the client request to APIM, as depicted below:&lt;/p&gt;

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

&lt;p&gt;In the screenshot above you can notice the two requests originated in APIM towards AOAI have the same &lt;em&gt;trace-id&lt;/em&gt; but different &lt;em&gt;parent-id&lt;/em&gt;, as in accordance with &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C context specification&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  KQL rules!
&lt;/h2&gt;

&lt;p&gt;So far I described how I extended the existing &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/provisioned-throughput" rel="noopener noreferrer"&gt;smart load balancing policy for Azure API Management&lt;/a&gt; to collect additional information to correlate the APIM request to the AOAI requests. This is just the starting point.&lt;/p&gt;

&lt;p&gt;Another relevant customer request was to be able to measure AOAI tokens consumed by client application (or project) which translate to APIM subscription. By default, none of the APIM concepts intersect with the AOAI diagnostic BUT with this additional trace, everything become possible while we unleash the power of the &lt;a href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/?toc=%2Fazure%2Fazure-monitor%2Ftoc.json" rel="noopener noreferrer"&gt;Kusto Query Language (KQL)&lt;/a&gt; powering Azure Monitor!&lt;/p&gt;

&lt;p&gt;Let's consider the following screenshot, using a join between the APIM and AOAI diagnostic tables I can summarize by &lt;em&gt;ApimSubscriptionId&lt;/em&gt; (representing each application) and by &lt;em&gt;modelName&lt;/em&gt;.&lt;/p&gt;

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

&lt;h2&gt;
  
  
  Extending OpenAI Insights
&lt;/h2&gt;

&lt;p&gt;The marvelous workbook provided by the &lt;a href="https://github.com/dolevshor/Azure-OpenAI-Insights#how-to-use-it" rel="noopener noreferrer"&gt;Azure OpenAI Insights&lt;/a&gt; solution offers a rich representation of AOAI diagnostics.&lt;/p&gt;

&lt;p&gt;As discussed in previous sections, the workbook is rightfully built on top of AOAI Diagnostic logs only. As an example, the view by CallerIP, once you introduced APIM, would be similar to the following screenshot as the outbound IP would to be the only client reaching the AOAI endpoint.&lt;/p&gt;

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

&lt;p&gt;My colleagues &lt;a href="https://www.linkedin.com/in/vincenzo-paolo-bacco-a8897413b/" rel="noopener noreferrer"&gt;Vincenzo Paolo Bacco&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/edoardozonca/" rel="noopener noreferrer"&gt;Edoardo Zonca&lt;/a&gt; took on the challenge of giving a UX to the tracing added to the APIM + AOAI scenario. They accomplished the task by extending the Azure OpenAI Insights with a set of visualization. As an example:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Present AOAI logs replacing the CallerIPAddress from AOAI logs with the &lt;em&gt;real&lt;/em&gt; client IP reaching APIM.&lt;/li&gt;
&lt;li&gt;Enable filtering by APIM subscription and product on many visualizations.&lt;/li&gt;
&lt;li&gt;Analyze used tokens by &lt;em&gt;modelName&lt;/em&gt; and &lt;em&gt;modelType&lt;/em&gt; per APIM subscription.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The following screenshot is just an example of a tab added by &lt;a href="https://www.linkedin.com/in/vincenzo-paolo-bacco-a8897413b/" rel="noopener noreferrer"&gt;Vincenzo&lt;/a&gt; and &lt;a href="https://www.linkedin.com/in/edoardozonca/" rel="noopener noreferrer"&gt;Edoardo&lt;/a&gt; to the workbook.&lt;/p&gt;

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

&lt;p&gt;The displayed data originates from AOAI diagnostics but it is enriched with APIM diagnostics. As you can see an IP is prevalent (it is my home office IP, sorry about that) yet it clearly represent the value added to an already exceptional asset, thank to the additional traces I defined.&lt;/p&gt;

&lt;p&gt;Also in the screenshot above, please note the ability to filter by APIM subscription and product has been added to all new visualizations.&lt;/p&gt;

&lt;p&gt;Closing with the view on token based utilization filtered by APIM subscription.&lt;/p&gt;

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

&lt;p&gt;The data displayed in this last screenshot shows how some client requests to a high priority AOAI endpoint had to fallback to a lower priority endpoint to sustain request load.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up (and disclaimer)
&lt;/h2&gt;

&lt;p&gt;It has been an interesting journey learning more about the Azure API Management (APIM) integration scenario with Azure OpenAI (AOAI) and identify a feature to build on top of exceptional assets provided by great colleagues and contributors. &lt;/p&gt;

&lt;p&gt;I strongly believe that adding even a tiny portion of value is more beneficial than re-inventing the wheel.&lt;/p&gt;

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

&lt;p&gt;That being said, this project is meant to be experiment: there are surely other approaches to achieve the same goals and therefore constraints &amp;amp; objectives guided this effort. &lt;/p&gt;

&lt;p&gt;You can find code, queries and workbook in this &lt;a href="https://github.com/dabedin/apim-aoai-smart-loadbalancing/tree/feature/tracingAOAI" rel="noopener noreferrer"&gt;feature branch dabedin/apim-aoai-smart-loadbalancing/tree/feature/tracingAOAI&lt;/a&gt; which is a fork of the great &lt;em&gt;Smart load balancing for OpenAI endpoints and Azure API Management&lt;/em&gt; solution repository described above.&lt;/p&gt;

&lt;p&gt;Please enjoy!&lt;/p&gt;

</description>
      <category>openai</category>
      <category>azure</category>
      <category>api</category>
      <category>kql</category>
    </item>
    <item>
      <title>Hugo on Azure with Static Web Apps</title>
      <dc:creator>Davide Bedin</dc:creator>
      <pubDate>Tue, 18 May 2021 18:21:34 +0000</pubDate>
      <link>https://dev.to/bedindavide/hugo-on-azure-with-static-web-apps-306k</link>
      <guid>https://dev.to/bedindavide/hugo-on-azure-with-static-web-apps-306k</guid>
      <description>&lt;p&gt;I have few web sites based on &lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; framework currently running as Web Apps on Azure App Service Plan at the Shared tier: one of the least expensive options with the ability to use a custom domain names, you can learn more about the &lt;a href="https://docs.microsoft.com/en-us/azure/app-service/overview-hosting-plans" rel="noopener noreferrer"&gt;differences beteween hosting plans here&lt;/a&gt;.&lt;br&gt;
At the time I renovated my sites with Hugo 3 years ago this was the easiest path to move to Azure. Now I want to improve my static web sites in few areas: #1 automate the deployment workflow and #2 use less resources. #3 I also want to leverage GitHub Actions.&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Actions
&lt;/h2&gt;

&lt;p&gt;As &lt;a href="https://github.com/features/actions" rel="noopener noreferrer"&gt;GitHub documentation&lt;/a&gt; describes it:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;GitHub Actions makes it easy to automate all your software workflows, now with world-class CI/CD. Build, test, and deploy your code right from GitHub.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;GiHub Actions are a powerful way to define YAML based pipelines triggered by events. There is an excellent post describing how to use &lt;a href="https://ruddra.com/hugo-deploy-static-page-using-github-actions/" rel="noopener noreferrer"&gt;GitHub Actions to publish Hugo websites&lt;/a&gt; and from this source I discovered the GitHub Actions for Hugo, available from the &lt;a href="https://github.com/marketplace?type=actions" rel="noopener noreferrer"&gt;Actions marketplace&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/peaceiris" rel="noopener noreferrer"&gt;
        peaceiris
      &lt;/a&gt; / &lt;a href="https://github.com/peaceiris/actions-hugo" rel="noopener noreferrer"&gt;
        actions-hugo
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      GitHub Actions for Hugo ⚡️ Setup Hugo quickly and build your site fast. Hugo extended, Hugo Modules, Linux (Ubuntu), macOS, and Windows are supported.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;GitHub Actions for Hugo&lt;/h2&gt;
&lt;/div&gt;
&lt;p&gt;&lt;a rel="noopener noreferrer" href="https://github.com/peaceiris/actions-hugo./images/ogp.svg"&gt;&lt;img width="400" alt="GitHub Actions for Hugo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fpeaceiris%2Factions-hugo.%2Fimages%2Fogp.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.repostatus.org/#active" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/7e20e1589fb5e2aa55bb1de94227dc006b7639c2d2e1020eb08025ffae9f61df/68747470733a2f2f7777772e7265706f7374617475732e6f72672f6261646765732f6c61746573742f6163746976652e737667" alt="Project status: active – The project has reached a stable, usable state and is being actively developed."&gt;&lt;/a&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo/blob/main/LICENSE" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/706a9fca847120afbec2998f8c532a1f98228cade4904a08da987611928d3afb/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f7065616365697269732f616374696f6e732d6875676f2e737667" alt="license"&gt;&lt;/a&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo/releases/latest" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/2cac501ad16ea92398f0bca07157b9a82bab2c7831e49ea194293b15c9c64393/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652f7065616365697269732f616374696f6e732d6875676f2e737667" alt="release"&gt;&lt;/a&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo/releases" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/e28bbbe51082e17229aad5cb3fd323ec579e786f1ef9ba26079bb9c2842c0e4c/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f72656c656173652d646174652f7065616365697269732f616374696f6e732d6875676f2e737667" alt="GitHub release date"&gt;&lt;/a&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo/releases.atom" rel="noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dae0442cac6fb940ae2d77e4f33173a251a456dcdf5b72b21cade4a77c8523d9/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f72656c656173652d666565642d79656c6c6f77" alt="Release Feed"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/peaceiris/actions-hugo/workflows/Test/badge.svg?branch=main&amp;amp;event=push"&gt;&lt;img src="https://github.com/peaceiris/actions-hugo/workflows/Test/badge.svg?branch=main&amp;amp;event=push" alt="Test"&gt;&lt;/a&gt;
&lt;a rel="noopener noreferrer" href="https://github.com/peaceiris/actions-hugo/workflows/Code%20Scanning/badge.svg?event=push"&gt;&lt;img src="https://github.com/peaceiris/actions-hugo/workflows/Code%20Scanning/badge.svg?event=push" alt="Code Scanning"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.codefactor.io/repository/github/peaceiris/actions-hugo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/dfeff493ab251eb05c2305946ce7e4ad6bb15f3b9209b49745b06942c0968d2c/68747470733a2f2f7777772e636f6465666163746f722e696f2f7265706f7369746f72792f6769746875622f7065616365697269732f616374696f6e732d6875676f2f6261646765" alt="CodeFactor"&gt;&lt;/a&gt;
&lt;a href="https://codecov.io/gh/peaceiris/actions-hugo" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/68beb91af53994e675fdaf407efe1887ab7adaf1ee4ac93b61fb7dcda9dec37e/68747470733a2f2f636f6465636f762e696f2f67682f7065616365697269732f616374696f6e732d6875676f2f6272616e63682f6d61696e2f67726170682f62616467652e737667" alt="codecov"&gt;&lt;/a&gt;
&lt;a href="https://codeclimate.com/github/peaceiris/actions-hugo/maintainability" rel="nofollow noopener noreferrer"&gt;&lt;img src="https://camo.githubusercontent.com/fca3dfa152f908b3db1c70ddadc3103c7745a2186afb015a242caefd91546de1/68747470733a2f2f6170692e636f6465636c696d6174652e636f6d2f76312f6261646765732f65626632656566336130343662333936626139632f6d61696e7461696e6162696c697479" alt="Maintainability"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This &lt;strong&gt;Hugo Setup Action&lt;/strong&gt; can install &lt;a href="https://github.com/gohugoio/hugo" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; to a virtual machine of &lt;strong&gt;GitHub Actions&lt;/strong&gt;
&lt;strong&gt;Hugo extended&lt;/strong&gt; version, &lt;strong&gt;Hugo Modules&lt;/strong&gt;, Linux (Ubuntu), macOS, and Windows are supported.&lt;/p&gt;
&lt;p&gt;From &lt;code&gt;v2&lt;/code&gt;, this Hugo Setup Action has migrated to a JavaScript (TypeScript) action
We no longer build or pull a Hugo docker image.
Thanks to this change, we can complete this action in less than a few seconds.
(A docker base action was taking about 1 min or more execution time to build and pull a docker image.)&lt;/p&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;OS (runs-on)&lt;/th&gt;
&lt;th&gt;ubuntu-latest, ubuntu-20.04, ubuntu-22.04&lt;/th&gt;
&lt;th&gt;macos-latest&lt;/th&gt;
&lt;th&gt;windows-2019&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Support&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Hugo type&lt;/th&gt;
&lt;th&gt;Hugo Extended&lt;/th&gt;
&lt;th&gt;Hugo Modules&lt;/th&gt;
&lt;th&gt;Latest Hugo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Support&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;td&gt;✅️&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Table of Contents&lt;/h2&gt;

&lt;/div&gt;


&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo#getting-started" rel="noopener noreferrer"&gt;Getting started&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-create-your-workflow" rel="noopener noreferrer"&gt;⭐️ Create your workflow&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo#options" rel="noopener noreferrer"&gt;Options&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-use-hugo-extended" rel="noopener noreferrer"&gt;⭐️ Use Hugo extended&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-use-the-latest-version-of-hugo" rel="noopener noreferrer"&gt;⭐️ Use the latest version of Hugo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo#tips" rel="noopener noreferrer"&gt;Tips&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-caching-hugo-modules" rel="noopener noreferrer"&gt;⭐️ Caching Hugo Modules&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-read-hugo-version-from-file" rel="noopener noreferrer"&gt;⭐️ Read Hugo version from file&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/peaceiris/actions-hugo#%EF%B8%8F-workflow-for-autoprefixer-and-postcss-cli" rel="noopener noreferrer"&gt;⭐️ Workflow for autoprefixer&lt;/a&gt;…&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
  &lt;/div&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/peaceiris/actions-hugo" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
With it you can build your Hugo content and then deploy in a subsequent step. &lt;br&gt;
I could rely on the &lt;a href="https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blob-static-website" rel="noopener noreferrer"&gt;Static Web Site on Azure Storage&lt;/a&gt; as the deployment target to fulfill my objective #2 but there is another, even better, option to publish static content with an automated approach: &lt;em&gt;Azure Static Web Apps&lt;/em&gt;! 
&lt;h2&gt;
  
  
  Azure Static Web Apps
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/" rel="noopener noreferrer"&gt;Azure Static Web Apps&lt;/a&gt; is &lt;em&gt;a service that automatically builds and deploys full stack web apps to Azure from a code repository&lt;/em&gt;: exactly what I needed for objectives #1, #2 and also #3 as GitHub Actions are supported.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80ynayprid5r41ts55pr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F80ynayprid5r41ts55pr.png" alt="Alt Text" width="800" height="334"&gt;&lt;/a&gt;  These are the steps to reach my goal:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create an Azure Static Web Apps&lt;/li&gt;
&lt;li&gt;Integrate GitHub Actions with Azure Static Web Apps&lt;/li&gt;
&lt;li&gt;Trigger my GitHub Actions only when needed&lt;/li&gt;
&lt;li&gt;Publish my updates to a separate environment before moving into production&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let's start with the activities!&lt;/p&gt;
&lt;h3&gt;
  
  
  Hugo in Azure Static Web Apps
&lt;/h3&gt;

&lt;p&gt;The value of Azure Static Web Apps sits in its close integration with the code repository and the CI/CD pipeline. You can find a &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/publish-hugo" rel="noopener noreferrer"&gt;Hugo focused walkthough here&lt;/a&gt;: for the sake of brevity let's say I have an existing GitHub repository containing my Hugo site.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45byjtf7qfmmyzfp4vdf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F45byjtf7qfmmyzfp4vdf.png" alt="Alt Text" width="764" height="1187"&gt;&lt;/a&gt;  As you can see from the picture above, I am connecting the newly created Static Web App to my GitHub Account and the &lt;em&gt;main&lt;/em&gt; branch of my web site with Hugo repository.&lt;/p&gt;
&lt;h2&gt;
  
  
  Ready to use GitHub Actions
&lt;/h2&gt;

&lt;p&gt;This integration does create a new &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/github-actions-workflow" rel="noopener noreferrer"&gt;GitHub Action workflow in my repository&lt;/a&gt;, well explained in the documentation. The main steps are the following:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths-ignore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/**'&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;types&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;opened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;synchronize&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;reopened&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;closed&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;main&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
    &lt;span class="na"&gt;paths-ignore&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.github/workflows/**'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With the controls on trigger &lt;code&gt;paths-ignore: '.github/workflows/**'&lt;/code&gt; I prevent the workflow from running when I edit my Actions.&lt;br&gt;
The workflow continues with the definition of build &amp;amp; deploy job triggered by push or PR:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;build_and_deploy_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'push' || (github.event_name == 'pull_request' &amp;amp;&amp;amp; github.event.action != 'closed')&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and Deploy Job&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v2&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;submodules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build And Deploy&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;builddeploy&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure/static-web-apps-deploy@v0.0.1-preview&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;azure_static_web_apps_api_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GRAY_DUNE_09D67E003 }}&lt;/span&gt;
          &lt;span class="na"&gt;repo_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt; &lt;span class="c1"&gt;# Used for Github integrations (i.e. PR comments)&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;upload"&lt;/span&gt;
          &lt;span class="c1"&gt;###### Repository/Build Configurations - These values can be configured to match your app requirements. ######&lt;/span&gt;
          &lt;span class="c1"&gt;# For more information regarding Static Web App workflow configurations, please visit: https://aka.ms/swaworkflowconfig&lt;/span&gt;
          &lt;span class="na"&gt;app_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/"&lt;/span&gt; &lt;span class="c1"&gt;# App source code path&lt;/span&gt;
          &lt;span class="na"&gt;api_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api"&lt;/span&gt; &lt;span class="c1"&gt;# Api source code path - optional&lt;/span&gt;
          &lt;span class="na"&gt;output_location&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;public"&lt;/span&gt; &lt;span class="c1"&gt;# Built app content directory - optional&lt;/span&gt;
          &lt;span class="c1"&gt;###### End of Repository/Build Configurations ######&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The job triggered once a PR close event is detected is similar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;  &lt;span class="na"&gt;close_pull_request_job&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;if&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github.event_name == 'pull_request' &amp;amp;&amp;amp; github.event.action == 'closed'&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Close Pull Request Job&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Close Pull Request&lt;/span&gt;
        &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;closepullrequest&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Azure/static-web-apps-deploy@v0.0.1-preview&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;azure_static_web_apps_api_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.AZURE_STATIC_WEB_APPS_API_TOKEN_GRAY_DUNE_09D67E003 }}&lt;/span&gt;
          &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Apart from the secrets defined while provisioning the Azure Static Web App and used to deploy the built Hugo site, the interesting part is the usage of the GitHub Action &lt;code&gt;Azure/static-web-apps-deploy@v0.0.1-preview&lt;/code&gt;: what is it?&lt;/p&gt;

&lt;h4&gt;
  
  
  What is Oryx?
&lt;/h4&gt;

&lt;p&gt;The GitHub Actions workflow, created in the project repository, does use the &lt;a href="https://github.com/marketplace/actions/azure-static-web-apps-deploy" rel="noopener noreferrer"&gt;Azure Static Web Apps Deploy GitHub action&lt;/a&gt; from the marketplace. This reusable Action utilizes Oryx system to build both static applications and Azure Functions for API and then deploys it. You can find more information on Oryx repository at &lt;a href="https://github.com/microsoft/Oryx" rel="noopener noreferrer"&gt;https://github.com/microsoft/Oryx&lt;/a&gt; and on &lt;a href="https://github.com/microsoft/Oryx/blob/master/doc/runtimes/hugo.md" rel="noopener noreferrer"&gt;how it does detect &amp;amp; build Hugo applications&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Verify changes before publishing
&lt;/h3&gt;

&lt;p&gt;As the only author of my web sites, I am tempted to expedite the process to post something new: open VS Code, change the markdown files, git commit &amp;amp; push to the &lt;em&gt;main&lt;/em&gt; branch, followed by build &amp;amp; deploy to the public site! It seems simpler only if you do not account for any error: forcing yourself to follow a more rigorous process, while automating it as much as possible, is the way to go. &lt;br&gt;
With &lt;a href="https://docs.github.com/en/github/administering-a-repository/managing-a-branch-protection-rule" rel="noopener noreferrer"&gt;branch protection rules&lt;/a&gt; you can demand that a pull request on a branch must be reviewed before merging into the protected branch, &lt;em&gt;main&lt;/em&gt; in my case, preventing the author  from pushing directly into it. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbujvgrbnw7zmm1y24kjz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbujvgrbnw7zmm1y24kjz.png" alt="Alt Text" width="800" height="933"&gt;&lt;/a&gt;  Even by requesting the minimum of approvers, I am 1 short on my team of one. &lt;em&gt;For the time being&lt;/em&gt; I will force myself to start PRs and not leverage branch protection.  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4udtboib9z0mw2d4wf6c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4udtboib9z0mw2d4wf6c.png" alt="Alt Text" width="800" height="296"&gt;&lt;/a&gt;  In the previous screenshot I created a branch for my commit and started a PR. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1g0l5v1a2256xmka9t7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn1g0l5v1a2256xmka9t7.png" alt="Alt Text" width="800" height="535"&gt;&lt;/a&gt;  This is the &lt;a href="https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests" rel="noopener noreferrer"&gt;pull request&lt;/a&gt; I am starting, the final goal is to merge that branch into the &lt;em&gt;main&lt;/em&gt; one.  What does happen when we start a PR?&lt;/p&gt;

&lt;h3&gt;
  
  
  PRs and Environments
&lt;/h3&gt;

&lt;p&gt;Azure Static Web Apps is so well integrated with the GitHub Actions that it translate pull requests (PR) into &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/review-publish-pull-requests" rel="noopener noreferrer"&gt;staging environments, so you can review the changes before publishing it in front of your users&lt;/a&gt;.&lt;br&gt;
We just created a branch and started a PR: the following screenshot show what the workflow, created for us by Azure Static Web Apps, does provide.  &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7piir2ovwrmstw7mqfb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fn7piir2ovwrmstw7mqfb.png" alt="Alt Text" width="800" height="593"&gt;&lt;/a&gt; The GitHub Action has been triggered and is going to build and publish to a staging environment. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe92h41pgd2ucz5hyfi3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe92h41pgd2ucz5hyfi3v.png" alt="Alt Text" width="800" height="702"&gt;&lt;/a&gt; Once complete, we are presented with the url of the Azure Static Web Apps environment &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82azcer0r7ju6p5vykq8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F82azcer0r7ju6p5vykq8.png" alt="Alt Text" width="800" height="428"&gt;&lt;/a&gt;  In the Azure Portal we see the staging environment listed for our Azure Static Web App.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s61klkw8lx3cdkei5q3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5s61klkw8lx3cdkei5q3.png" alt="Alt Text" width="800" height="316"&gt;&lt;/a&gt; We have the chance to verify our changes: Once we are ready to apply these to the public site, we go back to the PR and merge it into the &lt;em&gt;main&lt;/em&gt; branch. &lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdda3cjt3r3rgu3haornn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdda3cjt3r3rgu3haornn.png" alt="Alt Text" width="800" height="126"&gt;&lt;/a&gt; The GitHub Action will then start two job runs to close the pull request, remove the staging environment, build and publish the Hugo site on the production environment. &lt;/p&gt;

&lt;h2&gt;
  
  
  At last!
&lt;/h2&gt;

&lt;p&gt;In this article we explored the great CI/CD integration provided by Azure Static Web App with GitHub Actions, for Hugo static site and for &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/overview#what-you-can-do-with-static-web-apps" rel="noopener noreferrer"&gt;many other frameworks&lt;/a&gt; as well.&lt;br&gt;
With Azure Static Web App I will decommission the App Service that is currently supporting my static sites, while keeping the &lt;a href="https://docs.microsoft.com/en-us/azure/static-web-apps/custom-domain?tabs=azure-dns" rel="noopener noreferrer"&gt;ability to have custom domain and free TLS certificate!&lt;/a&gt;. &lt;br&gt;
As my sites are personal/hobby related, I will be able to &lt;a href="https://azure.microsoft.com/en-us/pricing/details/app-service/static/" rel="noopener noreferrer"&gt;use it for free&lt;/a&gt;! &lt;/p&gt;

</description>
      <category>azure</category>
      <category>github</category>
      <category>devops</category>
    </item>
    <item>
      <title>Locust on Azure: an end-to-end experience</title>
      <dc:creator>Davide Bedin</dc:creator>
      <pubDate>Wed, 15 Apr 2020 09:02:09 +0000</pubDate>
      <link>https://dev.to/bedindavide/locust-on-azure-an-end-to-end-experience-48f7</link>
      <guid>https://dev.to/bedindavide/locust-on-azure-an-end-to-end-experience-48f7</guid>
      <description>&lt;p&gt;&lt;a href="https://locust.io/" rel="noopener noreferrer"&gt;Locust.io&lt;/a&gt; is a simple and powerful load testing framework, based on python, perfectly suited for developers and APIs.&lt;/p&gt;

&lt;p&gt;Locust can be installed locally on your dev workstation, deployed on a cloud VM or on a Kubernetes cluster. &lt;br&gt;
IMO the best scenario, perfectly fitting  into the idea that load testing should be an easy and recurring task, is enabled by the excellent work &lt;a href="https://dev.to/azure/running-locust-on-azure-2k40"&gt;by Davide Mauri to deploy a master-slave Locust configuration on &lt;strong&gt;Azure Container Instances&lt;/strong&gt;&lt;/a&gt; which was my starting point. &lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/azure" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__org__pic"&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F512%2F64ce0b82-730d-4ca0-8359-2c21513a0063.jpg" alt="Microsoft Azure" width="400" height="400"&gt;
      &lt;div class="ltag__link__user__pic"&gt;
        &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F332137%2F05a2cc11-c789-47fb-9c43-a5b13ba86e7b.jpeg" alt="" width="460" height="460"&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="https://dev.to/azure/running-locust-on-azure-2k40" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Running Locust on Azure&lt;/h2&gt;
      &lt;h3&gt;Davide Mauri for Microsoft Azure ・ Feb 17 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#python&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#testing&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#azure&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#webdev&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;h2&gt;
  
  
  Deployment options
&lt;/h2&gt;

&lt;p&gt;This project provides you with as many slaves hatching as many locust users you configure: a deployment script setup the Azure Storage shared between the Azure Container Instances, upload the test scripts and via an Azure template deploys all the other resources.&lt;br&gt;
Once Locust performed the tests and you gathered the results, you can delete the testing infrastructure, not incurring in any additional costs. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6mlfegox0nomggojb2j.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fn6mlfegox0nomggojb2j.png" alt="Alt Text" width="600" height="485"&gt;&lt;/a&gt;&lt;br&gt;
To give a perspective of a test run cost on this infrastructure, according &lt;a href="https://azure.microsoft.com/en-us/pricing/details/container-instances/" rel="noopener noreferrer"&gt;to public prices of Azure Container Instances&lt;/a&gt;, a 1 hour long test with 4 slaves can cost about 0.24€.&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/yorek" rel="noopener noreferrer"&gt;
        yorek
      &lt;/a&gt; / &lt;a href="https://github.com/yorek/locust-on-azure" rel="noopener noreferrer"&gt;
        locust-on-azure
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Running distributed Locust.io on Azure Container Instances
    &lt;/h3&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;br&gt;
I contributed to the Locust on Azure repository with a &lt;strong&gt;VNet integrated&lt;/strong&gt; option: the Locust master and slaves are deployed in a private Virtual Network, the Locust web UI is exposed via an Application Gateway and access is automatically constrained to the client IP. &lt;br&gt;
This more complex deployment takes longer to complete, also please note that the Application Gateway &lt;a href="https://azure.microsoft.com/en-us/pricing/details/application-gateway/" rel="noopener noreferrer"&gt;Application Gateway v2&lt;/a&gt; is billed by the hour. &lt;br&gt;
This is how Locust on Azure in VNet looks like:&lt;br&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fu20e224czub5hd37gcu6.png" alt="Alt Text" width="800" height="534"&gt;
&lt;h2&gt;
  
  
  Be conscious about storage
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;A reminder: the deployment script copies everything from the local /locust folder to the Azure Files, so your Locust will have the scripts and files/payloads for test mounted on the Azure Container Instances&lt;/em&gt;&lt;br&gt;
As soon as my first test tried to swarm an Azure Function by POSTing images I noticed it did not perform as I expected. &lt;br&gt;
It took me a while to understand the impact of my Locust python code on the test itself: I was reading the image from the mounted Azure File Share at each Locust task execution, therefore #1 I was wasting my testing resources (slaves) in repetitive task while not pushing enough requests, missing the whole point of a load test, and #2 I did not take into account that &lt;a href="https://docs.microsoft.com/en-us/azure/storage/files/storage-files-scale-targets" rel="noopener noreferrer"&gt;Azure Files has its own scalability target&lt;/a&gt;. &lt;br&gt;
This was my 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="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TaskSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APICalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TaskSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;    
    &lt;span class="nd"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyzeimage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/locust/sample_face.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/analyzeimage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;APICalls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# seconds
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I changed the python code by loading the payload into global variable at &lt;a href="https://docs.locust.io/en/stable/writing-a-locustfile.html#the-locust-class" rel="noopener noreferrer"&gt;the definition of the Locust user&lt;/a&gt;, therefore the image is read from Azure File Share once for each of the few hundreds of Locust I hatched, instead of several thousands per minute.&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;from&lt;/span&gt; &lt;span class="n"&gt;locust&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;TaskSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;between&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;events&lt;/span&gt;

&lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APICalls&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TaskSet&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;    
    &lt;span class="nd"&gt;@task&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;analyzeimage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Analyze&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;shot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/analyzeimage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;APIUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;HttpUser&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;APICalls&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;wait_time&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# seconds
&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;on_start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;global&lt;/span&gt; &lt;span class="n"&gt;image&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/locust/sample_face.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;rb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After this much-needed change the tests, free of unnecessary constraints on the slaves behavior, performed as expected: I am always amazed by the power of &lt;a href="https://azure.microsoft.com/en-us/services/functions/" rel="noopener noreferrer"&gt;Azure Functions&lt;/a&gt;!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzf2ac4z9o6pq6gir6l4k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fzf2ac4z9o6pq6gir6l4k.png" alt="Alt Text" width="800" height="549"&gt;&lt;/a&gt;&lt;br&gt;
There surely are other more efficient options to define the tests: please refer to the &lt;a href="https://docs.locust.io/en/stable/writing-a-locustfile.html#" rel="noopener noreferrer"&gt;Locust documentation&lt;/a&gt; for details.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to record Locust test
&lt;/h2&gt;

&lt;p&gt;While my search for a load testing framework was based on a specific need (API load testing primarily) it is common to define a test suite by recording the browsing of a complex resource such as a web site, therefore avoiding the need to manually write the tests. &lt;br&gt;
Namely JMeter is the champion of this use case: if you are interested about JMeter please check the great work by &lt;a href="https://github.com/paolosalvatori/jmeter-distributed-test-harness" rel="noopener noreferrer"&gt;Paolo Salvatori on JMeter load testing on Azure&lt;/a&gt;. &lt;br&gt;
It is possible to accomplish the same goal with Locust too: by using &lt;a href="https://mitmproxy.org/" rel="noopener noreferrer"&gt;MitM&lt;/a&gt;, an open source interactive proxy project, the browsing of a website is recorded and &lt;a href="https://github.com/zlorb/locust.replay" rel="noopener noreferrer"&gt;Locust.Replay&lt;/a&gt; lets you export captured flows to Locust script format.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0b75z1bgys8w0jwy9ouw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2F0b75z1bgys8w0jwy9ouw.png" alt="Alt Text" width="800" height="592"&gt;&lt;/a&gt;&lt;br&gt;
The dev experience is complete with Firefox browser, as it lets you have a separate proxy from the system-wide configuration, therefore not impacting the rest of the apps and services on your local dev machine.&lt;br&gt;
This is how the dev flow looks like:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fotq6qoa0zvkcx27h6wt1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fi%2Fotq6qoa0zvkcx27h6wt1.png" alt="Alt Text" width="800" height="489"&gt;&lt;/a&gt;&lt;br&gt;
I love how immediate is Locust in defining simple test scenario and complex ones as well. &lt;br&gt;
Start now to leverage &lt;a href="https://dev.toyorek%20/%20locust-on-azure"&gt;Locust on Azure&lt;/a&gt; in your testing plan!&lt;/p&gt;

</description>
      <category>locust</category>
      <category>azure</category>
      <category>testing</category>
      <category>python</category>
    </item>
  </channel>
</rss>
