This article proceeds through the steps to deploy an Azure App Service web app written in Java to an Arc-hosted Kubernetes cluster hosted in another cloud service.
The article demonstrates how to:
- Provision an app service Kubernetes environment
- Deploy a Java web app to an Arc-enabled cluster on Google Cloud
It concludes by demonstrating that our Azure app is running on Google Cloud while being managed from Azure.
These instructions target PowerShell on Windows but can be recreated in Bash with little additional effort. They are also broken down to aid in understanding the process. However, they can be compiled into a singular, more concise script in production.
Prerequisites
This demo uses:
- Azure CLI
- Java 11
- Maven
- An Azure subscription (free trials are available)
If you are not already familiar with Java and web apps in Azure, we recommend that you read through the Azure App Service documentation.
The first article of this series reminded us how to connect our Azure Arc to Kubernetes on Google Cloud. Before starting here, let’s run a few checks to ensure everything is as we left it. Remember to account for any naming changes you might have made.
First, confirm the presence of the resource group named arc-rg
in your Azure subscription.
(Note that while in preview, resource groups for Azure Arc must be in either West Europe or East US.)
Then confirm registration of the Kubernetes providers:
az provider show -n Microsoft.Kubernetes --query registrationState -tsv
az provider show --namespace Microsoft.KubernetesConfiguration --query registrationState -o tsv
Next, confirm that the connected Azure Arc resource, k8s-gke
, exists in the resource group.
Additionally, confirm that you have a kubeconfig
entry to authenticate with the GKE cluster.
Check that the azure-arc
is Active
:
kubectl get namespace
Next, verify that the Azure Arc Kubernetes agents are deployed:
kubectl get pods -n azure-arc
They should all show as running.
Then validate the connection with the following command.:
az connectedk8s show --resource-group arc-rg --name k8s-gke
This should show the provisioningState
property as Succeeded
.
Finally, check that the connected Kubernetes cluster is a resource group in the Azure portal.
Step 1: Provision an App Service Kubernetes Environment
You are free to use any names for SQL Server to suit your preference. (You will also need to set your Azure subscription ID.) However, for the sake of simplicity, this article uses the following names:
k8s-gke
– Azure Arc resource arc-rg
– Azure resource group for Azure Arc arc-app-service
– Kubernetes App Service extension arc-ns
– App namespace arc-app-custom-location
– custom location
Log in to Azure
For deployment pipelines, it can be helpful to connect Kubernetes clusters to Azure Arc using service principles with limited-privilege role assignments. You can find instructions for this here.
For this demo, we log in to our Azure account:
az login
az account set -s "<SUBSCRIPTION_ID>"
Add or Update the Prerequisites for Custom Location
az provider register --namespace Microsoft.ExtendedLocation --wait
az extension add --upgrade -n customlocation
Enable the Feature on the Cluster
az connectedk8s enable-features -n k8s-gke -g arc-rg --features custom-locations
This also enables the cluster-connect feature for us.
Install the App Service Extension in Your Azure Arc-connected Cluster
az k8s-extension create -g "arc-rg" --name "arc-app-service"
--cluster-type connectedClusters -c "k8s-gke" `
--extension-type 'Microsoft.Web.Appservice' --release-train stable --auto-upgrade-minor-version true `
--scope cluster --release-namespace "arc-ns" `
--configuration-settings "Microsoft.CustomLocation.ServiceAccount=default" `
--configuration-settings "appsNamespace=arc-ns" `
--configuration-settings "clusterName=arc-app-service" `
--configuration-settings "keda.enabled=true" `
--configuration-settings "buildService.storageClassName=standard" `
--configuration-settings "buildService.storageAccessMode=ReadWriteOnce" `
--configuration-settings "customConfigMap=arc-ns/kube-environment-config"
If we want Log Analytics enabled in production, we must add it now following the additional configuration shown here. We will not be able to do this later.
There are two parameters for persistent storage that need proper consideration. You can learn more about Kubernetes persistent volumes, including a breakdown of all available access modes on the storage resources provided by all major cloud service providers, in the official Kubernetes documentation. In this demo, we’re hosting our cluster in Google Cloud Engine (GCE), which provides GCEPersistentDisk
. This supports the following PersistentVolume
(PV) types for Kubernetes:
ReadWriteOnce
ReadOnlyMany
The AppService
extension supports:
ReadWriteOnce
ReadWriteMany
Suppose we don’t explicitly set the AppService
extension to ReadWriteOnce
. In that case, it defaults to ReadWriteMany
. There will be a mismatch between the PersistentVolumeClaim
(PVC), the request for storage from the AppService
extension, and the available PVs in GCE.
We also must configure the correct class name for the persistent storage on GCP.
The command can take a few minutes. If you encounter any errors during this process, check the troubleshooting documentation.
When the command completes, navigate to the Kubernetes Azure Arc resource in Azure Portal and click Extensions in the sidebar. You should see the arc-app-service extension listed as Installed.
Create the Custom Location
We must set up a custom location to enable the deployment of Azure Functions to an Arc-enabled Kubernetes cluster. We looked at these in the first article.
Get the id
property of the App Service extension:
$extensionId=(az k8s-extension show --name arc-app-service --cluster-type connectedClusters -c k8s-gke -g arc-rg --query id -o tsv)
Get the id
property of the Azure Arc-connected cluster:
$connectedClusterId=(az connectedk8s show -n k8s-gke -g arc-rg --query id -o tsv)
Use these values to create the custom location:
az customlocation create `
--resource-group arc-rg `
--name arc-app-custom-location `
--host-resource-id $connectedClusterId `
--namespace arc-ns `
--cluster-extension-ids $extensionId
When this completes, validate it with the following command:
az customlocation show --resource-group arc-rg --name arc-app-custom-location --query privisioingState -o tsv
The output should read Succeeded. If not, wait a minute and run the command again.
Create the App Service Kubernetes Environment
Get the custom location id
property:
$customLocationId=$(az customlocation show `
--resource-group arc-rg `
--name arc-app-custom-location `
--query id `
--output tsv)
Use this id
to create the App Service Kubernetes environment:
az appservice kube create `
--resource-group arc-rg `
--name k8s-gke `
--custom-location $customLocationId
Check the provisioning state is set to Succeeded
:
az appservice kube show --resource-group arc-rg --name k8s-gke
Step 2: Create the Web App in Azure
Use the Azure Portal to create a new Web App with the following settings:
- Resource group: arc-rg
- Name: azure-arc-demo-java-app
- Publish: Code
- Runtime stack: Java 11
- Java webserver stack: Tomcat 9.0
The only difference to how we usually make apps in Azure is the region. You must select the custom location instead of a regular region name.
Step 3: Deploy an Azure Java Web App
We will adapt the Maven Hello World
Java web app to display the host’s IP address and prove that it is running in Google Cloud.
Create the Java App
For this step, you can clone the completed code from this demo’s GitHub repository or complete the steps yourself:
mvn archetype:generate "-DgroupId=example.demo" "-DartifactId=helloworld" "-DarchetypeArtifactId=maven-archetype-webapp" "-Dversion=1.0-SNAPSHOT"
Then change the page to output the host’s IP own address:
<p>
<% try (java.util.Scanner s=new java.util.Scanner(new java.net.URL("https://api.ipify.org").openStream())) {
out.println(s.useDelimiter(" \\A").next()); } catch (Exception ex) { ex.printStackTrace(); } %>
</p>
Deploying to Azure
Note: Usually, the next step for a Java Azure Web App would be to configure the Maven plugin for deployment. However, at the time of writing, Maven cannot see Java apps in custom locations as it can with those in standard regions. As a result, we won’t deploy with Maven. We will instead use the Azure CLI.
The next step is to package our web app in a Web Application Resource (WAR) file:
cd helloworld
mvn clean package
Now we can deploy it to our newly created Java web app in Azure:
az webapp deploy --name azure-arc-demo-java-app --resource-group arc-rg --src-path .\target\helloworld.war
Summary
We have provisioned an App Service Kubernetes Environment in Azure, hosted in Google Cloud, and successfully deployed a Java Azure Web App.
When we visit the URL for our deployed app, it calls ipify.org to retrieve its host’s IP address, which it then renders in the page output:
When we perform a whois
query on that IP address, we see the following:
This is definitive proof that our Java Azure Web App is running in Google Cloud!
After the initial steps required to connect our Google Cloud Kubernetes cluster and configure it to host Azure App Services, creating and deploying new apps is the same as working with those native to Azure, regardless of whether we are deploying them from a pipeline or a local machine.
To learn how to manage, govern, and secure your Kubernetes clusters across on-premises, edge, and cloud environments with Azure Arc Preview, check out Azure webinar series Manage Kubernetes Anywhere with Azure Arc Preview.
Ben is the Principal Developer at a gov.uk and .NET Foundation foundation member. He previously worked for over 9 years as a school teacher, teaching programming and Computer Science. He enjoys making complex topics accessible and practical for busy developers.