How to use a global load balancer to efficiently route requests on a custom domain to the nearest IBM Cloud Code Engine web application.
IBM Cloud Code Engine is the easiest way to deploy and run your source code or container on the IBM Cloud. Our goal has always been to allow you to focus on the development of the application code itself, while Code Engine manages the underlying infrastructure.
Part of that infrastructure is automatically providing a secure and reliable HTTPS endpoint for your application, which includes DNS routing and TLS certificates. Once your application is deployed, it will be accessible via a generic URL like this: https://<myapp>.<randomcharacters>.<region-name>.codeengine.appdomain.cloud. By configuring domain mappings, you can also expose your Code Engine application on your own domain, meaning it would be accessible via https://<myapp>.<mydomain>
This setup is great to serve an application that may reside in a specific region of the world for your localized users. However, if your audience is global, there is a slight issue. All requests will be routed to one specific IBM cloud data center. Assuming you’ve chosen the Frankfurt MZR for your deployment, users in Australia or South America will have to deal with some latency. For a personal or hobby application that might not be a big deal, but it could become an issue for enterprise-grade applications.
To address these use cases, we will combine Code Engine with another IBM Cloud service—IBM Cloud Internet Services (CIS)—that provides capabilities for exposing internet applications in a secure, scalable and reliable manner.
In this blog post, we’ll demonstrate how one specific CIS capability called DNS-based geo-locational routing can be used to reduce latency for a globally distributed Code Engine application and achieve high availability using multiple regions.
Setting up your domain in Cloud Internet Services
Let’s start by creating a Cloud Internet Services instance in your IBM Cloud account. Start by navigating to the appropriate place in the Catalog. There is a free 30-day trial available that provides enough capabilities for our setup.
After your instance is created, you will need to add your domain to it. This process includes delegation of the domain management to Cloud Internet Services. The full set of steps is described in Add and configure your domain. If you haven’t done this already, then it is time for a cup of coffee, because the propagation of DNS records can take a few minutes up to a couple of hours.
Deciding on the application domain
Once your domain is ready for use in Cloud Internet Services, it’s time to decide which domain to use to serve your application. Let’s assume your domain is “example.com” and you allowed your CIS instance to control it in the step above. Obviously, you can use any other domain name, but example.com is what we’ll use for this blog post.
Note: From now on, this example will assume that “example.com“ was handed over to CIS and uses “global-app.example.com” as the domain name for the application. Make sure that you adjust sample commands when you copy them from the following steps.
Generating a certificate for your application domain
The next step is to order a certificate for your domain name. We will use the Let’s Encrypt service:
- Install certbot. Certbot is a client for the Automatic Certificate Management Environment (ACME) protocol that Let’s Encrypt uses to verify domain ownership and to hand out certificates. From the instructions page, select Other as software and the operating system of your workstation to get the right instructions to install the certbot command line tool.
- Run the following command and adjust it for you domain:
- To verify that you own the domain, you are now required to set a TXT record for the domain that you requested with a value provided by the tool, in above example “_acme_challenge.global-app.example.com”. As the domain’s DNS is already delegated to Cloud Internet Services, we must perform this there:
- Navigate to Reliability > DNS.
- In the section DNS records, click Add.
- Set the Type to TXT.
- Set the Name to “_acme-challenge.global-app” (yes, without example.com because this suffix is what CIS is managing).
- Set the Content to the value from the certbot command.
- Click Add.
- You can verify that the record was set by running the following command:
dig _acme-challenge.global-app.example.com txt
- Press Enter in certbot to continue. Certbot now retrieves the certificate that is signed by Let’s Encrypt. It will provide the location where the certificate is stored. The two files that you will need from there are “fullchain.pem” and “privkey.pem”.
Setting up your application in Code Engine
In the next step, we’ll set up the same Code Engine application in three projects that are distributed across the world in three different IBM Cloud regions. I suggest that you take one region that is near to your current location and two locations that are far away. Since I’m located in Europe, I will use Frankfurt, Sao Paolo and Sydney.
Go to Code Engine’s project page, and create three projects. You may want to use a common naming pattern and a shared tag like shown in the screenshot below:
Next, we’ll go into all of three projects and create a simple application:
- On the Overview page, click Create application.
- Provide a Name (e.g., “global-app”).
- As image reference, you can use your own application. For this scenario, I will stay with the IBM-provided hello-world-image “icr.io/codeengine/helloworld” for a good reason—it shows the Code Engine environment variables that indicate the region in which the app runs. This will be interesting once we have a global endpoint for the application.
- Scroll down to the Runtime Settings section. In there, set the minimum number of instances to 1. The reason that we require an application that responds instantaneously to health checks is that we will later configure it in Cloud Internet Services.
- You can leave all other settings at their default. Finally, click Create.
- Wait for the application to become Ready, then click Test application followed by the Send request button. If you used the hello-world-image, then you’ll see a welcome message that includes some environment variables. Look for CE_DOMAIN, which indicates the region in which the app runs:
- Back in Code Engine, go to the Domain mappings tab of the application.
- Click Create to setup a custom domain mapping for your application:
- Copy the content of the “fullchain.pem” into the Certificate chain field.
- Copy the content of the “privkey.pem” into the Private key field.
- Enter the Domain name (e.g., “global-app.example.com”).
- Capture the CNAME target. We will not use it to setup a direct CNAME for your domain but will need it for the routing in Cloud Internet Services.
- Click Create to create the domain mapping. Wait for its status to become Ready. Note: Because the domain setup is not done, you will not be able to access it through the domain yet.
- Optionally, you can change the visibility to No external system domain mapping. This will disable the public https://global-app.<randomcharacters>.<region-name>.codeengine.appdomain.cloud endpoint.
We now have the same Code Engine application running in three regions that are mapped to the same domain. Next, we need to make sure that Cloud Internet Services distributes the load to these three regions.
Setting up a health check
Let’s move over to Cloud Internet Services and configure a health check that is suitable for the application:
- Navigate to Reliability > Global load balancers > Health checks.
- Click Create.
- As Name, I suggest using the application name (e.g., “global-app”).
- Set the Monitor Type to HTTPS and the Port to 443.
- The other settings can be kept at their default assuming you are using the hello-world-image. If you are using your own application, then you may need to adjust the Path and further settings within the Advanced options. Ideally, you implement a dedicated health-check endpoint in your application that you can call here.
- Click Create.
Please see Setting up health checks for more information.
Configuring the origin pools
As a next step, let’s define one origin pool per region:
- Navigate to Reliability > Global load balancers > Origin pools.
- Click Create.
- As Name, I suggest using APP_NAME-REGION (e.g., “global-app-au-syd”).
- As Origin name, I suggest using the region (e.g., “au-syd”).
- Set the Origin address to the CNAME target that you have captured during the domain mapping setup in Code Engine. The value is “custom.<randomcharacters>.au-syd.codeengine.appdomain.cloud”.
- Set the Host header to your domain name (e.g., “global-app.example.com”).
- Under Health check, select Existing health check. Then select the “global-app” health check that you previously created.
- The Health check region drop-down is now unlocked. Select a region that is near the Code Engine region (e.g., Oceania for Sydney).
- Click Save.
After the origin pool is saved, it will initially be shown with critical health. After around two minutes (at most), it should change to healthy. A manual page refresh in your browser may be necessary to see the status update.
Before moving on to the next section, make sure to create an origin pool for each region that you want to address.
Configuring the load balancer
Finally, you can set up the load balancer:
- Navigate to Reliability > Global load balancers > Load balancers and click Create.
- The Name defines the domain or your application. As I am controlling “example.com” in Cloud Internet Services, I must enter “global-app” so that the domain will be “global-app.example.com”.
- Set Traffic steering to Geo.
- Add Geo routes:
- You can define a geo route for all Cloud Internet Services regions. In all of them, add all of the origin pools you created. Sort them so that the nearest Code Engine region is having top priority. In my case, for Oceania, the origin pool of Sydney came first. In Eastern and Western Europe, I put Frankfurt to the top. In Northern and Southern South America, I chose Sao Paolo.
- If you chose to define a geo route only for some Cloud Internet Services regions, you must add a route for the Default region where you can select also available origin pools of the application. This route will be the fallback.
- Click Create to create the load balancer.
And that’s it. We have set up an application that will be routed to the nearest Code Engine region.
Seeing it in action
Open your domain in your browser. Using the CE_DOMAIN environment variable, you can see which Code Engine region you are targeting. If the region does not match your expectation, it could have one of these reasons:
- Are you inside an enterprise network? They sometimes use internet gateways far away from your physical location.
- Are you running with a VPN connection? The VPN server may be in a different geo region.
- Are you using an HTTP proxy? Again, that proxy server may be far away.
Public internet services such as https://www.iplocation.net/ can help you to determine where the IP address that you are using to connect to the internet is located.
I ended up, as expected, in Frankfurt.
Now, let’s do an exercise. Let’s assume there is a regional problem, and for whatever reason, your application is down in one region. Will CIS still route the request?
Let’s delete the Code Engine application in the region that is currently being targeted. In my case, I am deleting the “global-app” from the Code Engine project in Frankfurt. Directly after the app is deleted, refresh the browser. Assuming you were quick enough, you will now see a failure. At this point in time, the target application in Frankfurt is down, but the health check that runs in Cloud Internet Services every 60 seconds by has not yet determined this. Eventually, the endpoint will be functional again. You now reach the Code Engine region that is the second priority original pool in the geo route. In my case, this is Sydney..
Note: 60 seconds is the default and the minimum interval for the Cloud Internet Services Free and Standard plans. You can reduce this to five seconds in the advanced options of the health check if you are using an enterprise plan.
You can now recreate the application in Code Engine including the domain mapping. While creating the domain mapping, you can select the existing secret that still exists assuming you did not delete it. After at most a minute, the app will be served again from Frankfurt.
Note: Browsers will, by default, reuse the existing connection. Given the other region is still alive, you may still get the response from there (in my case, from Sydney). A browser restart may be required to force using a new connection. Alternatively, use another browser or
curl from the command line.
We have set up an IBM Cloud Code Engine application in several IBM Cloud regions and used its domain mapping feature together with IBM Cloud Internet Services to set up a global endpoint that is routed by geo location to provide optimized latency and high availability.
If you have feedback, suggestions, or questions about this post, please reach out to us on StackOverflow by using one of the following tags “ibm-cloud” or “ibm-cloud-code-engine“.