Python Cloud Advocate at Microsoft
Formerly: UC Berkeley, Coursera, Khan Academy, Google
Find me online at:
Mastodon | @pamelafox@fosstodon.org |
@pamelafox | |
GitHub | www.github.com/pamelafox |
Website | pamelafox.org |
Option | Description |
---|---|
Azure Database for PostgreSQL – Single Server | Microsoft's original offering. No longer recommended for new apps. |
Azure Database for PostgreSQL – Flexible Server | Microsoft's most recent PostgreSQL offering. Fully managed service with vertical scaling. |
Azure Cosmos DB for PostgreSQL | Distributed database using PostgreSQL and the Citus extension. Can scale horizontally. |
🔗 aka.ms/flex-vs-single
Comparison: Flexible vs. Single Server
🔗 aka.ms/flex-vs-cosmos
Cosmos DB for PostgreSQL vs. Flex Server
😃 Easy to get started 😭 but difficult to replicate.
Method | Example |
---|---|
Azure CLI |
|
Azure PowerShell |
|
😃 Replication is now possible!
😭 Updating resource parameters requires different commands than creation.
ARM (Azure Resource Manager) templates are JSON files that describe the resources you want to create.
{
"type": "Microsoft.DBforPostgreSQL/flexibleServers",
"apiVersion": "2021-06-01",
"name": "pg-srv",
"location": "westus",
"sku": {
"name": "Standard_B1ms",
"tier": "Burstable"
},
"properties": {
"administratorLogin": "admin-user",
"administratorLoginPassword": "NeverShowThis",
"version": "14"
}
...
👁️ See full example in postgres_database.bicep
😃 Repeatable provisioning of all resource types
😭 JSON files can be unwieldy and hard to parameterize
Bicep is a DSL (domain-specific language) that compiles to ARM templates.
resource server 'Microsoft.DBforPostgreSQL/flexibleServers@2021-06-01' = {
name: 'pg-srv'
location: 'eastus'
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
properties: {
administratorLogin: 'myadmin'
administratorLoginPassword: 'NeverShowThis'
version: '14'
storage: {
storageSizeGB: 128
}
}
}
Feature | Example |
---|---|
Parameters |
|
Types |
|
Logic |
|
Loops |
|
Functions |
|
Modules |
|
Use parameters for values that vary across deployments.
param serverName string = 'pg-srv'
param location string = 'eastus'
@secure()
param adminPassword string
resource srv 'Microsoft.DBforPostgreSQL/flexibleServers@2021-06-01' = {
name: serverName
location: location
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
properties: {
administratorLogin: 'myadmin'
administratorLoginPassword: adminPassword
version: '14'
storage: { storageSizeGB: 128 }
}
}
Use az deployment group create
to create or update resources in an existing resource group:
$ az deployment group create --resource-group pg-grp --template-file pg.bicep
Please provide securestring value for 'adminPassword' (? for help):
{ "name": "postgres_example1",
"properties": {
"outputResources": [{
"id": "/subscriptions/32ea8a26-5b40-4838-b6cb-be5c89a57c16/resourceGroups/cituscon-examples-eastus/providers/Microsoft.DBforPostgreSQL/flexibleServers/pg-srv",
"resourceGroup": "pg-grp"
}], ...
Optionally, specify parameters on the command line:
$ az deployment group create --resource-group pg-grp --template-file pg.bicep \
--parameters adminPassword=ADMIN_PASSWORD
A child resource exists solely within the scope of its parent resource.
Child resources that can be created for PostgreSQL:
administrators
configurations
databases
firewallRules
migrations
+ Read-only child resources: advisors
, backups
, queryTexts
.
The server will always include a database called postgres
plus system databases azure_maintenance
, azure_sys
.
Create an additional database:
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = {
name: serverName
location: location
...
resource database 'databases' = {
name: 'webapp'
}
}
👁️ See full example in postgres_database.bicep
Create multiple databases with an array
and for
loop:
param databaseNames array = ['webapp', 'analytics']
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = {
name: serverName
location: location
...
resource database 'databases' = [for name in databaseNames: {
name: name
}]
}
👁️ See full example in postgres_databases.bicep
By default, the server is not accessible from any IP ranges.
You can use firewallRules
to allow access from IPs.
Allow access from any Azure service within Azure:
resource firewallAzure 'firewallRules' = {
name: 'allow-all-azure-internal-IPs'
properties: {
startIpAddress: '0.0.0.0'
endIpAddress: '0.0.0.0'
}
}
⚠️ Any Azure dev can now access, if they know user/pass.
👁️ See full example in postgres_azurefirewall.bicep
Conditionally create a rule using if
with a bool
param:
param allowAllIPsFirewall bool
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = {
...
resource firewallAll 'firewallRules' = if (allowAllIPsFirewall) {
name: 'allow-all-IPs'
properties: {
startIpAddress: '0.0.0.0'
endIpAddress: '255.255.255.255'
}
}
}
⚠️⚠️ Anyone at all can access, if they know user/pass.
👁️ See full example in postgres_condfirewall.bicep
Create rules for each IP in list using array
with for
:
param allowedSingleIPs array = ['103.64.88.254', '44.143.22.28']
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-12-01' = {
...
resource firewallSingle 'firewallRules' = [for ip in allowedSingleIPs: {
name: 'allow-single-${replace(ip, '.', '')}'
properties: {
startIpAddress: ip
endIpAddress: ip
}
}]
}
👁️ See full example in postgres_loopfirewall.bicep
As a security best practice, Azure recommends that deploying PostgreSQL servers in a Virtual Network (VNet), along with other Azure resources that need access to it.
That requires multiple resources:
Create a VNet with a private address space of 10.0.0.0 - 10.0.255.255:
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
name: '${name}-vnet'
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
}
}
Create subnet with address space of 10.0.0.1
- 10.0.0.255
and delegate to PostgreSQL servers:
resource databaseSubnet 'subnets' = {
name: 'database-subnet'
properties: {
addressPrefix: '10.0.0.0/24'
delegations: [
{
name: '${name}-subnet-delegation'
properties: {
serviceName: 'Microsoft.DBforPostgreSQL/flexibleServers'
}
}]
}
}
🔗 aka.ms/vnet-cidr Configuring Azure VNet subnets with CIDR notation
Create subnet with address space of 10.0.1.1
- 10.0.1.255
and delegate to other resource (App Service):
resource webappSubnet 'subnets' = {
name: 'webapp-subnet'
properties: {
addressPrefix: '10.0.1.0/24'
delegations: [
{
name: '${name}-subnet-delegation-web'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}]
}
}
🔗 aka.ms/vnet-cidr Configuring Azure VNet subnets with CIDR notation
To let other service access the PostgreSQL server, create a private DNS Zone:
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: '${pgServerPrefix}.private.postgres.database.azure.com'
location: 'global'
resource vNetLink 'virtualNetworkLinks' = {
name: '${pgServerPrefix}-link'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: { id: virtualNetwork.id }
}
}
}
🔗 aka.ms/private-dns
What is Azure Private DNS?
🔗 aka.ms/pg-vnet-dns
PostgreSQL networking: Private DNS Zone and VNets
Add the network
property on the PostgreSQL server to inject it into the VNet and
connect it to the DNS Zone:
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = {
name: pgServerPrefix
...
properties: {
...
network: {
delegatedSubnetResourceId: virtualNetwork::databaseSubnet.id
privateDnsZoneArmResourceId: privateDnsZone.id
}
}
}
Add a networkConfig
child resource on the Web App Service to inject it into the VNet:
resource web 'Microsoft.Web/sites@2022-03-01' = {
name: '${name}-app-service'
...
resource webappVnetConfig 'networkConfig' = {
name: 'virtualNetwork'
properties: {
subnetResourceId: virtualNetwork::webappSubnet.id
}
}
}
param name string = 'pgvnet4'
param location string = 'eastus'
@secure()
param adminPassword string
var pgServerPrefix = '${name}-postgres-server'
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2019-11-01' = {
name: '${name}-vnet'
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
}
resource databaseSubnet 'subnets' = {
name: 'database-subnet'
properties: {
addressPrefix: '10.0.0.0/24'
delegations: [
{
name: '${name}-subnet-delegation'
properties: {
serviceName: 'Microsoft.DBforPostgreSQL/flexibleServers'
}
}
]
}
}
resource webappSubnet 'subnets' = {
name: 'webapp-subnet'
properties: {
addressPrefix: '10.0.1.0/24'
delegations: [
{
name: '${name}-subnet-delegation-web'
properties: {
serviceName: 'Microsoft.Web/serverFarms'
}
}
]
}
}
}
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: '${pgServerPrefix}.private.postgres.database.azure.com'
location: 'global'
resource vnetLink 'virtualNetworkLinks' = {
name: '${pgServerPrefix}-link'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: virtualNetwork.id
}
}
}
}
resource web 'Microsoft.Web/sites@2022-03-01' = {
name: '${name}-app-service'
location: location
kind: 'app,linux'
properties: {
serverFarmId: appServicePlan.id
siteConfig: {
alwaysOn: true
linuxFxVersion: 'PYTHON|3.11'
ftpsState: 'Disabled'
appCommandLine: 'startup.sh'
}
httpsOnly: true
}
identity: {
type: 'SystemAssigned'
}
resource appSettings 'config' = {
name: 'appsettings'
properties: {
AZURE_POSTGRESQL_HOST: '${postgresServer.name}.postgres.database.azure.com'
AZURE_POSTGRESQL_USER: postgresServer.properties.administratorLogin
AZURE_POSTGRESQL_PASS: adminPassword
AZURE_POSTGRESQL_DBNAME: 'postgres'
}
}
resource webappVnetConfig 'networkConfig' = {
name: 'virtualNetwork'
properties: {
subnetResourceId: virtualNetwork::webappSubnet.id
}
}
}
resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = {
name: '${name}-service-plan'
location: location
sku: {
name: 'B1'
}
properties: {
reserved: true
}
}
resource postgresServer 'Microsoft.DBforPostgreSQL/flexibleServers@2022-01-20-preview' = {
name: pgServerPrefix
location: location
sku: {
name: 'Standard_B1ms'
tier: 'Burstable'
}
properties: {
administratorLogin: 'postgresadmin'
administratorLoginPassword: adminPassword
storage: {
storageSizeGB: 128
}
version: '14'
network: {
delegatedSubnetResourceId: virtualNetwork::databaseSubnet.id
privateDnsZoneArmResourceId: privateDnsZone.id
}
}
}
👁️ See full example in postgres_vnet.bicep
Install the Bicep extension for syntax highlighting, auto-complete, and more.
Use az bicep build
command to check file for errors:
$ az bicep build -f pg.bicep
Use a CI/CD workflow to always check for errors:
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Azure CLI script
uses: azure/CLI@v1
with:
inlineScript: az bicep build -f infra/main.bicep
👁️ See whole file: azure-bicep.yaml
Use Microsoft Security DevOps action to find security issues in Bicep files:
- name: Run Microsoft Security DevOps Analysis
uses: microsoft/security-devops-action@preview
id: msdo
with:
tools: templateanalyzer
Example output:
Error: 1. TemplateAnalyzer Error AZR-000284 - File: infra/main.bicep. Line: 54. Column 0.
Tool: TemplateAnalyzer: Rule: AZR-000284 (Azure.Deployment.AdminUsername).
https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Deployment.AdminUsername/
Resource properties can be configured using a hardcoded value or Azure Bicep/ template
expressions. When specifing sensitive values, use secure parameters such as secureString
or secureObject.
👁️ See whole file: azure-dev-validate.yaml
You can use Bicep to create all of your Azure resources, and use the Azure Developer CLI to take care of the whole deployment workflow.
Find app templates that use PostgreSQL in the AZD templates gallery:
Grab the slides @ aka.ms/postgres-bicep-slides
Find me online at:
Mastodon | @pamelafox@fosstodon.org |
@pamelafox | |
GitHub | www.github.com/pamelafox |
Website | pamelafox.org |
Let me know about your experiences with PostgreSQL and Bicep!