My Docs Site

Sometimes when I want to write some technical posts or articles, I cannot find a proper place to post them. I know I can post them on this blog and I have been doing it all the time. But sometimes when you have a series of posts about a topic, especially when you want to keep those posts alive, have them versioned and update them from time to time, the blog post is not a very good way to organize them. For these kind of posts, writing them in the markdown format and keeping them in a git repo would be a more natural choice, like what azure-docs does. I don’t use markdown for this blog site because I quite like the Gutenberg editor of WordPress. It is quite good for the casual writings. WordPress also doesn’t have the versioning feature.

So I decided to create my own docs site. It is a static site built with mdbook and hosted on GitHub with GitHub Pages. mdbook is a tool to create online books from markdown files. It is written in Rust. Why I choose to use it is because it is just one single binary file and quite easy to use. I can easily use it in GitHub actions to build the site automatically.

I don’t know what I am going to put on my docs site. A rough idea is it would be for technical, especially Azure related, posts or articles I write, something not proper for a blog. The first one on the site is a series of tutorials which talks about step by step integration of Application Gateway, API Management and self-hosted gateway in an internal virtual network. It consists of multiple parts and is organized into sections. It is too heavy to be posted as blog posts.

So if I write something similar in the future, I will post it there.

sttf-url-generator — A Chrome/Edge Extension

In the past few days, I created a small Chrome/Edge extension, sttf-url-generator, and published it to both Chrome web store and Edge Add-ons. What this extension does is merely the user sharing use case that is described in the text fragment spec.

With text fragments, browsers may implement an option to ‘Copy URL to here’ when the user opens the context menu on a text selection. The browser can then generate a URL with the text selection appropriately specified, and the recipient of the URL will have the specified text conveniently indicated.

The idea of creating this extension was originated from an internal discussion with my colleagues. As customer engineers, we need to share document links with customers quite often. Quoting the text from the document directly in the email could make the email awfully long, while just sending a link may not help customers quickly locate the information in a long document. We need a better way to share the information so that the customer can get it quickly and accurately.

Text fragment is a perfect solution for this case. It is a new feature supported by browsers natively, and it is available in the latest version of Chrome and Edge. We just need a way to easily generate a url with the text fragment. Browsers might provide such a feature in the future, but before that I hope this extension can help to address the needs.

The implementation of the extension is quite simple and easy, just several lines of javascript. I don’t use any framework or libraries, and purely rely on the DOM when I need to deal with HTML elements. Node and webpack are used simply because I’d like to use eslint to sanitize the code and babel to minimize the js files.

The more interesting part is about packaging and submitting it to Chrome and Edge. To meet the requirements of these stores, I have to consider the permissions required by the extension carefully, create the images and assets, setup a home page with GitHub Pages, and even write a privacy policy by myself. These tasks took more time than the coding itself, but it’s a good experience of bringing an idea to production. I learned a lot from it.

The extension currently consists of 3 functions: copy the generated url, open the generated url in a new tab, and copy the text fragment and the generated url in the markdown format. I think these 3 features are more than enough for a single purpose extension.

Hope you enjoy it.

Node Allocatable in AKS

Kubernetes’ Node Allocatable feature allows the cluster to reserve the resources of node for system daemons of OS and Kubernetes itself. For example, when I ran kubectl describe node for a node in my AKS cluster, I got the following capacity related output. The size of this node was Standard DS2 v2 which had 2 CPU cores and 7GB memory.

Capacity:
  attachable-volumes-azure-disk:  8
  cpu:                            2
  ephemeral-storage:              101445900Ki
  hugepages-1Gi:                  0
  hugepages-2Mi:                  0
  memory:                         7113660Ki
  pods:                           110
Allocatable:
  attachable-volumes-azure-disk:  8
  cpu:                            1900m
  ephemeral-storage:              93492541286
  hugepages-1Gi:                  0
  hugepages-2Mi:                  0
  memory:                         4668348Ki
  pods:                           110

From the output, out of 2 cores and 7GB memory, there were 1900 millicores and 66% memory (4668348/7113660) allocatable to pods. The details about how these numbers were calculated are described in this document. In short, the configuration of node allocatable in AKS is as follows:

CPU

CPU cores on host1248163264
Kube-reserved (millicores)60100140180260420740

Memory

  • eviction-hard: 750Mi, which is the default configuration of the upstream aks-engine.
  • kube-reserved: regressive rate
    • 25% of the first 4 GB of memory
    • 20% of the next 4 GB of memory (up to 8 GB)
    • 10% of the next 8 GB of memory (up to 16 GB)
    • 6% of the next 112 GB of memory (up to 128 GB)
    • 2% of any memory above 128 GB

However, the above memory reservation doesn’t seem to applicable to Windows nodes. The following was the output when I ran the kubectl describe node for a Windows node. More memory was reserved for Windows nodes.

Capacity:
  attachable-volumes-azure-disk:  8
  cpu:                            2
  ephemeral-storage:              133703676Ki
  memory:                         7339572Ki
  pods:                           30
Allocatable:
  attachable-volumes-azure-disk:  8
  cpu:                            1900m
  ephemeral-storage:              133703676Ki
  memory:                         3565108Ki
  pods:                           30

The document doesn’t have any information regarding Windows node. GKE reserves approximately 1.5 times more resources on Windows Server nodes. Not sure if it’s the same for AKS. I’ve opened an issue to ask for further information.

This post provides a good comparison of reserved resources for 3 major cloud offerings: AKS, GKE and EKS.

PowerToys for Windows 10

I’ve been using Microsoft PowerToys on my work machine for some time and find myself more and more rely on it every day. The name, PowerToys, is a very old name on Windows. The 1st version of PowerToys came with Windows 95 more than two decades ago. It’s actually a fantastic idea to build new the set of productivity tools on top of a legacy brand. It feels like the good old days were finally connected with the new era, and the history continues.

I cannot remember exactly when I used PowerToys for Windows last time. It could be the time around Windows 98 when I was still in college, long time ago. Honestly, I was not a fan of it at that time. Although I’ve forgotten why I didn’t like it, it was not something that I must install on my machine. But PowerToys for Windows 10 has changed my mind and it has made its way to my must-installed software list.

The two features that I used most in PowerToys are FancyZones and File Explorer Preview. As I’m using a 4k monitor, FancyZones helped me to better utilize the space of the monitor. It made me feel like the laptop screen becomes redundant that I turned it off most of the time. File Explorer Preview is an add-on of Windows file explorer. I can preview file content directly in Windows explorer with it. The best part is it supports markdown preview. Now I can read those README files without even opening them with markdown editors.

Another tool that I am going to use frequently is PowerToys Run which was just released with version 0.18. I always wanted to have such a tool to run apps quickly and I’ve already tried some 3rd tools. PowerToys Run looks quite promising.

The PowerToys is quite stable. I didn’t hit any problem with it since I started using it. So if you are on Windows 10 and haven’t tried it, maybe it’s the time. 🙂

Return HTTP 405 When HTTP Method Not Match in Azure API Management

In Azure API Management, when the HTTP method of a request doesn’t match the one defined in the corresponding operation, APIM returns status code HTTP 404 Resource Not Found to the client. For example, the OOTB Echo API defined the Retrieve resource (cached) operation with HTTP GET. If you call it with HTTP POST, you’ll get HTTP 404 Resource Not Found in the response.

The HTTP 404 returned by APIM in this scenario doesn’t really follow the definition of HTTP status code strictly. According to the definition, HTTP 405 Method Not Allowed is designated for this situation. There was a feedback for this issue to APIM team and it would be addressed in the future according to the response. But before that, we have to use some workaround to bypass this issue. Here is how you can do it with policies in APIM.

Handle the error

When APIM failed to identify an API or operation, it will raise a configuration error which will returns HTTP 404. What we need to do is to handle this error and change the status code to HTTP 405. In this way, you avoid the overhead of creating operations for each of the HTTP methods to handle the situation. The next question is on which scope the error should be handled. Depending on the configurations of your APIM, I think you can handle the error on either all operations or all APIs.

The policy code

The following policy code is a sample for Echo API all operations.

<on-error>
    <base />
    <choose>
        <when condition="@(context.LastError.Source == "configuration" && context.Request.Url.Path == "/echo/resource-cached")">
            <return-response>
                <set-status code="405" reason="Method not allowed" />
                <set-body>@{
                    return new JObject(
                        new JProperty("status", "HTTP 405"),
                        new JProperty("message", "Method not allowed")
                    ).ToString();
                }</set-body>
            </return-response>
        </when>
        <otherwise />
    </choose>
</on-error>

The tricky part is in the <when> condition. The first part of the condition is to check if this is a configuration error. If it is, the second part will test if the request is to Retrieve resource (cached) operation. The second test is to avoid the situation where a real HTTP 404 happens.

You may wonder why I used context.Request rather than context.Operation to test which operation it is. The reason is APIM sets context.Operation to null in this case because it cannot identify which operation it is (and that is why the configuration error happens).

You can use this walkaround to return HTTP 405 until APIM fixes its behavior.

Azure Batch – Create a Custom Pool using Shared Image Gallery

If you have a custom image in a Shared Image Gallery and you want to use it to create a pool in Azure Batch, this document, Use the Shared Image Gallery to create a custom pool, provides a pretty good guidance for it. I followed it to test the scenario and hit two minor issues.

  • As it is mentioned in another doc, AAD authentication is also a prerequisite for using shared image gallery. If you use --shared-key-auth with az batch account login, you would hit an anthentication error with Azure Cli. I raised an issue for the document and hopefully a note will be added to it.
  • There is no sample code to demonstrate how to create a pool with shared image gallery with Python.

So I wrote a simple sample in Python. It is based on the latest version (9.0.0) of Azure Batch package for Python. And it uses a service principal for the AAD authentication. The custom image I used for test was built on top of Ubuntu 18.04-LTS. So the node agent sku is ubuntu 18.04. It needs to be changed accordingly if other os version is used.

# Import the required modules from the
# Azure Batch Client Library for Python
import azure.batch._batch_service_client as batch
import azure.batch.models as batchmodels
from azure.common.credentials import ServicePrincipalCredentials

# Specify Batch account credentials
account = "<batch-account-name>"
batch_url = "<batch-account-url>"
ad_client_id = "<client id of the SP>"
ad_tenant = "<tenant id>"
ad_secret = "<secret of the SP>"

# Pool settings
pool_id = "LinuxNodesSamplePoolPython"
vm_size = "STANDARD_D2_V3"
node_count = 1

# Initialize the Batch client
creds = ServicePrincipalCredentials(
    client_id=ad_client_id,
    secret=ad_secret,
    tenant=ad_tenant,
    resource="https://batch.core.windows.net/"
)
config = batch.BatchServiceClientConfiguration(creds, batch_url)
client = batch.BatchServiceClient(creds, batch_url)

# Create the unbound pool
new_pool = batchmodels.PoolAddParameter(id=pool_id, vm_size=vm_size)
new_pool.target_dedicated = node_count

# Configure the start task for the pool
start_task = batchmodels.StartTask(
    command_line="printenv AZ_BATCH_NODE_STARTUP_DIR"
)
start_task.run_elevated = True
new_pool.start_task = start_task

# Create an ImageReference which specifies the Marketplace
# virtual machine image to install on the nodes.
ir = batchmodels.ImageReference(
    virtual_machine_image_id="<resource id of the image version in sig>"
)

# Create the VirtualMachineConfiguration, specifying
# the VM image reference and the Batch node agent to
# be installed on the node.
vmc = batchmodels.VirtualMachineConfiguration(
    image_reference=ir,
    node_agent_sku_id="batch.node.ubuntu 18.04"
)

# Assign the virtual machine configuration to the pool
new_pool.virtual_machine_configuration = vmc

# Create pool in the Batch service
client.pool.add(new_pool)

Update: I polished the above sample code and pushed it into the document I mentioned at the beginning of this post via a PR. The Python sample code in that document is based on the one in this post.

My dotfiles

Recently I spent more and more time on WSL 2. I ran a Ubuntu 18.04 on WSL 2 and mainly used the tools such as zsh, tmux and vim. To make fun with the environment, I customized them little by little, and now the environment looks like this:

my terminal

As there are several customized dotfiles now, I put them together and created a simple script so that I can run a single script to get the environment ready. Now I have my own dotfiles repo and it is here. I will update it if I make other changes to these dotfiles in the future.

And I also shared the profile of my Windows Terminal here. Now I can easily get Windows Terminal and WSL 2 environment configured on any Windows 10 machine.

CKAD Certified

Early this week, I took the 2nd try of the CKAD exam and passed it. I scored 89% this time despite the situation where I ran out of time and couldn’t finish the last question completely. Now I am CKAD certified!

I had my first try of the exam in Aug last year without many preparations. As CNCF gives you a free 2nd chance if the 1st one is not successful, I wanted to take the 1st exam as an opportunity to test my Kubernetes knowledge, to get familiar with the test environment, and to sense how difficult it is. I scored 64% of the 1st exam which is 2% short of the passing score.

Late last year I was busy on my work and until recently I got some time to properly prepare for and take the exam again. I spent about 2 weeks to polish my skills with kubectl and other tools, and used the resources in the following two github repos heavily.

Here are several my tips regarding the exam which I hope could help those who are preparing for the certificate.

  • The only tools that you can use in the exam environment are kubectl, vim and tmux. So be very familiar with them.
  • In the exam, you are allowed to open another browser tab to connect to https://kubernetes.io/docs, but you may not have enough time to read the docs in detail. I relied on kubectl explain more than checking the docs.
  • As the exam environment runs in Chrome, a big screen definitely helps.
  • Most importantly, a lot of practices. The history showed that I tapped k/kubectl for 1372 times and vim for 429 times in 2 weeks before the exam.

Configuring VNET integration of Azure Redis and Web App

To configure Azure Redis with the VNET support, we can follow the steps described in this document. And to integrate Azure web app with a VNET, there is a detailed document for it as well. In this post, I listed some of the common issues that one might hit during the configuration.

  1. The VNET integration for Azure Redis requires an empty subnet for the VNET that is created with Resource Manager. This subnet needs to be created before you create Azure Redis. Otherwise, the configuration would fail.
  2. The subnet for Azure Redis can be protected with a network security group (NSG). Usually the default NSG rules are good enough for protecting the connections. If you need further hardening, you will have to create rules based on the ports list in the Azure Redis document.
  3. To troubleshoot the connection between Azure web app and Azure Redis, you can use the Kudu of web app. There are two tools built in with the web app for network troubleshooting:
    nameresolver.exe can be used to test the DNS functionalities, and
    tcpping.exe
    can be used to test if the host and port can be pinged. But you cannot test the Redis function directly from the Kudu.
  4. Once the VNET integration is configured, the Redis console in Azure Portal will not work anymore. To test the Redis functions with tools such as redis-cli, you will have to build a VM in the VNET and connect to Azure Redis from it.
  5. If somehow your web app cannot access the Azure Redis, although the network configurations are correct, you can try to sync the network for App Service Plan. See this issue for details. Make sure you don’t hit any error when syncing the network.

Move WordPress sites to a new domain

Today I finally decided to move this site from its old domain to this new one, chunliu.me, which I got last year. For a personal hosted WordPress site, changing domain name is not a straight forward task, especially when you don’t want to break the existing external links that point to the site. Here is how I did it, with a lot of search on internet of course.

Copy the database and WordPress folder

To minimize the impact to the existing site, I duplicated the MySQL database of this site and its WordPress folder. Copy WordPress folder is easy, simply use cp command to copy the whole folder. Copy database is a bit tricky because I forgot the password of the MySQL root user. I used the following way to reset the password of the root user.

  1. Run mysql with --skip-grant-tables.
    $ sudo service mysql stop
    $ sudo mkdir -p /var/run/mysqld
    $ sudo chown mysql:mysql /var/run/mysqld
    $ sudo mysqld --skip-grant-tables --skip-networking &
    $ jobs
    
  2. Update the password of root user with mysql client.
    $ mysql -u root
    mysql> FLUSH PRIVILEGES;
    mysql> ALTER USER 'root'@'localhost' IDENTIFIED BY 'MyNewPass';
    mysql> QUIT;
    
  3. Restart mysql service.
    $ sudo pkill mysqld
    $ jobs
    $ sudo service mysql start
    

With the above steps, the database can be copied with the following command.

mysqldump -u root --password=<pwd> <original db> | mysql -u root -p <new db>

Update the URL of the new site

With the copied WordPress and database, the URL needs to be updated in the following several places.

  • wp_config.php: update DB_NAME and DOMAIN_CURRENT_SITE;
  • wp_options table: update site_url and home;
  • wp_site and wp_blogs table: update domain;

Create a URL rewrite rule in the old site

The last thing is to create a url rewrite rule in the old site so that all requests to the old site can be redirected to the new site. This will help to keep the external links without broken.

Edit the .htaccess file of the old site with the following code.

RewriteEngine On
RewriteCond %{HTTP_HOST} ^olddomain\.com$ [OR]
RewriteCond %{HTTP_HOST} ^blog\.olddomain\.com$
RewriteRule (.*)$ https://newdomain/$1 [R=301,L]

With that, the site is moved to the new domain successfully.