Wednesday, February 6, 2019

Creating a deployment on Azure using Terraform

In this post, I will explain how we may create a complete deployment on Azure using Terraform. How to do this using GCP and AWS will be covered in subsequent blogs.

Our deployment will have a Rest service with few APIs which are served by few Rest API servers sitting behind Nginx server(s). All the APIs are https and SSL termination happens at Nginx and internal traffic will be unencrypted.

Our services are microservice (a buzzword people love :)).
They are reliable.
They are highly available.
They are scalable.

As the services need to be highly available, we need to be running more than one instances of the service. So, we will have more than 1 instances of the services running. For this example, we will have just one service. As they have to scale out and in, so we need to dynamically create new instances of the service when there is high traffic or destroy few existing instnaces of the services when we detect usage is low. We can autoscale in and out at machine (virtual machine) level. But doing that at virtual machine level is not good for microservices. We should scale at light-weight service or microservice level. We will use Kubernetes for doing that staff for us. Kubernetes is a container-orchestration framework that fits well for microservices land. I really don't want to use Kubernetes to control my data stores as that doesn't buy me much. In this example, we will not be using Azure service for provisioning Kubernetes cluster, but we will provision that on our own. We don't want vendor lockin as far as possible :)

Please get the client_id, client_secret, tenant_id and subscription id from Azure console which will be internally used by packer to communicate with Azure via APIs.
Now let us create the VMs for Kubernetes on Azure using packer.  Below is the packer deifinition JSON (let us save it in file k8smachine.json)


{
  "builders": [{
    "type": "azure-arm",
    "subscription_id": "PUT YOUR SUBSCRIPTION_ID HERE",
    "client_id":        "PUT YOUR CLIENT_ID HERE",
    "client_secret":   "PUT YOUR CLIENT_SECRET HERE",
    "tenant_id":     "PUT YOUR TENANT_ID HERE",
    "managed_image_resource_group_name": "Nipun1",
    "managed_image_name": "Kubernetes_image",

    "os_type": "Linux",
    "image_publisher": "Canonical",
    "image_offer": "UbuntuServer",
    "image_sku": "18.04-LTS",

    "azure_tags": {
        "purpose": "kubernetes",
        "role": "compute"
    },

    "location": "East US",
    "vm_size": "Standard_DS2_v2"
  }],
  "provisioners": [{
    "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
    "inline": [
      "sudo apt-get install -qy docker.io",
      "sudo apt-get install -y apt-transport-https",
      "curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -",
      "echo \"deb https://apt.kubernetes.io/ kubernetes-xenial main\" > /etc/apt/sources.list.d/kubernetes.list",
      "apt-get update",
      "apt-get install -y kubelet kubeadm kubectl",
      "apt-mark hold kubelet kubeadm kubectl",
      "wget https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz -P /home/ubuntu/"
    ],
    "inline_shebang": "/bin/sh -x",
    "type": "shell"
  }]
}

Here I am using "Nipun1" resource group which I have already created on Azure on location East US. You may have choose better names. The Azure VM image generated will have the name "Kuernetes_image"

Now we run the below command:


$ packer build 
k8smachine.json

And Ubuntu VM image with preinstalled Kubernetes binaries will be ready.


Now let us create the VM image with Elasticsearch binary and JRE (From OpenJDK) installed in it. Below is the packer JSON (let us put this in a file elasticsearch_vm_image.json).



{
  "builders": [{
    "type": "azure-arm",
    "subscription_id": "PUT YOUR SUBSCRIPTION_ID HERE",
    "client_id":        "PUT YOUR CLIENT_ID HERE",
    "client_secret":   "PUT YOUR CLIENT_SECRET HERE",
    "tenant_id":     "PUT YOUR TENANT_ID HERE",
    "managed_image_resource_group_name": "Nipun1",
    "managed_image_name": "elasticsearch_image",

    "os_type": "Linux",
    "image_publisher": "Canonical",
    "image_offer": "UbuntuServer",
    "image_sku": "18.04-LTS",

    "azure_tags": {
        "purpose": "elasticsearch",
        "role": "elasticsearchall"
    },

    "location": "East US",
    "vm_size": "Standard_DS2_v2"
  }],
  "provisioners": [{
    "execute_command": "chmod +x {{ .Path }}; {{ .Vars }} sudo -E sh '{{ .Path }}'",
    "inline": [
      "wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-5.6.9.deb",
      "apt-get -qy install openjdk-8-jre",
      "dpkg -i elasticsearch-5.6.9.deb",
      "rm elasticsearch-5.6.9.deb",
      "systemctl stop elasticsearch",
      "systemctl disable elasticsearch",
      "rm -rf /var/lib/elasticsearch/*",
      "rm -rf /var/log/elasticsearch/*"
    ],
    "inline_shebang": "/bin/bash -x",
    "type": "shell"
  }]
}


Now we create the Elasticsearch VM image by issuing the below command:

$ packer build  elasticsearch_vm_image.json

Let us create an Elasticsearch cluster now. We will use the terraform module for Elasticsearch from here.

Let us create the below terrform files:
main.tf

module createescluster {
    source = "github.com/nipuntalukdar/azureterraform/modules/escluster"
    azure_tenant_id = "${var.tenant_id}"
    azure_subscription_id = "${var.subscription_id}"
    azure_client_id = "${var.client_id}"
    azure_client_secret = "${var.client_secret}"
    ssh_public_key_path = "${var.ssh_pub_key}"
}

variables.tf

variable "subscription_id"  {
    description = "Azure subscription id"
    type = "string"

}
variable "client_id"  {
    description = "Azure client id"
    type = "string"
}
variable "client_secret"  {
    description = "Azure client secret"
    type = "string"
}
variable "tenant_id"  {
    description = "Azure tennant id"
    type = "string"
}
variable "ssh_pub_key"  {
    description = "SSH public key file"
    type = "string"

}

Put the above files main.tf and variables.tf in some directory and cd to that directory and issue the below commands:

$ terraform init

$ terraform apply

This will create a Elasticsearch cluster with 3 nodes in Azure.
If you want to destroy the Elasticsearch cluster, issue the below command:

$terraform destroy