<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Posts on Blog | Vaclav Jirovsky</title><link>https://blog.vjirovsky.cz/post/</link><description>Recent content in Posts on Blog | Vaclav Jirovsky</description><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Tue, 23 Sep 2025 00:00:00 +0000</lastBuildDate><atom:link href="https://blog.vjirovsky.cz/post/index.xml" rel="self" type="application/rss+xml"/><item><title>Running Zone-Redundant ExpressRoute Circuits with External Storage for Azure VMware Solution</title><link>https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/</link><pubDate>Tue, 23 Sep 2025 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/</guid><description>&lt;img src="https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/images/_post.png" alt="Featured image of post Running Zone-Redundant ExpressRoute Circuits with External Storage for Azure VMware Solution" />&lt;p>You might have landed on this blog post because of the email Microsoft recently sent to affected customers:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/images/email-public-ip.png"
width="1249"
height="903"
srcset="https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/images/email-public-ip_huc0e79598a520f2cbe5d0bbf921f327b9_156518_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/running-zonal-expressroute-public-ip-avs/images/email-public-ip_huc0e79598a520f2cbe5d0bbf921f327b9_156518_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Figure - Email shared to affected Azure customers"
class="gallery-image"
data-flex-grow="138"
data-flex-basis="331px"
>&lt;/p>
&lt;h2 id="what-changed-in-azure">What Changed in Azure&lt;/h2>
&lt;p>For Azure Public IP resources, Microsoft has more options how to deploy from resiliency perspective. In August 2024, &lt;a class="link" href="https://azure.microsoft.com/en-us/updates?id=Azure-Public-Ips-are-zone-redundant-by-default" target="_blank" rel="noopener"
>Azure announced&lt;/a> that any newly created Public IP will now be zone-redundant by default.&lt;/p>
&lt;h2 id="how-it-relates-to-azure-vmware-solution-avs">How It Relates to Azure VMware Solution (AVS)&lt;/h2>
&lt;p>Azure VMware Solution offers two models of integration with a customer&amp;rsquo;s private Azure virtual network:&lt;/p>
&lt;ul>
&lt;li>
&lt;p>&lt;strong>Direct virtual network integration&lt;/strong> – an AVS SDDC is directly available from virtual network. Available only with the latest AVS generation SKU, AV64 Gen 2&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>ExpressRoute Circuit integration&lt;/strong> – required for all other SKUs. This establishes a private connection (similar to a &amp;ldquo;VPN&amp;rdquo;) between the virtual network and the AVS SDDC via internal ExpressRoute circuit. This circuit itself is provided at no additional cost (included in the AVS pricing) and is not metered.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;h2 id="how-an-expressroute-circuit-is-deployed">How an ExpressRoute Circuit Is Deployed&lt;/h2>
&lt;p>This post won&amp;rsquo;t go into the full details of deploying an ExpressRoute (you can find that in the &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/azure-vmware/tutorial-configure-networking" target="_blank" rel="noopener"
>official documentation&lt;/a>). Instead, let&amp;rsquo;s look at the high-level steps:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>To connect an AVS SDDC to your virtual network, the first step is to prepare a virtual network and create a dedicated subnet called &lt;em>GatewaySubnet&lt;/em>.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>This subnet is then used by another Azure resource: &lt;strong>Azure Virtual Network Gateway&lt;/strong>. This resource enables secure connectivity between different networks - such as other Azure virtual networks, on-premises environments, or even AWS VPC.&lt;/p>
&lt;p>If you’ve worked with the &lt;em>Azure VPN Gateway&lt;/em>, that’s essentially a specific type of Virtual Network Gateway.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>With the Virtual Network Gateway in place, you can connect multiple networks to a single Azure virtual network.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The gateway requires an associated &lt;strong>Azure Public IP address&lt;/strong> resource. This assignment gives the gateway an external presence, enabling connections to be established between the source network and the target Azure virtual network - it&amp;rsquo;s used for control/management plane. This Public IP is used for control/management plane, but it&amp;rsquo;s not used for traffic itself - but if that IP fails, the gateway resource can fail due to not available control/management plane.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Once the connection is established, both networks become routable with each other via theirs private IP addresses.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;pre class="mermaid">graph LR
%% Styles
classDef azure fill:#eef6ff,stroke:#4f7dbd,stroke-width:1px;
classDef comp fill:#ffffff,stroke:#7e8aa2,stroke-width:1px;
classDef mgmt fill:#fef9e7,stroke:#d4ac0d,stroke-width:1px,color:#000000;
%% Azure VNet + Gateway
subgraph AZURE_VNET["🟦 Azure Virtual Network (vNET)"]
direction TB
subgraph GWSUB["🧩 GatewaySubnet"]
direction TB
VNG["🔗 Virtual Network Gateway"]
end
end
class AZURE_VNET,GWSUB azure
class VNG comp
%% External connections (left side)
AVS["☁️ Azure VMware Solution (SDDC)"]
ONPREM["🏢 On-Premises Network"]
PIP["🌐 Public IP (control-plane)"]
class AVS,ONPREM comp
class PIP mgmt
%% Data plane
AVS --- VNG
ONPREM --- VNG
%% Control plane dependency
PIP --- VNG
&lt;/pre>
&lt;h3 id="avs-networking-with-external-storage">AVS Networking with External Storage&lt;/h3>
&lt;p>A typical deployment that uses external storage - such as &lt;em>Pure Cloud Block Store&lt;/em> or &lt;em>Pure Storage Cloud Azure Native&lt;/em> - looks like this:&lt;/p>
&lt;pre class="mermaid">graph LR
%% Styles
classDef azure fill:#eef6ff,stroke:#4f7dbd,stroke-width:1px;
classDef comp fill:#ffffff,stroke:#7e8aa2,stroke-width:1px;
classDef pure fill:#F04E23,stroke:#C43E19,stroke-width:1px,color:#ffffff;
classDef mgmt fill:#fef9e7,stroke:#d4ac0d,stroke-width:1px,color:#000000;
%% Azure VNet + Gateway
subgraph AZURE_VNET["🟦 Azure Virtual Network"]
direction TB
PURE["🛢️ Pure Storage Cloud (external storage)"]
subgraph GWSUB["🧩 GatewaySubnet"]
direction TB
VNG["🔗 Virtual Network Gateway"]
end
end
class AZURE_VNET,GWSUB azure
class VNG comp
class PURE pure
%% External connections (left side)
AVS["☁️ Azure VMware Solution (SDDC)"]
ONPREM["🏢 On-Premises Network"]
PIP["🌐 Public IP (control-plane)"]
class AVS,ONPREM comp
class PIP mgmt
%% Data plane
AVS --- VNG
ONPREM --- VNG
VNG --- PURE
%% Control plane dependency
PIP --- VNG
&lt;/pre>
&lt;h2 id="impacts-on-resiliency">Impacts on Resiliency&lt;/h2>
&lt;p>Now we get to the real topic. We are in the cloud environment and everything still comes back to the fundamental building blocks - availability zones (AZs).&lt;/p>
&lt;p>When you deploy an AVS resource, you must choose the availability zone where the AVS SDDC will reside. AVS also supports deploying nodes across multiple zones (using the AVS Stretched Cluster feature). However, in this configuration any external storage solution is not supported.&lt;/p>
&lt;p>Because of that limitation, let&amp;rsquo;s design a Zonal deployment (and repeat in another AZ to spread across AZs) and the best practice is to place your storage in the same AZ as your AVS deployment. Otherwise, you increase the risk of downtime:&lt;/p>
&lt;p>If you deploy storage in a different AZ and the AVS&amp;rsquo;s zone goes down → &lt;em>your workloads are down&lt;/em>.&lt;/p>
&lt;p>If the storage&amp;rsquo;s zone goes down → &lt;em>your workloads are also down&lt;/em>.&lt;/p>
&lt;p>By keeping AVS and storage in the same AZ, you reduce the probability of a cross-AZ dependency failure. That&amp;rsquo;s why I strongly recommend explicitly specifying the AZ for all related resources and deploying them consistently in the same zone.&lt;/p>
&lt;h2 id="networking">Networking&lt;/h2>
&lt;p>So far, we haven&amp;rsquo;t looked at the deployment architecture from a &lt;strong>resiliency&lt;/strong> perspective.&lt;/p>
&lt;h3 id="virtual-network-gateway-expressroute">Virtual network gateway (ExpressRoute)&lt;/h3>
&lt;p>Azure offers multiple ExpressRoute gateway SKUs - &lt;em>Standard&lt;/em>, &lt;em>High Performance&lt;/em>, &lt;em>Ultra Performance&lt;/em>, and the AZ-enabled SKUs (&lt;em>ErGw1AZ / ErGw2AZ / ErGw3AZ&lt;/em>).&lt;br>
The AZ SKUs are &lt;em>zone-redundant&lt;/em>; they deploy gateway instances across availability zones and are designed to survive a single-AZ failure. Non-AZ SKUs are regional and don&amp;rsquo;t provide zone-level redundancy.&lt;/p>
&lt;p>At Pure Storage, we recommend &lt;code>ErGw3AZ&lt;/code> SKU for AVS environments that rely on external storage.&lt;br>
It delivers the highest capacity in the AZ family and supports &lt;em>ExpressRoute FastPath&lt;/em>, which lets data bypass the gateway for lower latency and higher throughput.&lt;/p>
&lt;h3 id="the-last-puzzle-piece-the-public-ip">The last puzzle piece: the Public IP&lt;/h3>
&lt;p>The &lt;strong>Public IP&lt;/strong> attached to the ExpressRoute gateway is what is going on here.&lt;/p>
&lt;p>By default, any Public IP was defaultly deployed as a &lt;em>multi-zonal&lt;/em> - in all AZs. But this deployment creates distinct per-zone IP instances and the gateway binds to one of them at startup; it does not failover to the sibling IPs in other zones during an outage and might make your connection to an external storage unavailable.&lt;/p>
&lt;p>To address this, Microsoft introduced the &lt;strong>Zone-redundant Public IP&lt;/strong> type. This is a &lt;em>single logical IP&lt;/em> that automatically spans all zones. With it, the ExpressRoute gateway remains reachable even if one AZ fails - fully aligning with the zone-redundant design of the AZ-enabled (such as &lt;code>ErGw3AZ&lt;/code>) SKUs.&lt;/p>
&lt;h2 id="impact-to-new-avs-deployments">Impact to New AVS Deployments&lt;/h2>
&lt;p>As announced by Microsoft, Azure has changed the default behavior for new deployments.&lt;br>
When you deploy a new &lt;em>Virtual Network Gateway&lt;/em>, the associated &lt;em>Public IP&lt;/em> will now default to &lt;strong>zone-redundant mode&lt;/strong>.&lt;/p>
&lt;p>In this configuration, the Public IP is not shown as a separate resource in your subscription—it is created as a hidden, Microsoft-managed resource.
This ensures resiliency by default.&lt;/p>
&lt;p>No additional action is required for new AVS deployments.&lt;/p>
&lt;hr>
&lt;h2 id="impact-to-existing-avs-deployments">Impact to Existing AVS Deployments&lt;/h2>
&lt;p>For existing environments, it is important to validate the resiliency of your AVS external storage setup to avoid potential outages.&lt;/p>
&lt;p>Also verify, that the gateway&amp;rsquo;s &lt;em>Public IP&lt;/em> is not a &lt;code>Basic&lt;/code> SKU.&lt;/p>
&lt;ul>
&lt;li>Inherited Basic IPs are a legacy artifact from older deployments.&lt;/li>
&lt;li>The &lt;code>Basic&lt;/code> SKU has been discontinued, and migration to a &lt;strong>Standard SKU&lt;/strong> is required by the end of September 2025.&lt;/li>
&lt;/ul>
&lt;p>Microsoft has prepared a guide on how migrate the associated Virtual Network Gateway and its associated Public IP. More details can be found &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/expressroute/gateway-migration" target="_blank" rel="noopener"
>in this KB&lt;/a>. Please be sure you make this change during a maintenance window, since it might introduce couple minutes of downtime.&lt;/p></description></item><item><title>Enterprise-Grade Block Storage with Portworx CSI and Cloud Block Store</title><link>https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/</link><pubDate>Sun, 15 Jun 2025 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/</guid><description>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/_post.png" alt="Featured image of post Enterprise-Grade Block Storage with Portworx CSI and Cloud Block Store" />&lt;h2 id="background">Background&lt;/h2>
&lt;p>Portworx by Pure Storage has introduced a new version of its Kubernetes CSI driver (PX-CSI), designed to simplify how Kubernetes clusters consume persistent volumes. Version 25.4.0 brings official support for Pure Cloud Block Store, enabling enterprise-grade block storage for a new frontier in the cloud - Kubernetes clusters such as Azure Kubernetes Service (AKS).&lt;/p>
&lt;p>In this post, we’ll take a closer look at how to install PX-CSI on an Azure Kubernetes Service (AKS) cluster and mount a persistent volume into a container, backed by a Pure Cloud Block Store (CBS) array.&lt;/p>
&lt;h2 id="architecture-overview">Architecture Overview&lt;/h2>
&lt;p>Before diving into the installation, let’s briefly review the architecture of our solution.
PX-CSI is deployed as a set of managed pods on your AKS cluster and integrates with the Pure Cloud Block Store array to provision and attach volumes on demand. In FlashArray Direct Access mode, each Kubernetes PV directly maps to a volume on the array, and the application’s data is written straight to the array. This provides the performance and data services of Pure’s enterprise storage to your containerized apps, without using any intermediate storage pools on the cluster.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/architecture.png"
width="1062"
height="942"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/architecture_hu185a24a294e36b85fecd78b174d8f0d1_131167_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/architecture_hu185a24a294e36b85fecd78b174d8f0d1_131167_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Pure Cloud Block Store connected into AKS via Portworx CSI driver as a backend storage for volumes"
class="gallery-image"
data-flex-grow="112"
data-flex-basis="270px"
>&lt;/p>
&lt;p>In essence, when a PVC is created with the storage class backed by CBS, PX-CSI communicates with the CBS&amp;rsquo;s management plane (via its REST API) to create a new volume. That volume is then attached over iSCSI to the appropriate AKS worker node and made available to the requesting pod. This architecture allows you to leverage Pure’s deduplication, compression, snapshots, and high availability features for your cloud-native workloads.
What you are going to need is an instance of Pure Cloud Block Store, deployed into the same vNET as AKS will be deployed into.&lt;/p>
&lt;h2 id="step-1---create-an-aks-cluster">Step 1 - Create an AKS Cluster&lt;/h2>
&lt;p>PX-CSI requires a Kubernetes cluster with a minimum of 3 worker nodes to maintain internal KVDB quorum, which is used to manage storage metadata and runtime state . It&amp;rsquo;s also recommended to use sufficiently sized nodes (e.g., 4 vCPU and 16 GB RAM or larger) to accommodate the storage services alongside your application workloads.&lt;/p>
&lt;p>When deploying the AKS cluster, make sure to select the &lt;strong>Bring your own virtual&lt;/strong> network option under the &lt;em>Networking&lt;/em> tab. This step is crucial because the AKS nodes must be able to communicate with the Pure Cloud Block Store instance’s endpoints — including the management interface and iSCSI target IPs.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/deployment-model.png"
width="771"
height="677"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/deployment-model_hub32dc5d4d6b1c204a6149116dcc25d21_60720_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/deployment-model_hub32dc5d4d6b1c204a6149116dcc25d21_60720_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Deploying into same Azure virtual network"
class="gallery-image"
data-flex-grow="113"
data-flex-basis="273px"
>&lt;/p>
&lt;p>Be sure that no &lt;strong>Network Security Groups (NSGs)&lt;/strong> are in place that could block this communication. For optimal performance and reliability, is recommended deploying the AKS cluster into the same virtual network (vNET) as the Pure Cloud Block Store instance. This ensures low-latency connectivity and helps avoid unexpected network-related costs.&lt;/p>
&lt;h2 id="step-2---connect-to-the-aks-cluster">Step 2 - Connect to the AKS Cluster&lt;/h2>
&lt;p>With the AKS cluster running and the Portworx spec in hand, the next step is to apply the configuration to the cluster. It’s convenient to use Azure Cloud Shell for this, as it comes pre-loaded with Azure CLI and kubectl. Open the Azure Cloud Shell in your browser (from the Azure Portal or via &lt;a class="link" href="https://shell.azure.com" target="_blank" rel="noopener"
>https://shell.azure.com&lt;/a>). Make sure your shell is set to Bash for familiarity.&lt;/p>
&lt;p>Authenticate and set Kubeconfig - ensure you’re connected to correct AKS:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>az aks get-credentials --resource-group &lt;span style="color:#f1fa8c">&amp;lt;&amp;lt;resour&lt;/span>ce group name&amp;gt;&amp;gt; --name &amp;lt;&amp;lt;AKUS cluster name&amp;gt;&amp;gt; --overwrite-existing
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now you are operating in the context of your AKS cluster.&lt;/p>
&lt;h2 id="step-3---create-the-configuration-file-purejson">Step 3 - Create the Configuration File (pure.json)&lt;/h2>
&lt;p>Portworx PX-CSI requires a JSON configuration file with information about your Pure Cloud Block Store array. This file (named &lt;code>pure.json&lt;/code>) will contain the management endpoint and API token for your array, enabling Portworx to authenticate and create volumes via the Pure REST API.
Portworx also supports &lt;a class="link" href="https://blog.purestorage.com/purely-technical/enhancing-pure-cloud-block-store-with-multi-tenant-capabilities/" target="_blank" rel="noopener"
>Cloud Block Store multi-tenant setups&lt;/a> - however, for the purpose of this post, we’ll use a single-tenant setup.&lt;/p>
&lt;p>To proceed, we’ll need to create a configuration file that requires two values. The first is the IP address of the CBS management console, which can be found as the &lt;code>floatingManagementIP&lt;/code> output variable in the Managed Application resource, representing your CBS array.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/floating-ip.png"
width="1407"
height="1008"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/floating-ip_hue05a564fdcc888f080485df9e958a12b_278954_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/floating-ip_hue05a564fdcc888f080485df9e958a12b_278954_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Output variable of CBS Managed Application"
class="gallery-image"
data-flex-grow="139"
data-flex-basis="335px"
>&lt;/p>
&lt;p>From the array management UI, find the Management Endpoint and generate an API Token for an account with &lt;em>Storage Admin&lt;/em> privileges (it can be a new dedicated user for Portworx, e.g. &lt;code>portworx-user&lt;/code>).&lt;/p>
&lt;p>Generate a new API token under &lt;strong>Settings &amp;gt; Users and Policies&lt;/strong> – create a token that doesn’t expire, or note its expiry if it does.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/create-api-token.png"
width="612"
height="233"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/create-api-token_hu2d1d5610273d9f947e06f5f0129ec292_22115_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/create-api-token_hu2d1d5610273d9f947e06f5f0129ec292_22115_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Create API token in the CBS Management Console"
class="gallery-image"
data-flex-grow="262"
data-flex-basis="630px"
>&lt;/p>
&lt;p>Now, create a file named &lt;code>pure.json&lt;/code> with the following structure (replace the placeholder values with your actual endpoint and token):&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;FlashArrays&amp;#34;&lt;/span>: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;MgmtEndPoint&amp;#34;&lt;/span>: &lt;span style="color:#f1fa8c">&amp;#34;&amp;lt;pure-cbs-management-endpoint&amp;gt;&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;APIToken&amp;#34;&lt;/span>: &lt;span style="color:#f1fa8c">&amp;#34;&amp;lt;pure-cbs-api-token&amp;gt;&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>With &lt;code>pure.json&lt;/code> ready, the next step is to provide it to Portworx by creating a Kubernetes Secret.&lt;/p>
&lt;p>Upload the &lt;code>pure.json&lt;/code> file to your Cloud Shell environment (you can use the upload button in Cloud Shell or use an SCP command to the Cloud Shell storage). Once the file is present in your Cloud Shell, run the following command to create the secret in your AKS cluster:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl create secret generic px-pure-secret &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --namespace portworx &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --from-file&lt;span style="color:#ff79c6">=&lt;/span>pure.json&lt;span style="color:#ff79c6">=&lt;/span>./pure.json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This command will create a secret named &lt;code>px-pure-secret&lt;/code> in the &lt;code>portworx&lt;/code> namespace (we will deploy Portworx in the &lt;code>portworx&lt;/code> namespace for clarity).&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice info" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#info-notice">&lt;/use>&lt;/svg>&lt;/span>Info&lt;/p>&lt;p>It is important to use the exact name &lt;code>px-pure-secret&lt;/code>, as the Portworx driver looks for a secret by this name during startup.&lt;/p>&lt;/div>
&lt;p>You should see a confirmation like &lt;code>secret/px-pure-secret created&lt;/code>.&lt;/p>
&lt;p>The secret contains the management endpoint and token and will be mounted by Portworx pods to authenticate with the array.&lt;/p>
&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>Note: Ensure the secret is created in the same namespace where Portworx will be installed. In our case, we use the portworx namespace (if you plan to deploy Portworx in a different namespace, adjust the &lt;code>--namespace&lt;/code> accordingly).&lt;/p>
&lt;p>The Portworx Operator will pass this secret to the PX-CSI driver automatically on deployment.&lt;/p>&lt;/div>
&lt;h2 id="step-4---configure-auto-enabling-of-multipath">Step 4 - Configure Auto-Enabling of Multipath&lt;/h2>
&lt;p>Since Azure Kubernetes Service (AKS) does not enable Multipath I/O by default on new nodes, we need to configure it manually.&lt;/p>
&lt;p>To automate this process, we’ve prepared a DaemonSet that automatically detects new nodes in the cluster and applies the required configuration changes.&lt;/p>
&lt;p>To deploy the DaemonSet, simply apply the following YAML:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl apply -f https://raw.githubusercontent.com/PureStorage-OpenConnect/cloudblockstore-scripts/refs/heads/main/CBS-Azure-Solutions/px-csi-driver/enable-aks.yaml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="step-5---generate-the-px-csi-installation-spec">Step 5 - Generate the PX-CSI Installation Spec&lt;/h2>
&lt;p>Portworx provides a convenient online tool, &lt;strong>PX-Central&lt;/strong> (accessible at &lt;a class="link" href="https://central.portworx.com" target="_blank" rel="noopener"
>https://central.portworx.com&lt;/a>), to generate a custom installation manifest for your Kubernetes cluster.&lt;/p>
&lt;p>We will use PX-Central to configure our Portworx deployment for our needs:&lt;/p>
&lt;ol>
&lt;li>
&lt;p>&lt;strong>Log in to PX-Central:&lt;/strong> Navigate to the PX-Central site and log in with your Pure Storage/Portworx credentials. If you don’t have an account, you can create one on the site.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Select PX-CSI and Version:&lt;/strong> In the spec generator, choose &lt;em>PX-CSI&lt;/em> as the product type. Ensure you select &lt;em>version 25.4.0 or higher&lt;/em> for PX-CSI, as version 25.4.0 introduced enhancements and is required for the features we need.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Configure Kubernetes Settings:&lt;/strong> Set Distribution to &lt;em>&amp;ldquo;None&amp;rdquo;&lt;/em> (since AKS is a standard Kubernetes environment). Select the Kubernetes version that matches your AKS cluster’s version (e.g., 1.31.8). This ensures the generated manifests are compatible with your cluster’s API version.&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;strong>Enable iSCSI Support:&lt;/strong> In the Portworx configuration options, make sure to set Storage Area Network to &lt;em>&amp;ldquo;iSCSI&amp;rdquo;&lt;/em>.&lt;/p>
&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Generate and Download Commands:&lt;/strong> After filling in the required settings, PX-Central will generate a set of Kubernetes manifests or commands, providing a Portworx Operator installation manifest and a StorageCluster custom resource (CR) manifest tailored to your config.&lt;/p>
&lt;p>You may see two kubectl commands - for example, PX-Central might instruct you to run:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl apply -f &amp;#39;https://install.portworx.com/……..# Install Portworx Operator
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>kubectl apply -f https://install.portworx.com/……… &lt;span style="color:#6272a4"># Deploy Portworx StorageCluster (PX-CSI)&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>These are placeholders – PX-Central will give you actual URLs - please keep them handy, we will need them in the next step.
The first command installs the Portworx Operator (which manages the deployment), and the second creates the StorageCluster CR which describes the desired Portworx cluster configuration (including the Pure Cloud Block Store integration).&lt;/p>
&lt;h2 id="step-6---install-the-portworx-operator-and-px-csi-driver">Step 6 - Install the Portworx Operator and PX-CSI Driver&lt;/h2>
&lt;p>Now it’s time to deploy Portworx on the cluster. We will apply the manifests obtained from PX-Central to install the Portworx Operator and the Portworx StorageCluster specification. The operator will then pull up all necessary components including the PX-CSI driver.&lt;/p>
&lt;p>&lt;strong>Apply the Portworx Operator manifest:&lt;/strong> Use the command or file from the PX-Central output to install the operator.&lt;/p>
&lt;p>This will create the Portworx Operator deployment and related roles. You should see resources like &lt;code>serviceaccount/portworx-operator&lt;/code> and &lt;code>deployment.apps/portworx-operator&lt;/code> get created. The operator is a controller that will watch for a StorageCluster custom resource and deploy Portworx components accordingly.&lt;/p>
&lt;p>&lt;strong>Apply the Portworx StorageCluster spec:&lt;/strong> Next, apply the PX-CSI cluster spec YAML (the second command from PX-Central). This command creates a StorageCluster object which describes the desired state of Portworx on your cluster.
When you create this object, the Portworx Operator will begin deploying the PX-CSI pods.&lt;/p>
&lt;p>You can verify the creation of the StorageCluster CR in the output: e.g., &lt;code>storagecluster.core.libopenstorage.org/px-cluster created.&lt;/code>&lt;/p>
&lt;p>Once both the operator and the StorageCluster manifests are applied, give the system a few minutes to pull the container images and initialize the pods on all nodes.&lt;/p>
&lt;h2 id="step-7---verify-the-portworx-px-csi-installation">Step 7 - Verify the Portworx PX-CSI Installation&lt;/h2>
&lt;p>After installation, you should verify that Portworx and its CSI components are up and running. Use the following command to check the status of Portworx pods:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>kubectl get pods -n portworx -o wide
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will list all pods in the portworx namespace (assuming you installed Portworx there). You should see the Portworx Operator pod and the PX-CSI driver pods in a &lt;code>Running&lt;/code> state.
For example, the output might include entries like:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>NAME READY STATUS RESTARTS NODE
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>portworx-operator-5c6b7bbd4d-abcde 1/1 Running &lt;span style="color:#bd93f9">0&lt;/span> aks-nodepool1-123456-vmss000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>px-csi-ext-controller-0 5/5 Running &lt;span style="color:#bd93f9">0&lt;/span> aks-nodepool1-123456-vmss000000
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>px-csi-ext-node-57zxp 3/3 Running &lt;span style="color:#bd93f9">0&lt;/span> aks-nodepool1-123456-vmss000001
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>px-csi-ext-node-nrj7k 3/3 Running &lt;span style="color:#bd93f9">0&lt;/span> aks-nodepool1-123456-vmss000002
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>px-csi-ext-node-l8tj2 3/3 Running &lt;span style="color:#bd93f9">0&lt;/span> aks-nodepool1-123456-vmss000003
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In the above example, the operator is running (1/1 containers ready), a CSI controller stateful set pod is running (with multiple containers for provisioner, attacher, etc.), and a CSI node plugin pod is running on each AKS worker node. The exact pod names may vary, but look for pods prefixed with portworx- or px-csi-. All pods should reach &lt;code>Running&lt;/code> status.&lt;/p>
&lt;p>If any pod is in &lt;code>CrashLoopBackOff&lt;/code> state, use &lt;code>kubectl describe&lt;/code> or check logs to troubleshoot (common issues might be missing iSCSI packages on nodes or secret misconfiguration).&lt;/p>
&lt;p>At this stage, Portworx PX-CSI has been successfully deployed and is integrated with Pure Cloud Block Store! The driver has likely created some default StorageClasses as well for you to use, including one for Pure FlashArray direct volumes.&lt;/p>
&lt;h2 id="step-8---create-a-persistent-volume-claim-pvc">Step 8 - Create a Persistent Volume Claim (PVC)&lt;/h2>
&lt;p>With Portworx running, you can now request persistent storage in your AKS cluster that resides on a CBS. Portworx by Pure provides a set of pre-configured StorageClass objects out-of-the-box for various use cases. The one we will use is &lt;code>px-fa-direct-access&lt;/code>, which is the StorageClass for Pure FlashArray Direct Access volumes.&lt;/p>
&lt;p>This storage class tells Portworx to provision volumes directly on the Pure Cloud Block Store/FlashArray for each PVC, rather than using Portworx-managed pools.
Let’s create a sample Pod with PVC to verify everything end-to-end:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-yaml" data-lang="yaml">&lt;span style="display:flex;">&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">apiVersion&lt;/span>: v1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">kind&lt;/span>: PersistentVolumeClaim
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">name&lt;/span>: px-demo-pvc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">spec&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">storageClassName&lt;/span>: px-fa-direct-access
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">accessModes&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - ReadWriteOnce
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">resources&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">requests&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">storage&lt;/span>: 10Ti
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>---
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">apiVersion&lt;/span>: v1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">kind&lt;/span>: Pod
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">metadata&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">name&lt;/span>: px-demo-pod
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">spec&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">volumes&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ff79c6">name&lt;/span>: px-demo-storage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">persistentVolumeClaim&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">claimName&lt;/span>: px-demo-pvc
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">containers&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ff79c6">name&lt;/span>: px-demo-container
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">image&lt;/span>: nginx
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">ports&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ff79c6">containerPort&lt;/span>: &lt;span style="color:#bd93f9">80&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">volumeMounts&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ff79c6">mountPath&lt;/span>: &lt;span style="color:#f1fa8c">&amp;#34;/px-storage&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">name&lt;/span>: px-demo-storage
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This PVC requests a 10 TiB volume using the &lt;code>px-fa-direct-access&lt;/code> storage class for the Pod &lt;code>px-demo-pod&lt;/code>.&lt;/p>
&lt;p>Kubernetes will submit the PVC to the Portworx CSI provisioner. Portworx then communicates with array to create a 10 TiB block volume on the array (thin-provisioned), and attaches that volume via iSCSI to one of the AKS nodes (the node where a pod using this PVC would be scheduled, or immediately if binding is immediate).&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/cbs-volume.png"
width="1748"
height="994"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/cbs-volume_hufea7dda0d2c8d82f0a426eab2fd3c94f_379780_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/cbs-volume_hufea7dda0d2c8d82f0a426eab2fd3c94f_379780_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Created volume on CBS"
class="gallery-image"
data-flex-grow="175"
data-flex-basis="422px"
>&lt;/p>
&lt;p>At this point, you can consume the PVC in any pod.
When we log in into the pod, we can see a mounted volume from CBS into the directory &lt;code>/px-storage&lt;/code>:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/mounted-volume.png"
width="1830"
height="483"
srcset="https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/mounted-volume_hu48792efa9c029397c188c278f2eb1213_327350_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/portworx-csi-with-aks-with-cbs/images/mounted-volume_hu48792efa9c029397c188c278f2eb1213_327350_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Mounted volume from CBS into our pod on AKS"
class="gallery-image"
data-flex-grow="378"
data-flex-basis="909px"
>&lt;/p>
&lt;p>Congratulations – you have successfully set up Portworx PX-CSI on AKS and created a persistent volume on Pure Cloud Block Store!&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>Portworx CSI brings together the best of Kubernetes and Pure Storage by allowing AKS administrators to consume Pure Cloud Block Store as a native Kubernetes storage resource. In this tutorial, we created a highly-available AKS environment, configured Portworx’s PX-CSI driver, and demonstrated provisioning a PVC backed by Pure’s enterprise-grade cloud storage.&lt;/p>
&lt;p>This integration simplifies cloud deployments by offloading data persistence to CBS, giving you features like data reduction and snapshots out-of-the-box on AKS.&lt;/p></description></item><item><title>Optimizing Storage: Deploying Cloud SAN in Advanced Azure Landing Zones</title><link>https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/</guid><description>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/_post.png" alt="Featured image of post Optimizing Storage: Deploying Cloud SAN in Advanced Azure Landing Zones" />&lt;p>This post is follow up to my &lt;a class="link" href="../azure-may-2024-costs/" >previous post about pricing changes&lt;/a>, introduced on Microsoft Build 2024 and their cost implications for cloud storage solution.&lt;/p>
&lt;h2 id="motivation">Motivation&lt;/h2>
&lt;p>The &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/" target="_blank" rel="noopener"
>Azure Cloud Adoption Framework&lt;/a> (CAF) is Microsoft&amp;rsquo;s proven methodology for guiding organizations through their cloud adoption journey. It provides a structured approach to designing, implementing, and optimizing Azure environments based on industry best practices. CAF helps businesses align their cloud strategy with operational, security, and compliance requirements, ensuring a scalable and well-governed foundation.&lt;/p>
&lt;p>Native cloud storage solutions often struggle to meet the high performance, low latency, and resilience demands of enterprise workloads. SAN solutions in the cloud address these challenges by providing highly available, scalable, and feature-rich block storage, similar to on-premises SAN architectures.
SAN solutions like Pure Cloud Block Store enable seamless replication, disaster recovery, and multi-cloud mobility, making them ideal for mission-critical applications, databases, and virtualized environments that require consistent performance and data integrity.&lt;/p>
&lt;h3 id="challenges">Challenges&lt;/h3>
&lt;p>With adopting a SAN solution into any advanced Azure landing zone (inspired by CAF) comes few challenges that requires to be addressed.&lt;/p>
&lt;p>&lt;strong>Hidden Costs for the Data Path&lt;/strong> – Cloud networking and storage transactions can introduce unforeseen costs, especially when data is transferred across regions or via multiple peered vNETs (&lt;a class="link" href="../azure-may-2024-costs/" >see more details&lt;/a>). Understanding these costs is critical for FinOps efficiency.&lt;/p>
&lt;p>&lt;strong>No TLS Inspection or Additional Latency&lt;/strong> – Security policies enforcing TLS decryption can impact SAN replication traffic, potentially introducing performance overhead and requiring additional network configurations. Wrong design can disable to perform a TLS inspection, or in other cases bring an additional latency and lower storage performance.&lt;/p>
&lt;p>&lt;strong>Network Segregation&lt;/strong> – Proper network isolation and segmentation are crucial for security and compliance, but on the other hand a SAN solution might become a single point of failure from this perspective.&lt;/p>
&lt;h2 id="deployment-architecture-pattern-for-a-shared-san-solution">Deployment Architecture Pattern for a Shared SAN Solution&lt;/h2>
&lt;p>In this post, I&amp;rsquo;ll demonstrate this pattern using &lt;a class="link" href="https://www.purestorage.com/products/cloud-block-storage/cbs.html" target="_blank" rel="noopener"
>Pure Cloud Block Store&lt;/a> (CBS), a SAN block storage solution designed for both Azure and AWS.&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice info" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#info-notice">&lt;/use>&lt;/svg>&lt;/span>Info&lt;/p>&lt;p>Please note that the best performance and minimum costs for cloud SAN storage solutions are achieved within a single availability zone in a single virtual network deployments.&lt;/p>
&lt;p>This post describes a deployments in cases where it is necessary to share a single SAN instance between multiple workloads deployed across multiple virtual networks, while adhering to CAF’s design principles and leveraging cloud benefits.&lt;/p>&lt;/div>
&lt;h3 id="key-features">Key Features&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>Optimal SAN performance:&lt;/strong> The data path is optimized, ensuring no additional latency or performance degradation is introduced&lt;/li>
&lt;li>&lt;strong>Security isolation:&lt;/strong> Storage access is strictly limited to host VMs, preventing host VMs from communicating with each other&lt;/li>
&lt;li>&lt;strong>Support for TLS Inspection of traffic:&lt;/strong> All standard traffic from host VMs can be a subject to TLS inspection. The only exception is the CBS data path, which is routed directly for performance optimization&lt;/li>
&lt;li>&lt;strong>FinOps optimization:&lt;/strong> This architecture pattern is designed to minimize unnecessary vNET peering configurations, minimizing hidden costs associated with inter-vNET data transfer.&lt;/li>
&lt;/ul>
&lt;h3 id="diagram">Diagram&lt;/h3>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-caf-deployment.png"
width="5160"
height="2728"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-caf-deployment_hu5ba275983e05aef04c47b7adc48e61f2_320906_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-caf-deployment_hu5ba275983e05aef04c47b7adc48e61f2_320906_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Deployment diagram of cloud SAN using dual-NIC setup in advanced Azure landing zones"
class="gallery-image"
data-flex-grow="189"
data-flex-basis="453px"
>&lt;/p>
&lt;h3 id="components">Components&lt;/h3>
&lt;ul>
&lt;li>SAN solution (&lt;a class="link" href="https://www.purestorage.com/products/cloud-block-storage/cbs.html" target="_blank" rel="noopener"
>Pure Cloud Block Store&lt;/a> in our example)&lt;/li>
&lt;li>Virtual network (vNET)&lt;/li>
&lt;li>Network interface (NIC)&lt;/li>
&lt;li>Azure Firewall&lt;/li>
&lt;li>Network Security Group (NSG)&lt;/li>
&lt;li>Azure VM&lt;/li>
&lt;/ul>
&lt;h3 id="data-paths">Data path(s)&lt;/h3>
&lt;h4 id="iscsi-data-path">iSCSI Data Path&lt;/h4>
&lt;p>Each Host VM is in dual-NIC setup - &lt;em>primary&lt;/em> and &lt;em>secondary&lt;/em> NICs. A primary NIC is dedicated for standard traffic, routed to Azure Firewall and a secondary NIC is dedicated for iSCSI communication for block storage access.&lt;/p>
&lt;p>Host VMs connect directly to CBS over iSCSI via a vNET Peering between host VM vNET and CBS vNET.&lt;/p>
&lt;h4 id="replication-data-path-between-multiple-cbs-instances">Replication Data Path (Between Multiple CBS Instances)&lt;/h4>
&lt;p>CBS supports various replication types between storage arrays, ranging from ActiveCluster (synchronous) to multiple asynchronous options such as ActiveDR and CloudSnap. These replication processes utilize dedicated network interfaces to ensure efficient data transfer.&lt;/p>
&lt;p>In this setup, CBS instances replicate to each other directly via vNET Peering between the &lt;em>CBS #1 vNET&lt;/em> and the &lt;em>CBS #2 vNET&lt;/em>.&lt;/p>
&lt;h3 id="details">Details&lt;/h3>
&lt;p>For following examples, consider following IP ranges (IP ranges are selected to make this example easier to understand, you don&amp;rsquo;t need to follow them):&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>- CBS #1 vNET: 192.168.96.0/21
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet system: 192.168.100.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet iscsi: 192.168.101.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet mngmt: 192.168.102.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet replication: 192.168.103.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Spoke #1 vNET: 192.168.1.0/22
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet default: 192.168.1.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet cbs-nics: 192.168.2.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- Hub vNET: 10.0.0.0/16
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet AzureFirewallSubnet: 10.0.1.0/26
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>- CBS #2 vNET: 192.168.200.0/21
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet system: 192.168.200.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet iscsi: 192.168.201.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet mngmt: 192.168.202.0/24
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> |- subnet replication: 192.168.203.0/24
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="nsg-configurations">NSG configurations&lt;/h4>
&lt;p>To accomplish all the needs described above, you need to configure the following Network Security Groups:&lt;/p>
&lt;h5 id="cbs-1-vnet---iscsi-subnet">CBS #1 vNET - &lt;em>iSCSI&lt;/em> subnet&lt;/h5>
&lt;p>Purpose: This NSG allows iSCSI traffic from NICs in the subnet only and blocks any further communication from other vNET/subnets.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Inbound:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allow 3260 from &amp;lt;&amp;lt;host VM IPs&amp;gt;&amp;gt; to Any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Deny all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Outbound:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allow 3260 from Any to &amp;lt;&amp;lt;host VM IPs&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Deny all
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Example:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-iscsi-nsg.png"
width="1202"
height="464"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-iscsi-nsg_hu3229e8735edea8e1849c908347a8e594_121344_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/cbs-iscsi-nsg_hu3229e8735edea8e1849c908347a8e594_121344_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="NSG configuration on CBS vNET, the iSCSI subnet"
class="gallery-image"
data-flex-grow="259"
data-flex-basis="621px"
>&lt;/p>
&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>For a port configuration of inbound security rules, configure the Destination port range only and keep Source port ranges as wildcards.&lt;/p>&lt;/div>
&lt;h5 id="spoke-1-vnet---nsg-for-vm-nics-within-default-subnet">Spoke #1 vNET - NSG for VM NICs within &lt;em>default&lt;/em> subnet&lt;/h5>
&lt;p>This NSG is for testing purposes, to make sure the iSCSI traffic to CBS will not be routed via the incorrect interface.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Outbound - add following rule:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Deny 3260 from Any to &amp;lt;&amp;lt;CBS iSCSI subnet range&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h5 id="spoke-1-vnet---nsg-for-vm-nics-within-cbs-nics-subnet">Spoke #1 vNET - NSG for VM NICs within &lt;em>cbs-nics&lt;/em> subnet&lt;/h5>
&lt;p>Thanks to this NSG, the iSCSI traffic can be routed via this secondary NIC and communicate with CBS, but can’t communicate with each other.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Inbound Security Rules:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allow 3260 from &amp;lt;&amp;lt;CBS iSCSI subnet range&amp;gt;&amp;gt; to Any
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Deny all
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Outbound Security Rules:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Allow 3260 from Any to &amp;lt;&amp;lt;CBS iSCSI subnet range&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Deny all
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Example:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/hostvm-iscsi-nsg.png"
width="1563"
height="595"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/hostvm-iscsi-nsg_hu23d3a8d6ecd9336acd4b421afa23f18a_214556_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/hostvm-iscsi-nsg_hu23d3a8d6ecd9336acd4b421afa23f18a_214556_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="NSG configuration on host VM vNET, the cbs-nics subnet"
class="gallery-image"
data-flex-grow="262"
data-flex-basis="630px"
>&lt;/p>
&lt;h4 id="azure-firewall-configuration">Azure Firewall configuration&lt;/h4>
&lt;p>All non-datapath traffic from CBS (system or management interface) can be routed through Azure Firewall or other type of virtual appliance.&lt;/p>
&lt;p>Because there might be a SSL pinning in place, in the following example, we proceed with an Application rule allowing traffic to FQDN &lt;code>*.purestorage.com&lt;/code> and &lt;code>management.azure.com&lt;/code> without TLS inspection on &lt;code>Port 443&lt;/code> (HTTPS).&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-app-rules.png"
width="2159"
height="608"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-app-rules_hu11ff14fb88d521a6ff3bd66027579ab8_294447_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-app-rules_hu11ff14fb88d521a6ff3bd66027579ab8_294447_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Application rules in Azure Firewall (TLS inspection)"
class="gallery-image"
data-flex-grow="355"
data-flex-basis="852px"
>&lt;/p>
&lt;h4 id="routing-configurations">Routing configurations&lt;/h4>
&lt;h5 id="forced-tunneling">Forced Tunneling&lt;/h5>
&lt;p>Routing to Azure Firewall is achieved via routing table, with a User-Defined Route (UDR) &lt;code>0.0.0.0/0&lt;/code> with next hop to Azure Firewall (in our example &lt;code>10.0.1.4&lt;/code>).&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-udr.png"
width="1994"
height="634"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-udr_hu0af01573810aa89e91fdcc52ba711e5b_143541_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/azure-firewall-udr_hu0af01573810aa89e91fdcc52ba711e5b_143541_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Forced tunneling to Azure Firewall via UDR"
class="gallery-image"
data-flex-grow="314"
data-flex-basis="754px"
>&lt;/p>
&lt;h5 id="cbs-vnet">CBS vNET&lt;/h5>
&lt;p>For achieving the forced tunneling to Azure Firewall is this routing table attached to the &lt;em>system&lt;/em> and &lt;em>management&lt;/em> subnets. Traffic from these CBS interfaces will be routed via Azure Firewall.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-cbs-vnet.png"
width="1616"
height="292"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-cbs-vnet_hu4cd17050401b1c07563deafded8e9957_100843_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-cbs-vnet_hu4cd17050401b1c07563deafded8e9957_100843_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Routing table assignment for CBS vNET"
class="gallery-image"
data-flex-grow="553"
data-flex-basis="1328px"
>&lt;/p>
&lt;h5 id="host-vm-vnet">Host VM vNET&lt;/h5>
&lt;p>For achieving the forced tunneling to Azure Firewall is this routing table attached to the &lt;em>default&lt;/em> subnet. Standard traffic from the Host VM (except for iSCSI data path) will be routed via Azure Firewall. iSCSI path will be connected directly to the CBS iSCSI subnet via vNET peering.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-host-vnet.png"
width="1586"
height="172"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-host-vnet_hu45e8a047e1b7cce812ba53aa39b12df2_51782_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/udr-rt-host-vnet_hu45e8a047e1b7cce812ba53aa39b12df2_51782_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Routing table assignment for Host VM vNET"
class="gallery-image"
data-flex-grow="922"
data-flex-basis="2213px"
>&lt;/p>
&lt;h5 id="host-vm-routing-table">Host VM Routing Table&lt;/h5>
&lt;p>To ensure the operation system on the Host VM will know when to use secondary NIC for iSCSI traffic, you need to configure a route to CBS iSCSI interface.&lt;/p>
&lt;h6 id="windows">Windows&lt;/h6>
&lt;p>Before configuring a route, we need to identify the ID of the network interface within the &lt;em>cbs-nics&lt;/em> subnet.&lt;/p>
&lt;p>Following commands provide information about host VMs network interfaces:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>PS&amp;gt; ipconfig
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Ethernet adapter Ethernet &lt;span style="color:#bd93f9">3&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd;font-style:italic">Connection-specific&lt;/span> DNS Suffix . :
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd;font-style:italic">Link-local&lt;/span> IPv6 Address . . . . . : fe80::5fe4:1cd8:&lt;span style="color:#bd93f9">2983&lt;/span>:16ad&lt;span style="color:#ff79c6">%&lt;/span>&lt;span style="color:#bd93f9">11&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> IPv4 Address. . . . . . . . . . . : &lt;span style="color:#bd93f9">192.168&lt;/span>.2.5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Subnet Mask . . . . . . . . . . . : &lt;span style="color:#bd93f9">255.255&lt;/span>.255.0
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">Default&lt;/span> Gateway . . . . . . . . . :
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>PS&amp;gt; netsh interface ipv4 show interfaces
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Idx Met MTU State Name
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>--- ---------- ---------- ------------ ---------------------------
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#bd93f9">1&lt;/span> &lt;span style="color:#bd93f9">75&lt;/span> &lt;span style="color:#bd93f9">4294967295&lt;/span> connected Loopback &lt;span style="color:#8be9fd;font-style:italic">Pseudo-Interface&lt;/span> &lt;span style="color:#bd93f9">1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#bd93f9">4&lt;/span> &lt;span style="color:#bd93f9">10&lt;/span> &lt;span style="color:#bd93f9">1500&lt;/span> connected Ethernet
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> **&lt;span style="color:#bd93f9">11&lt;/span>** &lt;span style="color:#bd93f9">10&lt;/span> &lt;span style="color:#bd93f9">1500&lt;/span> connected Ethernet &lt;span style="color:#bd93f9">3&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>To configure a persistent route, use following command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>route -p add &amp;lt;&amp;lt;IP range of subnet iscsi&amp;gt;&amp;gt; MASK &lt;span style="color:#bd93f9">255.255&lt;/span>.255.0 &amp;lt;&amp;lt;gateway IP of subnet &lt;span style="color:#8be9fd;font-style:italic">cbs-nics&lt;/span>&amp;gt;&amp;gt; METRIC &lt;span style="color:#bd93f9">5015&lt;/span> &lt;span style="color:#ff79c6">IF&lt;/span> &amp;lt;&amp;lt;network interface Idx&amp;gt;&amp;gt;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>As IP address of subnet gateway use IP address: &lt;code>x.x.x.1&lt;/code>&lt;/p>&lt;/div>
&lt;p>Example:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>PS&amp;gt; route -p add &lt;span style="color:#bd93f9">192.168&lt;/span>.101.0 MASK &lt;span style="color:#bd93f9">255.255&lt;/span>.255.0 &lt;span style="color:#bd93f9">192.168&lt;/span>.2.1 METRIC &lt;span style="color:#bd93f9">5015&lt;/span> &lt;span style="color:#ff79c6">IF&lt;/span> &lt;span style="color:#bd93f9">11&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h6 id="linux">Linux&lt;/h6>
&lt;p>Firstly, we need to identify the ID of the network interface within the &lt;code>cbs-nics&lt;/code> subnet.&lt;/p>
&lt;p>Following commands provide information about host VMs network interfaces:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ ip addr show
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>3: **eth1**: &amp;lt;BROADCAST,MULTICAST,UP,LOWER_UP&amp;gt; mtu &lt;span style="color:#bd93f9">1500&lt;/span> qdisc mq state UP group default qlen &lt;span style="color:#bd93f9">1000&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> link/ether 7c:1e:52:5d:6d:9c brd ff:ff:ff:ff:ff:ff
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> inet 192.168.2.4/24 metric &lt;span style="color:#bd93f9">200&lt;/span> brd 192.168.2.255 scope global eth1
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valid_lft forever preferred_lft forever
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> inet6 fe80::7e1e:52ff:fe5d:6d9c/64 scope link
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> valid_lft forever preferred_lft forever
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>To configure a persistent route, use following command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ route add -net &lt;span style="color:#f1fa8c">&amp;lt;&amp;lt;IP range of subnet iscsi&amp;gt;&amp;gt; netmask 255.255.255.0 gw &amp;lt;&amp;lt;gateway IP&lt;/span> of subnet cbs-nics&amp;gt;&amp;gt; dev &lt;span style="color:#f1fa8c">&amp;lt;&amp;lt;ntw int&lt;/span>erface&amp;gt;&amp;gt; metric &lt;span style="color:#bd93f9">5015&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>As IP address of subnet gateway use IP address: &lt;code>x.x.x.1&lt;/code>&lt;/p>&lt;/div>
&lt;p>Example:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">$route&lt;/span> add -net 192.168.101.0 netmask 255.255.255.0 gw 192.168.2.1 dev eth1 metric &lt;span style="color:#bd93f9">5015&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="iscsi-target-configuration">iSCSI target configuration&lt;/h4>
&lt;p>For connecting CBS as an iSCSI target, make sure you select the right network interface in subnet cbs-nics to be used for ISCSI initiator.&lt;/p>
&lt;p>In our example it’s &lt;code>192.168.2.5&lt;/code>:&lt;/p>
&lt;p>For Windows - Powershell option:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>PS&amp;gt; &lt;span style="color:#8be9fd;font-style:italic">New-IscsiTargetPortal&lt;/span> -TargetPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.101.5 -InitiatorPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.2.5 -TargetPortalPortNumber &lt;span style="color:#bd93f9">3260&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">#ct0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">Get-IscsiTarget&lt;/span> | &lt;span style="color:#8be9fd;font-style:italic">Connect-IscsiTarget&lt;/span> -InitiatorPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.2.5 -IsMultipathEnabled $true -IsPersistent $true -TargetPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.101.5
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">#ct1&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">Get-IscsiTarget&lt;/span> | &lt;span style="color:#8be9fd;font-style:italic">Connect-IscsiTarget&lt;/span> -InitiatorPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.2.5 -IsMultipathEnabled $true -IsPersistent $true -TargetPortalAddress &lt;span style="color:#bd93f9">192.168&lt;/span>.101.8
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Or in Windows iSCSI wizard:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/windows-iscsi-wizard.png"
width="1569"
height="1072"
srcset="https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/windows-iscsi-wizard_huadd1c661e684cef35f9e734d2d8abd83_446621_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/san-storage-azure-landing-zone-caf/images/windows-iscsi-wizard_huadd1c661e684cef35f9e734d2d8abd83_446621_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Windows iSCSI wizard configuration"
class="gallery-image"
data-flex-grow="146"
data-flex-basis="351px"
>&lt;/p></description></item><item><title>May 2024 Azure Updates: Cost Implications for Cloud Storage</title><link>https://blog.vjirovsky.cz/azure-may-2024-cost-storage/</link><pubDate>Fri, 31 May 2024 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/azure-may-2024-cost-storage/</guid><description>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/_post.png" alt="Featured image of post May 2024 Azure Updates: Cost Implications for Cloud Storage" />&lt;p>When discussing cloud storage costs, network expenses are often a significant hidden factor. Data transfer between storage and other services can lead to unexpected charges, especially as usage scales. During the Build 2024 event, Microsoft silently published a small but important update to networking costs. This article aims to summarize how networking costs might impact cloud storage in Azure and what to consider during design from a FinOps perspective.&lt;/p>
&lt;p>&lt;em>Any costs in this article roughly estimate costs related to network data transfer only and don&amp;rsquo;t reflect any individual customer agreements. For precise estimates, I recommend checking the latest official pricing, contacting your Microsoft representative, or evaluating a limited PoC to prevent any incurred costs.&lt;/em>&lt;/p>
&lt;h2 id="single-az-transfers-within-a-single-vnet">Single-AZ transfers within a single vNET&lt;/h2>
&lt;p>Any data transfers between VMs within a single vNET in a single availability zone (AZ) have been free, and there are no announced plans to start charging for them.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-az.png"
width="648"
height="305"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-az_hu66e8d331807b303ab27b51377c75d6d6_16093_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-az_hu66e8d331807b303ab27b51377c75d6d6_16093_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="single-AZ communication in single vNET"
class="gallery-image"
data-flex-grow="212"
data-flex-basis="509px"
>&lt;/p>
&lt;h2 id="cross-az-transfers-within-a-single-vnet">Cross-AZ transfers within a single vNET&lt;/h2>
&lt;p>To improve your workload resiliency, you might want to scale out underlying resources across multiple availability zones. This architecture reduces the probability of downtime, so when one AZ experiences an outage, the other remains operational.&lt;/p>
&lt;p>A bit of history: In 2021, Microsoft announced plans to start billing for data transfers across availability zones (AZs) in the future. Microsoft noted that they would announce the launch date 3 months in advance. This plan remained in place for the past 3 years, until April 2024, when Microsoft began to implement these charges. Additionally, the Microsoft Learn/Azure pricing documentation has been inconsistent, and only the &lt;a class="link" href="https://azure.microsoft.com/en-us/pricing/details/bandwidth/" target="_blank" rel="noopener"
>page about Bandwidth pricing&lt;/a> mentioned this cost.&lt;/p>
&lt;p>With this cost in place, any data transfer between two AZs, even within a single vNET in the same region, is charged per transferred GB.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-azs.png"
width="647"
height="301"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-azs_hu65be77c5d05263c9c1e43eb9f4dc4fa0_16662_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/single-vnet-azs_hu65be77c5d05263c9c1e43eb9f4dc4fa0_16662_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="cross-AZ communication in single vNET"
class="gallery-image"
data-flex-grow="214"
data-flex-basis="515px"
>&lt;/p>
&lt;p>At end of May, during the Microsoft Build conference, Microsoft has announced in a &lt;a class="link" href="https://azure.microsoft.com/en-us/updates/update-on-interavailability-zone-data-transfer-pricing/" target="_blank" rel="noopener"
>small blog post&lt;/a> that they are removing the cost, and so far, it appears they do not plan to reintroduce it.&lt;/p>
&lt;h3 id="impact-to-cloud-storage">Impact to Cloud Storage&lt;/h3>
&lt;p>When I talk about Cloud Storage in this context, I am referring to TBs of data that are frequently accessed by an application. Due to missing important storage features (thin-provisioning, data deduplication, snapshots, etc.) on native disks services, this type of storage typically leverages a smarter, SAN solution, such as &lt;a class="link" href="https://www.purestorage.com/products/cloud-block-storage/cbs.html" target="_blank" rel="noopener"
>Pure Cloud Block Store™&lt;/a> &lt;em>(CBS)&lt;/em>.&lt;/p>
&lt;p>SAN solutions are typically AZ-specific, meaning the underlying resources are deployed within a single AZ. This architecture reduces storage latency and improves performance.&lt;/p>
&lt;p>For workloads deployed in a single AZ, the best practice is to co-locate a SAN in the same AZ as the workload. Deploying into a different AZ might lead to slower performance and violates the main concept of isolated AZs for resiliency. It increases the probability of downtime because if either the application&amp;rsquo;s AZ or the storage&amp;rsquo;s AZ experiences an outage, it leads to a solution downtime. Additionally, such an architecture would also bring additional charges to your invoice due to cross-AZ communication charges.&lt;/p>
&lt;p>For more important workloads deployed across multiple AZs, you may deploy 2 CBS arrays (into different AZs within same vNET) and leverage a replication between them.&lt;/p>
&lt;h4 id="activedr">ActiveDR&lt;/h4>
&lt;p>One of the replication types available in CBS is an asynchronous near-zero RPO, called &lt;em>ActiveDR&lt;/em>.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-dr-pods.png"
width="684"
height="310"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-dr-pods_hub0219c9e6043dce9bf42fbaa14444cb8_67707_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-dr-pods_hub0219c9e6043dce9bf42fbaa14444cb8_67707_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="ActiveDR replication between Purity OS devices"
class="gallery-image"
data-flex-grow="220"
data-flex-basis="529px"
>&lt;/p>
&lt;p>A big advantage of this replication is that only deduplicated and compressed data are transferred, not the raw data. After Microsoft removed the cross-AZ cost, this has no impact on the bill anymore, but it significantly reduces the time required to transfer the &amp;ldquo;same amount&amp;rdquo; of data. This will have a significant impact in the next FinOps tip below.&lt;/p>
&lt;h4 id="activecluster">ActiveCluster&lt;/h4>
&lt;p>Another type of replication between two CBS instances which can be used here is called &lt;em>ActiveCluster™&lt;/em>. This setup provides active-active synchronous replication with zero RPO and RTO, where each CBS instance primary serves applications within its respective AZ.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-cluster.png"
width="1135"
height="352"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-cluster_hu9830a3ad41949c134aed2e8c84b1221d_155508_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/active-cluster_hu9830a3ad41949c134aed2e8c84b1221d_155508_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="ActiveCluster replication between Purity OS devices"
class="gallery-image"
data-flex-grow="322"
data-flex-basis="773px"
>&lt;/p>
&lt;p>Please note, that in ActiveCluster replication the benefit for replicating only deduplicated and compressed data is not applicable, since every write needs to be done on both devices synchronously.&lt;/p>
&lt;h2 id="multiple-vnets-singlecross-az-communication">Multiple vNETs, Single/Cross-AZ Communication&lt;/h2>
&lt;p>All the scenarios above discussed having all resources within a single vNET.&lt;/p>
&lt;p>When a resource is located in a different vNET and accessed via vNET peering/NVA (regardless of whether it&amp;rsquo;s in the same or a different AZ), another networking cost applies—for data transferred via vNET peering. The vNET peering service itself is free, but ingress and egress traffic is charged at both ends of the peered networks.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/peered-vnets.png"
width="781"
height="312"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/peered-vnets_hu85224f87c8a33719d39c901bc935227f_18079_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/peered-vnets_hu85224f87c8a33719d39c901bc935227f_18079_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Communication via vNET peering"
class="gallery-image"
data-flex-grow="250"
data-flex-basis="600px"
>&lt;/p>
&lt;p>&lt;strong>VNET Peering within the same region&lt;/strong>:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Transfer&lt;/th>
&lt;th>Price&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Inbound data&lt;/td>
&lt;td>$0.01 per GB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Outbound data&lt;/td>
&lt;td>$0.01 per GB&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>vNET peering within the same region is charged at $0.02 per GB ($0.01 for egress from the first vNET and $0.01 for ingress to the second vNET), regardless of whether the data physically stays within the same AZ.&lt;/p>
&lt;p>Following best practices for creating an Azure landing zone in an enterprise environment typically results in a more complex network topology. The most common is the Hub and Spoke topology, where multiple &lt;em>Spoke vNETs&lt;/em> (containing various workloads) are connected to a single &lt;em>Hub vNET&lt;/em>, which provides common connectivity to on-premises, security appliances, and more, via vNET peering.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/hub-spoke.png"
width="442"
height="320"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/hub-spoke_hu80a83f294c7774c81e300d56df40cd7c_15953_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/hub-spoke_hu80a83f294c7774c81e300d56df40cd7c_15953_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Hub&amp;Spoke"
class="gallery-image"
data-flex-grow="138"
data-flex-basis="331px"
>&lt;/p>
&lt;p>With this topology, try to limit the number of hops as much as possible—each vNET peering in the path incurs charges for both egress and ingress.&lt;/p>
&lt;p>For communication between two Spoke vNETs (e.g. a VM consuming storage from SAN in another vNET), consider co-locating these resources into a single vNET or creating a direct vNET peering between these vNETs.&lt;/p>
&lt;pre class="mermaid">sequenceDiagram
participant vNET 1
participant Hub vNET
participant vNET 2
vNET 1->> Hub vNET: egress from vNET 1 ($0.01) + ingress to Hub vNET ($0.01) = $0.02
Hub vNET ->> vNET 2: egress from Hub vNET ($0.01) + ingress to vNET 2 ($0.01) = $0.02
&lt;/pre>
&lt;p>In total this architecture costs $0.04 per GB.&lt;/p>
&lt;p>&lt;strong>Model scenario no.1 - transferring 100 TB data, same-region, cross-AZ:&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Total&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Within single vNET&lt;/td>
&lt;td>&lt;del>$2,000&lt;/del> &lt;strong>$0 after April 2024&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hub&amp;amp;spoke (routed via Hub, 2x vNET peerings)&lt;/td>
&lt;td>$4,000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hub&amp;amp;spoke (direct spoke-spoke vNET peering)&lt;/td>
&lt;td>$2,000&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;br>
&lt;p>&lt;strong>Model scenario no.2 - transferring 100 TB data, same-region single-AZ:&lt;/strong>&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Method&lt;/th>
&lt;th>Total&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Within single vNET&lt;/td>
&lt;td>$0&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hub&amp;amp;spoke (routed via Hub, 2x vNET peerings)&lt;/td>
&lt;td>$4,000&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Hub&amp;amp;spoke (direct spoke-spoke vNET peering)&lt;/td>
&lt;td>$2,000&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h3 id="aws-difference">AWS difference&lt;/h3>
&lt;p>There is a significant difference in networking costs between Azure and AWS. AWS does not charge for data transfer between peered VPCs if the source and destination remain within the same availability zone. All variants in Scenario no.2 would not be charged.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/aws-costs.png"
width="1027"
height="628"
srcset="https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/aws-costs_hu5cf228bac47e6dd712154843b2d71860_178987_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/azure-may-2024-cost-storage/images/aws-costs_hu5cf228bac47e6dd712154843b2d71860_178987_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="AWS VPC peering costs"
class="gallery-image"
data-flex-grow="163"
data-flex-basis="392px"
>&lt;/p>
&lt;h2 id="private-link">Private Link&lt;/h2>
&lt;p>At the end of this article, I would like to discuss the option of exposing cloud storage via Private Link. Despite its various benefits, Private Endpoints incur additional costs, priced at $0.01 per hour for each Private Endpoint. Additionally, there are costs associated with processing inbound and outbound data transferred via Private Endpoints, and the data transfer charges mentioned earlier still apply. For large data transfers it might be a significant hidden cost.&lt;/p></description></item><item><title>Playing with GitHub Copilot: Interactive Documentation by RAG</title><link>https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/</link><pubDate>Thu, 28 Mar 2024 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/</guid><description>&lt;img src="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/_post.png" alt="Featured image of post Playing with GitHub Copilot: Interactive Documentation by RAG" />&lt;h2 id="intro">Intro&lt;/h2>
&lt;p>&lt;small>&lt;em>Disclaimer: Just a heads up, I&amp;rsquo;m not an AI Expert, I&amp;rsquo;m ordinary App Innovation architect, just trying to get AI to lend me a hand with my projects. If I slip up here and there, my bad—I&amp;rsquo;m sort of navigating uncharted waters for me.&lt;/em>&lt;/small>&lt;/p>
&lt;p>While experimenting with GitHub Copilot, I encountered frustration due to its limited or absent knowledge on my specific domain (or product), despite clearly specifying the topic, GitHub Copilot were unable to load them, and with just only basic or no knowledge about the topic tried to &amp;ldquo;invent&amp;rdquo; some answers, which were totally hallucinating.&lt;/p>
&lt;p>My conversation with GitHub Copilot:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>vjirovsky&lt;/strong>&lt;/th>
&lt;th>&lt;strong>GitHub Copilot&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;em>&amp;ldquo;create me a Bicep template for Pure Cloud Block Store deployment&amp;rdquo;&lt;/em>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;em>Sure, here&amp;rsquo;s a basic example of a Bicep template for deploying Pure Cloud Block Store on Azure. This template creates a virtual network, a subnet, and a network security group with necessary rules. Please note that this is a simplified example and you may need to adjust it according to your specific requirements.&lt;br> &lt;code>&amp;hellip;non-sense Bicep code (truncated)&amp;hellip; &lt;/code>&lt;br>Please note that this template does not actually deploy the Pure Cloud Block Store instance because as of now, there is no Azure Resource Manager (ARM) or Bicep support for Pure Cloud Block Store. You would need to use the Pure Storage console or API to deploy the Cloud Block Store instance.&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;em>&amp;ldquo;create me a Bicep template for Pure Cloud Block Store deployment,&lt;br>use guidance here (link)&amp;rdquo;&lt;/em>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;em>I&amp;rsquo;m sorry for any confusion, but as an AI, I don&amp;rsquo;t have the ability to access external URLs or content outside of the information provided in the conversation&lt;/strong>. This includes the URL you&amp;rsquo;ve provided.&lt;/em>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I understand that GitHub Copilot cannot encompass all knowledge, but what surprised me was its inability to access any supportive resources available online, even when public URLs to documentation are provided. My assumption is that such online access is restricted due to security/license reasons, preventing Copilot from loading external content, at least directly into my session.&lt;/p>
&lt;h2 id="concept-overview">Concept Overview&lt;/h2>
&lt;p>The idea is pretty straightforward - using Retrieval-Augmented Generation (RAG) prompt engineering: it&amp;rsquo;s about loading every prompt with comprehensive knowledge of my domain or product, akin to filling a suitcase with all essentials - as on &lt;em>Figure 1&lt;/em>, a component called &amp;lsquo;Enterprise Knowledge Base&amp;rsquo; in the diagram.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/nvidia-rag-seq.png"
width="2056"
height="964"
srcset="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/nvidia-rag-seq_hu435f26d7634d6816eb36edba3e5a7b25_363996_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/nvidia-rag-seq_hu435f26d7634d6816eb36edba3e5a7b25_363996_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Figure 1. Overview of RAG pipeline components: ingest and query flows (source: developer.nvidia.com)"
class="gallery-image"
data-flex-grow="213"
data-flex-basis="511px"
>&lt;/p>
&lt;p>A Knowledge Base, especially for tools like GitHub Copilot, can be any form of text document—ranging from source code files to simple text files. In my experiment, I opted for a Markdown formatted document. I chose Markdown because of its structured yet straightforward syntax, which is easily parseable for large language models (LLMs), you can include there also a source code blocks and it&amp;rsquo;s user-friendly for authors.&lt;/p>
&lt;h3 id="example-of-helper-file">Example of helper file&lt;/h3>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CBS means Pure Cloud Block Store
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">## Prerequisites before CBS deployment
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">&lt;/span>A CBS requires for deployment following components to be deployed before CBS (headlines 3 in this section):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">### Logged user&amp;#39;s permissions
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">&lt;/span>A logged user, who deploys a CBS, needs to have a permission Managed Application Contributor on the selected subscription and on the selected subnet(s) for CBS deployment, at the minimum.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">### Region / AZ support
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="font-weight:bold">&lt;/span>See this list of supported regions and Availability Zones to confirm CBS availability in your region and selected Availability zone.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>The up-to-date list is always available on URL: https://support.purestorage.com/Pure_Cloud_Block_Store/Pure_Cloud_Block_Store_on_Azure_-__Regions_Support
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>If a user asks about deployment into any concrete region or AZ, point them to the URL.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="using-in-github-copilot">Using in GitHub Copilot&lt;/h2>
&lt;p>I have created a single helper file called &lt;code>cbs-azure-copilot.md&lt;/code> within which I compiled essential details about the &lt;a class="link" href="https://www.purestorage.com/products/cloud-block-storage/cbs.html" target="_blank" rel="noopener"
>Pure Cloud Block Store&lt;/a> product. This included guidance on deployment, best practices, and a selection of deployment examples.&lt;/p>
&lt;p>The GitHub Copilot plugin in IDEs, such as Visual Studio Code, can reference a file in a prompt, but it needs to be done the right way. One key limitation is that the file must either be open or part of the repository you&amp;rsquo;ve selected.&lt;/p>
&lt;p>At this moment, you may reference it with special command &lt;code>#file: &amp;lt;&amp;lt;filename&amp;gt;&amp;gt;&lt;/code>:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-file-command.png"
width="854"
height="461"
srcset="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-file-command_huf49509e35be34a960ada4c6dedcf0446_50082_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-file-command_huf49509e35be34a960ada4c6dedcf0446_50082_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Figure 2. GitHub Copilot plugin in IDE - #file comamnd"
class="gallery-image"
data-flex-grow="185"
data-flex-basis="444px"
>&lt;/p>
&lt;p>When using a prompt from this article or similar sources, ensure that the &lt;code>#file:&lt;/code> command is recognized correctly. From my experience, you may need to manually type out this portion, as the IDE plugin does not support copying and pasting the entire prompt.&lt;/p>
&lt;p>Also, to maintain context from the helper file, I recommend repeating its reference in every prompt to ensure Copilot stays informed.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-rag-1.png"
width="489"
height="571"
srcset="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-rag-1_hu91a14b77c7eb388acdc327a351211d9d_38581_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/gh-rag-1_hu91a14b77c7eb388acdc327a351211d9d_38581_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Figure 3. GitHub Copilot responses enhanced with custom knowledge base"
class="gallery-image"
data-flex-grow="85"
data-flex-basis="205px"
>&lt;/p>
&lt;p>This &lt;code>#file&lt;/code> command offers a notable advantage: it enables the simultaneous referencing of a helper file along with an open file containing source code within a single prompt. This feature can be particularly useful for receiving proposal changes from GitHub Copilot, informed by the contents of the helper file.&lt;/p>
&lt;p>See example conversation below:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>&lt;strong>vjirovsky&lt;/strong>&lt;/th>
&lt;th>&lt;strong>GitHub Copilot&lt;/strong>&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>&lt;em>&amp;ldquo;please fix my code (something is wrong around Managed App publisher), use helper #file:cbs-azure-copilot.md&amp;rdquo;&lt;/em>&lt;/td>
&lt;td>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>&lt;/td>
&lt;td>&lt;small>&lt;em>used reference main.bicep, lines 1-48&lt;/em>&lt;/small>&lt;br>&lt;em>Based on the helper file, the publisher ID for ARM deployments of Managed Applications should be &lt;code>purestoragemarketplaceadmin&lt;/code>.&lt;/em>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h2 id="usage-in-github-copilot-enterprise">Usage in GitHub Copilot Enterprise&lt;/h2>
&lt;p>In the Enterprise version of GitHub Copilot, you have the ability to set it up so that it can include references to your private data (such as our helper file) in the responses generated for all users within your organization. This functionality offers a straightforward way to inject new knowledge into Copilot or to steer its output in a particular direction.&lt;/p>
&lt;p>For example, it could be utilized to embed corporate standards directly into the tool&amp;rsquo;s suggestions. Imagine ensuring that every Bicep template suggested by Copilot automatically aligns more closely with your company&amp;rsquo;s security or tagging standards.&lt;/p>
&lt;p>Additionally, it could serve to standardize the way frequently used content is structured and organized according to standard within your organization.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/ghce-rag.png"
width="500"
height="714"
srcset="https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/ghce-rag_huaf7b275c18cb0f0c94ddf0f0b2139832_194937_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/playing-with-github-copilot-on-cbs/images/ghce-rag_huaf7b275c18cb0f0c94ddf0f0b2139832_194937_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Figure 3. GitHub Copilot Enterprise Pipeline (source:medium.com/@tajinder.singh1985)"
class="gallery-image"
data-flex-grow="70"
data-flex-basis="168px"
>&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>In summary, using a helper file like &lt;code>cbs-azure-copilot.md&lt;/code> in GitHub Copilot offers an easy and effective way for making Copilot even more useful in your coding projects by aligning it with your specific needs and standards.&lt;/p>
&lt;p>For those interested, you can find the &lt;a class="link" href="https://github.com/vjirovsky/cbs-copilot-helper" target="_blank" rel="noopener"
>CBS helper file on GitHub&lt;/a>, providing a practical example of how to leverage this approach.&lt;/p>
&lt;h2 id="resources">Resources&lt;/h2>
&lt;small>
&lt;ol>
&lt;li>
&lt;p>&lt;a class="link" href="https://medium.com/@tajinder.singh1985/prompt-engineering-get-the-best-out-of-llm-using-these-5-simple-techniques-0cb58e226a07" target="_blank" rel="noopener"
>https://medium.com/@tajinder.singh1985/prompt-engineering-get-the-best-out-of-llm-using-these-5-simple-techniques-0cb58e226a07&lt;/a>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>&lt;a class="link" href="https://developer.nvidia.com/blog/rag-101-retrieval-augmented-generation-questions-answered/" target="_blank" rel="noopener"
>https://developer.nvidia.com/blog/rag-101-retrieval-augmented-generation-questions-answered/&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/small></description></item><item><title>Demystifying Azure Private Link service</title><link>https://blog.vjirovsky.cz/demystifying-azure-private-link/</link><pubDate>Sat, 03 Feb 2024 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/demystifying-azure-private-link/</guid><description>&lt;img src="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/_post.png" alt="Featured image of post Demystifying Azure Private Link service" />&lt;p>Personally, I find Azure Private Link Service incredibly valuable and beneficial. However, I recognize that it can be confusing at first glance, especially when it&amp;rsquo;s not actively used and becomes a bit fuzzy in memory. That&amp;rsquo;s precisely why I&amp;rsquo;m writing this article – to serve as a reference for future instances when I, and others like me, encounter this challenge.&lt;/p>
&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Long story short, Azure Private Link enables &lt;strong>one-directional access&lt;/strong> to resources via a virtual network (vNET) privately. A resource published via Private Link (and its associated services) into a vNET cannot access other resources within the vNET. However, other resources within the vNET can access the published resource. A resource can be any Azure PaaS service (such as Azure KeyVault or Azure CosmosDB, a &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/private-link/availability" target="_blank" rel="noopener"
>list of supported services can be found here&lt;/a>) or any your workload represented by Azure Load Balancer. The traffic goes via the Microsoft backbone network.&lt;/p>
&lt;h2 id="private-link-resources-in-detail">Private Link resources in detail&lt;/h2>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-resource-model.png"
width="1041"
height="346"
srcset="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-resource-model_hu6ceb2163c1ce639570bbcc4be4b4b322_33329_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-resource-model_hu6ceb2163c1ce639570bbcc4be4b4b322_33329_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Data diagram of resources for Private Link service"
class="gallery-image"
data-flex-grow="300"
data-flex-basis="722px"
>&lt;/p>
&lt;h3 id="azure-private-link-service-resource">&lt;em>Azure Private Link Service&lt;/em> resource&lt;/h3>
&lt;p>This resource is specifically designed for publishing a Load Balancer via Private Link. It&amp;rsquo;s not getting involved in the setup of Azure native PaaS service, just for Load Balancer. In essence, it&amp;rsquo;s associated with the Frontend IP of the chosen Load Balancer. Each Private Link Service can only be assigned to a single Load Balancer Frontend IP - a Standard Load Balancer can host up to 8 Private Link Services. Private Link Service is a standard Azure resource that requires deployment within a resource group and location.&lt;/p>
&lt;button type="button" class="collapsible" data-target="collapsible-bicep-pls">
Example Azure Bicep code to deploy a Private Link Service &lt;/button>
&lt;div id="collapsible-bicep-pls" class="content">
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> privatelinkServiceName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> location &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> loadBalancerFrontEndIpConfigurationResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> loadbalancerId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> privatelinkService &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateLinkServices@2021-05-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: privatelinkServiceName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> location: location
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> enableProxyProtocol: false
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> loadBalancerFrontendIpConfigurations: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id: loadBalancerFrontEndIpConfigurationResourceId
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ipConfigurations: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#f1fa8c">&amp;#39;snet-provider-default-1&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateIPAllocationMethod: &lt;span style="color:#f1fa8c">&amp;#39;Dynamic&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateIPAddressVersion: &lt;span style="color:#f1fa8c">&amp;#39;IPv4&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> subnet: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id: &lt;span style="color:#50fa7b">reference&lt;/span>(loadbalancerId, &lt;span style="color:#f1fa8c">&amp;#39;2019-06-01&amp;#39;&lt;/span>).frontendIPConfigurations[0].properties.subnet.id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> primary: false
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">output&lt;/span> id &lt;span style="color:#50fa7b">string&lt;/span> = privatelinkService.id
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/div>
&lt;h3 id="azure-private-endpoint-resource">&lt;em>Azure Private Endpoint&lt;/em> resource&lt;/h3>
&lt;p>Private Endpoint is a critical component in both approaches, whether for Azure PaaS services or for Load Balancers, and it must be present at all times. It serves as an instance (interface) of the published resource within a virtual network (vNET).&lt;/p>
&lt;p>A Private Endpoint has an associated resource, namely the Azure Network Interface (NIC) resource. This NIC allocates an IP address within a virtual network (vNET).&lt;/p>
&lt;p>&lt;button type="button" class="collapsible" data-target="collapsible-bicep-paas">Example Azure Bicep code to deploy Private Endpoint for a Key Vault service&lt;/button>&lt;/p>
&lt;div id="collapsible-bicep-paas" class="content">
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> keyvaultPrivateEndpointName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> location &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> keyVaultResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> subnetResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> keyVaultPrivateEndpoint &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateEndpoints@2022-01-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: keyvaultPrivateEndpointName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> location: location
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateLinkServiceConnections: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: keyvaultPrivateEndpointName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> groupIds: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f1fa8c">&amp;#39;vault&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateLinkServiceId: keyVaultResourceId
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> subnet: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id: subnetResourceId
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/div>
&lt;p>&lt;button type="button" class="collapsible" data-target="collapsible-bicep-lb">Example Azure Bicep code to deploy Private Endpoint for a Load Balancer&lt;/button>&lt;/p>
&lt;div id="collapsible-bicep-lb" class="content">
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> privateEndpointName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> location &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> vnetName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> subnetName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> privateLinkServiceResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> privateEndpoint &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateEndpoints@2023-06-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: privateEndpointName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> location: location
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> subnet: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id: &lt;span style="color:#50fa7b">resourceId&lt;/span>(&lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/virtualNetworks/subnets&amp;#39;&lt;/span>, vnetName, subnetName)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateLinkServiceConnections: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: privateEndpointName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateLinkServiceId: privateLinkServiceResourceId
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">module&lt;/span> networkInterface &lt;span style="color:#f1fa8c">&amp;#39;_nic-nested.bicep&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#f1fa8c">&amp;#39;nested&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> nicName: &lt;span style="color:#50fa7b">last&lt;/span>(&lt;span style="color:#50fa7b">split&lt;/span>(privateEndpoint.properties.networkInterfaces[0].id, &lt;span style="color:#f1fa8c">&amp;#39;/&amp;#39;&lt;/span>))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">output&lt;/span> ipAddressInVnet &lt;span style="color:#50fa7b">string&lt;/span> = networkInterface.outputs.ip
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">// _nic-nested.bicep file:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> nicName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> networkInterface &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/networkInterfaces@2021-08-01&amp;#39;&lt;/span> existing = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: nicName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">output&lt;/span> ip &lt;span style="color:#50fa7b">string&lt;/span> = networkInterface.properties.ipConfigurations[0].properties.privateIPAddress
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/div>
&lt;h3 id="why-to-care-about-dns">Why To Care about DNS&lt;/h3>
&lt;p>The seamless utilization of an Azure PaaS resource via Private Link, without any additional configuration, relies on correct DNS configuration.&lt;/p>
&lt;p>Let&amp;rsquo;s show it on example - when you deploy a Blob Storage, it gets a hostname like: &lt;code>&amp;lt;&amp;lt;something&amp;gt;&amp;gt;.blob.core.windows.net&lt;/code>.&lt;/p>
&lt;p>Whenever you access the hostname &lt;code>myblobstorage.blob.core.windows.net&lt;/code>, it automatically resolves to the assigned public IP address of the Blob Storage service. However, with Private Endpoint, a Network Interface (NIC) representing the Blob Storage is deployed within your vNET, allocated with a private IP address from your vNET range. Therefore, you need to access the Blob Storage using this private IP.&lt;/p>
&lt;p>Consider a scenario where you have a Blob Storage with the hostname &lt;code>myblobstorage.blob.core.windows.net&lt;/code>, and public access is blocked. If you perform an HTTPS call for &lt;code>https://myblobstorage.blob.core.windows.net/&lt;/code> from a resource within the vNET:&lt;/p>
&lt;ul>
&lt;li>In a standard setup, the hostname myblobstorage.blob.core.windows.net would be translated by a public DNS server to the associated Azure public IP address (e.g., 20.60.181.xxx) and routed via the vNET&amp;rsquo;s configured internet routing method. However, since the request appears to arrive from the &amp;ldquo;public internet&amp;rdquo; rather than the vNET, the Blob Storage service would refuse it due to disabled public access.&lt;/li>
&lt;/ul>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; nslookup myblobstorage.blob.core.windows.net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Server: 168.63.129.16
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Non-authoritative answer:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Name: blob.xxxxxx.store.core.windows.net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Addresses:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 20.60.181.xxx
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>Alternatively, if you wish to use the internal IP address directly, you could perform an HTTPS call for &lt;code>https://10.0.1.2/&lt;/code> (the private IP address within the vNET). In this case, TLS certificate validation comes in the game. The request would be routed to the Blob Storage server correctly, and the server would respond with a TLS certificate issued for &lt;code>myblobstorage.blob.core.windows.net&lt;/code> hostname. However, the web client initiating the request will validate the certificate and encounter a TLS error regarding the mismatching certificate (as the certificate is not issued for &lt;code>10.0.1.2&lt;/code>). While turning off certificate validation could be an option, it introduces vulnerability to Man-in-the-Middle (MiTM) attacks, which is not advisable.&lt;/li>
&lt;/ul>
&lt;p>To tackle this challenge, the Private Link concept relies on &amp;ldquo;overwriting DNS records&amp;rdquo; within the vNET. For this purpose, Azure provides a built-in service called Azure Private DNS Zone. This service allows hosting specific DNS zones, while forwarding the rest of the requests for resolutions to standard DNS servers.&lt;/p>
&lt;p>When your vNET utilizes default Azure DNS resolvers, you simply need to create the Azure Private DNS Zone for the selected PaaS service. Corresponding records for the PaaS resource should be configured within this private DNS zone. As a result, any DNS resolution requests originating from the selected vNET will automatically be overwritten to use Private Link&amp;rsquo;s IP records.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&amp;gt; nslookup myblobstorage.blob.core.windows.net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Server: 168.63.129.16
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Non-authoritative answer:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Name: blob.xxxxxx.store.core.windows.net
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Addresses:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> 10.0.1.2
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Aliases:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> myblobstorage.privatelink.store.core.windows.net
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In case your vNET utilizes a custom DNS resolvers, you may set up a forwarder within a Azure vNET, integrated with Azure Private DNS Zone and you may configure your environment to even resolve resources via Private Link from on-premises. More details about &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/architecture/example-scenario/networking/azure-dns-private-resolver" target="_blank" rel="noopener"
>Azure DNS Private Resolver can be found here&lt;/a>.&lt;/p>
&lt;p>&lt;button type="button" class="collapsible" data-target="collapsible-bicep-dns">Example Azure Bicep code to deploy Private DNS Zone for KeyVault&lt;/button>&lt;/p>
&lt;div id="collapsible-bicep-dns" class="content">
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> privateDnsZoneName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> keyVaultResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> virtualNetworkResourceId &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> keyVaultPrivateDnsZone &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateDnsZones@2020-06-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: privateDnsZoneName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> location: &lt;span style="color:#f1fa8c">&amp;#39;global&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> privateEndpointDnsGroup &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2022-01-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#f1fa8c">&amp;#39;vault-PrivateDnsZoneGroup&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parent: keyVaultPrivateEndpoint
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateDnsZoneConfigs: [
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: privateDnsZoneName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> privateDnsZoneId: keyVaultPrivateDnsZone.id
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ]
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> keyVaultPrivateDnsZoneVnetLink &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#50fa7b">uniqueString&lt;/span>(keyVaultResourceId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> parent: keyVaultPrivateDnsZone
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> location: &lt;span style="color:#f1fa8c">&amp;#39;global&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> properties: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> registrationEnabled: false
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> virtualNetwork: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> id: virtualNetworkResourceId
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;/div>
&lt;h2 id="key-takeaways">Key Takeaways&lt;/h2>
&lt;h3 id="the-vital-role-of-vnet">The Vital Role of vNET&lt;/h3>
&lt;p>Without a virtual network (vNET), consuming a resource via Private Link isn&amp;rsquo;t possible. The vNET serves as the essential conduit facilitating communication between the published resource and the consuming resource, such as a virtual machine (VM) or Azure Function.&lt;/p>
&lt;h3 id="dns-private-zones">DNS Private Zones&lt;/h3>
&lt;p>As mentioned earlier, configuring the corresponding Azure DNS Private Zone is crucial for ensuring the smooth operation of Private Endpoint. List of DNS zones for Azure PaaS services, such as &lt;code>privatelink.vaultcore.azure.net&lt;/code> for KeyVault, can be found &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns" target="_blank" rel="noopener"
>in Microsoft Docs here&lt;/a>.&lt;/p>
&lt;p>For debugging a effective resolution of DNS records within a vNET you may utilize my &lt;a class="link" href="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/" >open-source tool AzFuncDebugger&lt;/a>.&lt;/p>
&lt;h3 id="ingress-vs-egress-traffic">Ingress vs. Egress traffic&lt;/h3>
&lt;p>Azure Private Link communication is incoming-only from a resource perspective. This means that the resource accessed via Private Link cannot initiate communication with any other resource within the virtual network. Therefore, Private Link should not be considered as an option for routing bi-directional communication between two resources within the virtual network.&lt;/p>
&lt;p>For instance, if you access a resource like KeyVault from an AppService, you could utilize publishing KeyVault via Private Endpoint (incoming traffic from KeyVault perspective). However, for outbound traffic from the AppService, you would need to employ standard vNET outbound traffic integration.&lt;/p>
&lt;h3 id="sub-resource-targets">Sub-resource Targets&lt;/h3>
&lt;p>Some Azure PaaS services consist of multiple sub-resources (subdomains), as seen with Azure Storage, which has &lt;code>privatelink.blob.core.windows.net&lt;/code> for &lt;em>Blob&lt;/em>, &lt;code>privatelink.file.core.windows.net&lt;/code> for &lt;em>File&lt;/em>, and so on.&lt;/p>
&lt;p>This implies that to consume all sub-resources of an Azure Storage resource, you would need to create multiple Private Endpoints, each with its corresponding DNS records, to ensure full functionality.&lt;/p>
&lt;p>The full list of sub-resources for Azure PaaS resources can be &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/private-link/private-endpoint-dns" target="_blank" rel="noopener"
>found here&lt;/a>.&lt;/p>
&lt;h3 id="cross-region--cross-tenant-private-links">Cross-region / Cross-tenant Private Links&lt;/h3>
&lt;p>One significant benefit of Azure Private Link worth emphasizing is its capability to publish a service from one region to another by creating a Private Endpoint in a different region. Because the traffic travels via the Microsoft backbone network, the additional latency incurred is minimal. However, it&amp;rsquo;s important to keep in mind that splitting resources of your workload across regions may decrease the resiliency of the workload, as it becomes dependent on two regions.&lt;/p>
&lt;p>Another remarkable use case is the ability for Private Link connections to be cross-tenant. As an Azure ISV provider, you can construct a highly intricate SaaS solution and expose only a load balancer to the customer. This load balancer allows the customer to privately consume the SaaS service within their vNETs via Private Link.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/saas-privatelink.png"
width="2144"
height="1041"
srcset="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/saas-privatelink_hu632df8c9ebbaa00ea51019f56904722d_229206_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/demystifying-azure-private-link/images/saas-privatelink_hu632df8c9ebbaa00ea51019f56904722d_229206_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Example of SaaS solution published to customer via Private Link"
class="gallery-image"
data-flex-grow="205"
data-flex-basis="494px"
>&lt;/p>
&lt;h3 id="multiple-endpoints">Multiple Endpoints&lt;/h3>
&lt;p>Multiple Private Endpoints can be associated with a single Private Link Service, with a maximum of up to 1000 instances. For instance, this capability allows you to deploy a single Load Balancer for your SaaS solution and share it among multiple customers.&lt;/p>
&lt;h3 id="infrastructure-as-code-support">Infrastructure as Code Support&lt;/h3>
&lt;p>All resources related to Azure Private Link have full support to be deployed via IaC tooling like Azure ARM, Bicep or Terraform. See example within this article to deploy resources via Azure Bicep.&lt;/p>
&lt;h3 id="approvals">Approvals&lt;/h3>
&lt;p>Since Azure Private Link supports the creation of cross-tenant connections, establishing a secure connection requires initiating a new Private Link connection from the consumer&amp;rsquo;s tenant and obtaining approval from the tenant that owns the Private Link Service with resources.&lt;/p>
&lt;p>If the consumer has RBAC permissions on the published resources via the Private Link Service, the connection will be automatically approved. However, if the consumer does not have such permissions, manual approval from the customer is required. In this scenario, the Azure Private Link Center interface in the Azure Portal allows customers to view all pending connections and approve or reject Private Link connections as needed.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-center.png"
width="1200"
height="917"
srcset="https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-center_hu5b0929e823e20d20c337816668275bcc_310585_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/demystifying-azure-private-link/images/privatelink-center_hu5b0929e823e20d20c337816668275bcc_310585_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Azure Private Link Center"
class="gallery-image"
data-flex-grow="130"
data-flex-basis="314px"
>&lt;/p>
&lt;h3 id="costs">Costs&lt;/h3>
&lt;p>Please keep in mind that Private Endpoints incur additional costs, priced at &lt;code>$0.01 per hour&lt;/code> for each Private Endpoint. Additionally, there are additional costs associated with the processing of inbound and outbound data transferred via Private Endpoints, which are charged per GB. It&amp;rsquo;s important to note that these costs could potentially accumulate to significant amounts in certain scenarios. On the other hand, utilizing a Private Link Service itself is free of charge.&lt;/p></description></item><item><title>Troubleshooting Azure Functions deployments with AzFuncDebugger tool</title><link>https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/</link><pubDate>Wed, 10 Jan 2024 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/</guid><description>&lt;img src="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/_post.png" alt="Featured image of post Troubleshooting Azure Functions deployments with AzFuncDebugger tool" />&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>I created the tool I&amp;rsquo;m discussing in this blog post to simplify and expedite my troubleshooting of FunctionApp deployments. With this post, I am trying to explain how to use the &lt;a class="link" href="https://github.com/vjirovsky/azFuncDebugger" target="_blank" rel="noopener"
>AzureFuncDebugger tool&lt;/a> to resolve various FunctionApp deployment issues within complex enterprise environments - perfect for when your code&amp;rsquo;s all good, but the deployment&amp;rsquo;s hitting a wall because of some config mix-ups.&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice note" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#note-notice">&lt;/use>&lt;/svg>&lt;/span>Note&lt;/p>&lt;p>AzFuncDebugger tool is available for &lt;a class="link" href="https://github.com/vjirovsky/azFuncDebugger" target="_blank" rel="noopener"
>download on GitHub&lt;/a>.&lt;/p>&lt;/div>
&lt;p>The tool provides a one-page report detailing the current configuration, the behavior of the FunctionApp, and tips for resolving any issues found. &amp;ldquo;There&amp;rsquo;s no need to run any temporary VM for troubleshooting or to install SDKs and compile code; simply switch the runtime to .NET and load the package directly from URL on GitHub.&lt;/p>
&lt;img src="https://github.com/vjirovsky/azFuncDebugger/raw/master/docs/intro.gif" alt="AzFuncDebugger tool" style="border: 1px solid silver;">
&lt;h2 id="selected-content-storage-not-working-for-functionapp">Selected Content Storage not working for FunctionApp&lt;/h2>
&lt;p>In certain configurations, depending on the chosen plan and operating system, Azure Functions may utilize a File Share (a feature within Azure Storage Accounts) to store the source code for Functions (this storage is referred to as Content Storage throughout this article). Given that Azure Functions is built on the Azure AppService, it also supports package deployment, which can be done through a local ZIP file or a remote URL - and with package deployment, Content Storage configuration gets ignored.&lt;/p>
&lt;p>If you&amp;rsquo;ve implemented the security enhancements from my &lt;a class="link" href="https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/" target="_blank" rel="noopener"
>article on securing internal Azure Functions storage with PrivateLink&lt;/a>, you might find the setup quite intricate. Regrettably, any blockage to the Content Storage access can cause your Function App to become unresponsive, resulting in 50x server errors and timeouts after a certain duration. Debugging can be particularly taxing because each change requires a waiting period for the new configuration to be saved, the Function App to restart, and the new configuration to load.&lt;/p>
&lt;p>The package deployment method allows for the straightforward deployment of the open-source AzFuncDebugger tool directly from GitHub into a FunctionApp that&amp;rsquo;s encountering issues with:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>WEBSITE_RUN_FROM_PACKAGE = https://github.com/vjirovsky/azFuncDebugger/releases/download/v0.7/pre-release.zip
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This ensures the tool loads successfully without timeouts, and it activates the simulator feature for accessing the Content Storage within the given FunctionApp. By using the &lt;code>TEST_CCONNECTIONSTRING&lt;/code> and &lt;code>TEST_CSHARE&lt;/code> configuration parameters, you can verify the accessibility of the storage, even in &amp;ldquo;broken&amp;rdquo; FunctionApp.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-1.png"
width="1342"
height="806"
srcset="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-1_hu80bff6b175b734c7888220ab7026bea2_66614_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-1_hu80bff6b175b734c7888220ab7026bea2_66614_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Example how &amp;lsquo;Content Storage Access&amp;rsquo; test looks"
class="gallery-image"
data-flex-grow="166"
data-flex-basis="399px"
>&lt;/p>
&lt;h3 id="functionapp-has-no-network-visibility-to-content-storage">FunctionApp has no network visibility to Content Storage&lt;/h3>
&lt;p>When a Content Storage (the Storage Account for the File Share) is set to block public access (which is recommended) and is made available only within a vNET via PrivateLink, the following issues commonly occur:&lt;/p>
&lt;h3 id="file-share-storage-is-not-resolved-with-internal-ip">File Share storage is not resolved with internal IP&lt;/h3>
&lt;p>The Private Endpoint linked to the PrivateLink service is assigned an internal IP from within the designated vNET—for instance, &lt;em>mystorageaccount.blob.core.windows.net gets resolved to 10.0.1.5&lt;/em>. Private Endpoint automatically generates a record in the Azure DNS Private Zone, superseding the original public DNS record for the service. This modified record is only applied within the confines of the vNET.&lt;/p>
&lt;p>If the FunctionApp fails to recognize the internal IP record, you should verify the DNS server configuration by examining the &lt;code>WEBSITE_DNS_SERVER&lt;/code> parameter to ensure it&amp;rsquo;s correctly set. Additionally, the boolean parameter &lt;code>WEBSITE_CONTENTOVERVNET&lt;/code> must be enabled, as it dictates to connect to the Content Storage via the vNET instead of through a standard public internet outbound connection.&lt;/p>
&lt;p>Additionally, the Simulator feature of the tool is pretty handy. It lets you see the IP address that&amp;rsquo;s being resolved and gives you a glimpse into the effective networking configuration. This kind of debug info is a lifesaver for figuring out whether the trouble is with the DNS setup or something in the routing - like maybe a network security group or a firewall playing gatekeeper a bit too strictly.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-simulator.png"
width="1266"
height="826"
srcset="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-simulator_hu4606340a315d89c9326a068bf563e667_73944_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/content-storage-access-simulator_hu4606340a315d89c9326a068bf563e667_73944_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Example how &amp;lsquo;Content Storage Access&amp;rsquo; simulator test looks"
class="gallery-image"
data-flex-grow="153"
data-flex-basis="367px"
>&lt;/p>
&lt;h3 id="other-common-issues-with-content-storage">Other common issues with Content Storage&lt;/h3>
&lt;p>When you&amp;rsquo;ve double-checked and confirmed that the DNS and network visibility are on point, the issue might be with credentials. That&amp;rsquo;s where the Simulator feature comes in really handy again. It&amp;rsquo;ll show you if the stored credentials are doing their job and even give you a list of the detected Function source codes sitting in the content storage.&lt;/p>
&lt;h2 id="the-function-doesnt-get-loaded">The Function doesn&amp;rsquo;t get loaded&lt;/h2>
&lt;p>The package deployment has higher priority. If you notice that a new Function isn&amp;rsquo;t loading or updating, it&amp;rsquo;s a good idea to check if the &lt;code>WEBSITE_RUN_FROM_PACKAGE&lt;/code> configuration parameter is active. This parameter might be preventing the loading of Functions directly from the Content Storage. The tool provides insights into the active configuration of the code location for your FunctionApp.&lt;/p>
&lt;h2 id="tls-inspection-untrusted-internal-ca">TLS inspection (untrusted internal CA)&lt;/h2>
&lt;p>When resource accessed from FunctionApp is routed throught enterprise network, there could be some network appliance (e.g. Azure Firewall or Palo Alto), to inspect the encrypted traffic and gets re-signed by internal CA. With variable &lt;code>TEST_HTTPCLIENT_GET_URL&lt;/code> you can perform a HTTP(s) call to any resource and see details about HTTP request and response, including certs details.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/httpclient-test.png"
width="1231"
height="592"
srcset="https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/httpclient-test_hu425130385c3cdaf5c0250a01be594d5f_52823_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/troubleshooting-azure-functions-with-azfuncdebugger/images/httpclient-test_hu425130385c3cdaf5c0250a01be594d5f_52823_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Test to access any resources in vNET"
class="gallery-image"
data-flex-grow="207"
data-flex-basis="499px"
>&lt;/p>
&lt;h2 id="enterprise-custom-dns-server">Enterprise custom DNS server&lt;/h2>
&lt;p>Many corporate Azure landing zones in enterprise settings use their own on-premises custom DNS servers to resolve addresses, even within Azure vNETs. For accessing resources over PrivateLink, these DNS zones need to be delegated to the Azure DNS Private Zone. This can be done, for example, through the &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/dns/dns-private-resolver-overview" target="_blank" rel="noopener"
>Azure DNS Private Resolver service&lt;/a>. Alternatively, a DNS record for these resources needs to be manually created in the on-premises DNS servers. The debugger tool shows you which DNS servers are actually being used and with &lt;code>TEST_DNS_RESOLVE_DOMAIN&lt;/code> variable enables to test DNS record of a specific resource that&amp;rsquo;s published over PrivateLink.&lt;/p>
&lt;h2 id="whitelisted-ip-traffic">Whitelisted IP traffic&lt;/h2>
&lt;p>In scenarios where a FunctionApp is connected to a vNET and needs to access certain resources through this vNET (like when a resource only allows traffic from a specific vNET), it&amp;rsquo;s crucial to set up the outbound traffic routing correctly. By default, FunctionApp routes traffic via the public internet. To redirect egress traffic through the vNET, you need to set the &lt;code>WEBSITE_VNET_ROUTE_ALL&lt;/code> configuration parameter or the AppService setting &lt;code>vnetRouteAllEnabled&lt;/code> to &lt;code>true&lt;/code>.&lt;/p>
&lt;p>The tool can aid in troubleshooting by showing the vNET assigned to the FunctionApp, the IP address assigned to the FunctionApp within that vNET, and the public IP address from which the outbound request was actually sent by the FunctionApp to an external service &lt;a class="link" href="https://www.ipify.org" target="_blank" rel="noopener"
>Ipify.org&lt;/a>.&lt;/p>
&lt;h2 id="wrong-time-zone-including-cron-execution">Wrong time zone (including CRON execution)&lt;/h2>
&lt;p>If your code depends on local time, like running daily tasks at a specific time or performing time-based operations, setting the timezone for your FunctionApp runtime is essential. To specify a timezone for datetime operations, you need to configure &lt;code>WEBSITE_TIME_ZONE&lt;/code> parameter. It&amp;rsquo;s important to note that the format for specifying timezones differs between Windows and Linux runtimes. This same principle applies to other services based on AppService, such as Web Apps or API Apps.&lt;/p>
&lt;p>The tool will show you the current time with the currently selected timezone, and it will issue a warning if the timezone format doesn&amp;rsquo;t match what&amp;rsquo;s expected for the chosen operating system.&lt;/p>
&lt;h2 id="conclusion">Conclusion&lt;/h2>
&lt;p>I hope you find the tool helpful. With a few tweaks, I believe it could, with a few adjustments, be adapted to support general AppService deployments if necessary.&lt;br>&lt;br>
In case of any issue, please &lt;a class="link" href="https://github.com/vjirovsky/azFuncDebugger/issues" target="_blank" rel="noopener"
>report on GitHub&lt;/a>.&lt;/p></description></item><item><title>Securing Azure Managed Applications Resources</title><link>https://blog.vjirovsky.cz/secure-azure-managed-applications/</link><pubDate>Sat, 14 Oct 2023 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/secure-azure-managed-applications/</guid><description>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/_post.png" alt="Featured image of post Securing Azure Managed Applications Resources" />&lt;h2 id="introduction">Introduction&lt;/h2>
&lt;p>Deploying an Azure Marketplace offer is a powerful way to implement a cloud solution provided by Microsoft&amp;rsquo;s partners into your Azure subscription, while outsourcing the development to the publisher of the given offer. An offer can be anything from simple solution built on top of Virtual Machines or really complex solutions, utilizing AKS containers and other Azure native services - all these services will be deployed with a purchased offer.&lt;/p>
&lt;p>There are two models of a solution in Azure Marketplace:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Solution template&lt;/strong> - this plan type enables to deploy a non-transactable solution (Microsoft doesn&amp;rsquo;t facilitate the payment), where the customer will manage the solution after deployment themselves&lt;/li>
&lt;li>&lt;strong>Managed Application&lt;/strong> - solutions built as Managed Applications enables to deploy a transactable solution, where either the offer&amp;rsquo;s publisher or the customer manages the solution after deployment&lt;/li>
&lt;/ol>
&lt;p>For publisher is there a benefit, that their solution will be displayed in Azure Marketplace, what can help them to accquire new customers.&lt;/p>
&lt;h2 id="managed-application---introduction">Managed Application - Introduction&lt;/h2>
&lt;p>When you deploy a solution based on the Managed Application type, it appears as another resource within your Azure resource group.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-in-portal.png"
width="1544"
height="583"
srcset="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-in-portal_huecbcc32bc8ca7824d46be3e002b88462_52905_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-in-portal_huecbcc32bc8ca7824d46be3e002b88462_52905_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Managed Application resource representing a solution deployed from Azure Marketplace"
class="gallery-image"
data-flex-grow="264"
data-flex-basis="635px"
>&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-resource.png"
width="1892"
height="938"
srcset="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-resource_hu2b4870560a3fe123be986c0e5a3ae7ae_112919_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-resource_hu2b4870560a3fe123be986c0e5a3ae7ae_112919_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Representation of an Managed Application resource - showcasing the deployment of Pure Cloud Block Store solution from Azure Marketplace"
class="gallery-image"
data-flex-grow="201"
data-flex-basis="484px"
>&lt;/p>
&lt;p>With this resource, a new managed resource group is automatically created within your subscription, where all the necessary resources of solution are deployed.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-mrg.png"
width="1893"
height="938"
srcset="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-mrg_hud81a646b173d085492d417add3c6a295_163901_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/secure-azure-managed-applications/images/managed-app-mrg_hud81a646b173d085492d417add3c6a295_163901_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="An image illustrating the managed resource group of an Azure Managed Application containing all of its child resources"
class="gallery-image"
data-flex-grow="201"
data-flex-basis="484px"
>&lt;/p>
&lt;p>And when you delete the Managed Application resource, Azure Resource Manager will automatically delete the related managed resource group along with all the resources contained within it.&lt;/p>
&lt;h2 id="permissions">Permissions&lt;/h2>
&lt;p>A valuable benefit of the Managed Application deployment model is that it allows the publisher to securely access the solution&amp;rsquo;s resources (within your subscription) using the standard Azure RBAC model. This enables the publisher to monitor, upgrade, or perform maintenance on the solution as needed. The publisher will automatically be granted a &lt;em>Contributor&lt;/em> or &lt;em>Owner&lt;/em> role for the managed resource group (although this can be opted-out if desired).&lt;/p>
&lt;p>It&amp;rsquo;s a double-edged tool; while it allows customers to out source solution management to the publisher and simply consume it as a service, some customers opt to limit access to reduce their infrastructure&amp;rsquo;s attack surface. And opting out of management access completely revokes all privileges for the publisher, including read access for monitoring.&lt;/p>
&lt;h2 id="just-in-time-access-jit">Just-in-time Access (JIT)&lt;/h2>
&lt;p>In response to this challenge, Microsoft has introduced a new feature known as Just-in-time (JIT) access. This feature provides read-only access to the customer&amp;rsquo;s resources. When necessary, the publisher can request elevated permissions for these resources for a specific time period. The request requires approval from a responsible person on the customer&amp;rsquo;s side before access is granted.&lt;/p>
&lt;p>While this feature effectively addresses the challenge, it has one drawback: it necessitates the use of a Microsoft Entra ID (former Azure Active Directory) P2 license on the customer&amp;rsquo;s side.&lt;/p>
&lt;h2 id="read-only-access">Read-only Access&lt;/h2>
&lt;p>For customers who lack a P2 License but still need to grant publishers elevated access while maintaining read-only access, there is one available option.&lt;/p>
&lt;p>This concept hinges on utilizing the JIT feature, even in scenarios where the customer does not have a P2 License. License detection for JIT occurs during the creation of a JIT access request. If the required license is not present, the request will fail, but the deployment configuration remains unchanged.&lt;/p>
&lt;p>As a result, you have the ability to restrict the publisher&amp;rsquo;s access to solutions that are built on Managed Applications, such as Pure &lt;a class="link" href="https://www.purestorage.com/products/cloud-block-storage/cbs.html" target="_blank" rel="noopener"
>Cloud Block Store&lt;/a>.&lt;/p>
&lt;p>In this concept, the customer deploys a Managed Application with JIT access &lt;code>enabled&lt;/code> and set &lt;em>JIT Approval mode&lt;/em> to &lt;code>Manual&lt;/code>.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-1.png"
width="960"
height="549"
srcset="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-1_hu8661c7f4cba353f09dcb33aeddf37803_32518_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-1_hu8661c7f4cba353f09dcb33aeddf37803_32518_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="An image illustrating the JIT access configuration during Pure Cloud Block Store from Azure Marketplace deployment"
class="gallery-image"
data-flex-grow="174"
data-flex-basis="419px"
>&lt;/p>
&lt;p>The &lt;em>Approvers&lt;/em> list cannot be empty, so you need to add at least one responsible security group or user.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-2.png"
width="1132"
height="632"
srcset="https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-2_hudaee89297b13b48511df8c50166a5dec_40546_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/secure-azure-managed-applications/images/jit-config-2_hudaee89297b13b48511df8c50166a5dec_40546_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="An image illustrating the JIT access approvers configuration during Managed Application deployment"
class="gallery-image"
data-flex-grow="179"
data-flex-basis="429px"
>&lt;/p>
&lt;p>Thanks to the absence of an Microsoft Entra ID license, any attempt by the publisher to gain elevated access will fail, but the publisher still retains desired read-only access, and as a customer, you have more control over your infrastructure.&lt;/p>
&lt;p>For customers who have the P2 License, achieving read-only access for publishers is straightforward. Simply set the JIT Approval mode to &lt;code>Manual&lt;/code> and refrain from approving any requests.&lt;/p></description></item><item><title>8 tricks for parameters in Azure Bicep deployments</title><link>https://blog.vjirovsky.cz/bicep-parameters-tricks/</link><pubDate>Tue, 25 Jul 2023 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/bicep-parameters-tricks/</guid><description>&lt;h2 id="use-bicepparam-file-format">Use .bicepparam File Format&lt;/h2>
&lt;p>The Azure Bicep has introduced support for a new file format called &lt;code>.bicepparam&lt;/code> starting from version &lt;code>v0.18.4&lt;/code>. This format is used to pass parameters into Bicep templates.&lt;/p>
&lt;p>The syntax of this new file format is simpler and is more readable than the previous JSON schema type used for ARM deployments.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">//enables IntelliCode in VSCode to suggest and validate parameters against bicep template&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>using &lt;span style="color:#f1fa8c">&amp;#39;./file.bicep&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">//parameters&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> location = &lt;span style="color:#f1fa8c">&amp;#39;westeurope&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> param1 = &lt;span style="color:#f1fa8c">&amp;#39;value1&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> param2 = &lt;span style="color:#f1fa8c">&amp;#39;value2&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="use-sys-functions-in-parameters-files">Use sys. Functions In Parameters Files&lt;/h2>
&lt;p>The &lt;code>.bicepparam&lt;/code> file also allows the use of functions from the &lt;code>sys&lt;/code> namespace, such as &lt;code>concat()&lt;/code>, &lt;code>first()&lt;/code>, and &lt;code>uniqueString()&lt;/code>. These functions can be used to manipulate and process the parameters passed into Bicep templates.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> rgName = &lt;span style="color:#f1fa8c">&amp;#39;my-awesome-rg&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> uniqueSuffix = &lt;span style="color:#f1fa8c">&amp;#39;${sys.uniqueString(rgName)}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> storageName = &lt;span style="color:#f1fa8c">&amp;#39;storage${sys.uniqueString(uniqueSuffix)}&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This can help you to simplify your Bicep templates and remove additional parameter transformations inside the template.&lt;/p>
&lt;h2 id="consolidate-parameters-in-one-place">Consolidate Parameters In One Place&lt;/h2>
&lt;p>One property I find frustrating when deploying via Bicep/ARM is that certain parameters, such as &lt;code>rgName&lt;/code> and &lt;code>location&lt;/code>, must be in some cases specified as extra arguments for the &lt;code>az&lt;/code> command during execution, while the rest of the parameters are contained in a &lt;code>.bicepparam&lt;/code> or &lt;code>.json&lt;/code> parameters file.&lt;/p>
&lt;p>Instead of that, I do prefer to have all customization options for the current deployment in the parameter file in one place and make no change to script executing &lt;code>az&lt;/code> command.&lt;/p>
&lt;h3 id="in-a-arm-json-parameters-files">In a ARM JSON Parameters Files&lt;/h3>
&lt;p>My trick to consolidate ARM JSON parameters into one place is based on reading the JSON file before the deployment command, and storing the value into variable in script.&lt;/p>
&lt;h4 id="for-scripts-based-on-powershell">For Scripts Based On PowerShell:&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-powershell" data-lang="powershell">&lt;span style="display:flex;">&lt;span>(&lt;span style="color:#8be9fd;font-style:italic">Get-Content&lt;/span> mytemplate.parameters.json -Raw | &lt;span style="color:#8be9fd;font-style:italic">ConvertFrom-Json&lt;/span>).parameters.resourceGroupName
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="for-scripts-based-on-az-cli-with-bash">For Scripts Based On &lt;code>az-cli&lt;/code> (with &lt;code>bash&lt;/code>):&lt;/h4>
&lt;p>I do recommend to use &lt;a class="link" href="https://pypi.org/project/jq/" target="_blank" rel="noopener"
>&lt;code>jq&lt;/code> tool&lt;/a> for reading a JSON file from command line.
You can install the &lt;code>jq&lt;/code> via multiple ways, one of them is via &lt;code>pip&lt;/code>, on Ubuntu as official package:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>sudo apt-get update &lt;span style="color:#ff79c6">&amp;amp;&amp;amp;&lt;/span> sudo apt-get install jq
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>on MacOS via Homebrew:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>brew install jq
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>When you have &lt;code>jq&lt;/code> command ready, you can access the parameter easily:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>jq .parameters.resourceGroupName.value mytemplate.parameters.json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="in-bicepparam-files">In .bicepparam Files&lt;/h3>
&lt;p>The &lt;code>.bicepparam&lt;/code> is a new file format, and as such, it can be more challenging to parse the file using other tools.
One of the option could be to implement custom parser, but that&amp;rsquo;s too much complicated for such small task.&lt;/p>
&lt;p>As the simpliest way I do prefer to install a full &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install" target="_blank" rel="noopener"
>Bicep cli&lt;/a>.&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice note" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#note-notice">&lt;/use>&lt;/svg>&lt;/span>Note&lt;/p>&lt;p>The Bicep cli, included in &lt;code>az-cli&lt;/code> with commands &lt;code>az bicep&lt;/code>, doesn&amp;rsquo;t contain all Bicep&amp;rsquo;s commands as in full &lt;a class="link" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/install" target="_blank" rel="noopener"
>Bicep cli&lt;/a>.&lt;/p>&lt;/div>
&lt;p>With this full Bicep cli installed, you can compile input &lt;code>.bicepparam&lt;/code> into ARM JSON format, which can be easily parsed with the &lt;code>jq&lt;/code> tool.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">paramsInJson&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">`&lt;/span>bicep build-params &lt;span style="color:#8be9fd;font-style:italic">$parametersFilename&lt;/span> --stdout | jq -r &lt;span style="color:#f1fa8c">&amp;#34;.parametersJson&amp;#34;&lt;/span>&lt;span style="color:#f1fa8c">`&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resourceGroupName&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">`&lt;/span>&lt;span style="color:#8be9fd;font-style:italic">echo&lt;/span> &lt;span style="color:#8be9fd;font-style:italic">$paramsInJson&lt;/span> | jq -r &lt;span style="color:#f1fa8c">&amp;#34;.parameters.resourceGroupName.value&amp;#34;&lt;/span>&lt;span style="color:#f1fa8c">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="pass-a-parameter-with-dynamic-value">Pass A Parameter With Dynamic Value&lt;/h2>
&lt;p>Occasionally, it may be necessary to include a dynamic variable as a parameter during Bicep deployment, which can be sourced from an environment variable or retrieved as output from another REST API. But how to pass into the template?&lt;/p>
&lt;h3 id="arm-json-parameters">ARM JSON Parameters&lt;/h3>
&lt;p>When you use a Bicep deployment with the original ARM JSON parameters file format, there is a option to override the parameter in the parameters file, with passing addditional &lt;code>--parameters&lt;/code> argument in &lt;code>key=value&lt;/code> format.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>az deployment group create &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --name myDeployment &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --resource-group rg-bicep &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --template-file main.bicep &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --parameters @main.parameters.json &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --parameters &lt;span style="color:#8be9fd;font-style:italic">myParameter&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">&amp;#39;$bashVariable&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>myParameter&lt;/code> value in JSON parameters files will be overwritten with one in argument.&lt;/p>
&lt;div class="notice note" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#note-notice">&lt;/use>&lt;/svg>&lt;/span>Note&lt;/p>&lt;p>You can notice the parameters filename starts with &lt;code>@&lt;/code>, in .bicepparam is without this character.&lt;/p>&lt;/div>
&lt;h3 id="bicep-bicepparam-files">Bicep .bicepparam Files&lt;/h3>
&lt;p>At the time this article was written, the current version of &lt;code>az-cli&lt;/code> does not support the use of a &lt;code>.bicepparam&lt;/code> file with this trick. The way the &lt;code>.bicepparam&lt;/code> file is parsed is different, and attempting to use the &lt;code>--parameters&lt;/code> argument results in an error:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ az deployment group create &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --name myDeployment &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --resource-group rg-bicep &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --template-file main.bicep &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --parameters main.bicepparam &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> --parameters &lt;span style="color:#8be9fd;font-style:italic">myParameter&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">&amp;#39;$bashVariable&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ERROR: Can not use --parameters argument more than once when using a .bicepparam file
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>According to some comments found in the Azure Bicep project on GitHub, there are plans to change the behavior and enable overwriting parameters using the syntax &lt;code>--parameters main.bicepparam param1=localvalue&lt;/code>, but there is no timeline for this change.&lt;/p>
&lt;p>My current workaround around this limitation is based again on the &lt;code>bicep build-params&lt;/code> command:&lt;/p>
&lt;ol>
&lt;li>compile the &lt;code>.bicepparam&lt;/code> file into temp JSON file&lt;/li>
&lt;li>modify the temp JSON file (replace)&lt;/li>
&lt;li>provide the temp JSON file as a parameter file to &lt;code>az-cli&lt;/code>&lt;/li>
&lt;li>delete the temp JSON&lt;/li>
&lt;/ol>
&lt;h2 id="pass-current-ip-address">Pass Current IP Address&lt;/h2>
&lt;p>A specific scenario I want to demonstrate is how to restrict access to my current public IP address. Although I am not a fan of using IP-based restrictions, I use them often in my temporary PoCs.&lt;/p>
&lt;p>To achieve this, I use the above trick in combination with retrieving my IP address from an external service such as &lt;code>http://ifconfig.me&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">myIpAddress&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">`&lt;/span>curl ifconfig.me 2&amp;gt; /dev/null&lt;span style="color:#f1fa8c">`&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In the &lt;code>.bicepparam&lt;/code> file, I create a unique placeholder value (e.g., &lt;code>$myIpAddress&lt;/code>), compile into into ARM parameters JSON file and replace it there using the traditional &lt;code>sed&lt;/code> command, before it gets processed by deployment command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">(&lt;/span>...&lt;span style="color:#ff79c6">)&lt;/span> | sed &lt;span style="color:#f1fa8c">&amp;#34;s/\$myIpAddress/&lt;/span>&lt;span style="color:#8be9fd;font-style:italic">$myIpAddress&lt;/span>&lt;span style="color:#f1fa8c">/&amp;#34;&lt;/span> &amp;gt; temp.json
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="use-secure-attribute">Use secure() Attribute&lt;/h2>
&lt;p>Do not forget to annotate all sensitive Bicep parameters with attribute &lt;code>secure()&lt;/code>. This decorator prevents to include the value of the parameter into logs and deployment history.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>@secure()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> vmAdminPassword &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Also with this attribute applied, you can integrate Azure Key Vault and store these secrets within Key Vault, and access them during the deployment from Bicep template directly.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> keyVault &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.KeyVault/vaults@2023-02-01&amp;#39;&lt;/span> existing = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: kvName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> scope: &lt;span style="color:#50fa7b">resourceGroup&lt;/span>(subscriptionId, kvResourceGroup )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">module&lt;/span> sql &lt;span style="color:#f1fa8c">&amp;#39;./sql.bicep&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> name: &lt;span style="color:#f1fa8c">&amp;#39;deploySQL&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> params: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> sqlServerName: sqlServerName
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> adminLogin: adminLogin
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> adminPassword: keyVault.&lt;span style="color:#50fa7b">getSecret&lt;/span>(&lt;span style="color:#f1fa8c">&amp;#39;vmAdminPassword&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">// sql.bicep:&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@secure()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> adminPassword &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">resource&lt;/span> sqlServer &lt;span style="color:#f1fa8c">&amp;#39;Microsoft.Sql/servers@2022-08-01-preview&amp;#39;&lt;/span> = {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="markdown-description">Markdown Description&lt;/h2>
&lt;p>When working with a complex Bicep template, it can be useful to include more complex instructions on how to use specific parameters. This can be done by adding a user guide within the &lt;code>@description()&lt;/code> decorator in Markdown language, which will then be displayed in IntelliSense completion. This can help users understand how to use the parameter and make it easier for them to work with the template.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@description(&lt;span style="color:#f1fa8c">&amp;#39;&amp;#39;&lt;/span>&amp;#39;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Here can be a &lt;span style="color:#ff79c6">*&lt;/span>markdown-enabled&lt;span style="color:#ff79c6">*&lt;/span> text, displayed &lt;span style="color:#ff79c6">in&lt;/span> &lt;span style="color:#ff79c6">**&lt;/span>IntelliSense&lt;span style="color:#ff79c6">**&lt;/span> &lt;span style="color:#ff79c6">in&lt;/span> VSCode
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1. something A
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1. something B
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&amp;#39;&amp;#39;&lt;/span>&amp;#39;)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> subnetName &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://blog.vjirovsky.cz/bicep-parameters-tricks/images/vscode-bicep-markdown.png"
width="1396"
height="343"
srcset="https://blog.vjirovsky.cz/bicep-parameters-tricks/images/vscode-bicep-markdown_hue281549c463f34496cdd8cb6f39eea9b_30365_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/bicep-parameters-tricks/images/vscode-bicep-markdown_hue281549c463f34496cdd8cb6f39eea9b_30365_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="example of Bicep parameter with markdown syntax in VSCode"
class="gallery-image"
data-flex-grow="406"
data-flex-basis="976px"
>&lt;/p>
&lt;h2 id="using-a-single-parameter-file-for-multiple-bicep-templates">Using a Single Parameter File for Multiple Bicep Templates&lt;/h2>
&lt;p>If you have a complex project that consists of multiple Bicep templates, you may need to use the same parameters across several templates. For example, you may need to use the same common &lt;code>Resource Group&lt;/code> name in multiple templates.&lt;/p>
&lt;p>However, in Bicep, the parameter file must only contain parameters that are relevant to the specific template it is associated with. This means that if a parameter is only used in &lt;code>templateA.bicep&lt;/code>, it cannot be included in the parameter file for &lt;code>templateB.bicep&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>Error BCP259: The parameter &amp;#34;templateBParameter&amp;#34; is assigned in the params file without being declared in the Bicep file.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>One workaround for this issue is to include the extra parameters in the other template, but assign them default values and provide an explanation in the &lt;code>description()&lt;/code> decorator, that these parameters are not used in that particular template.&lt;/p>
&lt;p>&lt;em>Example:&lt;/em>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bicep" data-lang="bicep">&lt;span style="display:flex;">&lt;span>@description(&lt;span style="color:#f1fa8c">&amp;#39;common parameter used in both templates&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> commonParameterA &lt;span style="color:#50fa7b">string&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>@description(&lt;span style="color:#f1fa8c">&amp;#39;NOT USED in this template&amp;#39;&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd;font-style:italic">param&lt;/span> templateBParameter &lt;span style="color:#50fa7b">string&lt;/span> = &lt;span style="color:#f1fa8c">&amp;#39;&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This allows you to include the same parameters in multiple templates without causing any issues and minimizes confusion of another user of given template.&lt;/p></description></item><item><title>Cloud Cold Starts - How Can I Secure My Azure Infrastructure Part #1</title><link>https://blog.vjirovsky.cz/cloud-cold-starts-how-can-i-secure-my-azure-infrastructure-part-1/</link><pubDate>Wed, 30 Mar 2022 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/cloud-cold-starts-how-can-i-secure-my-azure-infrastructure-part-1/</guid><description>&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>In this series of &lt;a class="link" href="../categories/cloud-cold-starts/" >Cloud Cold Starts articles&lt;/a>, I’d like to address questions (at least an introduction to the topic) that may come to mind when starting out with the cloud.&lt;/p>&lt;/div>
&lt;h2 id="encryption-at-rest">Encryption At Rest&lt;/h2>
&lt;p>When we talk about encryption at rest, we are talking about encrypting data that is only stored and not currently being transferred or processed in memory – if the data is physically stored on the storage device (disk). We want to prevent anyone from connecting to the disk and starting to read the data without our permission – so we have the data encrypted with an encryption key as soon as it is written to the storage and decrypted as soon as it is read from the storage. The encryption key is stored elsewhere than the data. Same purpose as the BitLocker you have on your laptop. Without the encryption key, an attacker would not be able to read the data (within a reasonable time, unless there is a flaw in the encryption mechanism).&lt;/p>
&lt;p>Azure recognizes two modes of encryption – &lt;em>client-side encryption&lt;/em> (encryption is done outside of Azure and Azure works with encrypted data it doesn’t understand) and &lt;em>server-side encryption&lt;/em>, where the encryption is managed within Azure.&lt;/p>
&lt;h3 id="server-side-encryption">Server-side Encryption&lt;/h3>
&lt;p>Server-side encryption encrypts data automatically during storage on disk. But what encryption key is used for this? Microsoft offers two options, its &lt;em>platform-managed keys&lt;/em> and &lt;em>customer-managed keys&lt;/em> (&lt;em>CMK&lt;/em>, sometimes referred to as &lt;em>BYOK – Bring Your Own Key&lt;/em>).&lt;/p>
&lt;p>Platform keys are managed end-to-end by Azure and you don’t have to worry about anything – all the necessary key-related activities – generation, rotation, etc. are delegated to Microsoft. This solution is suitable for most workloads, but some workloads handling sensitive data must implement CMK by regulation. This increased security brings all key management tasks to the customer.&lt;/p>
&lt;p>What’s really good is that Azure has security in its DNA and encrypts with platform-managed keys the data for almost all services offered – from Azure Storage (including disks for virtual machines), Azure SQL Database (search for TDE feature), or perhaps Azure Database for MySQL and more. There is no additional charge for encryption and encryption cannot be turned off, it is enforced by default.&lt;/p>
&lt;p>More info about Encryption at the rest can be &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/security/fundamentals/encryption-atrest" target="_blank" rel="noopener"
>found here&lt;/a>.&lt;/p>
&lt;h2 id="encryption-in-transit">Encryption In Transit&lt;/h2>
&lt;p>Usually we need to exchange data between some applications or send it to the user – this is another place where we need to be careful about data protection. Sometimes this area is called &lt;em>&amp;ldquo;data in motion&amp;rdquo;&lt;/em>. When communicating over an unencrypted channel, any attacker can listen and/or modify the content of the data along the way. This is why, in 2022, we use the secured HTTPS protocol (via TLS protocol) to access any website (and our browser verifies the website’s certificate, otherwise it will drop the connection).&lt;/p>
&lt;p>To manage any resource in Azure (via the REST API or portal), Azure enforces a connection via an encrypted connection.&lt;/p>
&lt;p>Almost every Azure service provides the same level of security (if the protocol of given service supports it), so you can consume a service over a secure connection.&lt;/p>
&lt;p>Some services allow you to use also unsecured versions of protocols, so make sure you disable them.
A great example of this is the Azure Web App, where HTTP out of the box is enabled. To disable unencrypted connections, just enable the &lt;em>&amp;ldquo;HTTPS Only&amp;rdquo;&lt;/em> option in the &lt;em>&amp;ldquo;TLS/SSL settings&amp;rdquo;&lt;/em> section of the Web App.&lt;/p>
&lt;p>I also recommend setting the minimum TLS version to &lt;strong>1.2&lt;/strong> (as of March 2022, Azure does not yet support the higher TLS version &lt;strong>1.3&lt;/strong>).&lt;/p>
&lt;p>More details about Azure encryption in transit can be &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/security/fundamentals/encryption-overview#data-link-layer-encryption-in-azure" target="_blank" rel="noopener"
>found here&lt;/a>.&lt;/p>
&lt;h2 id="least-privilege-strategy">Least Privilege Strategy&lt;/h2>
&lt;p>Another important level of security for Azure resources is restricting permissions for users, administrators, service accounts – for everybody. Give them only the permissions they need to do their jobs, nothing more.&lt;/p>
&lt;div class="notice tip" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#tip-notice">&lt;/use>&lt;/svg>&lt;/span>Tip&lt;/p>&lt;p>&lt;em>Do you have a service account, that configures the DNS zone only?&lt;/em> &lt;br>&lt;br>&lt;/p>
&lt;p>That service account certainly doesn’t need to have a subscription owner permissions.&lt;/p>&lt;/div>
&lt;p>Azure has a lot of built-in roles that you can assign a user to a given resource, &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles" target="_blank" rel="noopener"
>see here&lt;/a>.
&lt;br>&lt;br>
&lt;br>&lt;/p>
&lt;p>&lt;small>In next article I would like to cover networking isolation and key management.
&lt;br>&lt;/p>
&lt;p>&lt;em>Do you like this format of Cloud Cold Starts articles? Do you have any topic you would like to cover here? Please share your idea in the comments!&lt;/em>
&lt;/small>&lt;/p></description></item><item><title>Protect Azure Function’s internal Storage via PrivateLink</title><link>https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/</link><pubDate>Wed, 29 Dec 2021 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/</guid><description>&lt;h2 id="what-data-are-stored-inside-storage-account">What data are stored inside Storage Account&lt;/h2>
&lt;p>Azure Function App uses two subservices blob and files in the Storage Account.
It always creates a two blob containers, called &lt;code>azure-webjobs-hosts&lt;/code> and &lt;code>azure-webjobs-secrets&lt;/code>.&lt;/p>
&lt;p>In the &lt;code>azure-webjobs-hosts&lt;/code> container – there are metadata files used for logging purposes, locks etc.&lt;/p>
&lt;p>In the container &lt;code>azure-webjobs-secrets&lt;/code> you can find really interesting data:&lt;/p>
&lt;ol>
&lt;li>every function has JSON file&lt;/li>
&lt;li>there is &lt;code>host.json&lt;/code> file, containing all function’s keys, including master FunctionApp key, in encrypted form&lt;/li>
&lt;/ol>
&lt;p>Function Apps, not using deployment type &lt;em>app package&lt;/em>, also stores even functions source code in File shares container, in the &lt;code>site/wwwroot&lt;/code> directory.&lt;/p>
&lt;h2 id="out-of-the-box-setup">Out-of-the-box setup&lt;/h2>
&lt;p>When you create a new Function App via standard Azure Portal wizard, it creates automatically a Storage Account. This Storage Account has opened public endpoint and the Function App interacts with storage via SAS (Shared Access Signature) via public endpoint.&lt;/p>
&lt;p>This is not so dangerous setup. as you could read it for first time – all file containers in this Storage Account has for Public access level value &lt;code>Private&lt;/code>, so you still need to have storage access key for accessing these files.&lt;/p>
&lt;p>On the other hand, in this setup, with a combination of some vulnerability, you could get the content on storage easily compromised and for some workloads you need to reduce the risk with adding extra layer of protection.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/standard-deployment.png"
width="716"
height="591"
srcset="https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/standard-deployment_hu9ea522bcae4d89c6cb4bfb918b9811b3_27561_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/standard-deployment_hu9ea522bcae4d89c6cb4bfb918b9811b3_27561_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Standard deployment architecture of storage for FunctionApp"
class="gallery-image"
data-flex-grow="121"
data-flex-basis="290px"
>&lt;/p>
&lt;h2 id="privatelink-approach">PrivateLink approach&lt;/h2>
&lt;p>This setup is based on limiting access to the Storage Account and requires having a vNET. Public endpoint of Storage Account is disabled and there is created a PrivateLink endpoint of the Storage Account into Function App’s vNET.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/privatelink-deployment.png"
width="848"
height="656"
srcset="https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/privatelink-deployment_hu085c465e00aae015144126bbd44e4878_47116_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/protect-azure-functions-internal-storage-via-privatelink/images/privatelink-deployment_hu085c465e00aae015144126bbd44e4878_47116_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="PrivateLink deployment architecture of storage for FunctionApp"
class="gallery-image"
data-flex-grow="129"
data-flex-basis="310px"
>&lt;/p>
&lt;h3 id="prerequisites">Prerequisites&lt;/h3>
&lt;p>The Function App need to have a plan supporting vNET integration for egress traffic (higher App Service tier, or for consumption-based resources you need to have a &lt;em>Premium plan&lt;/em>).&lt;/p>
&lt;p>Storage Account needs to be provisioned as &lt;em>General purpose V2&lt;/em> account kind (&lt;a class="link" href="https://docs.microsoft.com/en-us/azure/private-link/availability" target="_blank" rel="noopener"
>V1 is not supported by PrivateLink&lt;/a>).&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice note" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#note-notice">&lt;/use>&lt;/svg>&lt;/span>Note&lt;/p>&lt;p>I am reminding this, because it happened to me, that Azure Portal user wizard created a V1 Storage Account for Function App. When this happens, just create a new V2 Storage Account and point the Function App to the new storage resource.&lt;/p>&lt;/div>
&lt;h3 id="steps-to-protect-azure-function-via-azure-portal">Steps to protect Azure Function (via Azure Portal)&lt;/h3>
&lt;h4 id="turn-off-public-endpoint-on-storage-account">Turn off public endpoint on Storage Account&lt;/h4>
&lt;ol>
&lt;li>Go to &lt;code>Networking&lt;/code>, select &lt;code>Allow access from selected networks&lt;/code> and click &lt;code>Save&lt;/code>&lt;/li>
&lt;/ol>
&lt;h4 id="set-up-a-privatelink-endpoint-for-storage-account">Set up a PrivateLink endpoint for Storage Account&lt;/h4>
&lt;ol>
&lt;li>
&lt;p>Go to &lt;code>Networking&lt;/code>, select tab &lt;code>Private Endpoint connections&lt;/code> and click on &lt;code>+Private Endpoint&lt;/code>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Follow wizard, as resource select the Storage Account resource&lt;/p>
&lt;/li>
&lt;li>
&lt;p>As target sub-resource select &lt;code>blob&lt;/code> value
&lt;br>&lt;/p>
&lt;/li>
&lt;li>
&lt;p>Repeat steps, only as a target sub-resource select &lt;code>file&lt;/code> value&lt;/p>
&lt;/li>
&lt;/ol>
&lt;h4 id="set-up-required-feature-flag-for-function-app-runtime">Set up required feature flag for Function App runtime&lt;/h4>
&lt;ol>
&lt;li>Go to the Function App resource, select &lt;code>Configuration&lt;/code> and select tab &lt;code>Application settings&lt;/code>&lt;/li>
&lt;li>Add a new Application settings called &lt;code>WEBSITE_CONTENTOVERVNET&lt;/code>, with value &lt;code>1&lt;/code>&lt;/li>
&lt;li>Save new settings&lt;/li>
&lt;li>Test the Function App&lt;/li>
&lt;/ol>
&lt;p>Now the Storage Account is not accessible from public internet, but reachable from the Function App.&lt;/p></description></item><item><title>AWS SSO with Azure AD – Enterprise Adoption Tips</title><link>https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/</link><pubDate>Mon, 19 Jul 2021 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/</guid><description>&lt;img src="https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/_post.png" alt="Featured image of post AWS SSO with Azure AD – Enterprise Adoption Tips" />&lt;p>AWS IAM can federate with external IdP for a long time (called Single-Account Access), but this solution requires creating and managing AAD Enterprise app for every federated AWS account. In case you have hundreds of AWS accounts, it brings overhead costs to manage all these applications.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-apps.png"
width="761"
height="341"
srcset="https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-apps_hue1eb3c9a92f12cce8b61365d941d22e8_26678_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-apps_hue1eb3c9a92f12cce8b61365d941d22e8_26678_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="AAD with AWS integration diagram – Single-Account Access"
class="gallery-image"
data-flex-grow="223"
data-flex-basis="535px"
>&lt;/p>
&lt;h2 id="aws-sso-federation">AWS SSO federation&lt;/h2>
&lt;p>I found AWS SSO as a much better approach because all you have to do is set up and maintain a single AAD Enterprise app – long story short, metadata about users and groups (username, email address,… – NO passwords) are provisioned into AWS SSO service, deployed in AWS master account, together with role definitions (permission set). Inside AWS SSO user interface are available all connected AWS child accounts, you just select a role, destination AWS child account (where the role will be deployed), who should be granted the role in given AWS account and AWS SSO will provision and generate roles with trust relationship into AWS IAMs of selected child accounts.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-aws-sso.png"
width="882"
height="257"
srcset="https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-aws-sso_hu3d9bd4f844b2dc2629d7e740841ae42f_34016_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/aws-sso-with-azure-ad-enterprise-adoption-tipstricks/images/multi-aws-aad-aws-sso_hu3d9bd4f844b2dc2629d7e740841ae42f_34016_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="AAD with AWS integration diagram – via AWS SSO"
class="gallery-image"
data-flex-grow="343"
data-flex-basis="823px"
>&lt;/p>
&lt;p>When user logs into AWS console, after successful SSO login (via SAML protocol) is redirected to AWS SSO Portal, where user see all available AWS accounts and roles. The provisioning is one-way sync made via SCIM protocol and it works that AAD periodically pushes updates about users into AWS SSO service.&lt;/p>
&lt;p>This setup brings some limitations or maybe &lt;em>&amp;ldquo;strange&amp;rdquo;&lt;/em> behavior you need to count with.&lt;/p>
&lt;h3 id="empty-first-namesurname-results-in-scim-failure">Empty first name/surname results in SCIM failure&lt;/h3>
&lt;p>If you follow the &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/active-directory/saas-apps/aws-single-sign-on-tutorial" target="_blank" rel="noopener"
>official tutorial provided by Microsoft&lt;/a>, you installed the AAD Enterprise application &lt;code>AWS Single Sign-On&lt;/code> by AWS from the AAD application gallery.&lt;/p>
&lt;p>The application has all the required attribute mapping out of the box. In my AD directory, after the first run of provisioning, some of the users have not been provisioned in AWS SSO with strange &lt;code>SystemForCrossDomainIdentityManagementServiceIncompatible&lt;/code> error:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>StatusCode: BadRequest, {&lt;span style="color:#ff79c6">&amp;#34;schema&amp;#34;&lt;/span>:[&lt;span style="color:#f1fa8c">&amp;#34;urn:ietf:params:scim:api:messages:2.0:Error&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">&amp;#34;detail&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;Request is unparsable, syntactically incorrect, or violates schema.&amp;#34;&lt;/span>,&lt;span style="color:#ff79c6">&amp;#34;status&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;400&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>After some debugging, I found out that the application doesn’t cover cases when a user doesn’t have filled in first name/surname. In attribute mapping, you can add some default value to these attributes or you can switch these two attributes to expression like &lt;code>Coalesce([surname], [mail])&lt;/code> – in case surname is empty, email value is used.&lt;/p>
&lt;h3 id="scoping-users-and-groups-for-provisioning">Scoping users and groups for provisioning&lt;/h3>
&lt;p>AAD Enterprise app in AWS SSO is used for SSO access (you log in via SAML into AWS SSO portal) and for provisioning (to sync users into AWS SSO via SCIM protocol).&lt;/p>
&lt;p>AWS SSO service has &lt;a class="link" href="https://docs.aws.amazon.com/singlesignon/latest/userguide/limits.html" target="_blank" rel="noopener"
>few limits&lt;/a> you need to have on your mind. Currently, there is &lt;strong>a limit of 50 000 users to be provisioned into AWS SSO&lt;/strong>. This could be an issue in a scenario, you have a large organization of over 50 000 users and just a small subset of users need access to AWS console (developers, security,…) and you will need to limit number of users to be provisioned with scope filter.&lt;/p>
&lt;p>There are two modes, how to filter groups/users, who will be provisioned.&lt;/p>
&lt;h4 id="mode-1---sync-all-users-and-groups-scope">Mode #1 - Sync all users and groups scope&lt;/h4>
&lt;p>In AAD Enterprise app select &lt;code>Sync all users and groups&lt;/code> scope and in the section &lt;code>Mapping&lt;/code> create a scoping filter (one filter can be for users and one for groups – but there are two isolated filters). This settings will provision all users and groups matching given filter, but it doesn’t mean they will be able to login (on AAD side). AAD checks during login workflow, if user is assigned to AAD Enterprise application.
You can turn off this by switching off toggle &lt;code>User assignment required?&lt;/code> in Properties.&lt;/p>
&lt;p>As result, any user (including users with no assigned roles in AWS) will be able to login into AAD Enterprise app, but they will not see any AWS account after login.&lt;/p>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice warning" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#warning-notice">&lt;/use>&lt;/svg>&lt;/span>Warning&lt;/p>&lt;p>&lt;strong>Important limitation:&lt;/strong> AAD provisioning scope filter currently (as state in 2022) doesn’t support operator &lt;code>memberOf&lt;/code>, so you can’t filter users by membership in a security group&lt;/p>&lt;/div>
&lt;h4 id="mode-2---sync-only-assigned-users-and-groups-scope">Mode #2 - Sync only assigned users and groups scope&lt;/h4>
&lt;p>In AAD Enteprise app select &lt;code>Sync only assigned users and groups&lt;/code> scope. In this mode it will provision only users and groups, who have been directly assigned to the AAD Enterprise app. If you assign the app to &lt;code>UserA&lt;/code>, it will provision &lt;code>UserA&lt;/code> into AWS SSO, but with no other membership. If you have assigned the AAD Enterprise app to &lt;code>SecurityGroupB&lt;/code> (where &lt;code>UserA&lt;/code> is member of), it will provision the security group, &lt;code>UserA&lt;/code> and membership information into AWS SSO.&lt;/p>
&lt;p>Cons of this approach is that you have to manage list of assigned users in two places – first is the AAD Enterprise app assignment, wait for users to be updated in AWS SSO, and assign requested role inside AWS SSO.&lt;/p>
&lt;h3 id="nested-security-groups">Nested security groups&lt;/h3>
&lt;p>SCIM doesn’t support nested security groups. If you have a more complicated structure when a user is a member of a security group and the security group is a member of another security group, this membership information will not be transferred into AWS SSO. Only first-level security groups are supported.&lt;/p>
&lt;h3 id="personal-microsoft-accounts-with-aliases">Personal Microsoft accounts with aliases&lt;/h3>
&lt;p>A long time away, I had an idea to rename my personal Microsoft account (former Windows LiveID) hotmail account to use new alias &lt;code>@outlook.com&lt;/code>. This idea brought me much trouble – many apps (even in the Microsoft world) can’t handle the situation, somewhere is the account referred as &lt;code>@hotmail.com&lt;/code> and somewhere as &lt;code>@outlook.com&lt;/code>.&lt;/p>
&lt;p>AWS SSO is, unfortunately, the same story – I was not able to get the account working, during login I receive an error message &lt;code>Invalid MFA credentials&lt;/code>. I do not recommend using this kind of Microsoft accounts.&lt;/p>
&lt;h3 id="redshift-federation-support">Redshift federation support&lt;/h3>
&lt;p>In case you use federated login into AWS Redshift, I have bad news for you. Federation via AWS SSO is not supported for Redshift services. You will need to onboard an AWS account with deployed Redshift cluster with a traditional Single-Account access approach into AAD in parallel.&lt;/p></description></item><item><title>Return HTML Response Directly from Azure Functions (.NET)</title><link>https://blog.vjirovsky.cz/return-html-response-directly-from-azure-functions/</link><pubDate>Sun, 11 Apr 2021 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/return-html-response-directly-from-azure-functions/</guid><description>&lt;h2 id="net-core-31-and-lower">.NET Core 3.1 and lower&lt;/h2>
&lt;p>Following code is designed for Azure Functions running on .NET Core 3.1 (or lower). Just create a file &lt;code>HtmlObjectResult.cs&lt;/code> and return new class instance as result of your Azure Function.&lt;/p>
&lt;script type="application/javascript" src="https://gist.github.com/vjirovsky/7046cb7b3fbcc6e9fcf3a6195fabb77f.js">&lt;/script>
&lt;h2 id="what-about-net-5">What about .NET 5&lt;/h2>
&lt;p>In .NET 5 a guide above will not work. In fact, in .NET 5 (Azure Functions in isolated process) you don’t need any custom code, a Function has during runtime easy access to &lt;code>HttpRequestData&lt;/code> object, so you can easily create a response like this:&lt;/p>
&lt;script type="application/javascript" src="https://gist.github.com/vjirovsky/9d54f4e99b72cd86f8cc3c99ef2198a4.js">&lt;/script></description></item><item><title>Make Azure Front Door Origin Secured with Private Link</title><link>https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/</link><pubDate>Sat, 20 Feb 2021 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/</guid><description>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/_post.png" alt="Featured image of post Make Azure Front Door Origin Secured with Private Link" />&lt;p>When you deploy a Azure Front Door (or CDN) service usually you need to protect (with WAF) or you need to offload traffic from your workload – you point users to WAF/CDN url instead of origin (your workload) url. Thanks that you can protect any origin from App Service, Static website (deployed in Storage Account) or another custom origin (e.g. Kubernetes container or some on-prem workload).&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/front-door-protocol-summary.png"
width="784"
height="262"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/front-door-protocol-summary_huc986eb1d0e54c993359d1761fcbfeefd_60053_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/front-door-protocol-summary_huc986eb1d0e54c993359d1761fcbfeefd_60053_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Azure Front Door communication diagram"
class="gallery-image"
data-flex-grow="299"
data-flex-basis="718px"
>&lt;/p>
&lt;h2 id="risks-for-origin-in-public-endpoint-model">Risks for Origin in Public Endpoint Model&lt;/h2>
&lt;p>Main issue what comes up with this model is restricting the origin to be called by WAF/CDN only – an attacker is able to call origin directly and bypass any WAF protection and perform a SQL injection or DDoS attack on your workload. You could protect your workload by filtering traffic on your backend only, based on HTTP header &lt;code>X-Azure-FDID&lt;/code> value (you can find more details &lt;a class="link" href="https://henrihietala.fi/limit-access-to-your-azure-web-app-from-your-azure-front-door-only/" target="_blank" rel="noopener"
>here&lt;/a>) and whitelisting Azure’s infrastructure IP range (be careful about this option – IP range could change in future – you need to monitor Azure Service Tag &lt;code>AzureFrontDoor.Backend&lt;/code> for any changes).&lt;/p>
&lt;p>Your workload would stay exposed to public internet and could be vulnerable against attacks with network vectors, but at least there are some security controls in place.&lt;/p>
&lt;h2 id="securing-origin-with-private-link">Securing Origin with Private Link&lt;/h2>
&lt;p>In February 2021 Microsoft introduced a game changer feature – Azure Private Link integration for Front Door.&lt;/p>
&lt;h3 id="what-is-private-link">What is Private Link?&lt;/h3>
&lt;blockquote>
&lt;p>Azure Private Link enables you to access Azure PaaS Services (for example, Azure Storage and SQL Database) and Azure hosted customer-owned/partner services over a private endpoint in your virtual network.&lt;/p>
&lt;/blockquote>
&lt;p>Traffic between your workload and service is not routed via public internet, but via Microsoft private network (backbone) only. Thanks to that your workload can access required service via much more secure way and doesn’t have to be exposed to internet (but they can be if you need).&lt;/p>
&lt;p>Private Link is also good option versus IP whitelist filtering, because with private endpoint you associate (whitelist) your workload with a instance, not whole PaaS service.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink.png"
width="818"
height="271"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink_hu00dac75692191db27dd73c12f9eaf419_42011_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink_hu00dac75692191db27dd73c12f9eaf419_42011_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Azure PrivateLink description"
class="gallery-image"
data-flex-grow="301"
data-flex-basis="724px"
>&lt;/p>
&lt;p>Additional advantage of Private Link is, that your workload can be even in another subscription, even in another AAD tenant.&lt;/p>
&lt;h2 id="how-to-secure-static-website-origin-on-azure-storage-account">How to Secure Static Website Origin on Azure Storage Account&lt;/h2>
&lt;div>&lt;svg width="0" height="0" display="none" xmlns="http://www.w3.org/2000/svg">&lt;symbol id="tip-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"/>&lt;/symbol>&lt;symbol id="note-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zm-248 50c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="warning-notice" viewBox="0 0 576 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M569.517 440.013C587.975 472.007 564.806 512 527.94 512H48.054c-36.937 0-59.999-40.055-41.577-71.987L246.423 23.985c18.467-32.009 64.72-31.951 83.154 0l239.94 416.028zM288 354c-25.405 0-46 20.595-46 46s20.595 46 46 46 46-20.595 46-46-20.595-46-46-46zm-43.673-165.346l7.418 136c.347 6.364 5.609 11.346 11.982 11.346h48.546c6.373 0 11.635-4.982 11.982-11.346l7.418-136c.375-6.874-5.098-12.654-11.982-12.654h-63.383c-6.884 0-12.356 5.78-11.981 12.654z"/>&lt;/symbol>&lt;symbol id="info-notice" viewBox="0 0 512 512" preserveAspectRatio="xMidYMid meet">&lt;path d="M256 8C119.043 8 8 119.083 8 256c0 136.997 111.043 248 248 248s248-111.003 248-248C504 119.083 392.957 8 256 8zm0 110c23.196 0 42 18.804 42 42s-18.804 42-42 42-42-18.804-42-42 18.804-42 42-42zm56 254c0 6.627-5.373 12-12 12h-88c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h12v-64h-12c-6.627 0-12-5.373-12-12v-24c0-6.627 5.373-12 12-12h64c6.627 0 12 5.373 12 12v100h12c6.627 0 12 5.373 12 12v24z"/>&lt;/symbol>&lt;/svg>&lt;/div>&lt;div class="notice info" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#info-notice">&lt;/use>&lt;/svg>&lt;/span>Info&lt;/p>&lt;p>Private Link feature is available for &lt;code>Premium&lt;/code> SKU of Azure Front Door only.&lt;/p>&lt;/div>
&lt;h3 id="turn-on-static-website-feature">Turn On Static Website Feature&lt;/h3>
&lt;p>For deployment of SPA websites (or static export from WordPress, see my previous article &lt;a class="link" href="../pr-crisis-microsite-deployed-in-cloud-in-few-hours/" >PR Crisis Microsite Deployed in Cloud in Few Hours&lt;/a>). I prefer to deploy these files into Azure Blob Storage with turned on &lt;code>Static Website&lt;/code> feature.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-static-website.png"
width="876"
height="302"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-static-website_hu89650c01d8bfbb020f18bedeb29a699c_25272_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-static-website_hu89650c01d8bfbb020f18bedeb29a699c_25272_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Turn on static website feature on Blob Storage"
class="gallery-image"
data-flex-grow="290"
data-flex-basis="696px"
>&lt;/p>
&lt;p>When you turn on the feature, your will receive an auto-generated URL for your storage account – we will need this URL later.&lt;/p>
&lt;p>Anything, what you upload into blob storage folder &lt;code>$web&lt;/code>, will be accessible over this auto-generated URL.&lt;/p>
&lt;h3 id="restrict-public-access-for-azure-storage">Restrict Public Access for Azure Storage&lt;/h3>
&lt;p>Go to &lt;code>Networking&lt;/code> and restrict access to selected networks only. You can also uncheck &lt;code>Allow trusted Microsoft services to access to storage account&lt;/code> option.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/restrict-public-access.png"
width="1005"
height="733"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/restrict-public-access_hue679c9821d7dc15495b00fcb649b6fe5_79777_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/restrict-public-access_hue679c9821d7dc15495b00fcb649b6fe5_79777_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Restrict public access to Storage Account"
class="gallery-image"
data-flex-grow="137"
data-flex-basis="329px"
>&lt;/p>
&lt;h3 id="set-up-origin-in-azure-front-door">Set Up Origin in Azure Front Door&lt;/h3>
&lt;div class="notice info" >
&lt;p class="first notice-title">&lt;span class="icon-notice baseline">&lt;svg>&lt;use href="#info-notice">&lt;/use>&lt;/svg>&lt;/span>Info&lt;/p>&lt;p>As of February 2021 Private Link for Front Door is Preview feature and is available in East US, West 2 US, and South Central US regions only.&lt;/p>&lt;/div>
&lt;p>As next you need to set up the Storage Account as origin for Front Door.
Fill in auto-generated URL of Static website into fields &lt;code>Host name&lt;/code> and &lt;code>Origin host&lt;/code> header.&lt;/p>
&lt;p>Select &lt;code>Enable private link service&lt;/code> and select your Storage Account resource. Select a sub resource &lt;code>web&lt;/code>. Be sure, that values of &lt;code>Host name&lt;/code> and &lt;code>Origin host&lt;/code> headers didn’t changed to different one, overwrite them if that happened.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-origin.png"
width="574"
height="911"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-origin_hu6c92d01ae8b37c332a3eaa5453d29a4a_51857_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-origin_hu6c92d01ae8b37c332a3eaa5453d29a4a_51857_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Set up Private Link for Azure Storage as origin"
class="gallery-image"
data-flex-grow="63"
data-flex-basis="151px"
>&lt;/p>
&lt;p>Configuration above is derived from Private Link architecture – it will register in internal DNS zone different record (something close to private IP) for your resource – when Front Door try to access this host name, private IP record is used and it’s not routed via public internet.&lt;/p>
&lt;h3 id="approve-a-private-link-request">Approve a Private Link request&lt;/h3>
&lt;p>When you submit form from previous section, a request for new Private Link connection has been created.&lt;/p>
&lt;p>Go to &lt;code>Private Link Center&lt;/code> (you can find it by typing &amp;ldquo;Private Link&amp;rdquo; into Search resources field) and approve the request in &lt;code>Pending connections&lt;/code>.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-approve-pending.png"
width="1272"
height="443"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-approve-pending_huf2df32178fe8ed1975fd4f71c2c4ca8f_55395_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/privatelink-approve-pending_huf2df32178fe8ed1975fd4f71c2c4ca8f_55395_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Approve pending Azure PrivateLink connection"
class="gallery-image"
data-flex-grow="287"
data-flex-basis="689px"
>&lt;/p>
&lt;h3 id="the-result">The Result&lt;/h3>
&lt;p>Thanks to settings above, I am able to access the website via Front Door only:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-frontdoor.png"
width="599"
height="244"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-frontdoor_hue69a00807cb53c7a29d8b4d3b01a3714_9117_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-frontdoor_hue69a00807cb53c7a29d8b4d3b01a3714_9117_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Result of accessing private origin from Front Door"
class="gallery-image"
data-flex-grow="245"
data-flex-basis="589px"
>&lt;/p>
&lt;p>When I try to bypass Front Door and access Storage Account directly, I receive an error:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-storage.png"
width="635"
height="315"
srcset="https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-storage_hu1f9039e139fe8fd602cc4a130d294755_17350_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/make-azure-front-door-origin-secured-with-private-link/images/result-from-storage_hu1f9039e139fe8fd602cc4a130d294755_17350_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Result of accessing private origin directly without Front Door"
class="gallery-image"
data-flex-grow="201"
data-flex-basis="483px"
>&lt;/p></description></item><item><title>Caching whole API response in ASP.NET Core</title><link>https://blog.vjirovsky.cz/caching-whole-api-response-in-asp-net-core/</link><pubDate>Thu, 16 Jul 2020 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/caching-whole-api-response-in-asp-net-core/</guid><description>&lt;p>Following method implements REST &lt;code>GET&lt;/code> method in Web API controller &lt;code>Order&lt;/code>. This method provides information about order from database:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">// GET: order/{orderId}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Get manifest file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Order ID&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[HttpGet(&amp;#34;order/{orderId}&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> IActionResult Get(&lt;span style="color:#8be9fd">int&lt;/span> orderId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> entry = _repository.GetById(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">if&lt;/span> (entry == &lt;span style="color:#ff79c6">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> NotFound(&lt;span style="color:#f1fa8c">&amp;#34;Order not found.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> Ok(entry);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>I needed to add response cache, but I didn’t wanted to care about status codes – if i would only cache the output text, I would miss in cached responses alternative codes like 404 – or i would have to save also some another metadata – which is not so developer-friendly solution.&lt;/p>
&lt;p>I got idea – what about cache whole response? Response is object implementing &lt;code>IActionResult&lt;/code>, so what about serializate whole response, save to cache – and when is accessed already cached, only load from cache and deserializate and send to client. And how to do it really developer-friendly? I have idea to wrap it into lambda, so original content of method is invoked in lambda and result of the lambda is saved to cache:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">// GET: order/{orderId}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &amp;lt;summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Get manifest file&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &amp;lt;/summary&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &amp;lt;param name=&amp;#34;orderId&amp;#34;&amp;gt;Order ID&amp;lt;/param&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[HttpGet(&amp;#34;order/{orderId}&amp;#34;]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> IActionResult Get(&lt;span style="color:#8be9fd">int&lt;/span> orderId)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> cacheKey = &lt;span style="color:#f1fa8c">&amp;#34;Get-Order-&amp;#34;&lt;/span> + orderId;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> cachedResponse = _distributedCache.GetString(cacheKey);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">if&lt;/span> (!&lt;span style="color:#8be9fd">string&lt;/span>.IsNullOrEmpty(cachedResponse))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> JsonConvert.DeserializeObject(cachedResponse);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Func createResponse = () =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> entry = _repository.GetById(orderId);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">if&lt;/span> (entry == &lt;span style="color:#ff79c6">null&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> NotFound(&lt;span style="color:#f1fa8c">&amp;#34;Order not found.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> Ok(entry);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> };
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> actionResponse = createResponse();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> serializatedResponse = JsonConvert.SerializeObject(actionResponse);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _distributedCache.SetString(cacheKey, serializatedResponse);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> actionResponse;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description></item><item><title>Run .exe Application in Azure Functions in Powerful Way</title><link>https://blog.vjirovsky.cz/serverless-executables-azure-functions/</link><pubDate>Sun, 12 Jul 2020 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/serverless-executables-azure-functions/</guid><description>&lt;img src="https://blog.vjirovsky.cz/serverless-executables-azure-functions/images/_post.png" alt="Featured image of post Run .exe Application in Azure Functions in Powerful Way" />&lt;p>If your workload fits into Azure Functions limits, you can save costs&amp;amp;time and run your executable totally serverless – you will not need to patch your OS and easily scale workloads dynamically as you need. You can pay for every invocation in &lt;code>consumption plan&lt;/code> (with some free grant offered every month), or alternatively you can have dedicated &lt;code>App Service plan&lt;/code>, which can be shared between multiple Function Apps (ideal for multiple small workloads to optimize your costs).&lt;/p>
&lt;p>In Azure Functions you have multiple options, how to run your .exe – some of workloads can be solved even without writing a line of code, but you really need to ensure the workflow fits limitations of solution.&lt;/p>
&lt;h2 id="powershell-wrapper">PowerShell wrapper&lt;/h2>
&lt;p>Easiest option – you don’t need to write any code (Microsoft did it instead of you) – they have created a wrapper and &lt;a class="link" href="https://github.com/Azure-Samples/functions-dotnet-migrating-console-apps/blob/master/code/run.csx" target="_blank" rel="noopener"
>published on GitHub&lt;/a>. This wrapper (written in &lt;code>.csx&lt;/code>) enables you to convert executable command to HTTP endpoint.&lt;/p>
&lt;p>Example of config:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;name&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;consoleAppToFunctions&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;input&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;command&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;ffmpeg.exe -i {inputFile} {output1}&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;arguments&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;inputFile&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;url&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;https://1drv.ms/v/&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;output1&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;localfile&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;out.mp3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> },
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;output&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;folder&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;outputFolder&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;binaryFile&amp;#34;&lt;/span>:{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;returnFile&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;*.mp3&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;returnFileName&amp;#34;&lt;/span>:&lt;span style="color:#f1fa8c">&amp;#34;yourFile.mp3&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This example would take URL of your input file on OneDrive (as HTTP parameter &lt;code>inputFile&lt;/code>), process it and answers with output file as HTTP response.&lt;/p>
&lt;p>It will help you when you need to serve a console app as web service, for anything else (for example when you need to call service by alternative trigger like &lt;code>Timer&lt;/code> or &lt;code>ServiceBus&lt;/code>) you need to customize the wrapper.&lt;/p>
&lt;h2 id="azure-functions-custom-handlers">Azure Functions custom handlers&lt;/h2>
&lt;p>Recently introduced feature custom handler enables to implement any programming language that supports HTTP (e.g. &lt;em>Go, Rust&lt;/em>). Your executable needs to understand HTTP request and Azure Function acts as proxy (e.g. transform Blob storage trigger to HTTP request) a translate trigger/input payload into HTTP request and optionally process response.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/serverless-executables-azure-functions/images/custom-handlers-overview.png"
width="855"
height="575"
srcset="https://blog.vjirovsky.cz/serverless-executables-azure-functions/images/custom-handlers-overview_huaa16a00bb41c6858e8cba9be71ac26ea_27855_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/serverless-executables-azure-functions/images/custom-handlers-overview_huaa16a00bb41c6858e8cba9be71ac26ea_27855_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Azure Functions custom handlers"
class="gallery-image"
data-flex-grow="148"
data-flex-basis="356px"
>&lt;/p>
&lt;p>I mention this feature because some kind of workloads can be solved by this easy no code method – when you have some legacy executable working as HTTP server.&lt;/p>
&lt;p>More details about custom handlers can be found on &lt;a class="link" href="https://docs.microsoft.com/en-us/azure/azure-functions/functions-custom-handlers" target="_blank" rel="noopener"
>Microsoft Docs&lt;/a>, samples &lt;a class="link" href="https://github.com/Azure-Samples/functions-custom-handlers" target="_blank" rel="noopener"
>on GitHub&lt;/a>.&lt;/p>
&lt;h2 id="writing-custom-wrapper">Writing custom wrapper&lt;/h2>
&lt;p>You can write custom wrapper in any of these language – C#, JavaScript (+TypeScript), F#, Java, PowerShell, Python – selection is up to you. Usually I write them in C#, but that’s my personal preferences only.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">8
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>Process p = &lt;span style="color:#ff79c6">new&lt;/span> Process();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.StartInfo.UseShellExecute = &lt;span style="color:#ff79c6">false&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.StartInfo.FileName = Path.Combine(readonlyCmdDir, &lt;span style="color:#f1fa8c">&amp;#34;myapp.exe&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.StartInfo.Arguments = &lt;span style="color:#f1fa8c">&amp;#34;myinput.txt&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.Start();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.WaitForExit();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">//p.ExitCode should be equal to 0 - standard exit&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="azure-functions-properties-and-limitations">Azure Functions Properties And Limitations&lt;/h2>
&lt;p>When you write your own wrapper, you need to know following properties and limitations about Azure Functions.&lt;/p>
&lt;h3 id="lifecycle-in-azure-functions-host">Lifecycle in Azure Functions Host&lt;/h3>
&lt;p>One of the most important thing you need to know about Azure Functions host is that your Function can be invocated in a new fresh host or inside re-used host. You can imagine host as an application, which performs supportive tasks (&lt;em>authentication, HTTP router, listening on triggers, timer planning&lt;/em>) and when request matches route (or other trigger fires), host will trigger method &lt;code>Run()&lt;/code> of static class, representing your &lt;code>Function&lt;/code>.&lt;/p>
&lt;h3 id="azure-function-host-is-an-app-service">Azure Function host is an App Service&lt;/h3>
&lt;p>Every Function App (I mean whole your Function App, not every Function in the app) runs in own sandbox – isolated from other Function Apps running on same machine and providing an additional degree of security. You are not permitted to perform some operations like writing to registry, open raw sockets, creating symlinks etc.&lt;/p>
&lt;p>More details about sandbox can be found on &lt;a class="link" href="https://github.com/projectkudu/kudu/wiki/Azure-Web-App-sandbox" target="_blank" rel="noopener"
>GitHub&lt;/a>.&lt;/p>
&lt;h3 id="filesystem-in-app-service">Filesystem in App Service&lt;/h3>
&lt;p>All files in &lt;code>%HOME%&lt;/code> (or /home on Linux) directory are persistent. When you scale out your Function App into multiple instances, this directory is shared between all of them.&lt;/p>
&lt;h2 id="resulting-effects">Resulting effects&lt;/h2>
&lt;p>All above lead to some behavior you need to count with.&lt;/p>
&lt;h3 id="constructor-is-called-in-fresh-host-only">Constructor is called in fresh host only&lt;/h3>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">MyFunction&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">static&lt;/span> MyFunction()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> myInitTask();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [FunctionName(&amp;#34;MyFunction&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">async&lt;/span> Task Run(...)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">//my task&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You could think that &lt;code>myInitTask()&lt;/code> will be called during startup of host application – that’s not true, because host app uses lazy-loading, constructor will be called only during first time, when your function is triggered.&lt;/p>
&lt;p>When you run the function again (in short period, usually 5-10 minutes), Azure will re-use existing host and call &lt;code>Run()&lt;/code> method of your Function only. If your Function will not be called for some time, host will release it from memory and given host will be a fresh host for your Function.&lt;/p>
&lt;p>If you would perform any singleton operation in constructor (for example. connection to database), check during every invocation (means in &lt;code>Run()&lt;/code> method) you have valid singleton objects (timeout).&lt;/p>
&lt;p>Also ensure, you clean all resources in right way in your &lt;code>Run()&lt;/code> method, otherwise you can reach out limits, like &lt;a class="link" href="../azure-functions-and-forbidden-socket-exception" >socket exception&lt;/a>, very easily.&lt;/p>
&lt;h3 id="possible-conflicts-between-invocationsnodes">Possible conflicts between invocations/nodes&lt;/h3>
&lt;p>Functions are not processed sequentially, Function host will try to save your costs/time and perform as many Functions as possible in parallel.&lt;/p>
&lt;p>When you have a workload, which require an exclusive access for a shared resource you need to redesign solution or implement some locking method.&lt;/p>
&lt;p>I have created a test application to illustrate this kind of conflicts – this test app was writing log in common file when invocation of Function started, wait 15 seconds and write into log file when invocation finished.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>[11:30:15 PM] [START] from invocationId &amp;#34;0e2e1b8c-2338-4ed3-9d35-77ade55491a2&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[11:30:39 PM] [START] from invocationId &amp;#34;e37c05f2-b55e-40fa-ad4d-cb5a257d79e6&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[11:30:45 PM] [FINISH] from invocationId &amp;#34;0e2e1b8c-2338-4ed3-9d35-77ade55491a2&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[11:30:47 PM] [START] from invocationId &amp;#34;3a77ddbe-1547-4dcf-ba75-26e458e00b50&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>[11:31:09 PM] [FINISH] from invocationId &amp;#34;e37c05f2-b55e-40fa-ad4d-cb5a257d79e6&amp;#34;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>As you can see, interactions with log file were overlapping – if I would have a executable, which requires to create some temp file with constant filename, it could case problems and damage outputs.&lt;/p>
&lt;p>Following workaround will work at most cases – if a binary needs to create some temp files in same directory as it runs, most of them will respect current working directory and will create temp files there.&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[FunctionName(&amp;#34;ConvertFileFunction&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">async&lt;/span> Task Run(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [HttpTrigger(AuthorizationLevel.Anonymous, &amp;#34;get&amp;#34;, Route = null)]&lt;/span> HttpRequest req,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILogger log,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ExecutionContext context)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// we need to inject ExecutionContext, which will provide us ID of invocation (our unique ID for creating a directory)&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">try&lt;/span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">#if DEBUG&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> tempRootDir = Path.GetTempPath();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> #&lt;span style="color:#ff79c6">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// temp location we will have in real Azure Function host &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> tempRootDir = &lt;span style="color:#f1fa8c">&amp;#34;D:\\home\\data&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> #endif
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// create a unique temp folder for given invocation of Function&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> workingDir = Path.Combine(tempRootDir, context.InvocationId.ToString());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.CreateDirectory(workingDir);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> //
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> readonlyCmdDir = Path.Combine(context.FunctionAppDirectory, &lt;span style="color:#f1fa8c">&amp;#34;cmd&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// set working directory where to create files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.SetCurrentDirectory(workingDir);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">try&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> .... CREATE A PROCESS...
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">catch&lt;/span> (Exception e)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">finally&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.SetCurrentDirectory(readonlyCmdDir);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// we have to clean after ourselves and delete our temp files&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Directory.Delete(workingDir, &lt;span style="color:#ff79c6">true&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In case a binary relies on TEMP variable, you can &lt;em>&amp;ldquo;fake&amp;rdquo;&lt;/em> environment variable for given process:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Process p = &lt;span style="color:#ff79c6">new&lt;/span> Process();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>p.startInfo.EnvironmentVariables[&lt;span style="color:#f1fa8c">&amp;#34;TEMP&amp;#34;&lt;/span>] = &lt;span style="color:#f1fa8c">&amp;#34;---MY-VALUE---&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;br>&lt;br>&lt;/p>
&lt;small>
Have you experienced any other properties or limitations of running executables in Azure Functions? please share your thought into comments.
&lt;/small></description></item><item><title>Azure Functions and forbidden sockets exception</title><link>https://blog.vjirovsky.cz/azure-functions-and-forbidden-socket-exception/</link><pubDate>Fri, 22 May 2020 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/azure-functions-and-forbidden-socket-exception/</guid><description>&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>System.Net.Sockets.SocketException
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Exception while executing function Function1: An exception occurred while opening a connection to the server.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>An attempt was made to access a socket in a way forbidden by its access permissions.
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The reason for this error is simple – you reached your’s sandbox (&lt;a class="link" href="https://github.com/projectkudu/kudu/wiki/Azure-Web-App-sandbox" target="_blank" rel="noopener"
>Project Kudu&lt;/a>) currently opened outcoming sockets limit and sandbox disabled access for new sockets.&lt;/p>
&lt;h2 id="azure-connection-limits">Azure connection limits&lt;/h2>
&lt;p>Limits per App Service Plans:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>App Service Plan&lt;/th>
&lt;th>Connection Limit&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Free F1&lt;/td>
&lt;td>250&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Shared D1&lt;/td>
&lt;td>250&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Basic B1 1 Instance&lt;/td>
&lt;td>1920&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Basic B2 1 Instance&lt;/td>
&lt;td>3968&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Basic B3 1 Instance&lt;/td>
&lt;td>8064&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Standard S1 1 Instance&lt;/td>
&lt;td>1920&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Standard S1 2 Instances&lt;/td>
&lt;td>1920 per instance&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Standard S2 1 Instance&lt;/td>
&lt;td>3968&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Standard S3 1 Instance&lt;/td>
&lt;td>8064&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Premium P1 1 Instance (Preview)&lt;/td>
&lt;td>1920&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;div style="text-align:right;">
Source: &lt;a href="http://www.azurefieldnotes.com/2016/11/02/app-service-plan-outbound-network-connection-limit/">azurefieldnotes.com&lt;/a>
&lt;/div>
&lt;h2 id="how-to-solve-this-issue">How to solve this issue&lt;/h2>
&lt;h3 id="reason-1--you-really-need-so-much-connections">Reason 1 – You really need so much connections&lt;/h3>
&lt;p>In case you really need so much connections opened at once, you have to upgrade you App Plan, according to limits table above.&lt;/p>
&lt;h3 id="reason-2--you-have-bug-in-you-app">Reason 2 – You have bug in you app&lt;/h3>
&lt;p>Check, if your application closes all the opened connections. For example in my application, source of this issue were not-released sockets by &lt;code>HttpClient&lt;/code>.&lt;/p>
&lt;h4 id="how-to-use-httpclient-smallalsosmall-in-azure-functionsweb-apps-correctly">How to use HttpClient &lt;small>(also)&lt;/small> in Azure Functions/Web apps correctly&lt;/h4>
&lt;p>&lt;span style="color:red">DON’T DO:&lt;/span>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">HowToNotUseHttpClientInFunction&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [FunctionName(&amp;#34;HowToNotUseHttpClientInFunction&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">async&lt;/span> Task Run(List urls, ILogger log)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.LogInformation(&lt;span style="color:#f1fa8c">&amp;#34;C# HTTP trigger function processed a request.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">foreach&lt;/span>(&lt;span style="color:#8be9fd">var&lt;/span> url &lt;span style="color:#ff79c6">in&lt;/span> urls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">using&lt;/span> (&lt;span style="color:#8be9fd">var&lt;/span> client = &lt;span style="color:#ff79c6">new&lt;/span> HttpClient())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> result = &lt;span style="color:#ff79c6">await&lt;/span> client.GetAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(result.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Wrapping up HTTPClient into &lt;code>using()&lt;/code> section will not work as you probably think – even the application exits, sockets stay to be opened and they will wait for OS’s timeout, after it can be reused. So in this case, function is going to allocate 10 sockets and will release them after timeout.&lt;/p>
&lt;p>You can read more details on &lt;a class="link" href="https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/" target="_blank" rel="noopener"
>aspnetmonsters.com&lt;/a>.&lt;/p>
&lt;p>&lt;span style="color:green">DO:&lt;/span>&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">HowToUseHttpClientInFunction&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> HttpClient client = &lt;span style="color:#ff79c6">new&lt;/span> HttpClient();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [FunctionName(&amp;#34;HowToUseHttpClientInFunction&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#ff79c6">async&lt;/span> Task Run(List urls, ILogger log)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.LogInformation(&lt;span style="color:#f1fa8c">&amp;#34;C# HTTP trigger function processed a request.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">foreach&lt;/span> (&lt;span style="color:#8be9fd">var&lt;/span> url &lt;span style="color:#ff79c6">in&lt;/span> urls)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> result = &lt;span style="color:#ff79c6">await&lt;/span> client.GetAsync(url);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Console.WriteLine(result.Content);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This way will open only one connection and reuse it for all HTTP requests needed by application, so you will not probably reach Kudu’s limits.&lt;/p></description></item><item><title>PR Crisis Microsite Deployed in Cloud in Few Hours</title><link>https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/</link><pubDate>Tue, 24 Mar 2020 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/</guid><description>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/_post.png" alt="Featured image of post PR Crisis Microsite Deployed in Cloud in Few Hours" />&lt;p>During PR crises of corporations (e.g. faulty service or product), main company websites often become overloaded. This situation typically leads to an increased amount of inbound telephone calls and overloading the call centers. Changing the main company website usually takes some time (they are built on enterprise CMS) and scaling the customer telephone lines is not so easy (and really expensive). All of the above are reasons why most of companies choose to create a crisis-dedicated microsite or move communication to external platform (e.g. Medium).&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/web.png"
width="768"
height="456"
srcset="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/web_hu4363adf28d729b5dd208ebae24f43670_149201_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/web_hu4363adf28d729b5dd208ebae24f43670_149201_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Koronavirus MZCR microsite"
class="gallery-image"
data-flex-grow="168"
data-flex-basis="404px"
>&lt;/p>
&lt;p>A microsite solution (looks more professional) needs a decision where to run it. I don’t see any better scenario for a website to be deployed into the cloud – we need to have website scale-able and quickly deployed. I am sure there are even better tools you could use to build the crisis website, but for our solution we had to make a compromise – sometimes because of usability but most importantly because of the time constraint – what is easiest to set up, with what we have the most experience to be able to launch MVP (Minimum Viable Product) as soon as possible.&lt;/p>
&lt;p>We wanted to have a solution which would be stable and we didn’t want to make the ministry’s PR department go through a Markdown language training, and so we decided to use WordPress. This CMS brings a friendly user interface but it’s a bigger challenge to configure it to be secure and ready for high performance.&lt;/p>
&lt;p>The solution described below is designed to be deployed in Microsoft Azure. It can be also easily ported to AWS (CloudFront, S3, etc.). We selected Azure because most enterprise companies have Azure implemented in their organizations (usually because Azure Active Directory or Office 365).&lt;/p>
&lt;h2 id="solution-architecture">Solution Architecture&lt;/h2>
&lt;p>Solution is built on idea, standard users don’t interact with CMS, only but with static HTML files generated by CMS. Thanks to this approach, we can easily achieve the desired performance and security.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/architecture.svg"
loading="lazy"
alt="Architecture diagram of PR crisis WordPress site"
>&lt;/p>
&lt;p>WordPress (WP) is installed on a virtual server, which is isolated and accessible only via VPN (or IP filter at least). WP installation is configured for a different URL, than the destination URL of the website. Without this WP installation doesn’t work well. When the author generates static files (output is located on the server’s filesystem), our custom built script is triggered – this script syncs these generated files to Azure Blob Storage via Shared Access Signature (this could be simplified by mounting Blob storage as directory on filesystem).&lt;/p>
&lt;p>Azure CDN is placed In front of Blob Storage. This service provides caching of content to the closest physical nodes (and in fact solves all our performance needs). When the author is satisfied with the exported version of articles, the author performs CDN cache invalidation and all readers will see the updated version in a few minutes. Thanks to saving SSL private key in Key Vault, the Azure CDN can behave as our website.&lt;/p>
&lt;p>The diagram also shows a „registered app in AAD“. This application is granted to perform the CDN cache invalidation and provides access for our custom WP plugin – this plugin enables to trigger the invalidation from WP user interface via the Azure REST API.&lt;/p>
&lt;p>Limitation of this solution is a lack of interactive content – e.g. search, poll etc. However, these features are usually not needed for a crisis website.&lt;/p>
&lt;h2 id="solution-details-and-usage">Solution Details and Usage&lt;/h2>
&lt;p>We used plugin &lt;a class="link" href="https://wp2static.com/" target="_blank" rel="noopener"
>WP2Static&lt;/a> for static files export. The plugin works as a crawler. It crawls all pages and articles, generates static HTML files from them and downloads all other necessary files (e.g. images, CSS files, etc.). The plugin replaces URL of WP installation with production website URL.&lt;/p>
&lt;p>Publishing the new content version was initially performed manually by running a bash script (in early stages of MVP). Few hours later we developed with &lt;a class="link" href="https://twitter.com/smitka" target="_blank" rel="noopener"
>Vladimir Smitka&lt;/a> custom WP plugin (for cache invalidation) and script, which detects new export and uploads it on Blob Storage. This combination enables the authors to make a deployment by themselves from WP directly.&lt;/p>
&lt;p>I also recommend setting up HTTPS-only redirection on Azure CDN (+redirection from a &lt;code>www.&lt;/code> subdomain) by custom rules in the &lt;code>Rules engine&lt;/code> feature.&lt;/p>
&lt;h2 id="result">Result&lt;/h2>
&lt;p>The website was pretty fast even with 11K concurrent active users. I believe Azure CDN capacity could handle a bigger amount but we haven’t needed it yet.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/ga-visitors.png"
width="480"
height="344"
srcset="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/ga-visitors_hu2993f3391a1fabe038da96f3143a0f42_20705_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/ga-visitors_hu2993f3391a1fabe038da96f3143a0f42_20705_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Real-time analytics 10676 active users at once"
class="gallery-image"
data-flex-grow="139"
data-flex-basis="334px"
>&lt;/p>
&lt;p>Despite this fact, the website was stable, according to &lt;a class="link" href="https://www.hlidacstatu.cz/StatniWeby" target="_blank" rel="noopener"
>Hlídač webů service&lt;/a> (a monitoring service of the government websites). The graph below shows the moments when content was deployed. The website slowed down at those points.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/hlidac-webu.png"
width="710"
height="55"
srcset="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/hlidac-webu_hu89cd56227b4fce2bf3cdba23b1a8a842_999_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/hlidac-webu_hu89cd56227b4fce2bf3cdba23b1a8a842_999_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Graph showing the website was up all the time"
class="gallery-image"
data-flex-grow="1290"
data-flex-basis="3098px"
>&lt;/p>
&lt;h2 id="checklist-before-you-start">Checklist Before You Start&lt;/h2>
&lt;ul>
&lt;li>Ensure you have an Azure access with sufficient permissions – including all mentioned Azure services above are enabled (Azure Policy/de-registered service type in subscription)&lt;/li>
&lt;li>Ensure you have an access to your Azure AD – usually other department have to create application in AAD and assign role for given CDN instance&lt;/li>
&lt;li>Set up DNS records&lt;/li>
&lt;li>If you want to use a custom SSL certificate, ensure you have private key ready (Azure CDN is able to generate SSL certificate by itself, only needs to have set up DNS records to CDN)&lt;/li>
&lt;li>In case you have a custom SSL certificate, ensure you have an access to AAD to set up the Azure CDN so that it has permissions to access the private key in Key Vault.&lt;/li>
&lt;/ul>
&lt;h2 id="general-tips-for-a-crisis-website">General Tips for a Crisis Website&lt;/h2>
&lt;ul>
&lt;li>Reduce TTL of DNS records to 5 minutes, in case you will need to change those quickly.&lt;/li>
&lt;li>Do the same change also for HSTS settings. In case you will misconfigured HTTPS, so you don’t want to cut off all your readers&lt;/li>
&lt;li>If your microsite will be subdomain &lt;code>crisis.acmecompany.com&lt;/code>, be sure also to establish a redirection from &lt;code>www.crisis.acmecompany.com&lt;/code>. The URLs are often preceded with www. by the press regardless of the canon URL form
Set up Google Analytics. Even if you don’t want to evaluate results (what is searched by visitors), it’s good to know what the traffic was and for what traffic size you need to prepare your main website&lt;/li>
&lt;/ul>
&lt;h2 id="high-availability-version">High-availability Version&lt;/h2>
&lt;p>To get this solution ready also for the possibility of an Azure region outage (where your website is running), I also describe this solution in HA version. However, for the basic crisis websites the solution described above is sufficient.&lt;/p>
&lt;p>The entire solution is duplicated also to another failover Azure region. An Azure Traffic Manager is placed in front of the Azure CDN. This service performs a health check and in case of failure of the primary region it automatically switches to the failover region. The Azure Site Recovery service is used for replication of VM with WP administration.&lt;/p>
&lt;p>The script and the plugin requires a small modification to perform all changes on both Azure Storage and CDN.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/pr-crisis-microsite-deployed-in-cloud-in-few-hours/images/architecture-HA.svg"
loading="lazy"
alt="Architecture diagram of PR crisis WordPress site in HA mode"
>&lt;/p>
&lt;h2 id="source-code-and-settings">Source Code and Settings&lt;/h2>
&lt;p>You can find the source code of WP plugin, the script and the necessary settings on &lt;a class="link" href="https://github.com/vjirovsky/pr-crisis-wp-website" target="_blank" rel="noopener"
>my GitHub&lt;/a>.&lt;/p>
&lt;p>&lt;i>&lt;small>Thanks to Adam J. Sporka for text correction of the article.&lt;/small>&lt;/i>&lt;/p></description></item><item><title>Optimize Azure CosmosDB costs</title><link>https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/</link><pubDate>Mon, 02 Sep 2019 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/</guid><description>&lt;img src="https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/_post.png" alt="Featured image of post Optimize Azure CosmosDB costs" />&lt;h2 id="azure-cosmosdb-pricing-explained">Azure CosmosDB pricing explained&lt;/h2>
&lt;p>Pricing of this service is not so complicated as it looks on the first try – you pay for two parameters together – &lt;strong>for saved document size&lt;/strong> ($0.25 per GB) + &lt;strong>for reserved database performance (throughput)&lt;/strong> – in &lt;code>Request Units (RU) / second unit&lt;/code>.&lt;/p>
&lt;p>Minimum performance you have to reserve is 400 RU/s (&lt;code>$0.008 per 1 hour&lt;/code>), scaleable by 100 RU/s.&lt;/p>
&lt;h3 id="pricing-1-per-document-collection">Pricing #1 per document collection&lt;/h3>
&lt;p>In this pricing mode, you pay for reserved performance per every document collection.&lt;/p>
&lt;p>So at the minimum, you will pay &lt;strong>for every document collection&lt;/strong> in your database &lt;em>$23.61 per month&lt;/em> &lt;small>(400 RU/s, 1GB storage, Single Region Write)&lt;/small>.&lt;/p>
&lt;p>You can scale performance for every collection separately. But if you don’t fill up reserved performance, you are wasting your money.&lt;/p>
&lt;h3 id="pricing-2-per-database">Pricing #2 per database&lt;/h3>
&lt;p>Azure also provides alternative pricing – you can set up reserved performance for whole database. This reserved performance is shared across all collections in database. Minimum is again 400 RU/s&lt;/p>
&lt;p>Every collection needs to have at least 100 RU/s – so &lt;strong>first 4 collections are included&lt;/strong>, extra collection will cost you &lt;strong>+100 RU/s per collection&lt;/strong>.&lt;/p>
&lt;p>And small note, this pricing mode has one extra requirement – you need to have shard key in all your collections.&lt;/p>
&lt;h2 id="pricing-problematic-use-case">Pricing-problematic use case&lt;/h2>
&lt;p>Imagine you want to migrate f.ex. application, which has 10x MongoDB entity types (collections).&lt;/p>
&lt;p>By &lt;strong>pricing mode #1&lt;/strong> you get amount &lt;strong>$233.6 per month&lt;/strong> (400 RU/s per collection = 4000 RU/s in total)
By &lt;strong>pricing mode #2&lt;/strong> you get amount &lt;strong>$58.4 per month&lt;/strong> (1000 RU/s in total) – both without storage cost&lt;/p>
&lt;p>That could be ok for some kind of applications. But what if I have application, which has some primary collections with frequent access and some supportive collections with infrequent access?&lt;/p>
&lt;p>So you can join the group of users, who pay $23.61 per month per every infrequent collection. Or you able to &lt;strong>not use CosmosDB for infrequent collections&lt;/strong>, but I would have to manage another connection to another database service, care about SLAs etc.&lt;/p>
&lt;p>First reasonable solution is to &lt;strong>use the pricing mode #2&lt;/strong>, but you have to pay at least 100 RU/s per every collection, so if you have a lot of infrequent collections, you still going to pay too much.&lt;/p>
&lt;h2 id="solution-based-on-mongodb-discriminators">Solution based on MongoDB discriminators&lt;/h2>
&lt;p>When I read the pricing description in detail, I got an idea:&lt;/p>
&lt;blockquote>
&lt;p>&lt;em>What about put all entities in one collection and mark into BSON serializated entity their type and use it during deserialization?&lt;/em>&lt;/p>&lt;/blockquote>
&lt;p>One of my friend reminded me, that MongoDB C# SDK (MongoDB SDKs for almost every language) already have built-in feature called &lt;em>discriminator&lt;/em>. This feature allows you to use polymorphism on entites saved to MongoDB database (&lt;a class="link" href="https://mongodb.github.io/mongo-csharp-driver/2.8/reference/bson/mapping/polymorphism/" target="_blank" rel="noopener"
>more info&lt;/a>).&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[BsonDiscriminator(&amp;#34;User&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> User {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">// fields and properties&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The idea is simple – I am going to create only one collection in pricing mode #1 (with 400RU/s), where are located entities across all infrequent collections.&lt;/p>
&lt;h3 id="example-implementation-in-mongodb-net-sdk">Example implementation in MongoDB .NET SDK&lt;/h3>
&lt;h4 id="acommonstoredentitymodelcs">ACommonStoredEntityModel.cs&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Abstract class of entity saved in MongoDB&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[BsonIgnoreExtraElements]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">[BsonDiscriminator(Required = true)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">abstract&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">ACommonStoredEntityModel&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">const&lt;/span> &lt;span style="color:#8be9fd">string&lt;/span> COMMON_COLLECTION_NAME = &lt;span style="color:#f1fa8c">&amp;#34;common_storage&amp;#34;&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [BsonId]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> ObjectId id;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> ACommonStoredEntityModel()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// Get collection name in database&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> &lt;span style="color:#8be9fd">string&lt;/span> GetCommonCollectionName()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> COMMON_COLLECTION_NAME;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="categorycs-entity-1">Category.cs (entity #1)&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">Category&lt;/span> : ACommonStoredEntityModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#8be9fd">string&lt;/span> title;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="usercs-entity-2">User.cs (entity #2)&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">User&lt;/span> : ACommonStoredEntityModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#8be9fd">string&lt;/span> username;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#8be9fd">string&lt;/span> email;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="amongocommonrepositorycs">AMongoCommonRepository.cs&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">13
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">14
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">15
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">16
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">17
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">18
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">19
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">20
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">21
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">22
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">23
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">24
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">25
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">26
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">27
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">28
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">29
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">30
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">31
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">32
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">33
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">34
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">35
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">36
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">37
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">38
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">39
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">40
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">41
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">42
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">43
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">44
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">45
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">46
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">47
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">48
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">49
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">50
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">51
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">52
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">53
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">54
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">55
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">56
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">57
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">58
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">59
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">60
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">61
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Repository interface for common collection saved entities&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Do not forget to register your new Repository in IoC in Startup.cs (ConfigureServices())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">abstract&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">AMongoCommonRepository&lt;/span> &lt;span style="color:#ff79c6">where&lt;/span> T : ACommonStoredEntityModel
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// collection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">protected&lt;/span> IMongoCollection _collection;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// mongodb connection&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">protected&lt;/span> IMongoDatabase _mongoDatabase = &lt;span style="color:#ff79c6">null&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> AMongoCommonRepository(IMongoDatabase mongoDatabase)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _mongoDatabase = mongoDatabase;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">if&lt;/span> (!IsCommonCollectionExists())
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _mongoDatabase.CreateCollection(ACommonStoredEntityModel.GetCommonCollectionName());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> _collection = _mongoDatabase.GetCollection(ACommonStoredEntityModel.GetCommonCollectionName()).OfType();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">if&lt;/span> (!BsonClassMap.IsClassMapRegistered(&lt;span style="color:#ff79c6">typeof&lt;/span>(T)))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> BsonClassMap.RegisterClassMap(cm =&amp;gt;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> cm.SetDiscriminator(&lt;span style="color:#ff79c6">typeof&lt;/span>(T).Name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// checks if given collectionName exists in database&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#8be9fd">bool&lt;/span> IsCommonCollectionExists()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> filter = &lt;span style="color:#ff79c6">new&lt;/span> BsonDocument(&lt;span style="color:#f1fa8c">&amp;#34;name&amp;#34;&lt;/span>, ACommonStoredEntityModel.GetCommonCollectionName());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">//filter by collection name&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> collections = _mongoDatabase.ListCollections(&lt;span style="color:#ff79c6">new&lt;/span> ListCollectionsOptions { Filter = filter });
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">//check for existence&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> collections.Any();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// Get all entities&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">/// all entities&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">async&lt;/span> Task&amp;gt; GetAllAsync()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#8be9fd">var&lt;/span> result = &lt;span style="color:#ff79c6">await&lt;/span> _collection.FindAsync(u =&amp;gt; &lt;span style="color:#bd93f9">1&lt;/span> == &lt;span style="color:#bd93f9">1&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> result.ToList();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="categoryrepositorycs">CategoryRepository.cs&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Repository interface for Category entity&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Do not forget to register Repository in IoC in Startup.cs (ConfigureServices())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">CategoryRepository&lt;/span> : AMongoCommonRepository
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h4 id="userrepositorycs">UserRepository.cs&lt;/h4>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">///&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Repository interface for User entity&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// Do not forget to register Repository in IoC in Startup.cs (ConfigureServices())&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#6272a4">/// &lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">class&lt;/span> &lt;span style="color:#50fa7b">UserRepository&lt;/span> : AMongoCommonRepository
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>By this repository approach you can access these entities like before the change:&lt;/p>
&lt;h3 id="usage">Usage&lt;/h3>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd">var&lt;/span> users = &lt;span style="color:#ff79c6">await&lt;/span> _userRepository.GetAllAsync();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#8be9fd">var&lt;/span> category = &lt;span style="color:#ff79c6">await&lt;/span> _categoryRepository.GetAllAsync();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In this approach there is no sharding key, but it’s easy to add it.&lt;/p>
&lt;h3 id="results">Results&lt;/h3>
&lt;p>As you see, SDK automatically adds discriminator attribute &lt;code>_t&lt;/code>. Thanks to this attribute MongoDB SDK recognizes entity type, how to deserialize document.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/dbcosmosdb.png"
width="661"
height="282"
srcset="https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/dbcosmosdb_huaca68c29c34ac0c90a840fe6dc13658f_21326_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/dbcosmosdb_huaca68c29c34ac0c90a840fe6dc13658f_21326_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Example of entity with discriminator saved in MongoDB database"
class="gallery-image"
data-flex-grow="234"
data-flex-basis="562px"
>&lt;/p>
&lt;h4 id="cost-savings--case-study">Cost savings – case study&lt;/h4>
&lt;p>We used this approach in one project – Web API application, which has some collections with frequent access and few with infrequent access. We moved these infrequent collections to this common collection.&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/costssavings.png"
width="621"
height="312"
srcset="https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/costssavings_hu1d452984704c5edeaff603aaaaa25359_3670_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/optimize-azure-cosmosdb-costs/images/costssavings_hu1d452984704c5edeaff603aaaaa25359_3670_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Cost savings graph in case study"
class="gallery-image"
data-flex-grow="199"
data-flex-basis="477px"
>&lt;/p>
&lt;p>As you see, we got about &lt;strong>-33%&lt;/strong> cost savings with fully utilized performance, without any performance issues in application (because existing prepaid reserved database performance was not utilized).&lt;/p>
&lt;p>Also we still have possibility to extend performance for common collection (by granularity +-100 RU/s).&lt;/p>
&lt;p>This solution will not save you every time, but for similar use cases can be really helpful.&lt;/p></description></item><item><title>Sections in Azure App configuration as in appsettings.json</title><link>https://blog.vjirovsky.cz/sections-in-azure-app-configuration-as-in-appsettings-json/</link><pubDate>Thu, 27 Jun 2019 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/sections-in-azure-app-configuration-as-in-appsettings-json/</guid><description>&lt;p>Do you have some Application settings in appsettings.json for App Service (or Azure Function) structured in sections? And do you know how to set up same structure as Application settings in Azure?&lt;/p>
&lt;p>It’s easy – just write Application settings &lt;code>Key&lt;/code> in format &lt;code>Section:MyAttribute&lt;/code>&lt;/p>
&lt;p>for appsettings.json:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;myGreatApi&amp;#34;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;baseUrl&amp;#34;&lt;/span>: &lt;span style="color:#f1fa8c">&amp;#34;https://localhost:7071/api/&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;#34;version&amp;#34;&lt;/span>: &lt;span style="color:#bd93f9">2&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>will Application settings look like:&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/sections-in-azure-app-configuration-as-in-appsettings-json/images/appconfig-example.png"
width="1271"
height="108"
srcset="https://blog.vjirovsky.cz/sections-in-azure-app-configuration-as-in-appsettings-json/images/appconfig-example_hua72737ede2e4d207a860d0ac406425f4_11455_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/sections-in-azure-app-configuration-as-in-appsettings-json/images/appconfig-example_hua72737ede2e4d207a860d0ac406425f4_11455_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="App settings examle"
class="gallery-image"
data-flex-grow="1176"
data-flex-basis="2824px"
>&lt;/p></description></item><item><title>Running Azure Functions in Docker container – crashes on production</title><link>https://blog.vjirovsky.cz/running-azure-functions-in-docker-container-memory-issues/</link><pubDate>Tue, 22 Jan 2019 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/running-azure-functions-in-docker-container-memory-issues/</guid><description>&lt;img src="https://blog.vjirovsky.cz/running-azure-functions-in-docker-container-memory-issues/images/_post.png" alt="Featured image of post Running Azure Functions in Docker container – crashes on production" />&lt;p>After searching on web for exit code 137, I found it’s &lt;code>SIGKILL signal&lt;/code> – so host environment sent this signal to process in container. Because of that, I tried to start my container and watch stats by command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>$ docker stats
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>CONTAINER ID NAME CPU % MEM USAGE / LIMIT
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>4feddd624a5e my-azurefunction-app 10.39% 398.4MiB / 1.795GiB
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="so-its-in-memory">So it’s in memory…&lt;/h2>
&lt;p>By this stats I was able to recognize, that my container drains memory so quickly, that all free memory is assigned to it after running 30 seconds. So the problem is in memory management.&lt;/p>
&lt;p>I searched for memory leakage in function source code and made some memory optimalization, but with no effect. It was strange to me, that these memory leaks are not present in dev environment – app runs stable around 65MB. I started to think, that there is no GC Collect performed by runtime.&lt;/p>
&lt;h2 id="net-core-environment">.NET Core environment&lt;/h2>
&lt;p>This was reason, why I started search more detail about .NET Core environment. And I found the problem – .NET Core uses for app different Server Garbage Collector on server, than on developer’s Workstation GC. Server GC is optimized for higher throughput and scalability – and counts with larger resources.
Servers GC has different limits for ephemeral memory segment (for objects from generation 0+1).
Very different.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>GC&lt;/th>
&lt;th>32-bit&lt;/th>
&lt;th>64-bit&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Workstation GC&lt;/td>
&lt;td>16 MB&lt;/td>
&lt;td>256 MB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server GC&lt;/td>
&lt;td>64 MB&lt;/td>
&lt;td>4 GB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server GC with &amp;gt; 4 logical CPUs&lt;/td>
&lt;td>32 MB&lt;/td>
&lt;td>2 GB&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Server GC with &amp;gt; 8 logical CPUs&lt;/td>
&lt;td>16 MB&lt;/td>
&lt;td>1 GB&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>So from workstation env my application went to server with 2GB RAM.
Because of func SDK is compiled 32-bit, .NET recieved this env:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ProcessorCount:4,Is64BitOperatingSystem:True,Is64BitProcess:False
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But inside Docker container on production:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>ProcessorCount:2,Is64BitOperatingSystem:True,Is64BitProcess:True
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h3 id="switching-to-workstation-gc">Switching to Workstation GC&lt;/h3>
&lt;p>So as solution, I switched GC for my Function app to Workstation GC. It’s not so much optimized, but it works also on so small resources.&lt;/p>
&lt;p>You can switch Server GC to Workstation GC by adding value &lt;code>ServerGarbageCollection&lt;/code> to .csproj file:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-xml" data-lang="xml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">&amp;lt;Project&lt;/span> &lt;span style="color:#50fa7b">Sdk=&lt;/span>&lt;span style="color:#f1fa8c">&amp;#34;Microsoft.NET.Sdk&amp;#34;&lt;/span>&lt;span style="color:#ff79c6">&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;lt;PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;lt;TargetFramework&amp;gt;&lt;/span>netstandard2.0&lt;span style="color:#ff79c6">&amp;lt;/TargetFramework&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;lt;AzureFunctionsVersion&amp;gt;&lt;/span>v2&lt;span style="color:#ff79c6">&amp;lt;/AzureFunctionsVersion&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;lt;ServerGarbageCollection&amp;gt;&lt;/span>false&lt;span style="color:#ff79c6">&amp;lt;/ServerGarbageCollection&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">&amp;lt;/PropertyGroup&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>If you are interested, you can find more informations about GC in .NET on &lt;a class="link" href="https://docs.microsoft.com/en-us/dotnet/standard/garbage-collection/fundamentals#generations" target="_blank" rel="noopener"
>Microsoft documentation&lt;/a>.&lt;/p></description></item><item><title>Running Azure Functions on your infrastructure</title><link>https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/</link><pubDate>Mon, 10 Dec 2018 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/</guid><description>&lt;img src="https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/_post.png" alt="Featured image of post Running Azure Functions on your infrastructure" />&lt;h2 id="introduction-to-azure-functions">Introduction to Azure Functions&lt;/h2>
&lt;p>Azure Functions provide easy way how to run serverless functions (small pieces of code) in Azure. You can run your C#/Javascript/F#/Java/Python function with HTTP/DB/queue/file trigger integration. Sample Function:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [FunctionName(&amp;#34;Function2&amp;#34;)]&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> &lt;span style="color:#ff79c6">static&lt;/span> HttpResponseMessage Run(
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#50fa7b"> [HttpTrigger(AuthorizationLevel.Anonymous, &amp;#34;get&amp;#34;, &amp;#34;post&amp;#34;, Route = &amp;#34;ping/&amp;#34;)]&lt;/span>HttpRequestMessage req,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ILogger log
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> )
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> log.LogInformation(&lt;span style="color:#f1fa8c">&amp;#34;Function 2 - I am OK!&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">return&lt;/span> req.CreateResponse(HttpStatusCode.OK, &lt;span style="color:#f1fa8c">&amp;#34;pong&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="dev-environment">Dev environment&lt;/h2>
&lt;p>Microsoft created nice cross-platform environment for developers. You are able to develop and debug Azure Functions on Windows/Linux/Mac with Visual Studio (Code). For testing and debugging you can use &lt;a class="link" href="https://github.com/Azure/azure-functions-core-tools" target="_blank" rel="noopener"
>Azure Functions Core Tools&lt;/a> (func.exe/func).&lt;/p>
&lt;p>To install AFCT via npm use command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm i -g azure-functions-core-tools --unsafe-perm &lt;span style="color:#8be9fd;font-style:italic">true&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>But this dev environment requires connection strings to Azure Storage for writing metadata about functions (locks, timers or even log outputs from functions) – variables &lt;code>AzureWebJobsStorage&lt;/code> and &lt;code>AzureWebJobsDashboard&lt;/code> in &lt;code>local.settings.json&lt;/code> file.&lt;/p>
&lt;h3 id="options">Options&lt;/h3>
&lt;h4 id="azure-storage-emulator">Azure Storage Emulator&lt;/h4>
&lt;p>A service, which emulates Azure’s Storage API (usually is installed with Azure SDK) and can be installed on any Windows computer.&lt;/p>
&lt;p>In that case use for both variables value:
&lt;code>&amp;quot;UseDevelopmentStorage=true&amp;quot;&lt;/code>&lt;/p>
&lt;p>&lt;span style="color:green">++ no costs&lt;/span>
&lt;span style="color:red">−− dependency on Windows, stability (especially for concurrent clients)&lt;/span>&lt;/p>
&lt;h4 id="azure-storage-acccount">Azure Storage Acccount&lt;/h4>
&lt;p>straightforward solution: just create another Storage in Azure and use it for development.&lt;/p>
&lt;p>&lt;span style="color:green">++ easy configuration, same behavior as production&lt;/span>
&lt;span style="color:red">−− you have to be online with connection to Azure to run and debug your function&lt;/span>&lt;/p>
&lt;h2 id="microsofts-production-runtime-environment">Microsoft’s (production) runtime environment&lt;/h2>
&lt;p>For production Azure Functions apps Microsoft created more runtime environments. When you deploy your Function to Azure, you can choose between Windows and Linux runtime. Both runtime are available as Docker container image &lt;a class="link" href="https://github.com/Azure/azure-functions-docker" target="_blank" rel="noopener"
>on GitHub&lt;/a>.&lt;/p>
&lt;p>But they have same issue as dev environment – they needs connection strings to Azure Storage for writing metadata about functions (locks, timers or even log outputs from functions) – variables &lt;code>AzureWebJobsStorage&lt;/code> and &lt;code>AzureWebJobsDashboard&lt;/code> in &lt;code>local.settings.json&lt;/code> file.&lt;/p>
&lt;p>Q: &lt;em>So how can I run production server on own infrastructure totally without connection to Azure?&lt;/em>&lt;br>
A: &lt;strong>You can’t, at least for now. Depedency to Azure Storage is too much strong at the moment.&lt;/strong>&lt;/p>
&lt;h2 id="introducing-vaseks-af-host-docker-image">Introducing Vasek’s AF Host Docker image&lt;/h2>
&lt;p>But you can get close to that. For this purpose I created my custom runtime Docker image for Azure Functions applications – to have better experience with applications deployed on own infrastructure.&lt;/p>
&lt;p>This image is based on Microsoft’s official docker runtime image, but with some modifications – e.g. redirected logs to console, exit code for Docker,…
So result is, that for runtime you need only Azure Storage Account for metadata (&lt;code>AzureWebJobsStorage&lt;/code>).&lt;/p>
&lt;h3 id="installation">Installation&lt;/h3>
&lt;p>Installation is easy – instead of official docker image use for runtime my image &lt;code>vjirovsky/vaseks-af-host:{runtimelanguage}-{version}&lt;/code>&lt;/p>
&lt;p>For dotnet runtime v2.0: &lt;code>FROM vjirovsky/vaseks-af-host:dotnet-2.0&lt;/code>&lt;/p>
&lt;h3 id="sample-project-based-on-vaseks-af-host-image">Sample project based on Vasek’s AF Host image&lt;/h3>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-docker" data-lang="docker">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">FROM&lt;/span>&lt;span style="color:#f1fa8c"> microsoft/dotnet:2.1-sdk AS installer-env&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">COPY&lt;/span> src/ /src/
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">RUN&lt;/span> &lt;span style="color:#8be9fd;font-style:italic">cd&lt;/span> /src/SampleFunctionApp &lt;span style="color:#ff79c6">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> mkdir -p /home/site/wwwroot &lt;span style="color:#ff79c6">&amp;amp;&amp;amp;&lt;/span> &lt;span style="color:#f1fa8c">\
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f1fa8c">&lt;/span> dotnet publish *.csproj --output /home/site/wwwroot
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">FROM&lt;/span>&lt;span style="color:#f1fa8c"> vjirovsky/vaseks-af-host:dotnet-2.0&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">ENV&lt;/span> &lt;span style="color:#8be9fd;font-style:italic">AzureWebJobsStorage&lt;/span>&lt;span style="color:#ff79c6">=&lt;/span>&lt;span style="color:#f1fa8c">&amp;#34;---YOUR-STORAGE_CONNECTION_STRING---&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">COPY&lt;/span> --from&lt;span style="color:#ff79c6">=&lt;/span>installer-env &lt;span style="color:#ff79c6">[&lt;/span>&lt;span style="color:#f1fa8c">&amp;#34;/home/site/wwwroot&amp;#34;&lt;/span>, &lt;span style="color:#f1fa8c">&amp;#34;/home/site/wwwroot&amp;#34;&lt;/span>&lt;span style="color:#ff79c6">]&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>You can find out sample project &lt;a class="link" href="https://github.com/vjirovsky/docker-vaseks-af-host-sample" target="_blank" rel="noopener"
>on GitHub&lt;/a>.&lt;/p>
&lt;p>Sample run (of app sample-app):&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-fallback" data-lang="fallback">&lt;span style="display:flex;">&lt;span>docker run -p 8080:80 -it sample-app:latest
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>&lt;img src="https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/af-running.png"
width="1282"
height="732"
srcset="https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/af-running_hu796580e0ff6b724612f2411460014f15_157298_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/af-running_hu796580e0ff6b724612f2411460014f15_157298_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Example of Azure Function&amp;rsquo;s output to console in Docker"
class="gallery-image"
data-flex-grow="175"
data-flex-basis="420px"
>&lt;/p>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/sample-app.png"
width="1101"
height="636"
srcset="https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/sample-app_hu88134ec608c8bc8d1c2b8c70e3cbbf63_72679_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/running-azure-functions-on-your-infrastructure/images/sample-app_hu88134ec608c8bc8d1c2b8c70e3cbbf63_72679_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Sample Azure Function is up"
class="gallery-image"
data-flex-grow="173"
data-flex-basis="415px"
>&lt;/p>
&lt;h3 id="source-code">Source code&lt;/h3>
&lt;p>Source code of Vasek’s Azure Functions Host runtime is available &lt;a class="link" href="https://github.com/vjirovsky/docker-vaseks-af-host-sample" target="_blank" rel="noopener"
>on GitHub&lt;/a>.&lt;/p></description></item><item><title>Force Garbage Collector during debugging in Visual Studio</title><link>https://blog.vjirovsky.cz/force-garbage-collector-debugging-and-visual-studio/</link><pubDate>Fri, 16 Nov 2018 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/force-garbage-collector-debugging-and-visual-studio/</guid><description>&lt;p>It’s really easy to force Garbage Collector to collect dead objects from heap.&lt;/p>
&lt;p>All you need is command:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>GC.Collect()
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="from-visual-studio-during-debugging">From Visual Studio during debugging&lt;/h2>
&lt;ol>
&lt;li>As first your application needs to be paused. This can be done when code reaches breakpoint, or you can use &lt;code>Break All&lt;/code> function (&lt;code>Ctrl+Alt+Back&lt;/code> or blue pause icon &lt;img src="https://blog.vjirovsky.cz/force-garbage-collector-debugging-and-visual-studio/images/vs-pause-icon.jpg"
width="86"
height="32"
srcset="https://blog.vjirovsky.cz/force-garbage-collector-debugging-and-visual-studio/images/vs-pause-icon_hue2bf18fe1f78b1f52fb256a19ce7c94e_3024_480x0_resize_q75_box.jpg 480w, https://blog.vjirovsky.cz/force-garbage-collector-debugging-and-visual-studio/images/vs-pause-icon_hue2bf18fe1f78b1f52fb256a19ce7c94e_3024_1024x0_resize_q75_box.jpg 1024w"
loading="lazy"
alt="blue pause icon in Visual Studio"
class="gallery-image"
data-flex-grow="268"
data-flex-basis="645px"
> in toolbar)&lt;/li>
&lt;li>When the code is paused, open the &lt;code>Immediate Window&lt;/code> (another tab of Output by default)
Write command into &lt;code>Immediate Window&lt;/code> and press &lt;code>Enter&lt;/code>:&lt;/li>
&lt;/ol>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>GC.Collect();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Garbage collection should be performed now and expected result is:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>GC.Collect();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>Expression has been evaluated and has no &lt;span style="color:#ff79c6">value&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="from-code-during-runtime">From code during runtime&lt;/h2>
&lt;p>You should not to care about GC in application – .NET runtime do it instead of you.&lt;br>&lt;br>&lt;/p>
&lt;p>But in case you really need it, you can call GC by:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>GC.Collect();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>GC.WaitForPendingFinalizers();
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div></description></item><item><title>Handling Xbox One Media Remote in UWP applications</title><link>https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/</link><pubDate>Thu, 27 Apr 2017 00:00:00 +0000</pubDate><guid>https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/</guid><description>&lt;img src="https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/images/_post.png" alt="Featured image of post Handling Xbox One Media Remote in UWP applications" />&lt;p>If you see it for first time, Xbox One Media Remote is remote controller (based on IR), which you can &lt;a class="link" href="http://www.xbox.com/en-US/xbox-one/accessories/controllers/media-remote" target="_blank" rel="noopener"
>buy from Microsoft Store&lt;/a> for ~$25 as an offical Xbox accessory.&lt;/p>
&lt;h2 id="buttons-on-xbox-one-media-remote">Buttons on Xbox One Media Remote&lt;/h2>
&lt;p>&lt;img src="https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/images/xboxmediacontroller.png"
width="800"
height="540"
srcset="https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/images/xboxmediacontroller_huba2a4c2e3236869c25720a9f273c0fcf_225200_480x0_resize_box_3.png 480w, https://blog.vjirovsky.cz/handling-xbox-one-media-remote-in-uwp-applications/images/xboxmediacontroller_huba2a4c2e3236869c25720a9f273c0fcf_225200_1024x0_resize_box_3.png 1024w"
loading="lazy"
alt="Button description on Xbox One Media Remote"
class="gallery-image"
data-flex-grow="148"
data-flex-basis="355px"
>&lt;/p>
&lt;h2 id="handling-pressed-keys">Handling pressed keys&lt;/h2>
&lt;p>In App.xaml.cs you have to change &lt;code>RequiresPointerMode&lt;/code>:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">7
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">public&lt;/span> App()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.InitializeComponent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.Suspending += OnSuspending;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.RequiresPointerMode = ApplicationRequiresPointerMode.WhenRequested;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>There is example, how you can handle buttons on page:&lt;/p>
&lt;div class="highlight">&lt;div style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">
&lt;table style="border-spacing:0;padding:0;margin:0;border:0;">&lt;tr>&lt;td style="vertical-align:top;padding:0;margin:0;border:0;">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 1
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 2
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 3
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 4
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 5
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 6
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 7
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 8
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f"> 9
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">10
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">11
&lt;/span>&lt;span style="white-space:pre;-webkit-user-select:none;user-select:none;margin-right:0.4em;padding:0 0.4em 0 0.4em;color:#7f7f7f">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td style="vertical-align:top;padding:0;margin:0;border:0;;width:100%">
&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#282a36;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-csharp" data-lang="csharp">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#ff79c6">public&lt;/span> MainPage()
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.InitializeComponent();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.RequiresPointer = RequiresPointer.WhenEngaged;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">this&lt;/span>.IsFocusEngagementEnabled = &lt;span style="color:#ff79c6">true&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> Window.Current.CoreWindow.KeyDown += CoreWindow_KeyDown;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ff79c6">private&lt;/span> &lt;span style="color:#ff79c6">void&lt;/span> CoreWindow_KeyDown(Windows.UI.Core.CoreWindow sender, Windows.UI.Core.KeyEventArgs args)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#6272a4">//let&amp;#39;s play with args.VirtualKey&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="remotes-buttons-and-virtualkeys-enum-mapping">Remote’s buttons and VirtualKeys enum mapping&lt;/h2>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>Button&lt;/th>
&lt;th>Runtime equivalent&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>Home button (*)&lt;/td>
&lt;td>&lt;code>(VirtualKey)(7)&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>View button&lt;/td>
&lt;td>&lt;code>System.VirtualKey.GamepadView&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Menu button&lt;/td>
&lt;td>&lt;code>System.VirtualKey.GamepadMenu&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Navigation arrows&lt;/td>
&lt;td>&lt;code>System.VirtualKey.GamepadDPadLeft&lt;/code>&lt;br> &lt;code>System.VirtualKey.GamepadDPadUp&lt;/code> &lt;br> &lt;code>System.VirtualKey.GamepadDPadRight&lt;/code> &lt;br> &lt;code>System.VirtualKey.GamepadDPadDown&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Select button&lt;/td>
&lt;td>&lt;code>System.VirtualKey.GamepadA&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Back button&lt;/td>
&lt;td>&lt;code>System.VirtualKey.GamepadB&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>OneGuide button&lt;/td>
&lt;td>&lt;em>not available during runtime&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Volume+mute buttons&lt;/td>
&lt;td>&lt;em>not available during runtime&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Channel buttons&lt;/td>
&lt;td>&lt;code>System.VirtualKey.PageUp System.VirtualKey.PageDown&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>Media control buttons&lt;/td>
&lt;td>&lt;em>not available during runtime&lt;/em>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;i>(*) Home button is special – when user press this button, Xbox opens system’s menu (you can’t override that), but system will also send you signal &lt;code>(VirtualKey)(7)&lt;/code> – so for example, you can pause game or movie in your app.&lt;/i>&lt;/p></description></item></channel></rss>