Skip to content

ciberado/azuretechtrainingday

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

24 Commits
 
 
 
 

Repository files navigation

Azure tech training day

poster

This workshop provides a detailed overview of the most popular services in Azure. After completing it as a lab, you will have experience regarding how to create a relatively complex solution following best practices. Previous knowledge of the platform (or the supervision of a trainer) is adviced to understand the details of the architecture.

Initial configuration

Resource groups

Let's configure our credentials and create the basic resources, including a Resource Group.

PS1=\\n\\n$PS1

export PREFIX=lab$RANDOM

# Optional, if not using the Web Shell
az login

az account show
az account list-locations --output table
az provider list --output table
az provider register --name Microsoft.KeyVault

az group create --name $PREFIX-rg --location westeurope

Storage accounts

Storage

We will use a Storage Account to create a very simple static website. A newer and more powerful approach to solve the same problem would be using the Azure Static WebApps.

az storage account create \
  --name ${PREFIX}stacc \
  -g $PREFIX-rg \
  --kind StorageV2 \
  --sku Standard_LRS

Static websites

Take this example as a way to familarize yourself with Storage Accounts and Blob Containers. Also, you will use the command line to transfer files between your laptop (or the cloud shell) and the container.

az storage blob service-properties update \
  --account-name ${PREFIX}stacc \
  --static-website \
  --404-document 404.html \
  --index-document index.html \
  --output table

mkdir web
wget https://pastebin.com/raw/Y1D5zYNR -O web/index.html
wget https://pastebin.com/raw/jxEqVD29 -O web/404.html

az storage blob upload-batch \
  --account-name ${PREFIX}stacc \
  --source web \
  --destination \$web \
  --output table

az storage account show \
  --name ${PREFIX}stacc \
  --query "primaryEndpoints.web" \
  --output tsv

Relational databases

Databases

Servers and databases

We will create a Azure SQL Server containing one database. And we will open direct access to it by creating a Firewall rule. In this case, this is not a recommended pattern, but the best approach (using Endpoints) will be shown later.

SQL_PASS=MyPassword$RANDOM
echo $SQL_PASS > sql_pass.txt

DB_PREFIX=$PREFIX

az sql server create \
  --resource-group $DB_PREFIX-rg \
  --name $DB_PREFIX-pokemondb-server \
  --admin-user dbadmin \
  --admin-password $SQL_PASS \
  --output table

az sql db create \
  --resource-group $DB_PREFIX-rg \
  --server $DB_PREFIX-pokemondb-server \
  --name $PREFIX-pokemonDB \
  --auto-pause-delay 600 \
  --edition GeneralPurpose \
  --family Gen5 \
  --capacity 1 \
  --compute-model Serverless \
  --zone-redundant false \
  --tags Owner=$DB_PREFIX Project=pokemon \
  --output table

Firewall rules

MY_IP=$(curl ifconfig.me)

az sql server firewall-rule create \
  --resource-group $DB_PREFIX-rg \
  --name $PREFIX-pokemondb-server-fw-rule \
  --server $DB_PREFIX-pokemondb-server \
  --start-ip-address $MY_IP \
  --end-ip-address $MY_IP \
  --output table

Interaction

mssql-cli is a very nice tool to interact with SQL Server and other compatible database engines. We will use it to connect to our instance and check everything has been properly setup.

wget -qO- https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
sudo curl -o /etc/apt/sources.list.d/microsoft.list "https://packages.microsoft.com/config/ubuntu/$(lsb_release -sr)/prod.list"
sudo apt-get update
sudo apt-get install mssql-cli -y
mssql-cli --version

The following instructions will list the content of the database and will execute a data dump stored in a pastebin page. Basically, we pipe the content of that file throght mssql-cli to insert rows into our new database.

SQL_URL=$DB_PREFIX-pokemondb-server.database.windows.net

mssql-cli \
  --username dbadmin \
  --password $SQL_PASS \
  --server $SQL_URL \
  --database $PREFIX-pokemonDB \
  --query "\ld"

curl https://pastebin.com/raw/3jkbTTSq -s | \
  mssql-cli \
    --username dbadmin \
    --password $SQL_PASS \
    --server $SQL_URL \
    --database $PREFIX-pokemonDB 

Application secrets

Identities

Managed Identities are the best way to provide credentials to our applications running inside VMs or WebApps. We will use them to allow an application to acces Azure Key Vault, the only correct way to store sensitive information like the connection string to our database.

Keyvault and User Managed Identities

az keyvault create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-vault \
  --output table

az identity create \
  --name $PREFIX-pokemonapp-msi \
  --resource-group $PREFIX-rg 

PRINCIPAL=$(az identity show \
  --name $PREFIX-pokemonapp-msi \
  --resource-group $PREFIX-rg \
  --query principalId \
  --output tsv) && echo $PRINCIPAL

az keyvault set-policy \
  --resource-group $PREFIX-rg \
  --name $PREFIX-vault \
  --secret-permission get \
  --object-id $PRINCIPAL \
  --output table

Vaults

Secret storage

The connection string contains sensitive information, like the database password. We will save it into the Key Vault created previously.

SQL_CONN=$(az sql db show-connection-string \
  --client odbc \
  --auth-type SqlPassword \
  --name $PREFIX-pokemonDB \
  --server $DB_PREFIX-pokemondb-server \
  --output tsv | \
  awk '{gsub("<username>", "dbadmin", $0); print}' | \
  awk '{gsub("<password>", p, $0); print}' p="$SQL_PASS") && echo $SQL_CONN
  
az keyvault secret set \
  --vault-name $PREFIX-vault \
  --name "PokemonDBConn" \
  --value "$SQL_CONN"

Networking infraestructure

Virtual networks

Networking

az network vnet create \
  -g $PREFIX-rg \
  --name $PREFIX-vnet \
  --address-prefix 10.0.0.0/16 \
  --subnet-name $PREFIX-subnet-public \
  --subnet-prefix 10.0.0.0/24 

Managed resource connectivity

Bridge

The correct way to allow accessing the managed database from a virtual machine is not opening Firewall ports like we did before. Instead, we will create a Service Endpoint to set a network card of database inside our Virtual Network.

# This will only work with subscription-wide privileges. Don't worry if it is not your case.
az network vnet list-endpoint-services \
  --location westeurope \
  --output table

az network vnet subnet update \
  --resource-group $PREFIX-rg \
  --vnet-name $PREFIX-vnet \
  --name $PREFIX-subnet-public \
  --service-endpoints Microsoft.Sql \
  --output table

# Subnet and Azure SQL may be in different resource groups, so we need the full resource name
SUBNET_ID=$(az network vnet subnet show --resource-group $PREFIX-rg --vnet-name $PREFIX-vnet --name $PREFIX-subnet-public --query id --output tsv) && echo Public subnet ID: $SUBNET_ID

az sql server vnet-rule create \
  --name $PREFIX-pokemondb-server-firewall-rule \
  --resource-group $DB_PREFIX-rg \
  --server $DB_PREFIX-pokemondb-server \
  --subnet $SUBNET_ID 

Virtual machines

Dogs

Firewall configuration using NSGs

Dog

Network Security Groups are the preferred way to protect traffic inside the Virtual Networks. They can be attached to both VMs and Subnets, and it is perfectly fine to do it both ways. In this case, we will create a security group opening administration ports (port 22, not the best practice) and web traffic (80 and 8080).

az network nsg create \
  --name $PREFIX-pokemon-nsg \
  --resource-group $PREFIX-rg 

az network nsg rule create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-nsg-ssh \
  --nsg-name $PREFIX-pokemon-nsg \
  --priority 220 \
  --access Allow \
  --source-address-prefixes $MY_IP \
  --destination-port-ranges 22 \
  --protocol Tcp \
  --description "Allow ssh administration (bad idea)" 

az network nsg rule create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-nsg-web \
  --nsg-name $PREFIX-pokemon-nsg \
  --priority 200 \
  --access Allow \
  --source-address-prefixes Internet \
  --destination-port-ranges 80 8080 \
  --protocol Tcp \
  --description "Allow traffic to pokemon app" 

Virtual machine creation

This Virtual Machine will have the Managed Identity attached to it, allowing access to the Key Vault from where an application (installed later) will retrieve the connection string to the database. Also, we will attach the previously created Network Security Group to it, so we will be able to ssh inside the VM and also access the server running inside using our browser.

# Show all resources created: PublicIP, VMNic, vm-Disk and vm
az vm create \
  --name $PREFIX-pokemon-vm \
  --resource-group $PREFIX-rg \
  --image UbuntuLTS  \
  --admin-username $PREFIX \
  --vnet-name $PREFIX-vnet \
  --subnet $PREFIX-subnet-public \
  --authentication-type ssh \
  --assign-identity $PREFIX-pokemonapp-msi \
  --nsg $PREFIX-pokemon-nsg \
  --generate-ssh-keys \
  --size Standard_DS1_v2 \
  --tags Owner=$PREFIX Project=pokemon Name="Pokemon VM" 

Software configuration with extensions

Building

Extensions are not recommended anymore for installing software, as it takes time to execute them. But they are still present in many scenarios, so we will take advantage of this mechanism to install a Pokemon web application inside the previosly created server.

The next instructions will generate the script to be run inside the VM.

cat << EOF > script-pokemon.sh
#!/bin/sh

sudo apt update
sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt -y install nodejs gcc g++ make

git clone https://github.com/ciberado/pokemon-nodejs
cd pokemon-nodejs
git checkout azure-demo
npm install
export PORT=8080
export KEY_VAULT_URI=https://$PREFIX-vault.vault.azure.net
# Important: avoid blocking the extension mechanism by running the app in background
node app.js > app.log &
EOF

Now we will use a Storage Account to store the script.

az storage blob upload \
  --account-name ${PREFIX}stacc \
  --container-name \$web \
  --file ./script-pokemon.sh \
  --name script-pokemon.sh \
  --output table

SCRIPT_URL=$(az storage account show \
  --name ${PREFIX}stacc \
  --query "primaryEndpoints.web" \
  --output tsv)script-pokemon.sh && echo $SCRIPT_URL

Now, we will instruct the VM to execute the run script Extension, downloading the script and executing it to install and run the Pokemon application.

az vm extension set \
  --resource-group $PREFIX-rg \
  --vm-name $PREFIX-pokemon-vm \
  --name customScript \
  --publisher Microsoft.Azure.Extensions \
  --settings "{\"fileUris\": [\"$SCRIPT_URL\"],\"commandToExecute\": \"./script-pokemon.sh\"}" 

Finally, we can retrieve the IP of the VM and (hopefully) we will be able to access the logs of the server and the application itself.

VM_IP=$(az vm list-ip-addresses \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vm \
  --query [0].virtualMachine.network.publicIpAddresses[0].ipAddress \
  --output tsv) && echo $VM_IP

ssh $PREFIX@$VM_IP \
  sudo cat /var/lib/waagent/custom-script/download/0/pokemon-nodejs/app.log

echo Click here: http://$VM_IP:8080

Asynchronous architectures

Treebookhouse

Storage account queues

az storage queue create \
  --account-name ${PREFIX}stacc \
  --name healthbeats \
  --output table

Permission assignment

Keys

SA_ID=$(az storage account show --name ${PREFIX}stacc -g $PREFIX-rg --query id --output tsv) && echo $SA_ID

PRINCIPAL=$(az identity show --name $PREFIX-pokemonapp-msi --resource-group $PREFIX-rg --query principalId --output tsv) && echo $PRINCIPAL

until az role assignment create \
  --assignee $PRINCIPAL \
  --role 'Owner' \
  --scope $SA_ID
do
  echo "Trying again role assignment."
  sleep 10
done 

until az role assignment create \
  --assignee $PRINCIPAL \
  --role 'Storage Queue Data Contributor' \
  --scope $SA_ID
do
  echo "Trying again role assignment."
  sleep 10
done 

Queue producer creation

Written order

cat << EOF > script-pokemon-healthbeat.sh
#!/bin/sh

sudo apt update
sudo apt -y install curl dirmngr apt-transport-https lsb-release ca-certificates
curl -sL https://deb.nodesource.com/setup_12.x | sudo -E bash -
sudo apt -y install nodejs gcc g++ make

git clone https://github.com/ciberado/pokemon-nodejs
cd pokemon-nodejs
git checkout azure-demo
npm install
export PORT=8080
export AZURE_STORAGE_NAME=${PREFIX}stacc
export KEY_VAULT_URI=https://$PREFIX-vault.vault.azure.net
node app.js > app.log &
EOF

# This time, using custom-data
az vm create \
  --name $PREFIX-pokemon-vm-healthbeat \
  --resource-group $PREFIX-rg \
  --image UbuntuLTS  \
  --admin-username $PREFIX \
  --vnet-name $PREFIX-vnet \
  --subnet $PREFIX-subnet-public \
  --authentication-type ssh \
  --assign-identity $PREFIX-pokemonapp-msi \
  --nsg $PREFIX-pokemon-nsg \
  --generate-ssh-keys \
  --size Standard_DS1_v2 \
  --tags Owner=$PREFIX Project=pokemon Name="Pokemon VM" \
  --custom-data script-pokemon-healthbeat.sh
  
VM_IP_HC=$(az vm list-ip-addresses \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vm-healthbeat \
  --query [0].virtualMachine.network.publicIpAddresses[0].ipAddress \
  --output tsv) && echo $VM_IP_HC


ssh $PREFIX@$VM_IP_HC \
  sudo tail /var/log/cloud-init-output.log --follow
ssh $PREFIX@$VM_IP_HC \
  sudo tail /pokemon-nodejs/app.log --follow

echo Click here: http://$VM_IP_HC:8080

Command line queue consumer

Welding

az storage message get \
  --account-name ${PREFIX}stacc \
  --queue-name healthbeats \
  --num-messages 32

HA architectures

Two bridges

Load balancers, backends, probes and rules

Lights

az network public-ip create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-lb-ip \
  --allocation-method Static \
  --sku Standard \
  --output table

az network lb create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-lb \
  --public-ip-address $PREFIX-lb-ip  \
  --frontend-ip-name $PREFIX-lb-frontend-pool \
  --backend-pool-name $PREFIX-backend-pool \
  --sku Standard \
  --tags Owner=$PREFIX Project=pokemon 

az network lb probe create \
  --resource-group $PREFIX-rg \
  --lb-name $PREFIX-lb \
  --name $PREFIX-lb-probe \
  --port 8080 \
  --protocol http \
  --path /health \
  --interval 30

az network lb rule create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-lb-rule \
  --backend-port 8080 \
  --frontend-port 80 \
  --lb-name $PREFIX-lb \
  --protocol Tcp \
  --backend-pool-name $PREFIX-backend-pool \
  --load-distribution Default \
  --probe-name $PREFIX-lb-probe

Firewall rules

Dog

az network nsg create \
  --name $PREFIX-pokemon-vmss-nsg \
  --resource-group $PREFIX-rg 

az network nsg rule create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vmss-nsg-rule-from-lb \
  --nsg-name $PREFIX-pokemon-vmss-nsg \
  --priority 200 \
  --access Allow \
  --source-address-prefixes AzureLoadBalancer \
  --destination-port-ranges 8080 \
  --protocol Tcp \
  --description "Allow traffic to pokemon app from any L4 LoadBalancer" 

az network nsg rule create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vmss-nsg-rule-from-troubleshooting \
  --nsg-name $PREFIX-pokemon-vmss-nsg \
  --priority 220 \
  --access Allow \
  --source-address-prefixes "*" \
  --destination-port-ranges 8080 22 \
  --protocol Tcp \
  --description "Allow traffic to pokemon app for troubleshooting" 

VM Fleets with VMSS

Clones

az vmss create \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vmss \
  --computer-name-prefix $PREFIX-pokemon-vmss-vm \
  --instance-count 2 \
  --vnet-name $PREFIX-vnet \
  --subnet $PREFIX-subnet-public \
  --public-ip-per-vm  \
  --public-ip-address-allocation static \
  --load-balancer $PREFIX-lb \
  --backend-pool-name $PREFIX-backend-pool \
  --zones 2 \
  --nsg $PREFIX-pokemon-vmss-nsg \
  --image UbuntuLTS \
  --vm-sku Standard_DS1_v2 \
  --upgrade-policy-mode Automatic \
  --admin-username $PREFIX \
  --generate-ssh-keys \
  --assign-identity $PREFIX-pokemonapp-msi \
  --tags Owner=$PREFIX Project=pokemon \
  --custom-data script-pokemon-healthbeat.sh

VMSS_VM0_IP=$(az vmss list-instance-public-ips \
  --resource-group $PREFIX-rg \
  --name $PREFIX-pokemon-vmss \
  --query [0].ipAddress \
  --output tsv) && echo $VMSS_VM0_IP

ssh $PREFIX@$VMSS_VM0_IP \
  sudo tail /var/log/cloud-init-output.log --follow

ssh $PREFIX@$VMSS_VM0_IP tail /pokemon-nodejs/app.log --follow

LB_IP=$(az network public-ip show \
  --resource-group $PREFIX-rg \
  --name $PREFIX-lb-ip \
  --query ipAddress \
  --output TSV) && echo "Click on http://$LB_IP"

Azure apps service

Happyness

Web app provisioning

az appservice plan create \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod-plan \
  --sku S1 \
  --is-linux

az webapp create \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --plan ${PREFIX}-prod-plan \
  --deployment-container-image-name nginx

az webapp log config \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --application-logging true \
  --detailed-error-messages true \
  --docker-container-logging filesystem 

Slots

Two phones

az webapp deployment slot create \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --slot secondary

az webapp log config \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --slot secondary \
  --application-logging true \
  --detailed-error-messages true \
  --docker-container-logging filesystem 

Web app configuration

SA_CONN_STR=$(az storage account show-connection-string \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}stacc \
  --query connectionString \
  --output tsv)

az webapp config appsettings set \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --settings AZURE_STORAGE_CONNECTION_STRING="$SA_CONN_STR" 
  
az webapp config appsettings set \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --slot secondary \
  --slot-settings AZURE_STORAGE_CONNECTION_STRING="$SA_CONN_STR" 

Container deployment

Whale

az webapp config container set \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --docker-custom-image-name ciberado/pokemon-dashboard:0.0.1 \
  --slot secondary 

WEBAPP_PRODUCTION=$(az webapp show \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --query defaultHostName \
  --output tsv) && echo Click on https://$WEBAPP_PRODUCTION

WEBAPP_SECONDARY=$(az webapp show \
  --resource-group $PREFIX-rg \
  --name ${PREFIX}-prod \
  --slot secondary \
  --query defaultHostName \
  --output tsv) && echo Click on https://$WEBAPP_SECONDARY

Traffic control

Traffic

az webapp traffic-routing set \
  --resource-group $PREFIX-rg \
  --name $PREFIX-prod \
  --distribution secondary=50 

az webapp deployment slot swap \
  --resource-group $PREFIX-rg \
  --name $PREFIX-prod \
  --action swap \
  --slot secondary 

Azure pipelines

Gear

This is the tutorial you are looking for.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published