Skip to content

Instantly share code, notes, and snippets.

@alexandreroman
Last active October 25, 2018 22:50
Show Gist options
  • Save alexandreroman/6fca82f76ed1e8559b7d3d6e7d2d4021 to your computer and use it in GitHub Desktop.
Save alexandreroman/6fca82f76ed1e8559b7d3d6e7d2d4021 to your computer and use it in GitHub Desktop.
Install PAS for Azure

Installing PCF for Azure

This document describes how to manually install Pivotal Cloud Foundry 2.3 for Azure, and how to avoid pitfalls during deployment.

Preparing installation

Install Azure CLI:

$ brew update && brew install azure-cli

Set Azure environment:

$ az cloud set --name=AzureCloud

Login to Azure:

$ az login
Note, we have launched a browser for you to login. For old experience with device code, use "az login --use-device-code"
You have logged in. Now let us find all the subscriptions to which you have access...
[
  {
    "cloudName": "AzureCloud",
    "id": "19972672-3fb3-4746-9499-xxxxxxxxxxxx",
    "isDefault": true,
    "name": "PA-xxx",
    "state": "Enabled",
    "tenantId": "29248f74-371f-4db2-9a50-xxxxxxxxxxxx",
    "user": {
      "name": "xxx@pivotal.io",
      "type": "user"
    }
  }
]

List Azure subscriptions:

$ az account list
[
  {
    "cloudName": "AzureCloud",
    "id": "19972672-3fb3-4746-9499-xxxxxxxxxxxx",
    "isDefault": true,
    "name": "PA-xxx",
    "state": "Enabled",
    "tenantId": "29248f74-371f-4db2-9a50-xxxxxxxxxxxx",
    "user": {
      "name": "xxx@pivotal.io",
      "type": "user"
    }
  }
]

Record the value of your subscription id (SUBSCRIPTION_ID), and the value of tenantId (TENAND_ID) as well:

$ export SUBSCRIPTION_ID=`az account list | jq -r ".[].id"`
$ export TENANT_ID=`az account list | jq -r ".[].tenantId"`

Generate a password for your client secret (denoted as CLIENT_SECRET in this document). Use this value to create an Azure Active Director (AAD):

$ export CLIENT_SECRET=mysecret
$ az ad app create --display-name "Service Principal for BOSH" --password "$CLIENT_SECRET" --homepage "http://BOSHAzureCPI" --identifier-uris "http://BOSHAzureCPI"
{
  "acceptMappedClaims": null,
  "addIns": [],
  "appId": "1c9445f6-d3a5-4c5c-b5ad-xxxxxxxxxxxx",
  "appPermissions": null,
  "appRoles": [],
  "availableToOtherTenants": false,
  "deletionTimestamp": null,
  "displayName": "Service Principal for BOSH",
  "errorUrl": null,
  "groupMembershipClaims": null,
  "homepage": "http://BOSHAzureCPI",
  "identifierUris": [
    "http://BOSHAzureCPI"
  ],
  "informationalUrls": {
    "marketing": null,
    "privacy": null,
    "support": null,
    "termsOfService": null
  },
  "isDeviceOnlyAuthSupported": null,
  "keyCredentials": [],
  "knownClientApplications": [],
  "logo@odata.mediaContentType": "application/json;odata=minimalmetadata; charset=utf-8",
  "logoUrl": null,
  "logoutUrl": null,
  "oauth2AllowIdTokenImplicitFlow": true,
  "oauth2AllowImplicitFlow": false,
  "oauth2AllowUrlPathMatching": false,
  "oauth2Permissions": [
    {
      "adminConsentDescription": "Allow the application to access Service Principal for BOSH on behalf of the signed-in user.",
      "adminConsentDisplayName": "Access Service Principal for BOSH",
      "id": "0f1b8ad3-0245-46f9-8017-xxxxxxxxxxxx",
      "isEnabled": true,
      "type": "User",
      "userConsentDescription": "Allow the application to access Service Principal for BOSH on your behalf.",
      "userConsentDisplayName": "Access Service Principal for BOSH",
      "value": "user_impersonation"
    }
  ],
  "oauth2RequirePostResponse": false,
  "objectId": "a715e3e2-29f0-40da-b836-xxxxxxxxxxxx",
  "objectType": "Application",
  "odata.metadata": "https://graph.windows.net/29248f74-371f-4db2-9a50-c62a6877a0c1/$metadata#directoryObjects/Microsoft.DirectoryServices.Application/@Element",
  "odata.type": "Microsoft.DirectoryServices.Application",
  "optionalClaims": null,
  "orgRestrictions": [],
  "parentalControlSettings": {
    "countriesBlockedForMinors": [],
    "legalAgeGroupRule": "Allow"
  },
  "passwordCredentials": [
    {
      "customKeyIdentifier": null,
      "endDate": "2019-10-25T09:46:05.19817Z",
      "keyId": "a6a8bd99-f7a2-4cb9-a4ff-xxxxxxxxxxxx",
      "startDate": "2018-10-25T09:46:05.19817Z",
      "value": null
    }
  ],
  "publicClient": null,
  "publisherDomain": null,
  "recordConsentConditions": null,
  "replyUrls": [],
  "requiredResourceAccess": [],
  "samlMetadataUrl": null,
  "signInAudience": "AzureADMyOrg",
  "tokenEncryptionKeyId": null
}

Record the value of appId (APPLICATION_ID):

$ export APPLICATION_ID=`az ad app list --identifier-uri "http://BOSHAzureCPI" | jq -r '.[].appId'`

Create a service principal:

$ az ad sp create --id $APPLICATION_ID
{
  "accountEnabled": true,
  "addIns": [],
  "alternativeNames": [],
  "appDisplayName": "Service Principal for BOSH",
  "appId": "1c9445f6-d3a5-4c5c-b5ad-xxxxxxxxxxxx",
  "appOwnerTenantId": "29248f74-371f-4db2-9a50-xxxxxxxxxxxx",
  "appRoleAssignmentRequired": false,
  "appRoles": [],
  "deletionTimestamp": null,
  "displayName": "Service Principal for BOSH",
  "errorUrl": null,
  "homepage": "http://BOSHAzureCPI",
  "keyCredentials": [],
  "logoutUrl": null,
  "oauth2Permissions": [
    {
      "adminConsentDescription": "Allow the application to access Service Principal for BOSH on behalf of the signed-in user.",
      "adminConsentDisplayName": "Access Service Principal for BOSH",
      "id": "0f1b8ad3-0245-46f9-8017-xxxxxxxxxxxx",
      "isEnabled": true,
      "type": "User",
      "userConsentDescription": "Allow the application to access Service Principal for BOSH on your behalf.",
      "userConsentDisplayName": "Access Service Principal for BOSH",
      "value": "user_impersonation"
    }
  ],
  "objectId": "f226a7f4-dc06-4be2-b52c-xxxxxxxxxxxx",
  "objectType": "ServicePrincipal",
  "odata.metadata": "https://graph.windows.net/29248f74-371f-4db2-9a50-c62a6877a0c1/$metadata#directoryObjects/Microsoft.DirectoryServices.ServicePrincipal/@Element",
  "odata.type": "Microsoft.DirectoryServices.ServicePrincipal",
  "passwordCredentials": [],
  "preferredTokenSigningKeyThumbprint": null,
  "publisherName": "Pivotal Corporation",
  "replyUrls": [],
  "samlMetadataUrl": null,
  "servicePrincipalNames": [
    "1c9445f6-d3a5-4c5c-b5ad-xxxxxxxxxxxx",
    "http://BOSHAzureCPI"
  ],
  "servicePrincipalType": "Application",
  "signInAudience": "AzureADMyOrg",
  "tags": [],
  "tokenEncryptionKeyId": null
}

Record the value of your service principal name (such as the same as APPLICATION_ID):

$ export SERVICE_PRINCIPAL_NAME=`az ad sp list | jq -r --arg APPLICATION_ID $APPLICATION_ID '.[] | select(.appId==$APPLICATION_ID) | .servicePrincipalNames[1]'`

Assign the role Contributor on your service principal:

$ az role assignment create --assignee "$SERVICE_PRINCIPAL_NAME" --role "Contributor" --scope /subscriptions/$SUBSCRIPTION_ID
{
  "canDelegate": null,
  "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleAssignments/4fe0a5d9-4ae2-4da8-b8ac-xxxxxxxxxxxx",
  "name": "4fe0a5d9-4ae2-4da8-b8ac-xxxxxxxxxxxx",
  "principalId": "f226a7f4-dc06-4be2-b52c-xxxxxxxxxxxx",
  "roleDefinitionId": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-xxxxxxxxxxxx",
  "scope": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx",
  "type": "Microsoft.Authorization/roleAssignments"
}

Make sure the assignment is set:

$ az role assignment list --assignee "$SERVICE_PRINCIPAL_NAME"
[
  {
    "canDelegate": null,
    "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleAssignments/4fe0a5d9-4ae2-4da8-b8ac-xxxxxxxxxxxx",
    "name": "4fe0a5d9-4ae2-4da8-b8ac-xxxxxxxxxxxx",
    "principalId": "f226a7f4-dc06-4be2-b52c-xxxxxxxxxxxx",
    "principalName": "http://BOSHAzureCPI",
    "roleDefinitionId": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-xxxxxxxxxxxx",
    "roleDefinitionName": "Contributor",
    "scope": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx",
    "type": "Microsoft.Authorization/roleAssignments"
  }
]

Verify your service principal:

$ az login --username $APPLICATION_ID --password $CLIENT_SECRET --service-principal --tenant $TENANT_ID
[
  {
    "cloudName": "AzureCloud",
    "id": "19972672-3fb3-4746-9499-xxxxxxxxxxxx",
    "isDefault": true,
    "name": "PA-xxx",
    "state": "Enabled",
    "tenantId": "29248f74-371f-4db2-9a50-xxxxxxxxxxxx",
    "user": {
      "name": "1c9445f6-d3a5-4c5c-b5ad-xxxxxxxxxxxx",
      "type": "servicePrincipal"
    }
  }
]

The login should be successful: if not, create a new service principal again.

Register your subscription with Microsoft.Storage:

$ az provider register --namespace Microsoft.Storage
Registering is still on-going. You can monitor using 'az provider show -n Microsoft.Storage'

Register your subscription with Microsoft.Network:

$ az provider register --namespace Microsoft.Network
Registering is still on-going. You can monitor using 'az provider show -n Microsoft.Network'

Register your subscription with Microsoft.Compute:

$ az provider register --namespace Microsoft.Compute
Registering is still on-going. You can monitor using 'az provider show -n Microsoft.Compute'

Deploying BOSH and Ops Manager

Create network resources

Create a resource group using Azure Portal:

Record the resource group name (RESOURCE_GROUP):

$ export RESOURCE_GROUP=xxx

Choose a location name corresponding to the location you used for the resource group (for instance: France Central):

$ az account list-locations
[
{
  "displayName": "France Central",
  "id": "/subscriptions/19972672-3fb3-4746-9499-0ffdc10b8f47/locations/francecentral",
  "latitude": "46.3772",
  "longitude": "2.3730",
  "name": "francecentral",
  "subscriptionId": null
},
{
  "displayName": "France South",
  "id": "/subscriptions/19972672-3fb3-4746-9499-0ffdc10b8f47/locations/francesouth",
  "latitude": "43.8345",
  "longitude": "2.1972",
  "name": "francesouth",
  "subscriptionId": null
}
]

Record the location you used for this resource group (LOCATION):

$ export LOCATION=`az account list-locations | jq -r --arg LOCATION_NAME "France Central" '.[] | select(.displayName==$LOCATION_NAME) | .name'`

Create a network security group named pcf-nsg:

$ az network nsg create --name pcf-nsg --resource-group $RESOURCE_GROUP --location $LOCATION
{
  "NewNSG": {
    "defaultSecurityRules": [
      {
        "access": "Allow",
        "description": "Allow inbound traffic from all VMs in VNET",
        "destinationAddressPrefix": "VirtualNetwork",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Inbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/AllowVnetInBound",
        "name": "AllowVnetInBound",
        "priority": 65000,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "VirtualNetwork",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      },
      {
        "access": "Allow",
        "description": "Allow inbound traffic from azure load balancer",
        "destinationAddressPrefix": "*",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Inbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/AllowAzureLoadBalancerInBound",
        "name": "AllowAzureLoadBalancerInBound",
        "priority": 65001,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "AzureLoadBalancer",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      },
      {
        "access": "Deny",
        "description": "Deny all inbound traffic",
        "destinationAddressPrefix": "*",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Inbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/DenyAllInBound",
        "name": "DenyAllInBound",
        "priority": 65500,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "*",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      },
      {
        "access": "Allow",
        "description": "Allow outbound traffic from all VMs to all VMs in VNET",
        "destinationAddressPrefix": "VirtualNetwork",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Outbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/AllowVnetOutBound",
        "name": "AllowVnetOutBound",
        "priority": 65000,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "VirtualNetwork",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      },
      {
        "access": "Allow",
        "description": "Allow outbound traffic from all VMs to Internet",
        "destinationAddressPrefix": "Internet",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Outbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/AllowInternetOutBound",
        "name": "AllowInternetOutBound",
        "priority": 65001,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "*",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      },
      {
        "access": "Deny",
        "description": "Deny all outbound traffic",
        "destinationAddressPrefix": "*",
        "destinationAddressPrefixes": [],
        "destinationApplicationSecurityGroups": null,
        "destinationPortRange": "*",
        "destinationPortRanges": [],
        "direction": "Outbound",
        "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
        "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg/defaultSecurityRules/DenyAllOutBound",
        "name": "DenyAllOutBound",
        "priority": 65500,
        "protocol": "*",
        "provisioningState": "Succeeded",
        "resourceGroup": "pas-res-group",
        "sourceAddressPrefix": "*",
        "sourceAddressPrefixes": [],
        "sourceApplicationSecurityGroups": null,
        "sourcePortRange": "*",
        "sourcePortRanges": [],
        "type": "Microsoft.Network/networkSecurityGroups/defaultSecurityRules"
      }
    ],
    "etag": "W/\"9c2fbc1f-826e-4aae-b3cf-xxxxxxxxxxxx\"",
    "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Network/networkSecurityGroups/pcf-nsg",
    "location": "francecentral",
    "name": "pcf-nsg",
    "networkInterfaces": null,
    "provisioningState": "Succeeded",
    "resourceGroup": "pas-res-group",
    "resourceGuid": "db3c47c1-76fc-4440-b41c-xxxxxxxxxxxx",
    "securityRules": [],
    "subnets": null,
    "tags": null,
    "type": "Microsoft.Network/networkSecurityGroups"
  }
}

Add rules to this network security group:

$ az network nsg rule create --name ssh --nsg-name pcf-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 100 --destination-port-range '22'
$ az network nsg rule create --name http --nsg-name pcf-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 200 --destination-port-range '80'
$ az network nsg rule create --name https --nsg-name pcf-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 300 --destination-port-range '443'
$ az network nsg rule create --name diego-ssh --nsg-name pcf-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 400 --destination-port-range '2222'

Create a network security group named opsmgr-ns:

$ az network nsg create --name opsmgr-nsg --resource-group $RESOURCE_GROUP --location $LOCATION

Add a network security group rule to the opsmgr-nsg group to allow HTTP traffic to the Ops Manager VM:

$ az network nsg rule create --name http --nsg-name opsmgr-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 100 --destination-port-range 80

Add a network security group rule to the opsmgr-nsg group to allow HTTPS traffic to the Ops Manager VM:

$ az network nsg rule create --name https --nsg-name opsmgr-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 200 --destination-port-range 443

Add a network security group rule to the opsmgr-nsg group to allow SSH traffic to the Ops Manager VM:

$ az network nsg rule create --name ssh --nsg-name opsmgr-nsg --resource-group $RESOURCE_GROUP --protocol Tcp --priority 300 --destination-port-range 22

Create a virtual network named PCF:

$ az network vnet create --name PCF --resource-group $RESOURCE_GROUP --location $LOCATION --address-prefixes 10.0.0.0/16

Add subnets to the network for Ops Manager, BOSH director and PCF VMs and attach the Network Security Group:

$ az network vnet subnet create --name Management \
--vnet-name PCF \
--resource-group $RESOURCE_GROUP \
--address-prefix 10.0.4.0/22 \
--network-security-group pcf-nsg
$ az network vnet subnet create --name Deployment \
--vnet-name PCF \
--resource-group $RESOURCE_GROUP \
--address-prefix 10.0.12.0/22 \
--network-security-group pcf-nsg
$ az network vnet subnet create --name Services \
--vnet-name PCF \
--resource-group $RESOURCE_GROUP \
--address-prefix 10.0.8.0/22 \
--network-security-group pcf-nsg

Create storage accounts

Generate a name for your BOSH storage account:

$ export STORAGE_NAME=`openssl rand -hex 12`

Create a Standard storage account for BOSH:

$ az storage account create --name $STORAGE_NAME --resource-group $RESOURCE_GROUP --sku Standard_LRS --location $LOCATION

Record the connection string for this storage account (CONNECTION_STRING):

$ export CONNECTION_STRING=`az storage account show-connection-string --name $STORAGE_NAME --resource-group $RESOURCE_GROUP | jq -r '.connectionString'`

Create three blob containers in the BOSH storage account, named opsmanager, bosh, and stemcell:

$ az storage container create --name opsmanager --connection-string $CONNECTION_STRING
$ az storage container create --name bosh --connection-string $CONNECTION_STRING
$ az storage container create --name stemcell --public-access blob --connection-string $CONNECTION_STRING

Create a table named stemcells:

$ az storage table create --name stemcells --connection-string $CONNECTION_STRING

Create 5 deployment storage accounts for BOSH:

$ export STORAGE_TYPE="Premium_LRS"
$ for i in $(seq 1 5); do \
az storage account create --name boshdeployment$i --resource-group $RESOURCE_GROUP --sku $STORAGE_TYPE --kind Storage --location $LOCATION && \
export CONNECTION_STRING=`az storage account show-connection-string --name boshdeployment$i --resource-group $RESOURCE_GROUP | jq -r '.connectionString'` && \
az storage container create --name bosh --connection-string $CONNECTION_STRING && \
az storage container create --name stemcell --connection-string $CONNECTION_STRING ;\
done

Create load balancers

Create a load balancer named pcf-lb:

$ az network lb create --name pcf-lb --resource-group $RESOURCE_GROUP --location $LOCATION --backend-pool-name pcf-lb-be-pool --frontend-ip-name pcf-lb-fe-ip --public-ip-address pcf-lb-ip --public-ip-address-allocation Static --sku Standard
$ az network lb probe create --lb-name pcf-lb --name tcp80 --resource-group $RESOURCE_GROUP --protocol Tcp --port 80
$ az network lb rule create --lb-name pcf-lb --name http --resource-group $RESOURCE_GROUP --protocol Tcp --frontend-port 80 --backend-port 80 --frontend-ip-name pcf-lb-fe-ip --backend-pool-name pcf-lb-be-pool --probe-name tcp80
$ az network lb rule create --lb-name pcf-lb --name https --resource-group $RESOURCE_GROUP --protocol Tcp --frontend-port 443 --backend-port 443 --frontend-ip-name pcf-lb-fe-ip --backend-pool-name pcf-lb-be-pool --probe-name tcp80

Get the allocated IP address for the load balancer:

$ az network public-ip show --name pcf-lb-ip --resource-group $RESOURCE_GROUP | jq -r '.ipAddress'
40.66.59.98

Create a wildcard DNS entry pointing this IP address. The DNS entry should look like this:

*.mydomain.com IN A 40.66.59.98

Create a load balancer named pcf-ssh-lb:

$ az network lb create --name pcf-ssh-lb --resource-group $RESOURCE_GROUP --location $LOCATION --backend-pool-name pcf-ssh-lb-be-pool --frontend-ip-name pcf-ssh-lb-fe-ip --public-ip-address pcf-ssh-lb-ip --public-ip-address-allocation Static --sku Standard
$ az network lb probe create --lb-name pcf-ssh-lb --name tcp2222 --resource-group $RESOURCE_GROUP --protocol Tcp --port 2222
$ az network lb rule create --lb-name pcf-ssh-lb --name diego-ssh --resource-group $RESOURCE_GROUP --protocol Tcp --frontend-port 2222 --backend-port 2222 --frontend-ip-name pcf-ssh-lb-fe-ip --backend-pool-name pcf-ssh-lb-be-pool --probe-name tcp2222

Get the allocated IP address for the load balancer:

$ az network public-ip show --name pcf-ssh-lb-ip --resource-group $RESOURCE_GROUP | jq -r '.ipAddress'
40.66.59.100

Create a wildcard DNS entry pointing this IP address. The DNS entry should look like this:

*.ssh.sys.mydomain.com IN A 40.66.59.100

Boot Ops Manager

Navigate to Pivotal Network and get the URL to the Ops Manager VM image. Record this URL:

$ export OPS_MAN_IMAGE_URL="https://opsmanagerwesteurope.blob.core.windows.net/images/ops-manager-2.3-build.188.vhd"

Import the Ops Manager image to Azure:

$ export CONNECTION_STRING=`az storage account show-connection-string --name $STORAGE_NAME --resource-group $RESOURCE_GROUP | jq -r '.connectionString'`
$ az storage blob copy start --source-uri $OPS_MAN_IMAGE_URL --connection-string $CONNECTION_STRING --destination-container opsmanager --destination-blob opsman-image-2.3.x.vhd
{
  "completionTime": null,
  "id": "0e421db0-8696-4315-ba35-b4547dd7fc4b",
  "progress": null,
  "source": null,
  "status": "pending",
  "statusDescription": null
}

Importing the Ops Manager image takes a long time. Monitor this operation using this command:

$ az storage blob show --name opsman-image-2.3.x.vhd --container-name opsmanager --connection-string $CONNECTION_STRING | jq '.properties.copy'
{
  "completionTime": null,
  "id": "0e421db0-8696-4315-ba35-b4547dd7fc4b",
  "progress": "2682601472/10737418752",
  "source": "https://opsmanagerwesteurope.blob.core.windows.net/images/ops-manager-2.3-build.188.vhd",
  "status": "pending",
  "statusDescription": null
}

Create a public IP address named ops-manager-ip:

$ az network public-ip create --name ops-manager-ip --resource-group $RESOURCE_GROUP --location $LOCATION --allocation-method Static

Record the public IP address for Ops Manager:

$ export OPS_MANAGER_IP=`az network public-ip list | jq -r '.[] | select(.name=="ops-manager-ip") | .ipAddress'`

Create a network interface for Ops Manager:

$ az network nic create --vnet-name PCF --subnet Management --network-security-group opsmgr-nsg --private-ip-address 10.0.4.4 --public-ip-address ops-manager-ip --resource-group $RESOURCE_GROUP --name opsman-nic --location $LOCATION

Create a key pair with the username ubuntu:

$ ssh-keygen -t rsa -f opsman -C ubuntu

Create a managed image from the Ops Manager image:

$ az image create --resource-group $RESOURCE_GROUP --name opsman-image-2.3.x --source https://$STORAGE_NAME.blob.core.windows.net/opsmanager/opsman-image-2.3.x.vhd --location $LOCATION --os-type Linux
{
  "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Compute/images/opsman-image-2.3.x",
  "location": "francecentral",
  "name": "opsman-image-2.3.x",
  "provisioningState": "Succeeded",
  "resourceGroup": "pas-res-group",
  "sourceVirtualMachine": null,
  "storageProfile": {
    "dataDisks": [],
    "osDisk": {
      "blobUri": "https://a307f431e61a19e10e05b521.blob.core.windows.net/opsmanager/opsman-image-2.3.x.vhd",
      "caching": "None",
      "diskSizeGb": 10,
      "managedDisk": null,
      "osState": "Generalized",
      "osType": "Linux",
      "snapshot": null,
      "storageAccountType": "Standard_LRS"
    },
    "zoneResilient": null
  },
  "tags": {},
  "type": "Microsoft.Compute/images"
}

Create the Ops Manager VM:

$ az vm create --name opsman-2.3.x --resource-group $RESOURCE_GROUP --location $LOCATION --nics opsman-nic --image opsman-image-2.3.x --os-disk-size-gb 128 --os-disk-name opsman-2.3.x-osdisk --admin-username ubuntu --size Standard_DS2_v2 --storage-sku Standard_LRS --ssh-key-value opsman.pub
{
  "fqdns": "",
  "id": "/subscriptions/19972672-3fb3-4746-9499-xxxxxxxxxxxx/resourceGroups/pas-res-group/providers/Microsoft.Compute/virtualMachines/opsman-2.3.x",
  "location": "francecentral",
  "macAddress": "00-0D-3A-95-31-E3",
  "powerState": "VM running",
  "privateIpAddress": "10.0.4.4",
  "publicIpAddress": "40.89.165.11",
  "resourceGroup": "pas-res-group",
  "zones": ""
}

Create a DNS entry pointing to the Ops Manager VM. Always access Ops Manager using this FQDN.

Configure BOSH Director

Navigate to the Ops Manager FQDN: https://opsman.mydomain.com.

Select Internal Authentication, and fill in the form:

  • Username: admin
  • Password: MYSECRET
  • Decryption passphrase: MYSECRET

Validate, and wait for the Ops Manager to start. On the login page, use your credentials to access the Ops Manager Dashboard.

Click the BOSH Director Tile. Enter configuration values by following instructions from this page.

Once you are ready, click on Review pending changes from the Installation Dashboard, then click on Apply changes.

☕ Grab a coffee!

Since the installation is done, BOSH Director is now installed on your environment. It's time to handle PAS.

Deploy PAS

Download PAS for Azure from Pivotal Network. Select Small Footprint PAS to install a lightweight installation (less VM, less resources). This installation is not production ready!

Once you have downloaded this file, you need to import it into Ops Manager. Click on Import a Product from Installation Dashboard.

When the upload is done, follow installation steps from this page.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment