Technorage

Where I rage about technology and stuff!

Deepu K Sasidharan
Deepu K Sasidharan JHipster co-lead, Java, JS, Cloud Native Advocate, Dev @ XebiaLabs, Author, Speaker, Software craftsman. Loves simple & beautiful code. bit.ly/JHIPSTER-BOOK | 10 mins read

Deploy a web app to Azure App Service using Terraform

Deploy a web app to Azure App Service using Terraform

Deploying Java web applications to Azure is easy and has been tried, tested and explained many times by many people. My friend Julien Dubois has a nice series on it here. Azure makes it really easy to use its App Service as it provides many different ways of deploying a web app.

If you are a modern full-stack Java developer there is a high chance that you are deploying your application as a Docker image. Hence today let’s see how we can deploy a Java web application to Azure App Service using Docker and Terraform in the true spirit of infrastructure as code. The approach is pretty much the same for any web application that is built as a docker image and not necessarily tied down to just Java.

To try this out you would need to have Java, NodeJS, Terraform, Docker and Azure CLI installed. Follow the links to install them if needed.

As one of the lead developer of JHipster (A handy development platform to generate, develop and deploy Spring Boot + Angular/React/Vue Web applications and Spring microservices), I would use a JHipster web application as the example here. So let’s get started.

Let’s build a very simple web application using JHipster. We will use the JDL feature to scaffold our application.

We will use the below JDL for our application. Save it to a file named app.jdl in a directory where you want to create the application.

application {
    config {
        baseName helloJHipster,
        applicationType monolith,
        packageName tech.jhipster.demo,
        authenticationType jwt,
        buildTool gradle,
        clientFramework react,
        databaseType sql,
        prodDatabaseType mysql,
        languages [en, nl]
    }
}

Now let us scaffold this using JHipster. Open your favorite console/terminal and run the below command in the directory where you saved the above JDL file, make sure it’s an empty directory.

1
$ npx generator-jhipster import-jdl app.jdl

If you already have JHipster installed you can just run

1
$ jhipster import-jdl app.jdl

This will scaffold the application and install the required client-side dependencies. It might take a few minutes(NPM!) so maybe its time for that coffee.

You can see the application in action by running ./gradlew on the same terminal once the scaffolding is done. You can refer to the generated Readme.md for more instructions regarding the application.

Now let’s move on to the focus of this post, deploying this to Azure App Service with Terraform. Let us first build and publish the docker image for our application.

JHipster conveniently provides everything that is required to build docker images. Let us use the provided docker integration using JIB to build the images. Run the below Gradle command.

1
$ ./gradlew bootJar -Pprod jibDockerBuild

Now let us tag and push this to our docker registry, make sure you have logged into docker and run these commands. Use your own docker hub account name.

1
2
3
$ docker tag hellojhipster:latest deepu105/hellojhipster:latest
$ docker push deepu105/hellojhipster:latest

You can also push to Azure Container registry instead of Docker Hub if you like.

Now that our application and Docker images are ready, let’s prepare the Terraform infrastructure for App Service and MySQL database. For other ways of deploying a JHipster web app to Azure check this out.

First, create a folder for our terraform files. Let’s name the folder terraform.

Now create three files called main.tf, outputs.tf, and variables.tf in this folder.

Let us define the variables we will use. Save the below in variables.tf.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
variable "prefix" {
  description = "The prefix used for all resources in this example"
  default     = "xl"
}

variable "location" {
  description = "The Azure location where all resources in this example should be created"
}

variable "subscription_id" {
  description = "Azure Subscription ID to be used for billing"
}

variable "my_sql_master_password" {
  description = "MySql master password"
}

variable "docker_image" {
  description = "Docker image name"
}

variable "docker_image_tag" {
  description = "Docker image tag"
}

Now let us define our main.tf

First, let us add a configuration for Azure resource manager and create an Azure resource group to hold our resources.

1
2
3
4
5
6
7
8
9
provider "azurerm" {
  version         = "=1.24.0"
  subscription_id = "${var.subscription_id}"
}

resource "azurerm_resource_group" "main" {
  name     = "${var.prefix}-resources"
  location = "${var.location}"
}

Now let us add the configuration to create a MySQL database server along with the required firewall rules to let App Service access the DB. If you want to add local access from your machine add a firewall rule block for your IP as well.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
# This creates a MySQL server
resource "azurerm_mysql_server" "main" {
  name                = "${var.prefix}-mysql-server"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"

  sku {
    name     = "B_Gen5_2"
    capacity = 2
    tier     = "Basic"
    family   = "Gen5"
  }

  storage_profile {
    storage_mb            = 5120
    backup_retention_days = 7
    geo_redundant_backup  = "Disabled"
  }

  administrator_login          = "mysqladminun"
  administrator_login_password = "${var.my_sql_master_password}"
  version                      = "5.7"
  ssl_enforcement              = "Disabled"
}

# This is the database that our application will use
resource "azurerm_mysql_database" "main" {
  name                = "${var.prefix}_mysql_db"
  resource_group_name = "${azurerm_resource_group.main.name}"
  server_name         = "${azurerm_mysql_server.main.name}"
  charset             = "utf8"
  collation           = "utf8_unicode_ci"
}

# This rule is to enable the 'Allow access to Azure services' checkbox
resource "azurerm_mysql_firewall_rule" "main" {
  name                = "${var.prefix}-mysql-firewall"
  resource_group_name = "${azurerm_resource_group.main.name}"
  server_name         = "${azurerm_mysql_server.main.name}"
  start_ip_address    = "0.0.0.0"
  end_ip_address      = "0.0.0.0"
}

This will create a MySQL server, a database for our app on the server and enable access from App Service.

Now let us configure the App Service itself along with a service plan.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
# This creates the plan that the service use
resource "azurerm_app_service_plan" "main" {
  name                = "${var.prefix}-asp"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
  kind                = "Linux"
  reserved            = true

  sku {
    tier = "Standard"
    size = "S1"
  }
}

# This creates the service definition
resource "azurerm_app_service" "main" {
  name                = "${var.prefix}-appservice"
  location            = "${azurerm_resource_group.main.location}"
  resource_group_name = "${azurerm_resource_group.main.name}"
  app_service_plan_id = "${azurerm_app_service_plan.main.id}"

  site_config {
    app_command_line = ""
    linux_fx_version = "DOCKER|${var.docker_image}:${var.docker_image_tag}"
    always_on        = true
  }

  app_settings = {
    "WEBSITES_ENABLE_APP_SERVICE_STORAGE" = "false"
    "DOCKER_REGISTRY_SERVER_URL"          = "https://index.docker.io"

    # These are app specific environment variables
    "SPRING_PROFILES_ACTIVE"     = "prod,swagger"
    "SPRING_DATASOURCE_URL"      = "jdbc:mysql://${azurerm_mysql_server.main.fqdn}:3306/${azurerm_mysql_database.main.name}?useUnicode=true&characterEncoding=utf8&useSSL=false&useLegacyDatetimeCode=false&serverTimezone=UTC"
    "SPRING_DATASOURCE_USERNAME" = "${azurerm_mysql_server.main.administrator_login}@${azurerm_mysql_server.main.name}"
    "SPRING_DATASOURCE_PASSWORD" = "${var.my_sql_master_password}"
  }
}

In this configuration, under site_config we use linux_fx_version to declare our docker image and set always_on to true so that the application is not shut down when there is inactivity for some time.

In the app_settings section we need to disable storage using the flag WEBSITES_ENABLE_APP_SERVICE_STORAGE and also specify DOCKER_REGISTRY_SERVER_URL. Everything else is specific to our app. The flags passed to the MySQL connection URL is important.

Now that our main.tf is ready let us define some output properties that are handy. In the outputs.tf file add the below

1
2
3
4
5
6
7
output "app_service_name" {
  value = "${azurerm_app_service.main.name}"
}

output "app_service_default_hostname" {
  value = "https://${azurerm_app_service.main.default_site_hostname}"
}

Now we are ready to rock and roll! let us deploy the app. Make sure you have set up your Azure CLI and have logged in using az login. Now in a terminal/console navigate to the terraform folder we created and execute these commands. Please change the values for prefix, location & docker_image accordingly.

1
2
3
4
5
6
7
$ terraform init

$ terraform apply \
-var prefix=myAwesomeApp \
-var location=northeurope \
-var docker_image=deepu105/hellojhipster \
-var docker_image_tag=latest

This will prompt you to enter a master password for MySQL server and your Azure subscription ID(You can find this from Azure portal or by running az account list- the id field is the subscription ID). Once you provide the values and confirm, Terraform will get to work and will start creating the resources. this could take a while since we are provisioning a Database server. Wait for it or go have that second coffee ;)

Once the deployment is complete, Terraform will print out the outputs which include the app_service_default_hostname. Copy the URL and open it in your favorite browser. The first time could take a while since the app will be started(cold start) only during the first request.

I hope you found this useful. This is my first post in dev.to, I hope to migrate my blogs from Medium to dev.to soon.

If you like this article, please leave a like or a comment.

You can follow me on Twitter and LinkedIn.

My other related posts:

comments powered by Disqus