Edi Wang.NET and Azure Developer© 2009 - 2024 edi.wang2024-03-19T10:27:51ZMoonglade v14.3.2 (344426)9A4B4E15-5375-4B74-A3A6-8CCA6459D2A9Deploy Website from Local Machine to Azure App Service without Git, CI/CD or VS/Code2024-03-05T06:12:38Z2024-03-05T06:12:38ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p><a href="https://learn.microsoft.com/en-us/azure/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service</a> offers a variety of deployment options tailored to the diverse needs of developers. These include integration with Git repositories, <a href="https://learn.microsoft.com/en-us/azure/devops/?view=azure-devops&WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure DevOps</a>, and direct deployment from IDEs like <a href="https://learn.microsoft.com/en-us/visualstudio/windows/?view=vs-2022&WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Visual Studio</a> or <a href="https://code.visualstudio.com/docs?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Visual Studio Code</a>.</p>
<p>But what if your project is a straightforward task that doesn't warrant the complexity of setting up an entire suite of tools or a continuous integration/continuous deployment (CI/CD) pipeline? Imagine you have a static website ready to go, sitting in a local folder—it's not part of a Git repository, and you don't have Visual Studio or Visual Studio Code at your disposal. Is there a straightforward way to deploy this website to Azure App Service without the usual overhead? Absolutely, and I'm going to walk you through a streamlined approach that minimizes effort to make your deployment ready in couple of minutes.</p>
<h2>Solution</h2>
<hr>
<h3>Install Azure CLI</h3>
<p>If you haven't already, install the <a href="https://learn.microsoft.com/en-us/cli/azure/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure CLI</a> on your machine. You can download it from the <a href="https://learn.microsoft.com/en-us/cli/azure/install-azure-cli?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">official Azure CLI source</a>.</p>
<h3>Sign in to Azure</h3>
<p>Run the following command to sign in to Azure. This will open a browser window asking you to log in with your Azure credentials.</p>
<pre class="language-bash"><code>az login</code></pre>
<p>If you have multiple Azure subscription, you need to choose one of them. Example:</p>
<pre class="language-bash"><code>az account set --subscription "DevTest"</code></pre>
<h3>Zip your website files</h3>
<p>Before deploying, make sure all your website files (HTML, CSS, JavaScript, images, etc.) are in a folder. Then, create a zip file of this folder.</p>
<p>Please note, files must be in the root directory, in this example, the root of demo.zip should contain <code>index.html</code>, <strong>DO NOT wrap a folder outside of your website content files like <code>demo/index.html</code></strong></p>
<p><img src="/image/img-c8cff757-1ab6-4a7f-99d2-4011bb31ba7c.png" border="0" loading="lazy"></p>
<h3>Set the deployment user</h3>
<p>If you haven't set a deployment user for Azure Web Apps, you'll need to create one. This user is not the same as your Azure account user. It's a user specifically for deployment purposes.</p>
<pre class="language-bash"><code>az webapp deployment user set --user-name <username> --password <password></code></pre>
<p>Replace <code><username></code> and <code><password></code> with your own value.</p>
<h3>Deploy your website</h3>
<p>Use the <code>az webapp deployment source config-zip</code> command to deploy the zip file to your Azure App Service. You'll need to know the name of your App Service and the resource group it belongs to.</p>
<pre class="language-bash"><code>az webapp deployment source config-zip --resource-group <resource-group-name> --name <app-service-name> --src demo.zip</code></pre>
<p>Replace <code><resource-group-name></code> with the name of your Azure resource group and <code><app-service-name></code> with the name of your Azure App Service.</p>
<p><img src="/image/img-45648e9e-abcc-43fa-b85d-486075d49bd9.png" border="0" loading="lazy"></p>
<p>After running this command, Azure will deploy the contents of your zip file to the App Service. You can then navigate to your App Service URL to see your static website live.</p>
<p><img src="/image/img-a582ab8e-a565-4e31-b2b9-76d1fca978a0.png" border="0" loading="lazy"></p>
<p><img src="/image/img-5978312a-2c0a-48d9-9bdb-be694f3c9add.png" border="0" loading="lazy"></p>18FA315A-0E55-426E-9466-59DB706836BCJson Serialization Caveat in Azure Function2024-02-07T08:35:05Z2024-02-07T08:35:05ZEdi WangEdi.Wang@outlook.com<h2>Background</h2>
<hr>
<p>Creating <a href="https://learn.microsoft.com/en-us/azure/azure-functions/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Functions</a> using C# and .NET can feel quite familiar to those versed in creating ASP.NET Core Web APIs. Despite the similarities, one key difference lies in the process of JSON serialization, which can present unexpected challenges. In this post, I'll explore the two most common issues developers encounter with JSON in Azure Functions and provide practical solutions to overcome them.</p>
<p><strong>Please be noted: This blog post only talks about Azure Function V4 with .NET 6.0 in process worker. Things may change in the future version of Azure Function.</strong></p>
<h2>Customize Json Property Name</h2>
<hr>
<h3>Problem</h3>
<p>Define a class, with <code>JsonPropertyName </code>attribute, like this:</p>
<pre class="language-csharp"><code>class Product
{
[JsonPropertyName("displayName")]
public string Name { get; set; }
public decimal Price { get; set; }
}
</code></pre>
<p>What I want is to output the <code>Name </code>property in Json as "<code>displayName</code>"</p>
<p>Now, guess what's the result of this function?</p>
<pre class="language-csharp"><code>[FunctionName("Function1")]
public static IActionResult Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)] HttpRequest req, ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var products = new List<Product>
{
new() { Name = "Apple", Price = 1.99m },
new() { Name = "Banana", Price = 2.99m },
new() { Name = "Cherry", Price = 3.99m }
};
return new OkObjectResult(products);
}
</code></pre>
<p>The <code>JsonPropertyName </code>does not work.</p>
<pre class="language-json"><code>[
{
"name": "Apple",
"price": 1.99
},
{
"name": "Banana",
"price": 2.99
},
{
"name": "Cherry",
"price": 3.99
}
]</code></pre>
<h3>Root cause</h3>
<p>The underlying issue stems from Azure Functions' reliance on a different serialization engine than one might expect. Instead of utilizing <code>System.Text.Json</code>, which is common in modern .NET applications, Azure Functions default to the venerable <code>Newtonsoft.Json</code> package. This package isn't explicitly listed in your project file; rather, it's included as a dependency of the <code>Microsoft.NET.Sdk.Functions</code> package.</p>
<p><img src="/image/img-59718cd9-bfbc-4550-b4d5-fbe5f42e5c03.png" border="0" loading="lazy"></p>
<p>Although the coding experience in C# for Azure Functions closely mirrors that of ASP.NET Core, and both may run on .NET 6.0, assumptions about consistency in JSON serialization can lead to confusion. Contrary to ASP.NET Core 6.0, which defaults to <code>System.Text.Json</code> for serialization, Azure Functions diverge in this aspect. Despite using the same <code>OkObjectResult</code> class from ASP.NET Core's framework, the serialization process under the hood is handled differently due to the reliance on <code>Newtonsoft.Json</code></p>
<pre class="language-csharp"><code>#region Assembly Microsoft.AspNetCore.Mvc.Core, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60
// location unknown
// Decompiled with ICSharpCode.Decompiler 8.1.1.7464
#endregion
using Microsoft.AspNetCore.Mvc.Infrastructure;
namespace Microsoft.AspNetCore.Mvc;
//
// Summary:
// An Microsoft.AspNetCore.Mvc.ObjectResult that when executed performs content
// negotiation, formats the entity body, and will produce a Microsoft.AspNetCore.Http.StatusCodes.Status200OK
// response if negotiation and formatting succeed.
[DefaultStatusCode(200)]
public class OkObjectResult : ObjectResult
{
private const int DefaultStatusCode = 200;
//
// Summary:
// Initializes a new instance of the Microsoft.AspNetCore.Mvc.OkObjectResult class.
//
//
// Parameters:
// value:
// The content to format into the entity body.
public OkObjectResult(object value)
: base(value)
{
base.StatusCode = 200;
}
}</code></pre>
<h3>Fix</h3>
<p>Use <code>JsonProperty</code> from <code>Newtonsoft.Json</code></p>
<pre class="language-csharp"><code>class Product
{
[JsonProperty("displayName")]
public string Name { get; set; }
public decimal Price { get; set; }
}</code></pre>
<p>Now, the response is what I desired.</p>
<pre class="language-json"><code>[
{
"displayName": "Apple",
"price": 1.99
},
{
"displayName": "Banana",
"price": 2.99
},
{
"displayName": "Cherry",
"price": 3.99
}
]</code></pre>
<h2>Model Binding</h2>
<hr>
<h3>Problem</h3>
<p>This is a real bug in my blog's email sending function. This code intends to serialize <code>req.Payload</code> to a string to be inserted in to Azure Storage Queue.</p>
<pre class="language-csharp"><code>[FunctionName("Enqueue")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] EnqueueRequest req,
ILogger log)
{
log.LogInformation($"C# HTTP trigger function `Enqueue` processed a request. OriginAspNetRequestId={req.OriginAspNetRequestId}");
var conn = Environment.GetEnvironmentVariable("moongladestorage");
var queue = new QueueClient(conn, "moongladeemailqueue");
var en = new EmailNotification
{
DistributionList = string.Join(';', req.Receipts),
MessageType = req.Type,
MessageBody = System.Text.Json.JsonSerializer.Serialize(req.Payload, MoongladeJsonSerializerOptions.Default),
};
await InsertMessageAsync(queue, en, log);
return new AcceptedResult();
}
</code></pre>
<p>The <code>EnqueueRequest </code>class has a <code>Payload </code>property with <code>object </code>type.</p>
<pre class="language-csharp"><code>public class EnqueueRequest
{
public string Type { get; set; }
public string[] Receipts { get; set; }
public object Payload { get; set; }
public string OriginAspNetRequestId { get; set; } = Guid.Empty.ToString();
}</code></pre>
<p>But in runtime, the <code>MessageBody </code>never get the correct Json string. </p>
<p>The values in the original <code>req </code>object are removed after serialization. </p>
<p>It intends to be:</p>
<pre class="language-json"><code>{
"name": "Fubao",
"price": 996.35
}</code></pre>
<p>But it just become this:</p>
<pre class="language-plaintext"><code>"{\r\n \"name\": [],\r\n \"price\": []\r\n}"</code></pre>
<p><img src="/image/img-2ca4020d-7a0c-4833-995a-fab253a662c9.png" border="0" loading="lazy"></p>
<h3>Root cause</h3>
<p>Again, this is because the model binding in Azure Function is also using <code>Newtonsoft.Json</code> instead of <code>System.Text.Json</code> like many will assume it to be. In this case, I use <code>object </code>type for <code>Payload </code>property. This will become <code>Newtonsoft.Json.Linq.Object</code>, which <code>System.Text.Json.JsonSerializer</code> has no idea how to serialize it correctly.</p>
<p><img src="/image/img-4cf66652-94dd-4a65-bc4c-689ffb193d47.png" border="0" loading="lazy"></p>
<h3>Fix</h3>
<p>Change the code to use <code>JsonConvert</code> from <code>Newtonsoft.Json</code></p>
<pre class="language-csharp"><code>MessageBody = JsonConvert.SerializeObject(req.Payload)</code></pre>
<p>Now it works fine</p>
<p><img src="/image/img-2e9702f9-6715-4bed-91e8-2e9c92929414.png" border="0" loading="lazy"></p>
<h2>Conclusion</h2>
<hr>
<p>While C# Azure Functions and ASP.NET Core share many similarities in their development models, there's a significant difference in how they handle JSON serialization and deserialization. Azure Functions, by default, use <code>Newtonsoft.Json</code> for both serializing response objects and deserializing incoming request data during model binding. This is a critical point of consideration for developers who might be accustomed to <code>System.Text.Json</code>, which is the default in ASP.NET Core. Special attention is needed when working with Azure Functions to ensure that any custom serialization settings or attributes are compatible with <code>Newtonsoft.Json</code>, and developers should be cautious when attempting to use <code>System.Text.Json</code> in scenarios where Azure Functions implicitly expect <code>Newtonsoft.Json</code>.</p>476C3876-A8C3-44A2-8CBD-C4DE0983833CRedirect Traffic to New CDN Endpoint in Azure CDN2024-01-19T02:20:51Z2024-01-19T02:20:51ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>I am in the process of transitioning from a <a href="https://learn.microsoft.com/en-us/azure/cdn/cdn-create-new-endpoint?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Classic Microsoft CDN</a> to a new CDN hosted under a different domain name. To ensure a seamless user experience during this migration, I intend to implement a redirection mechanism. This will automatically reroute users to the new CDN endpoint whenever they attempt to access resources from the old CDN. Let's see how to do it from Azure portal.</p>
<h2>Solution</h2>
<hr>
<p>My old CDN is using domain name <code>cdn-blog.edi.wang</code>, when user access resources under this domain, the full URL will look like this:</p>
<pre class="language-bash"><code>https://cdn-blog.edi.wang/web-assets/mvp-logo.svg</code></pre>
<p>I would like to make this request redirect to my new CDN <code>cdn.edi.wang</code>, which the URL should be:</p>
<pre class="language-bash"><code>https://cdn.edi.wang/web-assets/mvp-logo.svg</code></pre>
<p>It's very simple.</p>
<p>1. Go to <strong>Rules engine</strong> blade in the old CDN endpoint.</p>
<p>2. Add a new rule:</p>
<ul>
<li>Condition: Request URL
<ul>
<li>Operator: Any</li>
</ul>
</li>
<li>Action: URL Redirect
<ul>
<li>Type: 301</li>
<li>Hostname: <code>cdn.edi.wang</code></li>
</ul>
</li>
</ul>
<p><img src="/image/img-98d3f689-b185-4042-afa7-c94dad281090.png" border="0" loading="lazy"></p>
<p>That's it. Now save changes and wait for a few minutes, the redirect rule is now effective.</p>
<p><img src="/image/img-2dd1c623-dbc8-4cfd-b6e3-1008a634aaf8.png" border="0" loading="lazy"></p>94754AF1-8CF2-4600-A4D7-BAB8DC0DF76DPowerShell to Enable SQL Server Express Remote Access2024-01-18T05:52:41Z2024-01-18T05:52:41ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>SQL Server Express is a free edition of Microsoft's SQL Server, which is a relational database management system (RDBMS). It is designed for small-scale applications and for users who are getting started with learning about databases and SQL Server. However, remote access is not enabled by default on this SKU. To access a SQL Server Express instance from the network, we have to do a few steps. There's plenty of guide on the internet telling you how to enable remote access via GUI, but I would like to do it from PowerShell, so that I can integrate those steps in automated environment.</p>
<h2>Solution</h2>
<hr>
<h3>Steps</h3>
<p>To enable remote access for SQL Server Express, the general steps are</p>
<p>Check "Allow remote connections to this server" checkbox (Already checked in a new SQL Server 2022 Express installation)</p>
<ol>
<li>Enable TCP/IP Protocol</li>
<li>Set TCP port to 1433</li>
<li>Set Windows Firewall to allow TCP 1433</li>
<li>Restart related services</li>
</ol>
<p>You can refer to a GUI guide here: <a href="https://www.apesoftware.com/calibration-control/help/sql-remote-connections" target="_blank" rel="noopener">https://www.apesoftware.com/calibration-control/help/sql-remote-connections</a></p>
<h3>PowerShell</h3>
<p>Thanks to <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Open AI</a>, I don't have to <a href="https://996.icu" target="_blank" rel="noopener">work 996</a> this time to make the PowerShell script. The script generally is automating the steps above.</p>
<pre class="language-powershell"><code># Run this script as an Administrator
# Step 1: Set SQL Server Browser service to start automatically and start the service
Write-Host "Setting SQL Server Browser service to Automatic and starting the service..."
Set-Service 'SQLBrowser' -StartupType Automatic
Start-Service 'SQLBrowser'
# Step 2: Enable TCP/IP protocol for SQL Server
# Note: This step requires SQL Server Configuration Manager to be installed.
Write-Host "Enabling TCP/IP protocol for SQL Server..."
$sqlServerConfigManager = Get-WmiObject -Namespace "root\Microsoft\SqlServer\ComputerManagement16" -Class "ServerNetworkProtocol"
$tcpIp = $sqlServerConfigManager | Where-Object { $_.InstanceName -eq 'SQLEXPRESS' -and $_.ProtocolName -eq 'Tcp' }
$tcpIp.SetEnable()
# Step 3: Set the TCP Port for IPAll to 1433
Write-Host "Setting the TCP Port for IPAll to 1433..."
$regPath = "HKLM:\SOFTWARE\Microsoft\Microsoft SQL Server\MSSQL16.SQLEXPRESS\MSSQLServer\SuperSocketNetLib\Tcp\IPAll"
Set-ItemProperty -Path $regPath -Name TcpPort -Value "1433"
Set-ItemProperty -Path $regPath -Name TcpDynamicPorts -Value ""
# Step 4: Create a firewall rule to allow TCP port 1433
Write-Host "Creating a firewall rule for TCP port 1433..."
New-NetFirewallRule -DisplayName "SQL Server Remote Access" -Direction Inbound -Protocol TCP -LocalPort 1433 -Action Allow
Write-Host "SQL Server Express should now accept remote connections."
Restart-Service 'MSSQL$SQLEXPRESS'
Restart-Service 'SQLBrowser'</code></pre>
<p>Please note, this script is for SQL Server 2022 Express, with default instance name "SQLExpress". If you are targeting other version of SQL Server, you should change these items in the script.</p>
<ul>
<li><code>ComputerManagement16</code></li>
<li><code>MSSQL16.SQLEXPRESS</code></li>
<li><code>SQLEXPRESS</code></li>
</ul>
<p>To connect to your SQL Server Express instance via network. There are also a few things to keep in mind.</p>
<ul>
<li style="text-align: left;">Add port number in server name, that is "<code>IP\SQLExpress,1433</code>", NOT "<code>IP\SQLExpress</code>".</li>
<li style="text-align: left;">Trust server certificate if SSMS throw error about certificate. <br><img src="/image/img-c256787d-f316-49e8-b04d-fe90a1f2945a.png" border="0" loading="lazy"></li>
</ul>E26E7E63-4B18-41EF-A8CB-73674AF92A80How to Add a Public IPv6 Address to Azure VM2024-01-11T02:52:55Z2024-01-11T02:52:55ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>When configuring a new <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Virtual Machine</a> (VM), you'll notice that it isn't allocated a public IPv6 address automatically. By default, only an IPv4 address is assigned. In this guide, we'll walk you through the process of manually adding a public IPv6 address to your Azure VM, ensuring your setup is future-proof and ready to handle the latest internet protocol requirements.</p>
<h2>Solution</h2>
<hr>
<h3>Add IPv6 Address Space</h3>
<p>Go to the virtual network that is associated with your VM. Go to "Address space" blade, add a new address space as this:</p>
<pre class="language-plaintext"><code>ace:ceb:deca::/48</code></pre>
<p><img src="/image/img-cc9c7a26-9d6b-4d11-8749-79af0100c3a4.png" border="0" loading="lazy"></p>
<h3>Add IPv6 Subnet</h3>
<p>Go to "Subnets" blade, add a new subnet for IPv6, example:</p>
<ul>
<li>name: <code>default-ipv6</code></li>
<li>Subnet address range: <code>10.0.1.0/24</code></li>
<li>Check: <strong>Add IPv6 address space</strong></li>
<li>IPv6 address space: <code>ace:ceb:deca:deed::/64</code></li>
</ul>
<p><img src="/image/img-17068c76-8777-421f-ae91-ea3e1ed19eab.png" border="0" loading="lazy"></p>
<h3>Create New Network Interface</h3>
<p>Create a new network interface in the same resource group and region as your VM. Select the virtual network in previous step, which is used by your VM. Select the "<strong>default-ipv6</strong>" subnet we created before. Select <strong>IPv4 and IPv6</strong>, and <strong>Dynamic </strong>assignment.</p>
<p><img src="/image/img-d2e0a411-5733-431d-a1b6-d226324cee16.png" border="0" loading="lazy"></p>
<h3>Create Public IPv6 address</h3>
<p>Create a new Public IP address in the same resource group and region as your VM. Select <strong>IPv6 </strong>as IP Version.</p>
<p><img src="/image/img-fd637e09-bdcc-4445-b205-28ffcc4817c4.png" border="0" loading="lazy"></p>
<h3>Bind Public IPv6 to the new Network Interface</h3>
<p>Go back to the network interface we created before. Go to "<strong>IP configurations</strong>" blade, click "<strong>Ipv6config</strong>".</p>
<p>Select "<strong>Associate public IP address</strong>" and assign the Public IPv6 address we created in previous step.</p>
<p><img src="/image/img-0a5980a1-dc4d-43c1-8b77-e36454e3e6ad.png" border="0" loading="lazy"></p>
<h3>Unbind IPv4 on Old Network Interface</h3>
<p>Go to the old network interface for your VM, under IP configurations blade, uncheck the public IP address checkbox, this will unbind the existing public IPv4 address.</p>
<p><img src="/image/img-94995c0b-deda-4507-a31d-8736b3acaa63.png" border="0" loading="lazy"></p>
<h3>Rebind IPv4 to the new Network Interface</h3>
<p>Go to the new network interface once again and bind your existing public IPv4 address to the Ipv4config, steps are same as previous. </p>
<p>Please be notice, if your old IPv4 address is Basic SKU, you won't be able to rebind here. You can create a new public IPv4 address with Standard SKU, but this will change your old IPv4 address.</p>
<p>When you are done, you should see two public IP address, one for V4, one for V6 here.</p>
<p><img src="/image/img-22eb31fa-6c96-4b44-bd32-e7cdff90d25f.png" border="0" loading="lazy"></p>
<h3>Migrate Network Security Group</h3>
<p>If your VM has an existing NSG, which in most case you do. Please remove it in the old network interface and add it into the new network interface.</p>
<p><img src="/image/img-49b4999d-0c05-479a-93eb-a3f22cc7b79d.png" border="0" loading="lazy"></p>
<h3>Attach the new Network Interface to your VM</h3>
<p>Go to <strong>Networking </strong>blade for your VM, click "<strong>Attach network interface</strong>". Select the new Network Interface we configured before.</p>
<p><img src="/image/img-58d87f25-e978-454c-9941-2c7622cbf47c.png" border="0" loading="lazy"></p>
<p>Then detach the old Network Interface.</p>
<p><img src="/image/img-456b9d00-4223-4313-9d04-ac47a2a3678f.png" border="0" loading="lazy"></p>
<p>When you are done, you should see only the new Network Interface, with NSG configured like this:</p>
<p><img src="/image/img-dc7bed09-b5b4-43d7-ab98-89107fb57ba3.png" border="0" loading="lazy"></p>
<h3>Test Your Connection</h3>
<p>Now, you can use both IPv4 and IPv6 to connect to your VM. You can find the public IPv6 address on the Overview blade for your VM.</p>
<p><img src="/image/img-766bf83b-19ff-4037-9cba-46ee2c904019.png" border="0" loading="lazy"></p>
<p><img src="/image/img-91b45e76-f0f7-4474-b326-1be6a612a667.png" border="0" loading="lazy"></p>
<p>On your VM, you can visit <a href="https://ipv6-test.com/" target="_blank" rel="noopener">https://ipv6-test.com/</a> to see how the IPv6 perform. It even identifies the IPv6 address as <strong>native IPv6</strong>! </p>
<p><img src="/image/img-b4750aa2-d831-4c92-94f3-e814d33a8579.png" border="0" loading="lazy"></p>
<h3>Clean Up Old Resources</h3>
<p>Finally, you can delete your old Network Interface, your old public IPv4 (if you created a new Standard SKU IPv4), to save money.</p>814FCA16-B825-4A5E-A54E-C7BF4F2462C2How to Check If Code Is Running on Azure VM2023-12-19T05:03:12Z2023-12-19T05:03:12ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>An application needs to know if itself is running in Azure VM, so that it can apply special logic and optimization for Azure. </p>
<h2>Solution</h2>
<hr>
<p>You can check if your code is running on an Azure VM by querying the <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Instance Metadata Service</a>.</p>
<blockquote>
<p>The Azure Instance Metadata Service (IMDS) provides information about currently running virtual machine instances. You can use it to manage and configure your virtual machines. This information includes the SKU, storage, network configurations, and upcoming maintenance events. </p>
</blockquote>
<p>The service URL is: <code>http://169.254.169.254/metadata/instance?api-version=2021-02-01</code></p>
<h3>Test</h3>
<p>Access from outside Azure VM:</p>
<p><img src="/image/img-ec1c4758-68da-4968-a927-db31d96b961a.png" border="0" loading="lazy"></p>
<p>Access within Azure Windows VM:</p>
<pre class="language-powershell"><code>Invoke-RestMethod -Headers @{"Metadata"="true"} -Method GET -NoProxy -Uri "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | ConvertTo-Json -Depth 64</code></pre>
<p><img src="/image/img-c13ae612-63e8-456b-9d1a-6ab2e94d3f07.png" border="0" loading="lazy"></p>
<p>From a Linux VM:</p>
<pre class="language-bash"><code>curl -s -H Metadata:true --noproxy "*" "http://169.254.169.254/metadata/instance?api-version=2021-02-01" | jq</code></pre>
<p><img src="/image/img-da604c57-0d77-46fb-b220-6d4ef11ba35b.png" border="0" loading="lazy"></p>
<p>It proves the service endpoint can only be reachable from Azure VM.</p>
<h3>Example Code</h3>
<h4>PowerShell</h4>
<pre class="language-powershell"><code>$metadataServiceUrl = "http://169.254.169.254/metadata/instance?api-version=2021-02-01"
$headers = @{
"Metadata" = "true"
}
try {
$response = Invoke-RestMethod -Uri $metadataServiceUrl -Headers $headers -Method Get
Write-Host "Successfully retrieved metadata: "
Write-Output $response
}
catch {
Write-Host "Error retrieving metadata. This may not be an Azure VM or the service may not be available."
}
</code></pre>
<h4>C#</h4>
<pre class="language-csharp"><code>internal class Program
{
static readonly HttpClient httpClient = new HttpClient();
private static async Task Main(string[] args)
{
try
{
string url = "http://169.254.169.254/metadata/instance?api-version=2021-02-01";
HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, url);
request.Headers.Add("Metadata", "true");
HttpResponseMessage response = await httpClient.SendAsync(request);
if (response.IsSuccessStatusCode)
{
string responseData = await response.Content.ReadAsStringAsync();
Console.WriteLine("This code is running on an Azure VM.");
Console.WriteLine(responseData);
}
else
{
Console.WriteLine("This code is not running on an Azure VM.");
}
}
catch (HttpRequestException e)
{
Console.WriteLine("An error occurred: " + e.Message);
}
}
}</code></pre>
<p><img src="/image/img-c2d3df5a-e9a5-43b6-99f5-b83f4abe7f89.png" border="0" loading="lazy"></p>
<h2>Note</h2>
<hr>
<p dir="auto">Please note the following points when using the above code:</p>
<ol>
<li>The Metadata header is necessary to retrieve the information; without it, the request will be denied.</li>
<li>The IP address <code>169.254.169.254</code> is a link-local address used by the Azure Instance Metadata Service. This address is only reachable from within the VM.</li>
<li>You should use the latest <code>api-version</code> parameter value that is supported. At the time this blog post is written, the latest version is <code>2021-02-01</code>, but you should check the <a href="https://learn.microsoft.com/en-us/azure/virtual-machines/instance-metadata-service?tabs=windows" target="_blank" rel="noopener">Azure documentation</a> for any updates.</li>
</ol>34230828-8408-4830-8972-C9DD22F8EEC7How to Detect If Your Application is Running in Azure App Service2023-12-14T08:34:41Z2023-12-14T08:34:41ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>An application needs to know if itself is running in <a href="https://learn.microsoft.com/en-us/azure/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service</a>, so that it can apply special logic and optimization for Azure. </p>
<h2>Solution</h2>
<hr>
<p>When deploying an application to <a href="https://learn.microsoft.com/en-us/azure/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service</a>, the platform automatically assigns a set of environment variables that can be accessed by the application code.</p>
<p>To view the complete list of environment variables applicable to your application at runtime, you can navigate to the "<strong>Advanced Tools</strong>" blade within your App Service Instance.</p>
<p><img src="/image/img-f98c0164-08c7-44e9-8621-43db4cc98e40.png" border="0" loading="lazy"></p>
<p>Then, go to "<strong>Environment</strong>" page. You will see the entire list here. Among them, are a few environment variables that are suitable for identify if the application is running on <a href="https://learn.microsoft.com/en-us/azure/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service</a>.</p>
<ul>
<li><code>WEBSITE_SKU</code></li>
<li><code>WEBSITE_SITE_NAME</code></li>
<li><code>WEBSITE_HOSTNAME</code></li>
</ul>
<p><img src="/image/img-47b044e9-8456-4072-b22c-63f256fed3df.png" border="0" loading="lazy"></p>
<p>Now, in our code, we can try to get the values of those environment variables, example code for ASP.NET Core Razor Page is like:</p>
<pre class="language-markup"><code><tr>
<td>@SharedLocalizer["Azure App Service"]</td>
<td class="text-muted">
WEBSITE_SKU : @Environment.GetEnvironmentVariable("WEBSITE_SKU") <br />
WEBSITE_SITE_NAME: @Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME") <br />
WEBSITE_HOSTNAME : @Environment.GetEnvironmentVariable("WEBSITE_HOSTNAME") <br />
REGION_NAME : @Environment.GetEnvironmentVariable("REGION_NAME") <br />
</td>
</tr>
</code></pre>
<p>Example result:</p>
<p><img src="/image/img-6655443e-a6f3-43b3-a7cb-8abab61ea1db.png" border="0" loading="lazy"></p>9C4D64CA-5B5B-432E-8AE5-4C06125658BECall API with Windows Authentication from Blazor Web Assembly2023-10-20T07:17:07Z2023-10-20T07:17:07ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>I was rewriting an old Angular application to Blazor Web Assembly a couple of days ago. The App is an internal tool, which uses a backend API that has Windows Authentication. In Angular, we used to add <code>withCredentials: true</code> to HTTP interceptor to make it work. The most equivalent method in .NET is typically adding <code>UseDefaultCredentials = true</code> in HttpClient. However, Blazor WASM will gives you an exception for this as indicated in <a href="https://stackoverflow.com/questions/56542696/using-blazor-client-side-consuming-web-api-with-windows-authentication" target="_blank" rel="noopener">this StackOverflow thread</a>: WASM: System.PlatformNotSupportedException: System.Net.Http.HttpClientHandler is not supported on the current platform. </p>
<p>The majority of internet search result so far tell you it's not possible to call Windows Authentication API from Blazor. Really? Let's see how to make it work with a few lines of code. </p>
<h2>Solution</h2>
<hr>
<h3>Approach A</h3>
<p>The original <a href="https://stackoverflow.com/questions/56542696/using-blazor-client-side-consuming-web-api-with-windows-authentication" target="_blank" rel="noopener">StackOverflow thread</a> has 2 answers currently. The answer by <a href="https://stackoverflow.com/users/27754/ca8msm">ca8msm</a> works, but the cost is not to use <code>HttpClient</code>, that we need to do everything manually in order to add <code>BrowserRequestCredentials.Include</code> to the request header. This approach sacrifices what we love about the <code>HttpClient</code>, but gives you ultimate control over the http request details.</p>
<pre class="language-csharp"><code>string APIURL = "https://localhost:44390/api/models";
// create request object and pass windows authentication credentials
var request = new HttpRequestMessage(HttpMethod.Get, APIURL);
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
// send the request and convert the results to a list
var httpResponse = await Http.SendAsync(request);
models = await httpResponse.Content.ReadFromJsonAsync<myModel[]>();</code></pre>
<h3>Approach B</h3>
<p>I come up with a better approach, which can keep the <code>HttpClient </code>we already like.</p>
<p>First, add a custom <code>DelegatingHandler</code>, this is used for adding <code>BrowserRequestCredentials.Include</code> to each request.</p>
<pre class="language-csharp"><code>using Microsoft.AspNetCore.Components.WebAssembly.Http;
public class CredentialsMessageHandler : DelegatingHandler
{
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
return base.SendAsync(request, cancellationToken);
}
}</code></pre>
<p>Second, create your typed client as usual. I prefer typed client, you may also use <code>HttpClientFactory</code>, it doesn't matter. You can find more information about how to make http request in best practice in <a href="https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">this document</a>.</p>
<pre class="language-csharp"><code>public class CardClient
{
private readonly HttpClient http;
public CardClient(HttpClient http)
{
// ...
this.http = http;
}
// ...
}</code></pre>
<p>Finally, register the client in DI, with the <code>CredentialsMessageHandler</code> we wrote in the first step.</p>
<pre class="language-csharp"><code>builder.Services.AddHttpClient<CardClient>(client => client.BaseAddress = new Uri(ApiEndpoint.FullUrl)).AddHttpMessageHandler<CredentialsMessageHandler>();</code></pre>
<p>Now, you can still use the <code>HttpClient</code> the way you always been using it with Windows Authentication enabled API!</p>
<p>BTW, Why I am not answering this in StackOverflow? Because I lost access to my account years ago :(</p>13DA8BFF-9AB9-4109-921B-4EE704CD01F5No Need for a VM, Build Docker Image with Azure Container Registry2023-09-27T02:20:44Z2023-09-27T02:20:44ZEdi WangEdi.Wang@outlook.com<h2>Background</h2>
<hr>
<p>Sometimes, there are instances where we need to create a Docker image but do not have access to a Docker environment or prefer not to set up a virtual machine solely for installing Docker. Of course, we can do this with GitHub Actions, but there is an alternative option that I would like to share in this post: utilizing <a href="https://learn.microsoft.com/en-us/azure/container-registry/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Container Registry</a> to build your Docker image.</p>
<h2>Steps</h2>
<hr>
<h3>Create Azure Container Registry</h3>
<blockquote>
<p>Azure Container Registry is a managed registry service based on the open-source Docker Registry 2.0. Create and maintain Azure container registries to store and manage your container images and related artifacts.</p>
<p>Use Azure container registries with your existing container development and deployment pipelines, or use Azure Container Registry Tasks to build container images in Azure. Build on demand, or fully automate builds with triggers such as source code commits and base image updates.</p>
</blockquote>
<p>You can find and create an Azure Container Registry service from Azure Portal. I've already created one.</p>
<p><img src="/image/img-49d7d81d-3e57-4c44-8dbd-5acd6b9a5ab8.png" border="0" loading="lazy"></p>
<h3>Use ACR Tasks</h3>
<blockquote>
<p><a href="https://learn.microsoft.com/en-us/azure/container-registry/container-registry-tasks-overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener"><strong>ACR Tasks</strong></a> is a suite of features within Azure Container Registry. It provides cloud-based container image building for <a href="https://learn.microsoft.com/en-us/azure/container-registry/container-registry-tasks-overview#image-platforms" data-linktype="self-bookmark">platforms</a> including Linux, Windows, and ARM, and can automate <a href="https://learn.microsoft.com/en-us/azure/container-registry/container-registry-tasks-overview#automate-os-and-framework-patching" data-linktype="self-bookmark">OS and framework patching</a> for your Docker containers.</p>
</blockquote>
<p><img src="/image/img-8583cf04-62d3-4bec-a1ce-01ac055f972f.png" border="0" loading="lazy"></p>
<p dir="auto">To utilize ACR Tasks, you need to access the Azure CLI command line. You have two options for doing so:</p>
<ol>
<li>Install <a href="https://learn.microsoft.com/en-us/cli/azure/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure CLI</a> locally on your machine.</li>
<li>Use <a href="https://learn.microsoft.com/en-us/azure/cloud-shell/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Cloud Shell</a>, which allows you to access the Azure CLI command line directly within your browser.</li>
</ol>
<p><img src="/image/img-b85d2a5a-c0c9-417b-b0f3-f5cf75fce2a7.png" border="0" loading="lazy"></p>
<p>This will open a command line blade under your screen. You can choose PowerShell or Bash as you wish.</p>
<p><img src="/image/img-d54847ac-db33-4585-b824-b870003cebcc.png" border="0" loading="lazy"></p>
<h3>Get source code</h3>
<p>The sample project in this post is Elf (<a href="https://github.com/EdiWang/Elf">https://github.com/EdiWang/Elf</a>), it is the link forward service used by <a href="https://go.edi.wang/" rel="nofollow">https://go.edi.wang</a>. It generates static URLs for redirecting third party URLs. </p>
<p>First, clone it into <a href="https://learn.microsoft.com/en-us/azure/cloud-shell/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Cloud Shell</a>'s home directory.</p>
<pre class="language-bash"><code>git clone https://github.com/EdiWang/Elf.git</code></pre>
<p><img src="/image/img-b43d91d0-7da4-4d09-bb7f-4368dcea320e.png" border="0" loading="lazy"></p>
<p>As you can see, this repo has a <strong>Dockerfile</strong>, which we will use to build the Docker image.</p>
<blockquote>
<p>A <em>Dockerfile</em> is a text document that contains all the commands a user could call on the command line to assemble an image.</p>
</blockquote>
<p><img src="/image/img-62d3e106-2c26-4996-8e23-46fbd14574df.png" border="0" loading="lazy"></p>
<h3>Build Docker Image</h3>
<p>Firstly, if you have multiple Azure subscriptions in your account, you need to set the current context to use the subscription in which the Azure Container Registry is created.</p>
<pre class="language-bash"><code>az account set --subscription "DevTest"</code></pre>
<p>Then, run the <code>az acr build</code> command to build the docker image. In my case, the command will be</p>
<pre class="language-bash"><code>az acr build -t ediwang/elf:acrdemo -r ediwang .</code></pre>
<p>The <code>-r ediwang</code> refers to the Azure Container Registry name.</p>
<p>Now, ACR Tasks will begin to build your docker image.</p>
<p><img src="/image/img-8e9dbd48-e33c-4ef2-b99a-8987de934245.png" border="0" loading="lazy"></p>
<p>After it is completed, you can find your image in <strong>Repositories</strong> blade of the Azure Container Registry.</p>
<p><img src="/image/img-3a9e99de-6aec-4084-b002-37994b6ccffe.png" border="0" loading="lazy"></p>
<h2>Conclusion</h2>
<hr>
<p>In this post, we used ACR Tasks from Azure CLI to build Docker image without setting up a Docker environment either on local machine or a cloud VM. </p>B230D8E8-1758-4ABB-A551-31ED12339952Deploy ChatGPT Next Web to Azure Container Apps with Individual Account Login in 3 minutes2023-09-11T05:30:52Z2023-09-11T05:30:52ZEdi WangEdi.Wang@outlook.com<h2>Background</h2>
<hr>
<p>A couple of months ago, I wrote a blog post of "<a href="/post/2023/5/18/deploy-chatgpt-next-web-to-azure-app-service-with-individual-account-login-in-3-minutes" target="_blank" rel="noopener">Deploy ChatGPT Next Web to Azure App Service with Individual Account Login in 3 minutes</a>". In this blog post, I will share a new approach to deploy ChatGPT Next Web besides App Service, which is to use <a href="https://learn.microsoft.com/en-us/azure/container-apps/overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Container Apps</a>.</p>
<p>Before getting started, let's review the background and goal for doing this task. </p>
<p><a href="https://github.com/Yidadaa/ChatGPT-Next-Web" target="_blank" rel="noopener">ChatGPT Next Web</a> is a widely used ChatGPT web UI project that enables you to deploy a website to your own server using only your Open API key. This allows you to enjoy the same chat experience without having to visit the official Open AI website, which is particularly useful when dealing with network or regional restrictions.</p>
<p>However, the official repository of ChatGPT Next Web does not offer a one-click installation for Azure, and the developer has no plans to add a built-in login feature to restrict access to the website. To address these issues, my goal is to:</p>
<ol>
<li>Deploy ChatGPT Next Web to Azure without the need to create a VM.</li>
<li>Add access control that requires individual accounts to log in to use ChatGPT, without making any changes to the code.</li>
</ol>
<p>My objective is to complete these tasks within three minutes. Let's explore how this can be achieved.</p>
<h2>Create Azure Container Apps</h2>
<hr>
<p>The official <a href="https://github.com/Yidadaa/ChatGPT-Next-Web" target="_blank" rel="noopener">ChatGPT Next Web</a> provides a Docker image. This is how I can run it on Azure without VM. Azure has many PaaS ways to deploy Docker containers:</p>
<ol>
<li>Azure App Service</li>
<li>Azure Container Instance</li>
<li>Azure Container Apps</li>
<li>AKS</li>
</ol>
<p>I choose Azure Container Apps this time because it also has built in Authentication features that I will use in the next step.</p>
<blockquote>
<p>Azure Container Apps is a fully managed environment that enables you to run microservices and containerized applications on a serverless platform. Common uses of Azure Container Apps include:</p>
<ul>
<li>Deploying API endpoints</li>
<li>Hosting background processing jobs</li>
<li>Handling event-driven processing</li>
<li>Running microservices</li>
</ul>
</blockquote>
<p>Go to Azure Portal, create a new Container App.</p>
<p><img src="/image/img-ea3a8a5e-caad-4c92-84f9-647ec48593c4.png" border="0" loading="lazy"></p>
<p>In the <strong>Container </strong>tab, uncheck "Use quickstart image" and select "<strong>Docker Hub or other registries</strong>", enter <strong>yidadaa/chatgpt-next-web</strong> in "Image and tag".</p>
<p><img src="/image/img-ea9065b2-db31-491a-b549-16058dd16b7c.png" border="0" loading="lazy"></p>
<p>We need to configure two environment variables for the Docker container before running the application.</p>
<ol>
<li><strong>OPENAI_API_KEY</strong>: Use your own key bought from Open API official website, usually start with "sk-"</li>
<li><strong>PORT</strong>: 443</li>
</ol>
<p><img src="/image/img-1be8bccd-4ed9-47e0-97c6-04f499566ae6.png" border="0" loading="lazy"></p>
<p>Next, go to "<strong>Ingress</strong>" tab. <strong>Enable </strong>the Ingress option, and set to <strong>Accepting traffic from anywhere</strong>, set port to <strong>443</strong>. Finish creating the Container App.</p>
<p><img src="/image/img-653b5d1f-596a-415d-82a8-688c0ead68c3.png" border="0" loading="lazy"></p>
<p>By now, your ChatGPT web UI is ready to run. You can access the page by default domain or bind your custom domain.</p>
<p><img src="/image/img-d6ca88f2-d5b9-4e1a-a5aa-267473f74a51.png" border="0" loading="lazy"></p>
<p><img src="/image/img-72c0a44d-e590-4f05-90b4-9533752d3fb8.png" border="0" loading="lazy"></p>
<h2>Enable Authentication</h2>
<hr>
<p>By default, your website is publicly accessible from anywhere on the Internet. This can blow up your credit card. We need to enable authentication that asks users to login before using the application.</p>
<p>Although the original ChatGPT Next Web project did not have such features, it is extremely easy to add login providers on Azure without writing any code.</p>
<p>Go to <strong>Authentication </strong>blade, click "<strong>Add identity provider</strong>"</p>
<p><img src="/image/img-b65e74fc-e4d0-4dca-8940-427fa9698a3e.png" border="0" loading="lazy"></p>
<p>You can add any of the listed providers. I am using Microsoft for example. </p>
<p><img src="/image/img-24b79992-90b5-424f-b0ab-45f66444f4d3.png" border="0" loading="lazy"></p>
<p>For Microsoft provider, by default, all accounts in the given Azure AD will have access. To limit accounts to only selected people, see my previous blog post <a href="/post/2019/12/13/how-to-allow-only-selected-users-to-access-an-application-in-azure-ad" target="_blank" rel="noopener"><em>How to Allow Only Selected Users to Access an Application in Azure AD</em></a></p>
<p>After completing this step. Users will be asked to login before opening ChatGPT web UI. </p>
<h2>Conclusion</h2>
<hr>
<p>Azure Container Apps is a wonderful PaaS service. It can run Docker based applications and add individual account login flow to the application without modifying code. All steps are just a few mouse clicks that can be finished in a few minutes.</p>C94932AD-7FBA-4E49-9150-FAD157F216EAHow Azure App Service for Linux Runs Your .NET Application2023-09-01T00:53:09Z2023-09-01T00:53:09ZEdi WangEdi.Wang@outlook.com<h2>Abstract</h2>
<hr>
<p><a href="https://learn.microsoft.com/en-us/azure/app-service/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service</a> expanded its support to <a href="https://azure.microsoft.com/en-us/blog/general-availability-of-app-service-on-linux-and-web-app-for-containers/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">include Linux and containers</a> in 2017. This update brought similar functionalities to the classic App Service on Windows Server. Both versions allow you to run various types of applications, such as .NET, Java, NodeJS, PHP, and Ruby, with features like continuous integration and deployment (CI/CD) and automatic scaling. To illustrate the workings of Azure App Service, this post will focus on an ASP.NET Core application as an example.</p>
<h2>A Simple Test </h2>
<hr>
<p>I created a new web app using <strong>Code </strong>deployment on <strong>Linux</strong>. Notice, I am NOT choosing Docker Container here. </p>
<p><img src="/image/img-95294143-4b6b-45b9-8981-9a10fa57f0e3.png" border="0" loading="lazy"></p>
<h3>Code vs Docker Container</h3>
<p>The difference between <strong>Code </strong>and <strong>Docker Container</strong> is that Code deployment doesn't require you to have a <code>Dockerfile</code> and build your docker image before publishing, while Docker Container requires your image to be ready before publishing to Azure App Service, which means you need to write a <code>Dockerfile</code> for your website and also build the image, push it to Docker Hub or other container registries.</p>
<p>A typical ASP.NET Core application that uses Docker will have a <code>Dockerfile </code>look like this:</p>
<pre class="language-dockerfile"><code>FROM mcr.microsoft.com/dotnet/aspnet:7.0 AS base
LABEL maintainer="edi.wang@outlook.com"
LABEL repo="https://github.com/EdiWang/Elf"
WORKDIR /app
EXPOSE 80
EXPOSE 443
ENV ASPNETCORE_FORWARDEDHEADERS_ENABLED=true
FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build
WORKDIR /src
COPY ["Elf.Api/Elf.Api.csproj", "Elf.Api/"]
RUN dotnet restore "Elf.Api/Elf.Api.csproj"
COPY . .
WORKDIR "/src/Elf.Api"
RUN dotnet build "Elf.Api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "Elf.Api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Elf.Api.dll"]</code></pre>
<p>And, because you need to build your own image when using Docker Container option, this means the Runtime stack is also included in your image, so Azure won't let you choose the program runtime here. When use Code deployment, you only need to build the code, in my case, I am using ASP.NET Core 6.0, Azure manages the runtime for me, so Azure will need to setup the ASP.NET Core 6.0 runtime ready to run my code.</p>
<h3>Detecting container</h3>
<p>This application I wrote is just to detect if the current process is running in Docker as mentioned in <a href="https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-environment-variables?WT.mc_id=AZ-MVP-5002809#dotnet_running_in_container-and-dotnet_running_in_containers?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">this Microsoft document</a>.</p>
<pre class="language-csharp"><code>var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
var isInDocker = Environment.GetEnvironmentVariable("DOTNET_RUNNING_IN_CONTAINER") ?? false.ToString();
app.MapGet("/", () => $"DOTNET_RUNNING_IN_CONTAINER: {isInDocker}");
app.Run();</code></pre>
<p>And it doesn't have a <code>Dockerfile</code></p>
<p><img src="/image/img-44fa1090-a0b8-4a7b-bed0-653b507d19a1.png" border="0" loading="lazy"></p>
<p>When running on my local machine without docker, it shows <code>False</code></p>
<p><img src="/image/img-f63c285a-d499-4d0b-b707-eb8204745f17.png" border="0" loading="lazy"></p>
<p>Now, I publish this code to the Azure App Service Linux instance I created before from Visual Studio.</p>
<p><img src="/image/img-97cbdb4f-0db3-4f57-b339-15265537ed6d.png" border="0" loading="lazy"></p>
<p>As the output indicates, this ASP.NET Core application is running in Docker.</p>
<p><img src="/image/img-c8f5394a-aa25-4425-9ccc-96f12f0dfd60.png" border="0" loading="lazy"></p>
<p>As we can see, even if I did not choose "Docker Container" when creating Azure App Service, my code still runs in Docker. I don't have to build a Docker image for my application, I don't need to learn Docker, I don't even need to be aware of the Docker underlying, Azure manages the runtime for me. </p>
<h2>Explore the underlying Docker infrastructure</h2>
<hr>
<p>To future explore how it works. I use the <a href="https://learn.microsoft.com/en-us/azure/app-service/resources-kudu?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Kudu tool</a> that integrated in every Azure App Service resource. </p>
<p><img src="/image/img-83fc6b88-1e2c-4b3f-ad98-bb93cd015d32.png" border="0" loading="lazy"></p>
<h3>Logs</h3>
<p>From the landing page, I can download "Current Docker logs".</p>
<p><img src="/image/img-fbdeb459-1253-401d-a10a-db44b87ef996.png" border="0" loading="lazy"></p>
<p>In the "<strong>Environment</strong>" menu, I can also find a few information about the Docker version Microsoft is using, and even Azure App Service's SSH password.</p>
<p><img src="/image/img-6008c430-7fe6-4824-9b77-e6b14288ef56.png" border="0" loading="lazy"></p>
<p><img src="/image/img-0b722006-2a4c-40a3-81e8-31c07b9e0b90.png" border="0" loading="lazy"></p>
<p>Docker log file shows how Azure App Service is using Docker to run my application.</p>
<pre class="language-plaintext"><code>2023-08-23T01:47:56.969Z INFO - Logging is not enabled for this container.
Please use https://aka.ms/linux-diagnostics to enable logging to see container logs here.
2023-08-23T01:48:04.719Z INFO - Initiating warmup request to container linux996_3_f13b8c09 for site linux996
2023-08-23T01:48:24.563Z INFO - Container linux996_3_f13b8c09 for site linux996 initialized successfully and is ready to serve requests.
2023-08-23T01:48:25.195Z INFO - Starting container for site
2023-08-23T01:48:25.213Z INFO - docker run -d --expose=8080 --name linux996_4_ed97a4fb -e WEBSITE_SITE_NAME=linux996 -e WEBSITE_AUTH_ENABLED=False -e WEBSITE_ROLE_INSTANCE_ID=0 -e WEBSITE_HOSTNAME=linux996.azurewebsites.net -e WEBSITE_INSTANCE_ID=90f7789cd4cc1d5a4fe2a8c0e9db0d4e3d0c73856fa77c2d8c30202608ce99f6 -e ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=Microsoft.ApplicationInsights.StartupBootstrapper -e DOTNET_STARTUP_HOOKS=/agents/core/StartupHook/Microsoft.ApplicationInsights.StartupHook.dll -e WEBSITE_USE_DIAGNOSTIC_SERVER=True appsvc/dotnetcore:6.0_20230726.2.tuxprod dotnet DockerDetect.dll
2023-08-23T01:48:25.214Z INFO - Logging is not enabled for this container.
Please use https://aka.ms/linux-diagnostics to enable logging to see container logs here.
2023-08-23T01:48:33.368Z INFO - Initiating warmup request to container linux996_4_ed97a4fb for site linux996
2023-08-23T01:48:45.009Z INFO - Container linux996_4_ed97a4fb for site linux996 initialized successfully and is ready to serve requests.</code></pre>
<h3>The Docker image</h3>
<p>From the log, I can see it is using a Docker image named <strong>appsvc/dotnetcore</strong></p>
<p>This used to be published in Docker Hub (<a href="https://hub.docker.com/r/appsvc/dotnetcore/tags">https://hub.docker.com/r/appsvc/dotnetcore/tags</a>), but it hasn't been updated for 2 years. So is its code on GitHub (<a href="https://github.com/Azure-App-Service/dotnetcore)">https://github.com/Azure-App-Service/dotnetcore)</a>. The tag from the log is <strong>6.0_20230726.2.tuxprod</strong>, it isn't on the outdated Docker Hub image. After some digging, it is moved to Microsoft Artifact Registry (mcr.microsoft.com), which can be pulled from anywhere:</p>
<pre class="language-bash"><code>docker pull mcr.microsoft.com/appsvc/dotnetcore:6.0_20230726.2.tuxprod</code></pre>
<h3>How does it run my code</h3>
<p>This <strong>appsvc/dotnetcore</strong> image is only the ASP.NET Core 6.0 runtime, it doesn't include the application files used by Azure customers. My code isn't in the image, how does it run my application?</p>
<p>From the log above, I can see a parameter that starts my application</p>
<pre class="language-bash"><code>dotnet DockerDetect.dll </code></pre>
<p>This is the exact same command used by .NET runtime to run any ASP.NET Core application.</p>
<p>Now, go deeper into the image itself to find it's <code>Entrypoint</code>:</p>
<pre class="language-bash"><code>docker inspect mcr.microsoft.com/appsvc/dotnetcore:6.0_20230726.2.tuxprod</code></pre>
<p>Gives </p>
<pre class="language-bash"><code>"WorkingDir": "/home/site/wwwroot",
"Entrypoint": [
"/bin/init_container.sh"
],</code></pre>
<p>It also shows how "<code>DOTNET_RUNNING_IN_CONTAINER</code>" environment variable is set to <code>true</code>, which is what I used to determine a Docker environment in the previous section.</p>
<pre class="language-bash"><code>"Env": [
"PATH=/opt/dotnetcore-tools:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/home/site/wwwroot",
"ASPNETCORE_URLS=",
"DOTNET_RUNNING_IN_CONTAINER=true",
"USER_DOTNET_AI_VERSION=2.8.42",
"ORYX_AI_CONNECTION_STRING=InstrumentationKey=4aadba6b-30c8-42db-9b93-024d5c62b887",
"DOTNET_VERSION=6.0",
"ASPNETCORE_LOGGING__CONSOLE__DISABLECOLORS=true",
"PATH_CA_CERTIFICATE=/etc/ssl/certs/ca-certificate.crt",
"LANG=C.UTF-8",
"LANGUAGE=C.UTF-8",
"LC_ALL=C.UTF-8",
"CNB_STACK_ID=oryx.stacks.skeleton",
"PORT=8080",
"SSH_PORT=2222",
"WEBSITE_ROLE_INSTANCE_ID=localRoleInstance",
"WEBSITE_INSTANCE_ID=localInstance",
"ASPNETCORE_FORWARDEDHEADERS_ENABLED=true",
"HOME=/home"
]</code></pre>
<p>Run the image on my local machine to see the content of <code>init_container.sh</code></p>
<pre class="language-bash"><code>docker run -it --rm --entrypoint "bash" mcr.microsoft.com/appsvc/dotnetcore:6.0_20230726.2.tuxprod</code></pre>
<pre class="language-bash"><code>cat /bin/init_container.sh</code></pre>
<p>From this file, we can see the startup command location</p>
<pre class="language-bash"><code>startupCommandPath="/opt/startup/startup.sh"</code></pre>
<p>But this file is created on the fly, so we can't <code>cat </code>this currently. It is generated by <code>generate_and_execute_startup_script.sh</code></p>
<pre class="language-bash"><code># Dockerfile copies generate_and_execute_startup_script.sh from /templates/startup_scripts directory based on assetDirs in tuxib.yml
source /bin/generate_and_execute_startup_script.sh</code></pre>
<p>This file is simple. We can see it is calling <a href="https://github.com/microsoft/Oryx" target="_blank" rel="noopener">Oryx</a> to generate startup command. </p>
<blockquote>
<p>Oryx is a build system which automatically compiles source code repos into runnable artifacts. It is used to build web apps for <a href="https://azure.microsoft.com/services/app-service/?WT.mc_id=AZ-MVP-5002809">Azure App Service</a> and other platforms.</p>
</blockquote>
<pre class="language-bash"><code>#!/bin/bash
oryxArgs="create-script -appPath $appPath -output $startupCommandPath -defaultAppFilePath $defaultAppPath \
-bindPort $PORT -bindPort2 '$HTTP20_ONLY_PORT' -userStartupCommand '$userStartupCommand' $runFromPathArg"
echo "Running oryx $oryxArgs"
eval oryx $oryxArgs
exec $startupCommandPath</code></pre>
<p>I don't want to <a href="https://996.icu/" target="_blank" rel="noopener">work 996</a> to test the script generation on my local environment. Instead, the generated script can be found from the web SSH in Kudu.</p>
<p><img src="/image/img-77af18a7-71a8-44e7-99b2-640172676a98.png" border="0" loading="lazy"></p>
<p>As you can see, the script is finally calling <code>dotnet DockerDetect.dll</code>, which is from the <code>docker run</code> parameter that Azure provided.</p>
<h3>What else is in the image</h3>
<p>Labels that show its build information.</p>
<pre class="language-bash"><code>"Labels": {
"com.microsoft.oryx.build-number": "20230721.1",
"com.microsoft.oryx.git-commit": "e01b34550f75a38df92af2041687cb2b0ad41ec0",
"com.microsoft.oryx.release-tag-name": "20230721.1",
"io.buildpacks.stack.id": "oryx.stacks.skeleton",
"maintainer": "Azure App Services Container Images <appsvc-images@microsoft.com>"
}</code></pre>
<p>A bunch of .NET tools are also available inside the image. App Service can use these tools to monitor and trouble shoot the application.</p>
<pre class="language-bash"><code>root@55a2f2c9e303:/opt# ls
dotnetcore-tools startup startupcmdgen
root@55a2f2c9e303:/opt# cd dotnetcore-tools/
root@55a2f2c9e303:/opt/dotnetcore-tools# ls -la
total 856
drwxr-xr-x 3 root root 4096 Jul 21 01:34 .
drwxr-xr-x 1 root root 4096 Jul 26 11:53 ..
drwxr-xr-x 9 root root 4096 Jul 21 01:34 .store
-rwxr-xr-x 1 root root 142840 Jul 21 01:34 dotnet-counters
-rwxr-xr-x 1 root root 142840 Jul 21 01:33 dotnet-dump
-rwxr-xr-x 1 root root 142840 Jul 21 01:34 dotnet-gcdump
-rwxr--r-- 1 root root 142840 Jul 6 20:17 dotnet-monitor
-rwxr-xr-x 1 root root 142840 Jul 21 01:33 dotnet-sos
-rwxr-xr-x 1 root root 142840 Jul 21 01:33 dotnet-trace</code></pre>
<p>.NET runtime is located in <code>/usr/share/dotnet</code></p>
<pre class="language-bash"><code>root@55a2f2c9e303:/usr/share/dotnet# ls -la
total 244
drwxrwxr-x 4 root root 4096 Jun 20 19:39 .
drwxr-xr-x 1 root root 4096 Jul 26 11:53 ..
-rw-rw-r-- 1 root root 1116 Jun 20 19:31 LICENSE.txt
-rw-rw-r-- 1 root root 78479 Jun 20 19:31 ThirdPartyNotices.txt
-rwxr-xr-x 1 root root 141760 Jun 20 19:33 dotnet
drwxrwxr-x 3 root root 4096 Jun 20 19:39 host
drwxrwxr-x 4 root root 4096 Jul 21 01:34 shared</code></pre>
<p>Default application that shows a welcome page of a new created App Service instance is located in <code>/defaulthome/hostingstart</code></p>
<pre class="language-bash"><code>root@55a2f2c9e303:/defaulthome/hostingstart# ls -la
total 296
drwxr-xr-x 1 root root 4096 Jul 26 11:53 .
drwxr-xr-x 1 root root 4096 Jul 26 11:53 ..
-rwxr-xr-x 1 root root 142760 May 10 2021 hostingstart
-rw-r--r-- 1 root root 103261 May 10 2021 hostingstart.deps.json
-rw-r--r-- 1 root root 5632 May 10 2021 hostingstart.dll
-rw-r--r-- 1 root root 18756 May 10 2021 hostingstart.pdb
-rw-r--r-- 1 root root 311 May 10 2021 hostingstart.runtimeconfig.json
-rw-r--r-- 1 root root 488 May 10 2021 web.config
drwxr-xr-x 2 root root 4096 Jul 26 11:53 wwwroot
root@55a2f2c9e303:/defaulthome/hostingstart# cd wwwroot/
root@55a2f2c9e303:/defaulthome/hostingstart/wwwroot# ls
hostingstart.html</code></pre>
<p>A part of the HTML code</p>
<pre class="language-markup"><code><div class="pl-5 ml-5 col-xl-5 col-lg-5 col-md-10 col-sm-11 col-xs-11">
<div class="container-fluid">
<div class="row">
<h2 id="upRunning">Your web app is running and waiting for your content</h2>
</div>
<div class="row mt-4 pt-4">
<div id="appIsLive" style="font-size:16px;width: 516px;">Your web app is live, but we don’t have
your content yet. If you’ve already deployed, it could take up to 5 minutes for your content
to show up, so come back soon.</div>
</div>
<div class="row mt-4">
<h5 id="supporting" class="mt-5"><img height="50" width="50"
src="https://appservice.azureedge.net/images/linux-landing-page/v4/built-dotnet.svg" /> Built with .NET</h5>
</div>
</div>
</div></code></pre>
<h3>Application files</h3>
<p>The final puzzle is how it find my <code>DockerDetect.dll</code>, it took some tricks to find my application file. Just <code>ls</code> in the default SSH landing directory won't show anything. </p>
<p><img src="/image/img-6a0a5213-fdd7-4a5b-b208-6ab9437e41fa.png" border="0" loading="lazy"></p>
<h2>Summary</h2>
<hr>
<p>In this post, I created an ASP.NET Core 6.0 web application and deployed it to <a href="https://learn.microsoft.com/en-us/troubleshoot/azure/app-service/faqs-app-service-linux?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure App Service for Linux</a>. Azure is using a Docker image to run my code. The image contains ASP.NET Core runtime and uses Oryx to generate a startup script that finally calls dotnet to run my application files. </p>7F5C271A-3FF1-4738-8B3D-FF26626245A0Microsoft MVP Testimonial: Transforming Lives in the Elite Tech Community2023-08-17T01:59:50Z2023-08-17T01:59:50ZEdi WangEdi.Wang@outlook.com<blockquote>
<p>This is the English translation of my post in Simplified Chinese by <a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Open AI</a>, you may also <a href="/post/2023/8/15/microsoft-mvp" target="_blank" rel="noopener">read the article in original language</a>.</p>
</blockquote>
<h2>Introduction</h2>
<hr>
<p>The Microsoft Most Valuable Professional (MVP) award is a prestigious recognition bestowed by Microsoft to individuals who have made outstanding contributions in the technical community. I am honored to have received this award continuously for the past six years. In this article, I will share my understanding of the MVP award and its impact on both myself and the community.</p>
<p style="text-align: center;"><img src="/image/img-f122dff1-7663-4b1a-ad7f-2179e5a02908.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: Microsoft MVP Award Trophy)</span></p>
<h2>Foreshadowing</h2>
<hr>
<p>Since my childhood, I have had a strong interest in computers. In the summer vacation of 2008, I took advantage of that time to self-study .NET programming, preparing myself for the upcoming college life. During that period, I primarily relied on tutorials provided by the "MSDN Webcast" website, and I distinctly remember that before each lesson, the instructor, Mr. Su Peng, proudly announced his title: <strong>Microsoft Most Valuable Professional (MVP) and Microsoft Invited Lecturer</strong>.</p>
<p>Upon entering college, I realized that the course content was far from sufficient to meet the needs of real-world programmers. As a result, I personally purchased the book "C# and .NET 3.5 Advanced Programming" and was pleasantly surprised to see the<strong> Microsoft MVP logo </strong>on its cover.</p>
<p style="text-align: center;"><img src="/image/img-181b09ef-6916-4704-93d1-91cb30366f30.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: <em>Pro C# 2008 and the .NET 3.5 Platform</em> book)</span></p>
<p>The first year after graduation, being a passionate Microsoft fan, I spent half of my monthly salary to attend the TechEd 2013 technology conference. To my delight, I discovered that <strong>many of the speakers were Microsoft MVPs</strong>. The title "Most Valuable Professional" continued to shine brightly in my heart, filling me with deep admiration.</p>
<h2>A Good Person's Recommendation</h2>
<hr>
<p>Although my college life was ordinary and unremarkable, I have been fortunate in my journey in the field of technology. My first job was at <a href="https://www.infosys.com/" target="_blank" rel="noopener">Infosys</a>, an Indian software outsourcing company. While it may not have the same reputation as BAT (Baidu, Alibaba, Tencent), my colleagues there were all exceptional individuals. Among them was a person known as "Loushang Nage Shushu" (<a href="https://mvp.microsoft.com/en-US/mvp/profile/54b8c201-acc5-e611-80f9-3863bb34cb20" target="_blank" rel="noopener">Zheng Xing</a>), who crossed paths with me on the same project. At that time, Microsoft's <a href="https://en.wikipedia.org/wiki/Windows_Phone" target="_blank" rel="noopener">Windows Phone</a> was rising rapidly (although it eventually met its demise), and <a href="https://mvp.microsoft.com/en-US/mvp/profile/54b8c201-acc5-e611-80f9-3863bb34cb20" target="_blank" rel="noopener">Zheng Xing</a>, with his unique talents in Windows Phone development, won the legendary "<strong>Microsoft Most Valuable Professional</strong>" award, which deeply impressed and inspired me.</p>
<p>I developed a habit during my college days, which was to <strong>write technical blogs</strong>. I built my blog from scratch using ASP.NET, allowing me to learn, practice, and document my experiences simultaneously. Since I didn't have a girlfriend and had plenty of free time outside of work, I dedicated a significant amount of time to technical research. Every month, I managed to produce 6-7 articles, unaware that this was already a substantial output. Little did I know that writing blogs was considered an important contribution path in the evaluation process of the Microsoft MVP program.</p>
<p>In 2015, the release of Windows 10 once again ignited the excitement within me as a passionate follower. I stayed up late, tirelessly engaging in UWP (<a href="https://learn.microsoft.com/en-us/windows/uwp/get-started/universal-application-platform-guide?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Universal Windows Platform</a>) development and accumulated a substantial amount of blog content and a few popular applications (although Microsoft's product line in this area did not ultimately succeed). One day, <a href="https://mvp.microsoft.com/en-US/mvp/profile/54b8c201-acc5-e611-80f9-3863bb34cb20" target="_blank" rel="noopener">Zheng Xing</a> suggested that I consider applying for the Microsoft MVP program, and he recommended me to <a href="https://twitter.com/liangmew" target="_blank" rel="noopener">Christina Liang</a>, the MVP lead for the Greater China region. At that time, I didn't have high expectations. After all, I was just an ordinary person from a second-tier university, working in an average company. How could I possibly be on the same level as those experts who write books and give speeches?</p>
<p>To my surprise, after several months of communication and evaluation by Microsoft's headquarters in the United States, I was successfully awarded the Microsoft Most Valuable Professional (MVP) title in October 2017, focusing on Windows development technology. This news caught me completely off guard and left me speechless.</p>
<p>Now I believe even more strongly that <strong>education is just a phase in one's life, and that skills and abilities are lifelong assets</strong>.</p>
<p>Two years later, I shifted my focus away from Windows and delved into the field of cloud computing. I applied for the <a href="https://go.edi.wang/aka/mvp" target="_blank" rel="noopener">Azure MVP</a> and was successfully awarded the title, a recognition that continues to this day.</p>
<h2>The enthusiastic community and top-tier technical circles</h2>
<hr>
<p>One month before the official announcement, Christina invited me and a few current MVPs in Shanghai to meet up. During this process, I gradually learned that Microsoft MVPs have their own activities, such as the <strong><a href="https://summit.microsoft.com/en-us/" target="_blank" rel="noopener">MVP Global Summit</a>, MVP China Summit</strong>, and they often send out small gifts. They also form a close-knit community, where they can organize and participate in technical events, and deliver speeches at conferences. All of this surprised me greatly and left me in awe, as I had previously mistakenly believed that becoming an MVP was like passing an English proficiency test and that was the end of it. In reality, after becoming an MVP, I realized that I had become part of an active technical community.</p>
<p><img src="/image/img-ea4f04c7-cae4-46ae-a584-baea3271f99e.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: 2017 China MVP Community Connection)</span></p>
<p>This technical community is unlike any I have encountered before. Compared to the dull and traditional programmers, this community is filled with enthusiasm and a positive atmosphere. This is precisely the spirit of Microsoft MVPs: <strong>not only possessing technical expertise but also being eager to share it</strong>.</p>
<p>Due to the community members coming from all over the world, <strong>diversity and inclusion</strong> have become particularly important. Regardless of gender, race, skin color, beliefs, political views, or even physical and mental characteristics, MVPs prioritize mutual respect. In the Chinese MVP community, there are even some who have met through this program and eventually entered into marriage.</p>
<p><img src="/image/img-6b09ddd1-bc02-4f77-8cfb-0cc35b99bca9.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: China MVP at Tech Summit 2018 conference)</span></p>
<p>One of the most unforgettable experiences during my time in the MVP program was attending the <strong>MVP Global Summit at the Microsoft headquarters in the United States in 2019</strong>, which I mentioned in an article before. This annual event brings together MVPs from around the world, gathering at the Microsoft campus to listen to Microsoft's untold technical secrets and share their own research achievements. In the Microsoft campus, <strong>every step you take could potentially lead you to encounter globally renowned tech gurus</strong>. Some of these gurus have <a href="https://en.wikipedia.org/wiki/Anders_Hejlsberg" target="_blank" rel="noopener">created programming languages used by 40% of programmers worldwide</a>, and some have released software packages with <a href="https://github.com/NuGet/NuGetGallery/issues/8476" target="_blank" rel="noopener">download counts about to exceeding the maximum value of a 32-bit integer</a>. MVPs had the privilege of taking photos with them, creating a rare and shining moment in their lives.</p>
<p><img src="/image/img-42308ab1-5868-4a42-8655-edbf3b1066cc.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: My photo with <a href="https://www.hanselman.com/" target="_blank" rel="noopener">Scott Hanselman</a>)</span></p>
<p>Even more enviable is the opportunity to personally meet Microsoft founder <a href="https://en.wikipedia.org/wiki/Bill_Gates" target="_blank" rel="noopener">Bill Gates</a> and CEO <a href="https://en.wikipedia.org/wiki/Satya_Nadella" target="_blank" rel="noopener">Satya Nadella</a>. However, as a homebody, I deeply regret missing out on these two chances, and the feeling of remorse fills me with regret.</p>
<p>As the saying goes, a person's social circle is crucial as it determines their knowledge and perspective. The Microsoft MVP community is a top-tier social circle in the field of technology. Compared to the demanding <a href="https://en.wikipedia.org/wiki/996_working_hour_system" target="_blank" rel="noopener">996 work culture</a>, being able to join this community is like <a href="https://www.cnbc.com/2019/04/15/alibabas-jack-ma-working-overtime-is-a-huge-blessing.html" target="_blank" rel="noopener">a true blessing</a> earned from past lives.</p>
<h2>Making friends with open-source software</h2>
<hr>
<p>Before becoming an MVP, I had reservations about open-source software. Until 2014, Microsoft itself was widely criticized as an adversary of open-source. However, through interactions with the MVP community, I began to<strong> embrace open-source</strong> and gradually changed my perspective.</p>
<p>The very first program I chose to open-source was one of my Windows 10 UWP applications that led to my MVP recognition. One of them, "<a href="https://github.com/character-map-uwp/Character-Map-UWP" target="_blank" rel="noopener">Character Map UWP</a>," received significant contributions from the community after being open-sourced. It not only made great strides in terms of functionality and performance but also achieved tremendous success among professionals and a large user base. The app has surpassed millions of installations, and it is even being used by employees of Microsoft's Windows product group. Such achievements would not have been possible without the support of open-source.</p>
<p><img src="/image/img-c2f566d7-ad68-43d4-b8c3-a9036849a189.png" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(Figure: Character Map UWP Application Rewritten by Community Experts)</span></p>
<p>Another highly successful open-source project of mine is <a href="https://github.com/EdiWang/Moonglade" target="_blank" rel="noopener">my technical blog</a>. In 2018, when I chose to completely rewrite this project using ASP.NET Core, I also made the decision to open-source it. I integrated it with my technical expertise and incorporated 14 Azure services. However, I also listened to the community's feedback and avoided vendor lock-in, making my blog system capable of running independently of Azure. This decision was well received by users, and the community provided support for non-Microsoft platforms such as MySQL and MinIO. Now, numerous technical professionals both domestically and internationally, including Microsoft employees, use my blog system to build their own blogs. Without open-source, none of these achievements would have been possible.</p>
<p>By leading my own open-source projects and contributing to other projects, I often encounter issues related to the .NET and Azure platforms and can provide constructive suggestions. I communicate with Microsoft's Product Group (PG) and utilize this feedback channel to convey these issues and suggestions to them. This is also one of the daily responsibilities as an MVP.</p>
<p>I have witnessed the power and flexibility of open-source software and its positive impact on the technology community. I have started actively participating in open-source projects, contributing code and documentation, and collaborating with other members of the open-source community. This mindset shift has <strong>not only expanded my technical horizons but also made me more open and inclusive in embracing different technology solutions</strong>. </p>
<p>Today, open-source software has become an indispensable part of my daily work and life. It has become a valuable asset that I have gained in the MVP community.</p>
<h2>Fame or profit</h2>
<hr>
<p>The book "<a href="https://en.wikipedia.org/wiki/The_Cathedral_and_the_Bazaar" target="_blank" rel="noopener">The Cathedral and the Bazaar</a>" provides a deep analysis of the open-source model, pointing out that in this model, the hacker spirit seeks a "gift culture" that goes beyond monetary rewards. This culture is more inclined towards gaining reputation and status within the technology community. People do not contribute without reason, and this is true for MVPs as well. Apart from the benefits such as genuine software provided by Microsoft and its partners, we do not receive monetary rewards but rather a sense of honor.</p>
<p>I come from humble beginnings, without a prestigious education or wealthy background. However, becoming a Microsoft MVP has been the sole title I proudly hold. Having the honor of being a Microsoft MVP undoubtedly provides me with many advantageous opportunities in my professional circle. I owe this to the reputation established by the veteran MVPs for this brand. Within my company, my colleagues trust my technical abilities more because I am an MVP. In my daily life, when I introduce myself to others, I often receive appreciation for the title of Microsoft MVP.</p>
<p>I deeply admire Microsoft's mission. From the initial goal of "<strong>a computer on every desk and in every home</strong>" to the current mission of "<strong>empower every person and every organization on the planet to achieve more</strong>", these great objectives drive the continuous progress of human civilization. Becoming a Microsoft MVP allows me to be a part of this driving force behind the mission, and it is something I take great pride in.</p>
<p>Compared to those who earn their living through performance and eloquence, it is not shameful for technical professionals to pursue fame and fortune through their intelligence and skills. This is another important asset that the MVP program possesses.</p>
<h2>Always be humble</h2>
<hr>
<p>Despite receiving the Microsoft MVP award, I always maintain a humble attitude. I understand that the significance of this award lies in my contributions to the technical community, rather than just personal honor.</p>
<p>Firstly, <strong>six years in the MVP community is not exceptional, in fact, it's relatively short</strong>. MVPs worldwide undergo a renew process every year, where Microsoft in the United States reviews the technical contributions of each MVP over the past year to ensure the quality and value of the program. If we become complacent and fail to persist in learning and improving, we run the risk of being phased out. Therefore, the six years of MVP experience are also a continuous growth process.</p>
<p>Secondly, in the field of technology, the more we learn, the more we realize our own ignorance. Take my award category as an example, Microsoft Azure has hundreds of products and services, and I am only familiar with a dozen or so. In the programming industry, we often encounter people who think they have mastered the entire software development just because they can write CRUD operations. I have also had this arrogant stage when I was young, although I have now left the realm of being a "frog at the bottom of a well", but I am well aware that the land and sky I see after climbing out of the well do not represent the entirety of the world. There is still the Earth, the solar system, the Milky Way galaxy, and the entire universe. The more we look outward, the more we realize our ignorance and insignificance.</p>
<p>I will continue to dedicate myself to sharing knowledge, helping others, and driving technological progress. I believe that <strong>humility is an important quality for a successful technical expert</strong>. It keeps me eager to learn and grow, and constantly reminds me that there is always more to learn.</p>
<h2>Closing</h2>
<hr>
<p>Several years ago, Microsoft Most Valuable Professional (MVP) set an excellent example for me in the technical community, which ultimately led me to decide to join the program through hard work. The Microsoft MVP program has allowed me to enter the top-tier technology circle, meet various outstanding talents, and has brought me abundant benefits and reputation. It has also changed my perspective on open-source software. I sincerely appreciate Microsoft for providing me with such a platform; it has been a crucial experience in my life. I will continue to remain humble and contribute to the technical community through my passion and abilities.</p>0B86092D-313B-4AC6-A64E-635FE38196E4Solving "npm install" ECONNRESET Error on Azure DevOps2023-08-16T13:34:49Z2023-08-16T13:34:49ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>A few months ago, my Angular project on Azure DevOps started to have build failure very often. Error message is "<span style="color: rgb(224, 62, 45);">npm ERR! network read ECONNRESET</span>", which indicates npm can't connect to internet. This problem does not happen every time, usually re-run failed jobs several times can produce a good build. But still, this is very annoying. So, I took some time to investigate and solve it.</p>
<p><img src="/image/img-5c2f53b9-9306-495b-beb4-2dd4998edae5.png" border="0" loading="lazy"></p>
<h2>Root Cause</h2>
<hr>
<p>First, I thought it was a IP rate limit by npm that reject Azure pipeline's agent. So I set up a VM on Azure as a self-host build agent. However, the problem continues.</p>
<p>Then, I setup the same build agent on my laptop, which is outside of Azure environment, the build succeed every time. It seems to be npm rejecting the entire Azure IP address. But I can open <a href="https://registry.npmjs.org/">https://registry.npmjs.org/</a> without any problem on my Azure VM, how come the IP limit?</p>
<p>Finally, I added <strong>verbose </strong>log to the npm install task in my pipeline yaml file like this:</p>
<pre class="language-yaml"><code>jobs:
- job: Angular
pool:
vmImage: 'ubuntu-latest'
steps:
- task: Npm@1
displayName: 'npm install'
inputs:
workingDir: '$(System.DefaultWorkingDirectory)/src'
command: 'install'
verbose: true</code></pre>
<p>Run the build again, check it's output</p>
<p><img src="/image/img-29179f16-8207-4c66-b91e-af755c7246df.png" border="0" loading="lazy"></p>
<p>The log indicates that some packages are being fetched from Tencent registry instead of the official <a href="https://registry.npmjs.org/">registry.npmjs.org</a></p>
<p>Azure has problems communicating with Tencent server, this is why the build will fail very often.</p>
<p>The reason why these packages are going to use Tencent server is because the URLs are written in <code>package-lock.json</code> file. This file is automatically modified when developers run <code>npm install</code> on their machines. A typical developer in China would have setup thier npm to use Tencent or Taobao source, due to the national firewall usually block official npm registry. </p>
<pre class="language-bash"><code>npm config set registry https://mirrors.cloud.tencent.com/npm/</code></pre>
<p>And when the developer submit the code, <code>package-lock.json</code> will have this URL inside it.</p>
<h2>Fix</h2>
<hr>
<p>First, let all developers in the team use official npm registry.</p>
<pre class="language-bash"><code>npm config set registry https://registry.npmjs.org/</code></pre>
<p>Then, delete <code>node_modules </code>folder and <code>package-lock.json</code> under the project directory</p>
<p>Then, run <code>npm install </code>on the dev machine again.</p>
<p>Now, all packages will be updated to use the official npm registry.</p>
<p><img src="/image/img-70a6638c-7c62-425e-9537-5b2a35a72450.png" border="0" loading="lazy"></p>
<p>Now, submit the code, Azure DevOps now runs <code>npm install</code> without network error!</p>
<p><img src="/image/img-8584a7d9-9e75-4cba-b59d-43af932cd5a8.png" border="0" loading="lazy"></p>F3B04A3A-7C46-4CF7-BAC9-2245E8387111微软最有价值专家(MVP)感言:改变人生的顶级技术圈2023-08-15T06:15:46Z2023-08-15T06:15:46ZEdi WangEdi.Wang@outlook.com<h2>引言</h2>
<hr>
<p>微软最有价值专家(<a href="https://mvp.microsoft.com/" target="_blank" rel="noopener">Microsoft Most Valuable Professional</a>,简称MVP)奖励是微软致力于表彰在技术社区中做出杰出贡献的个人的荣誉称号。我很荣幸地连续获得了这个奖项已经六年。在这篇文章中,我将分享我对MVP奖励的理解,以及这个奖项对我个人和社区的影响。</p>
<p style="text-align: center;"><img src="/image/img-f122dff1-7663-4b1a-ad7f-2179e5a02908.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:微软MVP奖杯)</span></p>
<h2>伏笔</h2>
<hr>
<p>从我小时候就对计算机充满了浓厚的兴趣,到了2008年高三暑假的时候,我利用着这段时间自学了.NET编程,为即将到来的大学生活做好准备。在那段时间,我主要参考了当年的“MSDN Webcast”网站提供的教程,其中苏鹏老师每一节课前都自豪地宣布自己的头衔:<strong>微软最有价值专家(MVP)和微软特邀讲师</strong>。进入大学后,我发现课程内容远远无法满足真实世界程序员的需求,于是我自费购买了《C#与.NET 3.5高级程序设计》这本书,在书的封面上惊喜地看到<strong>微软MVP的标志</strong>。</p>
<p style="text-align: center;"><img src="/image/img-181b09ef-6916-4704-93d1-91cb30366f30.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:《C#与.NET 3.5高级程序设计》)</span></p>
<p>毕业后的第一年,由于我一直是微软的狂热粉丝,我花了半个月的工资自费参加了TechEd 2013技术大会,惊喜地发现<strong>许多讲师都是微软MVP</strong>。“最有价值专家”称号一直在我心中闪耀,让我深深钦佩不已。</p>
<h2>一个好人的推荐</h2>
<hr>
<p>虽然我的大学平凡无奇,但在技术之路上,好运却对我青睐有加。我的首份工作是在印度软件外包公司Infosys,虽不及BAT的盛名,然而,我的同仁皆匿迹高人。其中一位“楼上那个蜀黍”(邢郑),便与我在同一项目中结缘。当时,微软的Windows Phone便势如破竹崛起(尽管最终暴尸荒野),楼叔凭借自己在Windows Phone开发领域中的独特才能,一举摘得了传说中的“<strong>微软最有价值专家</strong>”奖项,令我深感佩服之至。</p>
<p>我当时养成了一个从大学时代开始的习惯,那就是<strong>撰写技术博客</strong>。我的博客是我亲手采用ASP.NET编写的,可谓一边学习,一边实践,一边记录经验。因为没有能力寻觅女友,工作之余,我还有大量的时间用来进行技术研究。每个月,我都能产出6-7篇文章,而我自己并未意识到这已经是相当庞大的产出量。我同样不知道,编写博客在微软MVP项目组的评审中,是一种重要的技术贡献路径之一。</p>
<p>到了2015年,Windows 10的发布再次激发了我这个狂热追随者内心的激动。我熬夜奋战UWP开发,并积累了大量的博客内容和几款颇受欢迎的应用程序(最终微软这个产品线并未取得成功)。有一天,楼叔告诉我,我可以考虑申请微软MVP,于是他向大中华区的MVP项目负责人Christina Liang推荐了我。当时,我并没有抱有太大的期望。毕竟,我只是一个来自二本学校、工作在普通公司的普通人,如何能与那些撰写书籍、发表演讲的大神们堪称同一级别的存在呢?</p>
<p>没曾料到,在经过数个月的沟通和微软美国总部的评审后,我于2017年10月成功当选为微软最有价值专家(MVP),专注于Windows开发技术领域。这个消息着实让我措不及防,简直让我瞠目结舌。</p>
<p>现在我更加相信,<strong>学历只是过去的一个阶段,而能力是一个人终身的财富</strong>。</p>
<p>2年后,我不再向Windows投入精力,转向了云计算领域,并申请成功为Azure方向的MVP,一直至今。</p>
<h2>热情的社区,顶级的技术圈子</h2>
<hr>
<p>在正式公告之前的一个月,Christina邀请了我和几位在上海的现任MVP们见面。在此过程中,我逐渐了解到微软MVP们有着许多自己的活动,例如<strong>MVP全球峰会、MVP中国峰会</strong>等,经常发一些小礼品,并且他们自身也组成了一个紧密的群体,可以一起举办技术活动,组织和参与技术大会的演讲。这一切令我感到惊讶不已,简直让我膝盖一软,因为我之前错误地认为成为MVP就像通过英语四至六级考试后,一切就此结束了。实际上,成为MVP之后才意识到,自己融入了一个活跃的技术社区。</p>
<p><img src="/image/img-ea4f04c7-cae4-46ae-a584-baea3271f99e.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:2017 微软MVP中国峰会)</span></p>
<p>这个技术社区与我以前接触的任何一个都不一样。相比于那些沉闷守旧的程序员,这个社区充满了热情和积极的氛围。这正是微软MVP们的精神所在:<strong>不仅拥有技术,更乐于分享技术</strong>。</p>
<p>由于社区成员来自世界各地,拥有<strong>多样性和包容性</strong>变得尤为重要。无论是性别、种族、肤色、信仰、政治观点,甚至是身体和精神特点,MVP们都注重相互尊重。在中国MVP社区中,甚至有一些因为这个项目而相识并最终走到了婚姻殿堂,他们成功地超越了程序员鄙视链的桎梏。</p>
<p><img src="/image/img-6b09ddd1-bc02-4f77-8cfb-0cc35b99bca9.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:Tech Summit 2018 微软MVP合影)</span></p>
<p>在MVP项目的岁月中,最让我难以忘怀的体验莫过于<strong>2019年在美国微软总部参加的MVP全球峰会</strong>,我曾经在<a href="/post/2019/12/23/2019-year-review-looking-back-moving-ahead" target="_blank" rel="noopener">这篇文章</a>里有所介绍。这是每年一度的盛会,汇集了来自世界各国的MVP们,他们齐聚微软总部,一同聆听微软不可言宣的技术秘密,分享各自的研究成果。<strong>在微软美国园区,每走一步路都可能会遇到全球知名的技术大咖</strong>,其中一些大咖创造了全球40%的程序员使用的编程语言,还有一些大咖发布的软件包下载量甚至超过了32位整数类型的最大值,导致微软的系统直接溢出。MVP们有幸能与他们合影,这成为人生中难得的璀璨时刻。</p>
<p><img src="/image/img-42308ab1-5868-4a42-8655-edbf3b1066cc.jpg" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:与 <a href="https://www.hanselman.com/" target="_blank" rel="noopener">Scott Hanselman</a> 合影)</span></p>
<p>更加令人羡慕的是能与微软创始人比尔·盖茨和CEO萨提亚·纳德拉亲自见面。然而,作为一个宅男,我非常遗憾地错过了这两次机会,后悔之情让我懊悔不已。</p>
<p>常言道,一个人的交际圈十分重要,它决定了一个人的认知和眼界。微软MVP社区则是科技领域中一处顶级的交际圈。与<a href="https://996.icu" target="_blank" rel="noopener">996工作制</a>相比,能够加入这个圈子才是上辈子修来的福报。</p>
<h2>与开源软件化敌为友</h2>
<hr>
<p>在成为MVP之前,我对于开源软件一直持有保留的态度。直到2014年之前,微软本身就是一个被广为诟病的开源对手。然而,通过与MVP社区的互动,我开始尝试<strong>拥抱开源</strong>,并逐渐改变了我的观点。</p>
<p>我首先选择开源的就是让我当选MVP的两个Windows 10 UWP应用。其中“<a href="https://github.com/character-map-uwp/Character-Map-UWP" target="_blank" rel="noopener">Character Map UWP</a>” 一经开源后,它得到了社区的重要贡献,不仅在功能和性能上取得了长足的进步,而且在专业领域和广大用户群体中获得了巨大的成功。这款应用的安装量已经突破了百万级,甚至有微软Windows产品组的员工也在使用它。如果没有开源的支持,这样的成就是不可能实现的。</p>
<p><img src="/image/img-c2f566d7-ad68-43d4-b8c3-a9036849a189.png" border="0" alt="" loading="lazy"></p>
<p style="text-align: center;"><span style="color: rgb(126, 140, 141);">(图:被社区高手重写后的 <a style="color: rgb(126, 140, 141);" href="https://github.com/character-map-uwp/Character-Map-UWP" target="_blank" rel="noopener">Character Map UWP</a> 应用)</span></p>
<p>另一个非常成功的开源项目是<a href="https://github.com/EdiWang/Moonglade" target="_blank" rel="noopener">我的技术博客</a>。在2018年,当我选择使用ASP.NET Core完全重写这个项目时,我也决定将其开源。我将其与我的技术方向结合,整合了14种Azure服务。然而,我也倾听了社区的声音,避免了厂商绑定,让我的博客系统完全可以在脱离Azure的情况下运行。这一决策受到用户的好评,同时社区也为我提供了MySQL、MinIO等非微软平台的支持。现在已有众多国内外技术人员以及微软员工使用我的博客系统构建自己的博客。如果没有开源,这些成果都是无法实现的。</p>
<p>通过主导自己的开源项目,以及参与其他项目的贡献,我经常遇到一些与.NET和Azure平台相关的问题,并能提出有建设性的建议。我通过与微软的PG进行沟通,利用这个反馈渠道,将这些问题和建议传达给他们。这也是作为MVP的日常工作之一。</p>
<p>我看到了开源软件的力量和灵活性,以及它对技术社区的积极影响。我开始积极参与开源项目,贡献代码和文档,并与其他开源社区成员合作。这种思想转变<strong>不仅扩展了我的技术视野,还让我更加开放和包容地对待不同的技术解决方案</strong>。</p>
<p>如今,开源软件已经成为我日常工作和生活中不可或缺的一部分。它成为我在MVP社区中获得的一笔宝贵财富。</p>
<h2>名还是利</h2>
<hr>
<p><a href="https://book.douban.com/subject/25881855/" target="_blank" rel="noopener">《大教堂与集市》</a>一书对于开源模式进行了深入分析,指出在这种模式下,黑客精神更加追求的是一种"<strong>礼物文化</strong>",而不仅仅是金钱的回报。这种文化更加偏向于追求在技术社区中的声望和地位。人们并非毫无理由地付出,对于MVP们来说也是如此。除了微软和合作伙伴提供的正版软件等福利外,我们没有金钱上的奖励,而更多地是一种荣誉。</p>
<p>我出身平凡,没有显赫的学历,也不是富有的老板。然而,微软MVP成为了我引以为豪的唯一头衔。拥有微软MVP的荣誉,在职业圈中无疑给予了我许多优越的机会。这要感谢前辈MVP们为这个品牌树立的口碑。在公司中,我的同事们会因为我是MVP而更加信任我的技术能力。而在日常生活中,当我向他人介绍自己时,时常能因为微软MVP这个头衔而获得赞赏。</p>
<p>微软公司的使命让我深感钦佩。从最初的“<strong>让每个家庭都拥有一台电脑</strong>”,到如今的“<strong>予力全球每一人、每一组织,成就不凡</strong>”,这些伟大的目标推动着人类文明的不断进步。而成为微软MVP,使我能够成为这一使命推动力量的一部分,这是一件令人自豪的事情。</p>
<p><strong>和那些凭借表演和口才换取生活的人相比,技术人员通过智慧和双手追求名利并非可耻之事</strong>。这也是MVP项目所拥有的另一个重要财富。</p>
<h2>永远谦虚</h2>
<hr>
<p>尽管获得了微软MVP奖励,我始终保持谦虚的态度。我明白这个奖项的意义在于我对技术社区的贡献,而不仅仅是个人的荣誉。</p>
<p>首先,<strong>六年在MVP社区中并不是出类拔萃的,甚至还有点短</strong>。全球的MVP们每年都需要通过续任审核,微软美国会审查过去一年中MVP个人的技术贡献,以确保项目的高质量和价值。如果我们沉浸在暂时的荣誉中而自满,不去坚持学习和进步,就有可能被淘汰出局。因此,六年的MVP经历也是一种不断成长的过程。</p>
<p>其次,在技术领域,我们越学习就越意识到自己的无知。就以我的奖项类别为例,Microsoft Azure拥有数百种产品和服务,而我只熟悉其中的十几种。在程序员这个行业,常常会遇到那些只会编写CRUD操作就自以为已经精通了整个软件开发的人。我曾经也有过这种年少轻狂的阶段,虽然现在已经离开了那个“井底之蛙”的境地,但我清楚地知道,爬出井口之后所看到的陆地和天空,并不代表整个世界的全部。还有地球、太阳系、银河系乃至整个宇宙,越是向外看就越能认识到自己的无知和渺小。</p>
<p>我将继续致力于分享知识、帮助他人和推动技术进步。我相信<strong>谦虚是一个成功技术专家的重要品质</strong>,它让我保持对学习和成长的渴望,并时刻谨记自己始终有更多可以学习的东西。</p>
<h2>结语</h2>
<hr>
<p>在十几年前,微软最有价值专家(MVP)为我树立了一个优秀的技术社区榜样,这让我最终决定并通过努力加入这个项目。微软MVP项目使我进入了顶级的技术圈子,结识了各种卓越的人才,为我带来了丰厚的福利和声誉,也改变了我对开源软件的看法。我由衷地感谢微软为我提供了这样一个平台,这是我人生中一段非常重要的经历。我将继续保持谦逊,并通过我的热情和能力为技术社区做出贡献。</p>E77E2FB0-6B06-41C5-AF14-8987CEC7D1A7Deploy Public DNS Server on Microsoft Azure2023-06-10T02:51:29Z2023-06-10T02:51:29ZEdi WangEdi.Wang@outlook.com<h2>Intro</h2>
<hr>
<p>Deploying a public DNS server on Microsoft Azure can be a crucial step for organizations and individuals that want to provide DNS resolution to the public. Deploying a public DNS server on Azure can provide benefits such as scalability, high availability, and global reach. In this blog post, we will discuss the steps involved in deploying a public DNS server on Microsoft Azure.</p>
<p>My objective is to set up a DNS server that functions as a relay, utilizing forwarders to serve as an intermediary for client DNS queries to other public DNS servers, such as Microsoft or Google. Users can then set DNS IP address on their devices to this server. No custom domains are involved here. This is typically used in China where a lot of well-known DNS servers could be affected by some magic of the Chinese internet. </p>
<h2>Notice</h2>
<hr>
<p>Kindly note that the topic of discussion in this post does not pertain to Azure DNS Zones. Azure DNS Zones serve a different purpose, primarily when you have a custom domain name, and you need to add records on the Azure Portal. Users are not required to modify DNS IP addresses on their devices. For instance, my blog uses Azure DNS Zones, but users don't have to alter their network configurations to access it. Therefore, if you are seeking information about Azure DNS Zones, this is not the appropriate post for you.</p>
<h2>Platforms</h2>
<hr>
<p>We will be using VMs to deploy DNS services. We will not be covering the steps involved in creating the VMs themselves. Instead, we will be focusing on the key steps and configurations required to deploy the DNS services on the VMs.</p>
<h2>Network Security Group configuration</h2>
<p>No matter what type of OS you choose. NSG must be configured to open both <strong>TCP/53</strong> and <strong>UDP/53</strong> ports.</p>
<p><img src="/image/img-5a19fa2b-3c5c-46b3-928b-eeda19201d64.png" border="0" loading="lazy"></p>
<h3>Static IP Address</h3>
<p>Make sure your VM is bound with a static public IP address. </p>
<p><img src="/image/img-6bda8a9c-95be-4bb8-bc59-2a99b1e623e9.png" border="0" loading="lazy"></p>
<h3>Windows Server 2022 Server Core</h3>
<p>Go to PowerShell and run:</p>
<pre class="language-powershell"><code>Install-WindowsFeature -Name DNS -IncludeManagementTools</code></pre>
<p><img src="/image/img-fa1ac6ac-a736-4851-aba7-eaefac402eb4.png" border="0" loading="lazy"></p>
<p>Then, add forwarders. I am using Google's DNS server IP address as forwarders.</p>
<p>DNS forwarders are used to allow a DNS server to forward DNS queries to another DNS server, typically one that is authoritative for a particular domain. This is useful in situations where the DNS server is not authoritative for the requested domain, or when the DNS server is unable to resolve the query locally. By forwarding the query to another DNS server, the DNS server can obtain the necessary information to resolve the query and return the result to the client. This can improve DNS performance and reduce network traffic, as the DNS server does not need to perform recursive queries for every request.</p>
<pre class="language-powershell"><code>Add-DnsServerForwarder -IPAddress 8.8.8.8 -PassThru
Add-DnsServerForwarder -IPAddress 8.8.4.4 -PassThru</code></pre>
<p><img src="/image/img-8a6fc5ae-3940-40cb-aeea-0523b016ad79.png" border="0" loading="lazy"></p>
<p>Now, on your client device, set DNS server IP to this server, and run <code>nslookup </code>to check if DNS records can be resolved correctly.</p>
<p><img src="/image/img-ce8c31a8-fb01-44a9-adb2-30134fa977ed.png" border="0" loading="lazy"></p>
<h3>Ubuntu Server</h3>
<p>SSH into your VM and run</p>
<pre class="language-bash"><code>sudo apt update && sudo apt upgrade -y
sudo apt install bind9 -y</code></pre>
<p>If you are using a minimized Ubuntu Server image, you may also need to install a text editor like nano</p>
<pre class="language-bash"><code>sudo apt install nano -y</code></pre>
<p>Use nano to open bind9 configuration file</p>
<pre class="language-bash"><code>sudo nano /etc/bind/named.conf.options</code></pre>
<p>Add forwarders to Google DNS server as an example. Please note, we must also add <code>allow-query-cache { any; };</code></p>
<pre class="language-bash"><code>forwarders {
8.8.8.8;
8.8.4.4;
};
allow-query-cache { any; };</code></pre>
<p><img src="/image/img-924cd9c0-e01e-4f06-b17b-fa7cf34f0063.png" border="0" loading="lazy"></p>
<p>Save the configuration file, then restart bind9 services.</p>
<pre class="language-bash"><code>sudo systemctl restart bind9</code></pre>
<p>Test the DNS server on your client device. </p>
<p><img src="/image/img-ab3836dc-bc6a-46d7-9d8b-fa7e6683fd8e.png" border="0" loading="lazy"></p>5D906AC2-3A59-427E-B019-5BB07DA661A6Deploy ChatGPT Next Web to Azure App Service with Individual Account Login in 3 minutes2023-05-18T08:09:52Z2023-05-18T08:09:52ZEdi WangEdi.Wang@outlook.com<h2>Background</h2>
<hr>
<p><a href="https://github.com/Yidadaa/ChatGPT-Next-Web" target="_blank" rel="noopener">ChatGPT Next Web</a> is a widely used ChatGPT web UI project that enables you to deploy a website to your own server using only your Open API key. This allows you to enjoy the same chat experience without having to visit the official Open AI website, which is particularly useful when dealing with network or regional restrictions.</p>
<p>However, the official repository of ChatGPT Next Web does not offer a one-click installation for Azure, and the developer has no plans to add a built-in login feature to restrict access to the website. To address these issues, my goal is to:</p>
<ol>
<li>Deploy ChatGPT Next Web to Azure without the need to create a VM.</li>
<li>Add access control that requires individual accounts to log in to use ChatGPT, without making any changes to the code.</li>
</ol>
<p>My objective is to complete these tasks within three minutes. Let's explore how this can be achieved.</p>
<h2>Create Azure App Service</h2>
<hr>
<p>The official <a href="https://github.com/Yidadaa/ChatGPT-Next-Web" target="_blank" rel="noopener">ChatGPT Next Web</a> provides a Docker image. This is how I can run it on Azure without VM. Azure has many PaaS ways to deploy Docker containers:</p>
<ol>
<li>Azure App Service</li>
<li>Azure Container Instance</li>
<li>Azure Container Apps</li>
<li>AKS</li>
</ol>
<p>I choose App Service because it has built in Authentication features that I will use in the next step.</p>
<p>Go to Azure Portal, create a new Web App, select <strong>Docker Container + Linux</strong> in instance details.</p>
<p><img src="/image/img-20954142-e8b7-4fe2-a5bf-948aac4de76b.png" border="0" loading="lazy"></p>
<p>For pricing plans, as I tested, Basic SKUs are enough for a few users, you can choose a higher SKU if you have a lot more end users.</p>
<p>In Docker step, use single container from public Docker Hub, named: <strong>yidadaa/chatgpt-next-web</strong></p>
<p><img src="/image/img-50a0098e-8938-4823-a58d-dabf41e880d6.png" border="0" loading="lazy"></p>
<p>Configure other steps as you like, and finish creating the web app.</p>
<h2>Configure Environment Variables</h2>
<hr>
<p>After creating the Web App, we need to configure two environment variables for the Docker container before running the application.</p>
<p>Go to Configuration blade, and add two settings:</p>
<ol>
<li><strong>OPENAI_API_KEY</strong>: Use your own key bought from Open API official website, usually start with "sk-"</li>
<li><strong>PORT</strong>: 443</li>
</ol>
<p><img src="/image/img-f47a367a-67b8-48a6-8bf0-ced9b132a3ca.png" border="0" loading="lazy"></p>
<p>I would also suggest turn on HTTP2 in general settings for better performance.</p>
<p><img src="/image/img-9464118a-e9c4-4ba1-9cc0-7cb1243663ba.png" border="0" loading="lazy"></p>
<p>By now, your ChatGPT web UI is ready to run. You can access the page by default domain or bind your custom domain.</p>
<p><img src="/image/img-ec87115e-6894-4819-aa91-51e6f41a50f8.png" border="0" loading="lazy"></p>
<p><img src="/image/img-084e6838-a9f0-4966-8a89-2f512c6b2349.png" border="0" loading="lazy"></p>
<h2>Enable Authentication</h2>
<hr>
<p>By default, your website is publicly accessible from anywhere on the Internet. This can blow up your credit card. We need to enable authentication that asks users to login before using the application.</p>
<p>Although the original ChatGPT Next Web project did not have such features, it is extremely easy to add login providers on Azure without writing any code.</p>
<p>Go to <strong>Authentication </strong>blade, click "<strong>Add identity provider</strong>"</p>
<p><img src="/image/img-04e21e9a-9eeb-43b6-a754-bed68cb42c1e.png" border="0" loading="lazy"></p>
<p>You can add any of the listed providers. I am using Microsoft for example. </p>
<p><img src="/image/img-24b79992-90b5-424f-b0ab-45f66444f4d3.png" border="0" loading="lazy"></p>
<p>For Microsoft provider, by default, all accounts in the given Azure AD will have access. To limit accounts to only selected people, see my previous blog post <a href="/post/2019/12/13/how-to-allow-only-selected-users-to-access-an-application-in-azure-ad" target="_blank" rel="noopener"><em>How to Allow Only Selected Users to Access an Application in Azure AD</em></a></p>
<p>After completing this step. Users will be asked to login before opening ChatGPT web UI. </p>
<h2>Conclusion</h2>
<hr>
<p>Azure App Service is a wonderful PaaS service. It can run Docker based applications and add individual account login flow to the application without modifying code. All steps are just a few mouse clicks that can be finished in a few minutes.</p>4263343C-F83D-451E-AD61-B44EC50E5A75Setup Hyper-V Server 2019 in My Home PC2023-05-06T02:15:52Z2023-05-06T02:15:52ZEdi WangEdi.Wang@outlook.com<h2>Background</h2>
<hr>
<p>I recently acquired an HP 800G2 PC from my company for just 200 RMB. I decided to utilize it to host VMs for my tech experiments, as running VMs on my home LAN provides me with advantages over remote connections to Azure datacenters, which I typically use.</p>
<p>After installing Hyper-V Server 2019 on the machine, I deployed an Ubuntu Server VM and a Windows 10 VM. In this post, I will share the process and tips for setting up this kind of environment.</p>
<h2>Hyper-V Server, Windows Server with Hyper-V Role, or VMWare Esxi</h2>
<hr>
<h3>Type 1 or Type 2 Hypervisor</h3>
<p>I spent some time studying and making the choice among the host OS. First, there are two types of hypervisors. Type 1 and Type 2. All three products, Hyper-V Server, Windows Server with Hyper-V Role, and Esxi are Type 1 hypervisor. VMWare workstation on the other hand, is Type 2 hypervisor. </p>
<p>Type 1 hypervisor runs directly on the host machine's hardware, while type 2 hypervisor runs on top of an operating system. Type 1 hypervisors are often referred to as bare-metal hypervisors because they operate directly on the host machine's underlying hardware. They are typically used in enterprise-level virtualization environments because of their speed and security. Type 2 hypervisors, on the other hand, are installed on top of an existing operating system and are primarily used for desktop virtualization. They are slower than type 1 hypervisors and less secure but easier to use and install.</p>
<p>There is no difference between VMs running on Hyper-V Server vs Windows Server with Hyper-V Role. Hyper-V Server is a Windows Server Core with Hyper-V Role pre-installed. They are both type 1 hypervisors. The only diffrence is that Hyper-V Server has some other roles removed from the OS, Windows Server has a full list of roles, which in this case, you won't need them.</p>
<p>As stated in the book "Windows Internals", when you install Hyper-V for a Windows Server, or even in Windows 10/11, the host OS itself will also be running on hypervisor just like a VM. So, for anyone that is concerned if Hyper-V Server has better performance than Windows Server with Hyper-V Role, this is false. THEY ARE SAME! VM all runs as Type 1 hypervisor.</p>
<h3>Free or Paid</h3>
<p>Hyper-V Server 2019: Free</p>
<p>Windows Server 2022 with Hyper-V Role: Paid</p>
<p>VMWare Esxi: Can be free</p>
<h3>Last version of Hyper-V Server</h3>
<p>Hyper-V Server 2019 is the last version of Hyper-V Server and continues to be supported until 2029. There won't be a Hyper-V Server 2022 or later. Microsoft recommends using Azure Stack HCI, which is also a commercial product. As my goal is not to pay a cent for OS on a 200RMB low end machine, I won't use Azure Stack HCI. </p>
<p>If you really like Hyper-V Server and hate Azure. You can choose Windows Server Core and install Hyper-V Role (but you must pay for it). As described above, there is no diffrence for VMs.</p>
<h2>Install Hyper-V Server 2019</h2>
<hr>
<p>Installing Hyper-V Server 2019 is no diffrence from a typical installation of Windows Server Core. I use Rufus (<a href="https://rufus.ie/en/">https://rufus.ie/en/</a>)to burn ISO file into a USB drive and install on the machine.</p>
<h3>Initial Configuration</h3>
<p><strong>Computer Name</strong>: This is for remote managing your machine in later step. IP address can't be used for today's Windows management tools. So, you must give the server a name.</p>
<p><strong>Network</strong>: Required for remote management</p>
<p><strong>Remote Desktop</strong>: Required for remote management</p>
<p><strong>Configure Remote Management</strong>: Required for Windows Admin Center and Hyper-V Manager to access remotely</p>
<p><strong>Date and Time</strong>: Required for OS to work properly</p>
<p><strong>Download and Install Updates</strong>: Make sure your hardware drivers got installed and your OS got latest patch</p>
<p><img src="/image/img-cdeb208d-188b-4048-96ce-8ef8ca214e09.png" border="0" loading="lazy"></p>
<h3>Enable Remote Management on Client Machine</h3>
<p><strong>This is the most important setup in the entire process</strong>. If blow up, please watch <a href="https://www.youtube.com/watch?v=ZPjtiXB5k5s">https://www.youtube.com/watch?v=ZPjtiXB5k5s</a> </p>
<p>On your laptop, or other machine that you will use to manage this server. Run these commands in PowerShell with Admin. Replace the host name, IP, network name, and password for your own values.</p>
<pre class="language-powershell"><code>Set-Item WSMan:\localhost\Client\TrustedHosts -Value "EDI-800G2HVS"
# Add server IP to host file
Add-Content -Path "C:\Windows\System32\drivers\etc\hosts" -Value "192.168.0.102 EDI-800G2HVS"
# Get current network profile name for next command
Get-NetConnectionProfile
# Set as Private to allow connection
Set-NetConnectionProfile -InterfaceAlias "Wi-Fi" -NetworkCategory Private
Enable-PSRemoting
Get-WSManCredSSP
Enable-WSManCredSSP -Role Client -DelegateComputer "EDI-800G2HVS"
cmdkey /add:EDI-800G2HVS /user: Administrator /pass:*******</code></pre>
<h3>Hyper-V Management</h3>
<p>Run <code>optionalfeatures </code>and Install only the Hyper-V Management Tools on your client machine.</p>
<p><img src="/image/img-7f0bde05-cabb-410c-96e6-785a4e753038.png" border="0" loading="lazy"></p>
<p>You will now be able to add your server and run VMs on it.</p>
<p><img src="/image/img-20e551e8-d74c-4288-9462-33497d5246bc.png" border="0" loading="lazy"></p>
<h3>Windows Admin Center</h3>
<p>For a complete remote management capability, install Windows Admin Center (<a href="https://www.microsoft.com/en-us/windows-server/windows-admin-center">https://www.microsoft.com/en-us/windows-server/windows-admin-center</a>) on your client machine, then connect to your server.</p>
<p>For example, you can easily make file shares to transfer VM OS install ISO files. </p>
<p><img src="/image/img-4cb6b3fc-b9c7-4196-9de2-7639de721a26.png" border="0" loading="lazy"></p>F6CFA947-32A1-438B-919D-57A3BEFC14E1Rename Azure Function Developed in Portal without Deleting and Recreating It2023-04-05T01:40:27Z2023-04-05T01:40:27ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>When creating an <a href="https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Function</a> from Azure portal. The function name seems to be fixed once you created it. There's no way from portal UI to change the function name. To rename a function, you could copy its code first, then delete and recreate it. But this is not the only way. Let's see how we can rename a function easily without deleting and recreating it.</p>
<p><img src="/image/img-23204241-3fe7-4363-9177-90ba80e0f2cd.png" border="0" loading="lazy"></p>
<h2>Solution</h2>
<hr>
<p>For example, I want to rename "Work007" to "Work996"</p>
<p><img src="/image/img-6152a8cc-753a-4278-a77f-ec366de0fa3b.png" border="0" loading="lazy"></p>
<p>Go to "<strong>App Service Editor</strong>" menu and open the editor</p>
<p><img src="/image/img-e6ed6037-3e30-47cc-a8fc-e06e67caeda5.png" border="0" loading="lazy"></p>
<p>In the editor, go under "<strong>wwwroot</strong>", find "<strong>Work007</strong>" folder, select "<strong>Rename</strong>" from the context menu, enter "Work996".</p>
<p>The editor automatically saves changes, so you won't have to do anything, just close the editor.</p>
<p><img src="/image/img-5a8f95f8-603d-40fb-9e73-bb7120c434a9.png" border="0" loading="lazy"></p>
<p>Go back to the function overview page, <strong>restart </strong>the function.</p>
<p><img src="/image/img-977e898e-555a-4adb-81d6-7ea3845a5322.png" border="0" loading="lazy"></p>
<p>After a minute, the function is renamed successfully. Make sure to test it to ensure it still works.</p>
<p><img src="/image/img-7c7ce531-68d2-47a3-90c1-a6481f4171f9.png" border="0" loading="lazy"></p>
<p> </p>C9A57F19-5A8E-421C-AA1B-0370577070EAUse Azure Function to Update IP Address in Azure VM NSG Firewall Rule2023-03-17T05:29:23Z2023-03-17T05:29:23ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>I have Azure VMs that require being accessed only from 3 IP addresses. Two of these IPs are static, the other is dynamic. Every time the dynamic IP changes, I must manually go to Azure portal and update NSG rules. Because this happens every few days, it makes me <a href="https://996.icu/" target="_blank" rel="noopener">work 996</a>. To solve this issue, I created <a href="/admin/post/edit/?WT.mc_id=AZ-MVP-5002809" target="_blank" rel="noopener">Azure Function</a> to automatically update the NSG rule with a simple HTTP request. I no longer need to manually go into Azure portal. Let's see how to do it.</p>
<h2>Create Azure Function App</h2>
<hr>
<h3>PowerShell Function</h3>
<p>Create a new Azure Function and select <strong>PowerShell </strong>as runtime. After creation is completed, go to <strong>Identity </strong>blade, and enable <strong>system assigned managed identity</strong>.</p>
<p><img src="/image/img-fb8cba45-7bbf-42f4-9b09-271199397eb4.png" border="0" loading="lazy"></p>
<h3>Add Az modules</h3>
<p>Go to <strong>App files - requirements.psd1</strong> and add PowerShell modules:</p>
<pre class="language-powershell"><code>@{
'Az.Accounts' = '2.*'
'Az.Network' = '5.*'
}
</code></pre>
<p><img src="/image/img-cab8d92a-47f7-40bd-9842-d436a90b42de.png" border="0" alt="" loading="lazy"></p>
<h3>Allow Function Access for NSG</h3>
<p>Go to <strong>Access control (IAM)</strong> of the NSG, add a new <strong>role assignment</strong></p>
<p><img src="/image/img-131cf237-b616-4518-92bb-65645769fa01.png" border="0" alt="" width="672" height="363" loading="lazy"></p>
<p>Select "<strong>Contributor</strong>" in "<strong>Privileged administrator roles</strong>"</p>
<p><img src="/image/img-49fcc41e-9d6a-4b71-963e-7eb0718e2c5a.png" border="0" alt="" loading="lazy"></p>
<p>Select "<strong>Managed identity</strong>" and add the function app.</p>
<p><img src="/image/img-7952ace5-cb99-4591-9c9d-a4819ee89595.png" border="0" alt="" width="831" height="474" loading="lazy"></p>
<h2>Write Function Logic</h2>
<hr>
<h3>HTTP Trigger</h3>
<p>Create a new Function with HTTP Trigger</p>
<p><img src="/image/img-4390c2dc-872f-4012-b34e-28ff7c81b899.png" border="0" loading="lazy"></p>
<h3>Logic code</h3>
<p>Copy the code and replace variables to your own values. In my case, <code>cnBridgeIp </code>and <code>corpIp </code>are two static IP addresses. The dynamic IP will come from HTTP request query string or body. You can modify the code to your need.</p>
<pre class="language-powershell"><code>using namespace System.Net
param($Request, $TriggerMetadata)
# Write to the Azure Functions log stream.
Write-Host "PowerShell HTTP trigger function processed a request."
$ip = $Request.Query.ip
if (-not $ip) {
$ip = $Request.Body.Ip
}
$subscriptionId = "<id>"
$tenantId = "<id>"
$rsgName = "<group>"
$nsgName = "<nsg>"
$ruleName = "<rule>"
$cnBridgeIp = "<ip>"
$corpIp = "<ip>"
$body = "251"
if ($ip) {
# Set-AzNetworkSecurityRuleConfig
# https://learn.microsoft.com/en-us/powershell/module/az.network/set-aznetworksecurityruleconfig?view=azps-9.5.0
Select-AzSubscription -SubscriptionID $subscriptionId -TenantID $tenantId
$nsg = Get-AzNetworkSecurityGroup -Name $nsgName -ResourceGroupName $rsgName
# $httpsRule = $nsg | Get-AzNetworkSecurityRuleConfig -Name $ruleName
($nsg.SecurityRules | Where-Object { $_.Name -eq $ruleName }).SourceAddressPrefix = ([System.String[]] @($cnBridgeIp, $corpIp, $ip))
$nsg | Set-AzNetworkSecurityGroup | Get-AzNetworkSecurityRuleConfig -Name $ruleName
$body = "Updated NSG $nsgName for IP: $ip."
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::OK
Body = $body
})
}
else {
Push-OutputBinding -Name Response -Value ([HttpResponseContext]@{
StatusCode = [HttpStatusCode]::NotFound
})
}</code></pre>
<h3>Test</h3>
<p>Click <strong>Test/Run</strong>, enter an IP address in HTTP request body.</p>
<p><img src="/image/img-5da03571-e50d-448d-a539-1fc31944dbdf.png" border="0" alt="" loading="lazy"></p>
<p> </p>
<p>Then go to your NSG to verify if the function logic is successful.</p>
<p>If everything is fine, click "<strong>Get function URL</strong>", then you can integrate this API endpoint to other part of your system to update NSG rule fully automatically for a dynamic IP address.</p>
<p><img src="/image/img-2ad5c55f-edee-40f8-97fc-1b91232b835b.png" border="0" alt="" loading="lazy"></p>
<p>You may get your dynamic IP address by Azure Function or third-party APIs. For example, in a C# Function, you can do this:</p>
<pre class="language-csharp"><code>// #r "System.Text.Json"
using System.Net;
// using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
public static async Task<IActionResult> Run(HttpRequest req, ILogger log)
{
// log.LogInformation("C# HTTP trigger function processed a request.");
var clientIp = req.HttpContext.Connection.RemoteIpAddress.ToString();
return new OkObjectResult(clientIp);
}</code></pre>6A21E670-DD72-4D28-A1BB-E57111EB9E1DFix Azure Linux VM High Memory Usage Because of Microsoft Defender2023-03-12T02:12:26Z2023-03-12T02:12:26ZEdi WangEdi.Wang@outlook.com<h2>Problem</h2>
<hr>
<p>Recently, my Ubuntu 22.04 VMs are getting unresponsive. From Azure portal, I observe that something quickly eats up all memory on the VM. I'm also difficult to SSH into the VM. </p>
<p><img src="/image/img-da3c65f7-6466-449c-9e51-c29cbb721865.png" border="0" loading="lazy"></p>
<p>After <a href="https://996.icu/" target="_blank" rel="noopener">work 996</a>, I finally pinpointed the problem. It is caused by a process named <strong>"wdavdaemon",</strong> which is Microsoft Dender for Linux.</p>
<p><img src="/image/img-8f41966d-2d90-440e-b12d-5a70a137bd8b.png" border="0" alt="" loading="lazy"></p>
<p>This machine is B1s size and has only 1G memory, MDE is taking 416MB and this number is climbing every minute.</p>
<p><img src="/image/img-2b466a6c-70c7-4c57-8c64-09023e7edcb9.png" border="0" loading="lazy"></p>
<h2>Solution</h2>
<hr>
<h3>Restart VM</h3>
<p>First, you need to restart the affected VM. Because the VM is already in an unresponsive state, it will take around 10 minutes to restart. Or you can stop the VM and resize it to have a larger memory. After starting the VM, you will have a few minutes to continue the fix before MDE starts to eat up all memory.</p>
<h3>Uninstall MDE</h3>
<p>First, uninstall it on Azure portal. Go to extensions blade and remove <strong>MDE.Linux</strong>.</p>
<p><img src="/image/img-ba445cf7-b584-438a-99ac-c3ebbbd04b71.png" border="0" alt="" width="607" height="249" loading="lazy"></p>
<p><span style="background-color: rgb(251, 238, 184); color: rgb(224, 62, 45);"><strong>In the meantime, SSH into your VM and run</strong></span></p>
<pre class="language-bash"><code>sudo apt-get purge mdatp -y</code></pre>
<p>After this, your VM should be able to restore to a normal memory consumption state quickly</p>
<p><img src="/image/img-acf485be-d37b-4d00-b678-95795020846d.png" border="0" alt="" width="756" height="314" loading="lazy"></p>
<h3>Prevent MDE from automatically installed again</h3>
<p>If you see "Microsoft Defender for Cloud" message like this when creating VM. It means your subscription has Microsoft Defender enabled. You need to turn off its automatic install of MDE.</p>
<p><img src="/image/img-49081068-841a-416e-bc3e-06cfe187c0c1.png" border="0" alt="" width="970" height="476" loading="lazy"></p>
<p>Go to Microsoft Defender for Cloud and select the affected subscription.</p>
<p><img src="/image/img-d655e8c5-a5e5-42d5-91da-e2ba0b79e862.png" border="0" alt="" width="1042" height="819" loading="lazy"></p>
<p>Go to "Servers", "settings"</p>
<p><img src="/image/img-47f3087c-c964-4944-ad4d-b4de67fa11d1.png" border="0" alt="" width="1675" height="730" loading="lazy"></p>
<p>Turn off "Endpoint protection"</p>
<p><img src="/image/img-c5155062-99d1-4c72-a102-c4999631bc17.png" border="0" alt="" width="1444" height="664" loading="lazy"></p>