Deploying a Windows EC2 Instance with Hashicorp Terraform and Vault to AWS

Share on:

As part of demonstrating our products to customers, I tend to reprovision EC2 instances to show how easy it is to mount data in AWS to Cloud Block Store. After doing this manually a little too often, I figured why not automate it? Check out this blog on how I use Terraform to deploy a EC2 instance and configure it for in-guest iSCSI.


If you are not familiar with the Pure Cloud Block Store, it is a purpose build block storage system that currently sits in AWS. There are many benefits and use cases you can find out here.

Today customers wonder once the data is in the Pure Cloud Block Store, how can they rapidly stand up a system and gain access to this data? This blog will cover a piece of automation I am now using to stand up an EC2 instance, configure it with iSCSI and get access to my data.

In a future post, i’ll dive much deeper into the Cloud Block Store architecture and configuration. For this post I wanted to focus on the basic deployment of automating t he deployment of the EC2 instance.


Hashicorp Vault

  • This is used to store the AWS access and secret key securely.

Hashicorp Terraform

  • This is used to automate the provisioning using a Terraform .TF file.

Amazon Web Services Account

  • This is the infrastructure to run the EC2 virtual machines.

Setup and Addition of AWS Secrets to Vault

Since I am running this on MacOS. I used brew to install Vault

1brew tap hashicorp/tap
2brew install hashicorp/tap/vault

Once Vault is installed, you can run the server locally. This will provide you the environment variable to use and provide the unseal key and root token.

Since I am using this for a lab, I am using the built in vault dev server. This should not be used for production!

1vault server -dev

To add your AWS secret key and access key to the vault, run the following command

1export VAULT_ADDR=''
2vault kv put secret/<secretname> secret_key=<secretkey> access_key=<accesskey>

Terraform Manifest Configuration

Download the sample manifest from GitHub and update the variables for your environment. This includes the Vault Token and Secret Name, and the AWS Region, AMI, Instance Type, VPC Security Groups, Subnet ID, KeyPair and Instance Name.

 1provider "vault" {
 2    address = "http://localhost:8200"
 3    token = "<unsealtokenfromvault>"
 6data "vault_generic_secret" "aws_auth" {
 7    path = "secret/<keyname>"
10provider "aws" {
11    region = "us-west-2"
12    access_key =["access_key"]
13    secret_key =["secret_key"]
16resource "aws_instance" "example" {
17    ami           = "ami-id"
18    instance_type = "t2.micro"
19    vpc_security_group_ids = ["sg-id","sg-id2","sg-id3"]
20    get_password_data = true
21    subnet_id = "subnet-id"
22    key_name = "<secretkey>"
23    tags = {
24        Name = "<vmname>"
25    }
26    user_data = <<EOF
27        <powershell>
28        net user terraform Password1! /add /y
29        net localgroup administrators terraform /add
30        Install-PackageProvider -Name NuGet -MinimumVersion -Force
31        Install-Module -Name PureStoragePowerShellSDK -Force
32        if (((Get-WindowsFeature Multipath-io).InstallState) -like "Available") {
33            Set-Service -Name msiscsi -StartupType Automatic
34            Start-Service -Name msiscsi
35            Set-InitiatorPort -NodeAddress (Get-InitiatorPort).NodeAddress -NewNodeAddress "<customiqnname>"
36            Add-WindowsFeature -Name 'Multipath-IO' -Restart
37        }
38        if (((Get-WindowsFeature Multipath-io).InstallState) -like "Installed") {
39            if ((Get-IscsiTargetPortal).TargetPortalAddress -notcontains "<ct0-ip-iscsi-target>"){
40                New-IscsiTargetPortal -TargetPortalAddress <ct0-ip-iscsi-target>
41                Get-IscsiTarget | Connect-IscsiTarget -InitiatorPortalAddress (Get-NetIPAddress |Where-Object {$_.InterfaceAlias -like "Ethernet" -and $_.AddressFamily -like "IPv4"}).IPAddress -IsMultipathEnabled $true -IsPersistent $true -TargetPortalAddress <ct0-ip-iscsi-target>
42            }
43            if ((Get-IscsiTargetPortal).TargetPortalAddress -notcontains "<ct1-ip-iscsi-target>"){
44                New-IscsiTargetPortal -TargetPortalAddress <ct1-ip-iscsi-target>
45                Get-IscsiTarget | Connect-IscsiTarget -InitiatorPortalAddress (Get-NetIPAddress |Where-Object {$_.InterfaceAlias -like "Ethernet" -and $_.AddressFamily -like "IPv4"}).IPAddress -IsMultipathEnabled $true -IsPersistent $true -TargetPortalAddress <ct1-ip-iscsi-target>
46            }
47            if (((Get-MSDSMAutomaticClaimSettings).iSCSI) -ne "True") {
48                Enable-MSDSMAutomaticClaim -BusType iSCSI -Confirm:$false
49            }
50            if (((Get-MSDSMAutomaticClaimSettings).iSCSI) -notcontains "PURE") {
51                New-MSDSMSupportedHw -VendorId PURE -ProductId FlashArray
52            }
53            if (Get-MSDSMGlobalDefaultLoadBalancePolicy -ne "LQD") {
54                Set-MSDSMGlobalDefaultLoadBalancePolicy -Policy LQD
55            }
56            if (((Get-MPIOSetting).CustomPathRecoveryTime) -ne "20") {
57                Set-MPIOSetting -NewPathRecoveryInterval 20
58            }
59            if (((Get-MPIOSetting).UseCustomPathRecoveryTime) -ne "Enabled") {
60                Set-MPIOSetting -CustomPathRecovery Enabled
61            }
62            if (((Get-MPIOSetting).PDORemovePeriod) -ne "30") {
63                Set-MPIOSetting -NewPDORemovePeriod 30
64            }
65            if (((Get-MPIOSetting).DiskTimeoutValue) -ne "60") {
66                Set-MPIOSetting -NewDiskTimeout 60
67            }
68            if (((Get-MPIOSetting).PathVerificationState) -ne "Enabled") {
69                Set-MPIOSetting -NewPathVerificationState Enabled
70            }
71            if ((((Get-Disk).OperationalStatus) -contains "Offline") -and ((((Get-Disk).FriendlyName) -eq "PURE FlashArray"))) {
72                Get-Disk | ? {$_.OperationalStatus -eq "Offline"} | Set-Disk -IsOffline $false
73            }
74            if (((Get-Disk | ? {$_.IsReadOnly -eq "True"}).IsReadOnly) -and ((((Get-Disk).FriendlyName) -eq "PURE FlashArray"))) {
75                Get-Disk | ? {$_.IsReadOnly -eq "True"} | Set-Disk -IsReadOnly $false
76            }
77        }
78        </powershell>
79        <persist>true</persist>
80        EOF
83output "public_dns" {
84    value = ["${aws_instance.example.*.public_dns}"]
86output "public_ip" {
87    value = ["${aws_instance.example.*.public_ip}"]

Run the Terraform Manifest

Run terraform init to install any needed providers, terraform plan to make sure all the connectivity is working and then terraform apply to deploy!

1terraform init
2terraform plan
3terraform apply

If everything is successful your EC2 instance should be deployed in ~ 2minutes and after a reboot or two will be fully configured and running!

Viewing your Pure Cloud Block Store data

In my example I already had a host provisioned with the custom IQN on my Pure Cloud Block Store, this allowed the data to be automatically mounted and utilized in my EC2 instance. As Pure Storage has a widely supported Powershell module you can easily provision new hosts, volumes or even mount existing replicated snapshots to your instance. Stay tuned to an upcoming post where I dive into the options to bring your replicated data to life in AWS!


Hopefully this helped you get started with automating EC2 instance deployment with Terraform!

Any questions or comments? Leave them below.

comments powered by Disqus

See Also