Skip to content

Commit

Permalink
Compare and contrast azure-py-vdc and azureng-py-vdc (pulumi#806)
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesianberry committed Oct 8, 2020
1 parent cb53931 commit e942d61
Show file tree
Hide file tree
Showing 16 changed files with 1,719 additions and 403 deletions.
5 changes: 5 additions & 0 deletions azure-nextgen-py-virtual-data-center/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*.pyc
.venv/
.vscode/
venv/
__pycache__
34 changes: 34 additions & 0 deletions azure-nextgen-py-virtual-data-center/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: azureng-py-vdc
runtime:
name: python
options:
virtualenv: venv
description: A minimal Azure Virtual Data Center described in Python
template:
config:
azure_bastion:
description: Azure Bastion provides secure RDP and SSH connectivity to VMs (optional)
default: false
firewall_address_space:
description: Address space in the hub for Azure Firewall and DMZ
default: 192.168.100.0/24
forced_tunnel:
description: Route all Internet-bound traffic to this designated next hop IP address (optional)
default: 10.0.100.1
hub_address_space:
description: Address space in the hub for connectivity and shared services subnets
default: 10.100.0.0/16
location:
description: Azure region to deploy to (e.g. `australiaeast` or `australiasoutheast`)
default: australiaeast
org:
description: Pulumi organization in which this project resides (optional)
peer:
description: Another stack in same organization and project to peer hubs with (optional)
project:
description: Another project defining a stack with the same hub and spoke names to peer with (optional)
separator:
description: A dash (-) breaks up names by default; specify valid character or ' ' for none (optional)
suffix:
description: A short string appended to each name to allow multiple stacks (optional)
default: ae
235 changes: 235 additions & 0 deletions azure-nextgen-py-virtual-data-center/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
[![Deploy](https://get.pulumi.com/new/button.svg)](https://app.pulumi.com/new)

# Azure Virtual Data Center (VDC)

This example deploys Azure Virtual Data Center (VDC) hub-and-spoke network stacks in Azure, complete with ExpressRoute and VPN Gateways, Azure Firewall (with provision for forced tunnelling) guarding a DMZ, and Azure Bastion. In addition, as many subnets as required for shared services in the hub and application environments in the spokes may be simply specified.

In this implementation, the Azure Firewall is central. Custom routing redirects all traffic to and from hub and spokes, as well as all traffic to, within and from the DMZ, through the firewall (which scales out as a service to handle the throughput). Firewall rules are required to allow traffic through (not yet implemented). Traffic between shared services subnets in the hub and between subnets within the spokes is not redirected through the firewall, and should instead be controlled using Network Security Groups (not yet implemented).

With minimal configuration, matching stacks may be deployed in Azure [paired regions](https://docs.microsoft.com/en-us/azure/best-practices-availability-paired-regions), configured for Production/Disaster Recovery or High Availability (or both for different applications). Global VNet Peering between the hubs connects the separate stacks into one symmetric network.

Although the VDC pattern is in widespread use, Azure now offers a managed service intended to replace it, comprising Virtual Hub along with partner SD-WAN components, with a [migration plan](https://docs.microsoft.com/en-us/azure/virtual-wan/migrate-from-hub-spoke-topology) that illustrates the differences between the two patterns. But if you want or need to manage your own network infrastructure, VDC is still relevant.

This example uses `pulumi.ComponentResource` as described [here](https://www.pulumi.com/docs/intro/concepts/programming-model/#components) which demonstrates how multiple low-level resources can be composed into a higher-level, reusable abstraction. It also demonstrates use of `pulumi.StackReference` as described [here](https://www.pulumi.com/docs/intro/concepts/organizing-stacks-projects/#inter-stack-dependencies) to relate multiple stacks. Finally, it uses Python's ```ipaddress``` module to simplify and validate configuration of network addresses.

## Prerequisites

1. [Install Pulumi](https://www.pulumi.com/docs/get-started/install/)
1. [Configure Pulumi for Azure](https://www.pulumi.com/docs/intro/cloud-providers/azure/setup/)
1. [Configure Pulumi for Python](https://www.pulumi.com/docs/intro/languages/python/)

# Running the Example

After cloning this repo, `cd` into the `azure-py-virtual-data-center` directory and run the following commands.

1. (recommended) Create a Python virtualenv, activate it, and install the dependent packages [needed](https://www.pulumi.com/docs/intro/concepts/how-pulumi-works/) for our Pulumi program:

```bash
$ python3 -m venv venv
$ source venv/bin/activate
$ pip3 install -r requirements.txt
```

1. Create a new stack intended for Production (for example's sake):

```bash
$ pulumi stack init prod
```

This will appear within your Pulumi organization under the `azure-py-vdc` project (as specified in `Pulumi.yaml`).

1. Set the configuration variables for this stack to suit yourself, following guidance in `Pulumi.yaml`. This will create a new `Pulumi.prod.yaml` file (named after the stack) in which to store them:

Required:
```bash
$ pulumi config set firewall_address_space 192.168.100.0/24
$ pulumi config set hub_address_space 10.100.0.0/16
$ pulumi config set location australiaeast
```
Optional:
```bash
$ pulumi config set azure_bastion true
$ pulumi config set forced_tunnel 10.0.100.1
$ pulumi config set separator " "
$ pulumi config set suffix "ae"
```

Note that it is advisable to add Azure Bastion on the second pass to avoid contention.

1. Deploy the `prod` stack with the `pulumi up` command. This may take up to an hour to provision all the Azure resources specified, including gateways, firewall and bastion hosts:

```bash
$ pulumi up
```

1. After a while, your Production stack will be ready.

```
Updating (prod)
View Live: https://app.pulumi.com/organization/azureng-py-vdc/prod/updates/1
Type Name Status
+ pulumi:pulumi:Stack azureng-py-vdc-prod created
+ ├─ vdc:network:Hub hub created
+ │ ├─ azure-nextgen:network/latest:VirtualNetwork hub-vn created
+ │ ├─ azure-nextgen:network/latest:RouteTable hub-gw-rt created
+ │ ├─ azure-nextgen:network/latest:RouteTable hub-dmz-rt created
+ │ ├─ azure-nextgen:network/latest:RouteTable hub-fw-rt created
+ │ ├─ azure-nextgen:network/latest:RouteTable hub-fwm-rt created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress hub-fw-pip created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress hub-fwm-pip created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress hub-vpn-gw-pip created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress hub-er-gw-pip created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress hub-ab-pip created
+ │ ├─ azure-nextgen:network/latest:Route fwm-internet-r created
+ │ ├─ azure-nextgen:network/latest:Route fw-tunnel-r created
+ │ ├─ azure-nextgen:network/latest:Route gw-gw-r created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-fwm-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-dmz-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-fw-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-gw-sn created
+ │ ├─ azure-nextgen:network/latest:AzureFirewall hub-fw created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkGateway hub-vpn-gw created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkGateway hub-er-gw created
+ │ ├─ azure-nextgen:network/latest:Route gw-dmz-r created
+ │ ├─ azure-nextgen:network/latest:Route gw-hub-r created
+ │ ├─ azure-nextgen:network/latest:Route dmz-dmz-r created
+ │ ├─ azure-nextgen:network/latest:Route dmz-hub-r created
+ │ ├─ azure-nextgen:network/latest:Route dmz-dg-r created
+ │ ├─ azure-nextgen:network/latest:RouteTable hub-ss-rt created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-ab-sn created
+ │ ├─ azure-nextgen:network/latest:Route ss-dg-r created
+ │ ├─ azure-nextgen:network/latest:Route ss-dmz-r created
+ │ ├─ azure-nextgen:network/latest:Route ss-gw-r created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-domain-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet hub-files-sn created
+ │ └─ azure-nextgen:network/latest:BastionHost hub-ab created
+ ├─ vdc:network:Spoke s01 created
+ │ ├─ azure-nextgen:network/latest:VirtualNetwork s01-vn created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress s01-ab-pip created
+ │ ├─ azure-nextgen:network/latest:Route dmz-s01-r created
+ │ ├─ azure-nextgen:network/latest:Route gw-s01-r created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkPeering hub-s01-vnp created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkPeering s01-hub-vnp created
+ │ ├─ azure-nextgen:network/latest:Route ss-s01-r created
+ │ ├─ azure-nextgen:network/latest:RouteTable s01-rt created
+ │ ├─ azure-nextgen:network/latest:Subnet s01-ab-sn created
+ │ ├─ azure-nextgen:network/latest:Route s01-dg-r created
+ │ ├─ azure-nextgen:network/latest:Route s01-dmz-r created
+ │ ├─ azure-nextgen:network/latest:Route s01-hub-r created
+ │ ├─ azure-nextgen:network/latest:Subnet s01-app-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet s01-web-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet s01-db-sn created
+ │ └─ azure-nextgen:network/latest:BastionHost s01-ab created
+ ├─ vdc:network:Spoke s02 created
+ │ ├─ azure-nextgen:network/latest:VirtualNetwork s02-vn created
+ │ ├─ azure-nextgen:network/latest:PublicIPAddress s02-ab-pip created
+ │ ├─ azure-nextgen:network/latest:Route gw-s02-r created
+ │ ├─ azure-nextgen:network/latest:Route dmz-s02-r created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkPeering hub-s02-vnp created
+ │ ├─ azure-nextgen:network/latest:VirtualNetworkPeering s02-hub-vnp created
+ │ ├─ azure-nextgen:network/latest:Route ss-s02-r created
+ │ ├─ azure-nextgen:network/latest:RouteTable s02-rt created
+ │ ├─ azure-nextgen:network/latest:Subnet s02-ab-sn created
+ │ ├─ azure-nextgen:network/latest:Route s02-dg-r created
+ │ ├─ azure-nextgen:network/latest:Route s02-dmz-r created
+ │ ├─ azure-nextgen:network/latest:Subnet s02-web-sn created
+ │ ├─ azure-nextgen:network/latest:Route s02-hub-r created
+ │ ├─ azure-nextgen:network/latest:Subnet s02-app-sn created
+ │ ├─ azure-nextgen:network/latest:Subnet s02-db-sn created
+ │ └─ azure-nextgen:network/latest:BastionHost s02-ab created
+ └─ azure-nextgen:resources/latest:ResourceGroup prod-vdc-rg created
Outputs:
dmz_ar: "192.168.100.128/25"
fw_ip : "192.168.100.4"
hub_as: "10.100.0.0/16"
hub_id: "/subscriptions/subscription/resourceGroups/prod-vdc-rg-ae/providers/Microsoft.Network/virtualNetworks/hub-vn-ae"
s01_as: "10.101.0.0/16"
s01_id: "/subscriptions/subscription/resourceGroups/prod-vdc-rg-ae/providers/Microsoft.Network/virtualNetworks/s01-vn-ae"
s02_as: "10.102.0.0/16"
s02_id: "/subscriptions/subscription/resourceGroups/prod-vdc-rg-ae/providers/Microsoft.Network/virtualNetworks/s02-vn-ae"
Resources:
+ 70 created
Duration: 34m34s
```

Feel free to modify your program, and then run `pulumi up` again. Pulumi automatically detects differences and makes the minimal changes necessary to achieved the desired state. If any changes to resources are made outside of Pulumi, you should first do a `pulumi refresh` so that Pulumi can discover the actual situation, and then `pulumi up` to return to desired state.

Note that auto-naming is not yet implemented in azure-nextgen. Instead the same suffix is appended to each physical name so that multiple stacks may be created without conflict.

1. Create another new stack intended for Disaster Recovery (following the example):

```bash
$ pulumi stack init dr
```

This will also appear within your Pulumi organization under the `azure-py-vdc` project (as specified in `Pulumi.yaml`).

1. Set the configuration variables for this stack which will be stored in a new `Pulumi.dr.yaml` file (change the values below to suit yourself):

Required:
```bash
$ pulumi config set firewall_address_space 192.168.200.0/24
$ pulumi config set hub_address_space 10.200.0.0/16
$ pulumi config set location australiasoutheast
```
Optional:
```bash
$ pulumi config set azure_bastion true
$ pulumi config set forced_tunnel 10.0.200.1
$ pulumi config set separator "_"
$ pulumi config set suffix "ase"
```

1. Deploy the `dr` stack with the `pulumi up` command. Once again, this may take up to an hour to provision all the Azure resources specified, including gateways, firewall and bastion hosts:

```bash
$ pulumi up
```

1. Once you have both Production and Disaster Recovery stacks (ideally in paired regions), you can connect their hubs using Global (between regions) VNet Peering:

Required:
```bash
$ pulumi stack select prod
$ pulumi config set peer dr
$ pulumi up
$ pulumi stack select dr
$ pulumi config set peer prod
$ pulumi up
```
Optional (for each stack):
```bash
$ pulumi config set org organization
$ pulumi config set project project
```
Note: you may specify another organization and/or project (corresponding hub and spoke names should be the same). It isn't yet [possible](https://github.com/pulumi/pulumi/issues/2800) to discover the Pulumi organization from within the program.
If you later destroy a stack, you need to remove the corresponding `peer` variable in the other stack and run `pulumi up`. If you want to tear down the peerings, you should remove the `peer` variables in both stacks and run `pulumi up`:
```bash
$ pulumi stack select prod
$ pulumi config rm peer
$ pulumi up
$ pulumi stack select dr
$ pulumi config rm peer
$ pulumi up
```
You need to remove both peerings before you can connect the hubs again.
1. When you are finished experimenting, you can destroy all of the resources, and the stacks:
```bash
$ pulumi stack select prod
$ pulumi destroy
$ pulumi stack rm
$ pulumi stack select dr
$ pulumi destroy
$ pulumi stack rm
```
88 changes: 88 additions & 0 deletions azure-nextgen-py-virtual-data-center/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import config
import vdc
from hub import HubProps, Hub
from spoke import SpokeProps, Spoke
from pulumi import export

# set required vdc variables before calling function
vdc.location = config.location
vdc.s = config.separator
vdc.suffix = config.suffix
vdc.tags = config.default_tags
# all resources will be created in configuration location
resource_group_name = vdc.resource_group(config.stack)

# single hub with gateways, firewall, DMZ, shared services, bastion (optional)
hub = Hub('hub', # stem of child resource names (<4 chars)
HubProps(
azure_bastion = config.azure_bastion,
forced_tunnel = config.forced_tunnel,
firewall_address_space = config.firewall_address_space,
hub_address_space = config.hub_address_space,
location = config.location,
peer = config.peer,
reference = config.reference,
resource_group_name = resource_group_name,
separator = config.separator,
stack = config.stack,
subnets = [ # extra columns for future ASGs
('domain', 'any', 'any'),
('files', 'any', 'none'),
],
suffix = config.suffix,
tags = config.default_tags,
),
)

# multiple spokes for application environments with bastion access (optional)
spoke1 = Spoke('s01', # stem of child resource names (<6 chars)
SpokeProps(
azure_bastion = config.azure_bastion,
fw_rt_name = hub.fw_rt_name,
hub = hub,
location = config.location,
peer = config.peer,
reference = config.reference,
resource_group_name = resource_group_name,
separator = config.separator,
spoke_address_space = str(next(config.stack_sn)),
subnets = [ # extra columns for future ASGs
('web', 'any', 'app'),
('app', 'web', 'db'),
('db', 'app', 'none'),
],
suffix = config.suffix,
tags = config.default_tags,
),
)

spoke2 = Spoke('s02', # stem of child resource names (<6 chars)
SpokeProps(
azure_bastion = config.azure_bastion,
fw_rt_name = hub.fw_rt_name,
hub = hub,
location = config.location,
peer = config.peer,
reference = config.reference,
resource_group_name = resource_group_name,
separator = config.separator,
spoke_address_space = str(next(config.stack_sn)),
subnets = [ # extra columns for future ASGs
('web', 'any', 'app'),
('app', 'web', 'db'),
('db', 'app', 'none'),
],
suffix = config.suffix,
tags = config.default_tags,
),
)

# export information about the stack required for stack peering
export('dmz_ar', hub.dmz_ar)
export('fw_ip', hub.fw_ip)
export('hub_as', hub.address_space)
export('hub_id', hub.id)
export('s01_as', spoke1.address_space)
export('s01_id', spoke1.id)
export('s02_as', spoke2.address_space)
export('s02_id', spoke2.id)
Loading

0 comments on commit e942d61

Please sign in to comment.