다음을 통해 공유


Deploying Java Applications in Azure

You have an existing Java application or are developing a new one and want to take advantage of the cloud? Azure is an attractive platform for deploying a variety of applications, including Java based apps.

In Azure you simply upload your application and the plumbing is all put in place for you. No need to manage Operating System installs or patches. Understanding how to package a Java application for deployment in Azure and the options you have in that process is what I will discuss here.

I am assuming you have a basic understanding of Azure so I won’t introduce any concepts here. If that is not the case you might want to visit https://www.microsoft.com/windowsazure/ to get an overview of Azure prior to reading this page.

How to run a Java Application in Azure

Azure requires a ‘Role’ to run your application. The Worker Role is the best type of Role to host a Java application. Instances of a Worker Role can be seen as fresh installs of Windows Server ready to run an application.

The Worker Role configuration is included in the deployment package you upload to Azure along with your application. The Role provides hooks to launch a Java application and ways to communicate with the Azure runtime.

Creating a Role involves adding it to the service definition file, setting attributes in the service configuration file and providing a class to handle the Role lifecycle. Let’s look into the main options in each of these files as it relates to deploying Java applications. I am using the Azure SDK 1.3 and will be talking about features introduced in that release.

Service Definition

The service definition file lists all Roles in your Azure service. I will cover Port Reservation and Startup Tasks.

Port Reservation

Of critical importance to Java applications running in the cloud is the specification of the required TCP ports the application uses. Azure will block communications to any port you don’t explicitly list in your service definition. You will need to add a InputEndpoint entry for every public port your application uses to receive requests. These ports will be configured both as a Windows Firewall rule and programmed in the Load Balancer. Remember you can have any number of instances of the same Role and they will all be configured behind a load balancer.

This is an example of how you would configure a Role that hosts a Java application which uses port 80 to accept HTTP requests:

<InputEndpoint name="Http" protocol="tcp" port="80" localPort="8080" />

Notice the protocol is set to "tcp" even though the application is actually using HTTP. "http" is a valid value for the protocol attribute but you must never use it for Java applications. The "http" value has a specific meaning in Azure and is reserved for .NET applications.

The port attribute specifies the public port that will be available for client requests. The localPort tells Azure the port your application is actually listening on. Azure will map the public port on the load balancer to the local port your application is using. Those values don’t need to be different, in fact most applications will likely use the same local and external port.

It is also important to know that localPort is an optional attribute. If you don’t define it Azure will dynamically open a port for your application. Since all requests will be mapped to that port your application must inquire the Azure runtime for the assigned port number to receive requests. Although optional you should probably always set the localPort.

The local port reservation has some limitations when it comes to the Compute Emulator (or Development Fabric). Because the Compute Emulator is running on your desktop it may not be able to bind to the port you requested because another application might have reserved the port. That is always the case for port 80 because IIS is already using it. If you intend on using the Compute Emulator you will need to get the dynamically assigned port at runtime, at least in development.

Startup Tasks

Startup Tasks are commands launched before your Role is initialized. They were created to allow a Role to perform any configuration before requests are forwarded to the Role instance.

A unique feature of Startup Tasks is the ability to run with elevated (administrator) privileges. This is usually required if your application needs to launch an installer. All installers must also run in silent or unattended mode. In fact the Oracle JVM installer does require administrator privileges to run and does support silent mode. If you plan on installing it you will have to use a Startup Task.

Another characteristic of Startup Tasks is the ability to write to any folder. Regular Azure applications must define working directories and can only write to them. That limitation does not apply to Startup Tasks. That facilitates the job of some installers and can also be beneficial to some Java application servers.

By now you must be thinking that you can just launch your Java application as a Startup Task and be done. In fact that is one option for launching Java applications but as usual is has some drawbacks. I will talk about this option shortly.

Service Configuration

Configurations are companions to Service Definitions. For every Role you define you must have a configuration entry.

Here is where you specify how many instances of your Role you would like to have. You will also define various settings for your Role.

One such setting of interest is the version of Windows Server. You can choose between Windows Server 2008 and Windows Server 2008 R2. For the most part the underlying operating system is irrelevant to a Java application. That continues to be true in Azure. If your application doesn’t rely directly on a specific Windows feature nor it depends on a 3rd party tool that does you can ignore this setting.

There are important differences between these Windows Server families though. The one I am interested in is Power Shell. Windows Server 2008 R2 includes Power Shell 2.0 which – as I’ll soon demonstrate – can be used to greatly simply the deployment of Java applications.

Azure Role Entry Point

The Role Entry Point is the interface between your application and the Azure runtime. Azure will instantiate and call your Role Entry Point providing you with the opportunity to take actions at different stages of the Role lifecycle.

This is where you will be doing some .NET programming. At this point Azure requires the Role Entry Point to be written in either C# or VB.NET. The amount of code in your Role Entry Point will vary based on the complexity of your application. It can be as simple as a few lines of code.

Three methods define the role lifecycle: OnStart, OnStop and Run. OnStart is called before your application receives live traffic. You should do any initialization in this step. To a certain extent this is akin to a synchronous Startup Task. OnStop is called when Azure is shutting down your role to allow for a graceful shutdown. Run is called when Azure is ready to forward live traffic to your application. The call to Run must not return while your Role is operational. If it returns Azure will interpret it as a failure of your application and will recycle your Role instance. This behavior can be of interest to certain applications and should be used to request Azure to recycle your Role instance in case of a failure. You can envision a heartbeat check on the JVM being executed from Run and returning in case of either application or JVM failure, causing Azure to restart your Role and bring your application back to a running state.

Java Virtual Machine, Libraries and Resources

Now that you understand how to configure a Role to run your Java application you need to provide it with a JVM. The best way to do it is to upload the JVM to Azure Blob Storage and install it during Role initialization, either as a Startup Task or in the Role Entry Point’s OnStart.

Most Java runtimes can be simply copied to a new machine, no install is necessary. If your JVM can be installed this way you should create a ZIP archive and upload it to Azure Blob Storage. Because you may need to maintain multiple versions of the JVM I recommend creating a separate container to hold all the JVMs you need. Inside that container you can keep JDKs and/or JREs named after the corresponding Java version.

If you need to run an installer which requires administrator privileges you have to use a Startup Task. If you can copy the JVM you have a choice.

Depending on your build process you might want to use the same strategy for other dependencies. Libraries like Spring, Log4J, Apache Commons can also be in the cloud and pulled from Azure Blob Storage before your application starts. Imagine doing the same for the application server or web server your application uses. I personally like to create a separate container called ‘apps’ for Jetty, Tomcat and other servers.

Launching the Java application

With everything in place the only step left is to launch the Java application. It can also be done from either the Role Entry Point or from a Startup Task.

Believe it or not there is enough to discuss in each of these alternatives that will save them for future posts.

As a cliffhanger I will tell you the high level differences between the two options. Using the Role Entry Point to launch your Java application will give you full control over the lifecycle of the role and allows for your application to react to environment changes (e.g. discover when instances are added or removed). It does require you to write.NET code. Using Startup Tasks requires potentially no .NET knowledge plus gives you the ability to run with elevated privileges at the cost of having little to no access to the role lifecycle.

The path you take will vary based on your application.

Packaging and Deploying the Java application

The Azure SDK includes tools to package an application along with the Service Definition and Configuration. The Windows Azure Packaging Tool (cspack.exe) is the main tool to use for this job. Visual Studio provides a convenient frontend for cspack and is also capable of initiating a deployment.

Cspack.exe is more desirable for integrating with exiting Java builds. It is only available on Windows so you will have to do the packaging of the Azure deployment on a Windows box. I will also save the explanation on using cpspack for a future post.

Putting it all together: Jetty 7 in Azure

I did not want to finish without showing how all of this can work. I will demonstrate how to get Jetty 7 working with the minimum amount of effort. Running a full web application on Jetty will require extra steps like deploying your own WAR/EAR but for demonstration purposes I will deploy Jetty as is and access the ROOT web app.

I picked Jetty because I wanted to show an application that uses NIO working in Azure. You should be able to deploy any other web server the same way. David Chou has previously posted a great article on running Jetty in Azure. He wrote it for the SDK 1.2 but a lot of the information there still applies.

You will need a Java 6 compatible JVM, the latest Jetty 7 distribution in zip format, the Azure SDK 1.3 (or later) and Visual Studio 2010. The free edition of Visual Studio 2010 will do. You will also need a .NET library to handle ZIP files. I used https://www.icsharpcode.net/OpenSource/SharpZipLib/ but other popular libraries exist and can be used with little change to the deployment scripts.

Start by installing Visual Studio and the Azure SDK. If you are using Oracle’s JVM you will need to install it locally then create a zip file of the JRE. If you installed the JDK in the default location you can navigate to C:\Program Files\Java\jdk1.6.0_<update #>, right click on the JRE directory and create a zip folder. The deployment script expects this file to be named jre1.6.0_22.zip.

Upload both the JRE.zip and Jetty to your Azure Storage account. Put the jre.zip in a container called ‘java’ and the jetty-distribution-7.x.x.<date>.zip in a container called ‘apps’. You can change these container and file names but you will need to update the Launch.ps1 script.

You can download this Visual Studio 2010 project (almost) ready to be deployed. You will need to enter your storage account credentials to get it to work.

If you decide to create the project from scratch you should add the SharpZipLib DLL to a ‘lib’ folder in your project.

Minimal role code

This example will launch Jetty from a Startup Task. The only requirement for the Role Entry Point is to not return from Run so this code blocks indefinitely.

 

using System.Threading;

using Microsoft.WindowsAzure.ServiceRuntime;

 

namespace JettyWorkerRole

{

    public class WorkerRole : RoleEntryPoint

    {

        public override void Run()

        {

            while (true)

                Thread.Sleep(10000);

        }

    }

}

Service definition

There is a single Startup Task to execute ‘Run.cmd’ with regular user (limited) privileges. The task type ‘background’ decouples the execution of the task from the Role lifecycle.

I also specify the public port 80 to accept request but internally Jetty will use port 8080.

 

<?xml version="1.0" encoding="utf-8"?>

<ServiceDefinition name="MinimalJavaWorkerRole" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceDefinition">

  <WorkerRole name="JettyWorkerRole">

    <Startup> 

      <Task commandLine="Run.cmd" executionContext="limited" taskType="background" />

    </Startup> 

    <Endpoints> 

      <InputEndpoint name="Http" protocol="tcp" port="80" localPort="8080" />

    </Endpoints>

  </WorkerRole>

</ServiceDefinition>

Service configuration

The default service configuration sets the number of instances for the role to 1. Feel free to increase it if you’d like, this example will work with any number of instances.

A little hidden in this configuration is the osFamily. This is where you specify the family of Windows Server you would like to use. It must be set to Windows Server 2008 R2 which is indicated by the family 2. This is needed because I use PowerShell 2.0 in this example.

<?xml version="1.0" encoding="utf-8"?>

<ServiceConfiguration serviceName="MinimalJavaWorkerRole" xmlns="https://schemas.microsoft.com/ServiceHosting/2008/10/ServiceConfiguration" osFamily="2" osVersion="*">

  <Role name="JettyWorkerRole">

    <Instances count="1" />

  </Role>

</ServiceConfiguration>

Run.cmd

This is a command used to launch the PowerShell script which contains the actual deployment logic. This file is not strictly needed but Visual Studio makes checks for the existence of the commandLine. So if you use Visual Studio you must have it.

I’m relaxing the security on the PowerShell script execution. In production you should sign the PowerShell script instead.

powershell -executionpolicy unrestricted -file .\Launch.ps1

A word of caution if you are new to Visual Studio – text files are saved in UTF-8 by default. It should be otherwise fine with this content if it wasn’t for the byte order mark. Because of that you must make sure to change the encoding for this file. You can do this in the “Save As..” option, here are the steps.

image   image   image

Launch.ps1

This is an overly simplified version of a deployment script. I will expand on it and explain the details of how it works on the post on using Startup Tasks. Basically this script downloads both JRE and Jetty from your storage account, unzips both and launches Jetty.

You must change the $connection_string to use your Storage Account credentials. You might also need to change $jre and $jetty to match the versions you used.

function unzip ($zip, $destination) {

       Add-Type -Path ((Get-Location).Path + '\lib\ICSharpCode.SharpZipLib.dll')

       $fs = New-Object ICSharpCode.SharpZipLib.Zip.FastZip

       $fs.ExtractZip($zip, $destination, '')

}

 

function download_from_storage ($container, $blob, $connection, $destination) {

    Add-Type -Path ((Get-Location).Path + '\Microsoft.WindowsAzure.StorageClient.dll')

    $storageAccount = [Microsoft.WindowsAzure.CloudStorageAccount]::Parse($connection)

    $blobClient = New-Object Microsoft.WindowsAzure.StorageClient.CloudBlobClient($storageAccount.BlobEndpoint, $storageAccount.Credentials)

    $remoteBlob = $blobClient.GetBlobReference($container + '/' + $blob)

    $remoteBlob.DownloadToFile($destination + "\" + $blob)

}

 

$connection_string = 'DefaultEndpointsProtocol=http;AccountName=<YOUR_ACCOUNT>;AccountKey=<YOUR_KEY>'

 

# JRE

$jre = 'jre1.6.0_22.zip'

download_from_storage 'java' $jre $connection_string (Get-Location).Path

unzip ((Get-Location).Path + "\" + $jre) (Get-Location).Path

 

# Jetty

$jetty = 'jetty-distribution-7.2.2.v20101205.zip'

download_from_storage 'apps' $jetty $connection_string (Get-Location).Path

unzip ((Get-Location).Path + "\" + $jetty) (Get-Location).Path

 

# Launch Jetty

cd jetty-distribution-7.2.2.v20101205

..\jre\bin\java `-jar start.jar

You must also tell Visual Studio do include this script in your build. Right-click on Launch.ps1 and select Properties. Make sure the ‘Copy to Output Directory’ option is set to ‘Copy Always’. You will need to do the same thing for Run.cmd and the Zip library.

image

Now you are ready to deploy. You can deploy the app directly from Visual Studio, information on how to set it up on this page https://msdn.microsoft.com/en-us/library/ee460772.aspx. Once your Azure account is configured in Visual Studio right-click on the cloud project and select ‘Publish…’.

image

Once the deployment is complete you can navigate to your cloudapp.net domain to see Jetty running. You will be greeted by the welcome page.

image

Remember Jetty defaults to the SelectChannelConnector showing your NIO application can run fine in Azure.

Comments

  • Anonymous
    February 07, 2011
    This is a great article Mario. Not only did it allow me to set up a Glassfish setup on Azure, it also helped me understand the concepts of Windows Azure along the way. Thanks for posting this! I am curious about the use of startup tasks versus onStart(). I am currently undecided on which one to use for downloading and launching my JDK and Glassfish server. Secondly, I have a separate worker role running MySQL. I am interested in knowing your opinion on the pros and cons of running MySQL in the same worker role as Glassfish - versus running it in a separate worker role. So far, I am unable to get the Worker role running Glassfish to communicate with the one running MySQL (even though the latter has the requisite ports exposed via the csdef).

  • Anonymous
    February 09, 2011
    I need to elaborate on the differences of both approaches, that’s going to be my next article. In short, if you don’t need to interact with the Azure runtime you should be safe using startup tasks. If you do need to know when new instances of or your worker roles are brought up or down, discover IP addresses for inter-role communication, programmatically take an instance of the role running Glassfish off service, etc, then you must write your own Role Entry Point implementation and therefore use OnStart(). The best practice is to run DBs on separate hosts from application or web servers. As applications grow you most likely will need more application than DB instances, having them in separate hosts makes that possible.

  • Anonymous
    March 01, 2011
    ;-)  if you mean people to set an option to "Copy always" ... the screenshot you provide for this should not highlight a different option  ;-)