Made for… publishing blogs like this! Consists of a terraform script that creates a straightforward VM in Azure and an Ansible playbook that sets up the necessary software. It makes it easy to build a bit more of a fully featured system than a pre-packed container (e.g., editors, image manipulation software, etc).

The setup uses terraform and ansible, both which are pre-installed in Azure Cloud Shell and therefore deploying this is quick and convenient.

Note that in this version the disk is ephemeral and will be deleted when the environment is shutdown, so use git to store the actual content elsewhere.

To use

The scripts and some notes are given below, in this section are the commands to issue.

To create the VM:

terraform apply

Install the software:

ansible-playbook -i "`terraform output -raw vm_ip`," bnwebplay.yml

And finally ssh in:

ssh azureuser@`terraform output -raw vm_ip`

When done, destroy with (but note: disk will be deleted, so push changes to your git host):

terraform destroy

Terraform script

The architecture is the most straightforward virtual machine setup as illustrated below. Some notes:

  • The disk in this configuration will be deleted on shutdown, so this is useful for situation where all content is always pushed to a remote (git) repository after development

  • Port 22 is open to public to access via SSH

  • Use port forwarding to connect to Jekyll server jekyll serve

  • Spot instance is used to reduce cost – there is some chance of eviction

  • There is a daily automatic shutdown to avoid accidentally long running vms

arch


# Configure the Microsoft Azure Provider
terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "~>2.0"
    }
  }
}
provider "azurerm" {
  features {}
}

locals {
       region= "uksouth"
}

# Create a resource group if it doesn't exist
resource "azurerm_resource_group" "webdev" {
    name     = "webdev"
    location = local.region
}

# Create virtual network
resource "azurerm_virtual_network" "gen" {
    name                = "gennet"
    address_space       = ["10.0.0.0/16"]
    location = local.region
    resource_group_name = azurerm_resource_group.webdev.name
}

# Create subnet
resource "azurerm_subnet" "gensub" {
    name                 = "gensub"
    resource_group_name  = azurerm_resource_group.webdev.name
    virtual_network_name = azurerm_virtual_network.gen.name
    address_prefixes       = ["10.0.1.0/24"]
}

# Create public IPs
resource "azurerm_public_ip" "publicip" {
    name                         = "jekyllpublicip"
    location = local.region
    resource_group_name          = azurerm_resource_group.webdev.name
    allocation_method            = "Dynamic"

}

# Create Network Security Group and rule
resource "azurerm_network_security_group" "webdevsg" {
    name                = "webdevsg"
    location = local.region
    resource_group_name = azurerm_resource_group.webdev.name

    security_rule {
        name                       = "SSH"
        priority                   = 1001
        direction                  = "Inbound"
        access                     = "Allow"
        protocol                   = "Tcp"
        source_port_range          = "*"
        destination_port_range     = "22"
        source_address_prefix      = "*"
        destination_address_prefix = "*"
    }

}

# Create network interface
resource "azurerm_network_interface" "nic" {
    name                      = "webdevnic"
    location = local.region
    resource_group_name       = azurerm_resource_group.webdev.name

    ip_configuration {
        name                          = "nicfg"
        subnet_id                     = azurerm_subnet.gensub.id
        private_ip_address_allocation = "Dynamic"
        public_ip_address_id          = azurerm_public_ip.publicip.id
    }
}

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "example" {
    network_interface_id      = azurerm_network_interface.nic.id
    network_security_group_id = azurerm_network_security_group.webdevsg.id
}

# Create virtual machine
resource "azurerm_linux_virtual_machine" "webdevvm" {
    name                  = "webdev"
    location = local.region
    resource_group_name   = azurerm_resource_group.webdev.name
    network_interface_ids = [azurerm_network_interface.nic.id]
    size                  = "Standard_DS1_v2"
    priority = "Spot"
    eviction_policy = "Deallocate"

    os_disk {
        name              = "osdisk"
        caching           = "ReadWrite"
        storage_account_type = "Premium_LRS"
    }

    source_image_reference {
        publisher = "Canonical"
        offer     = "UbuntuServer"
        sku       = "18.04-LTS"
        version   = "latest"
    }

    computer_name  = "webdev"
    admin_username = "azureuser"
    disable_password_authentication = true

    admin_ssh_key {
        username       = "azureuser"
        public_key     = file("~/.ssh/id_rsa.pub")
    }
}


output "vm_ip" {
  value = azurerm_linux_virtual_machine.webdevvm.public_ip_address
}

resource "azurerm_dev_test_global_vm_shutdown_schedule" "sch" {
      virtual_machine_id = azurerm_linux_virtual_machine.webdevvm.id
      location           = azurerm_resource_group.webdev.location
      enabled            = true

      daily_recurrence_time = "2300"
      timezone              = "W. Europe Standard Time"

     notification_settings {
         enabled         = false
	  }

}

Ansible playbook:

Save as bnwebplay.yml :

---
- name: bnweb software stack
  hosts: all
  user: azureuser

  tasks:

    - name: install base packages
      apt: 
        pkg: ['git', 'build-essential', 'ruby-full', 'ruby-dev', 'emacs-nox'] 
        state: present
        update_cache: yes
        cache_valid_time: 604800
      become: true  

    - name: install ruby dependencies
      gem: name= state=present
      with_items:
        - bundler
        - jekyll
        - rake