diff --git a/.github/workflows/scenario-testing.yaml b/.github/workflows/scenario-testing.yaml index fc4828c9..d2d095eb 100644 --- a/.github/workflows/scenario-testing.yaml +++ b/.github/workflows/scenario-testing.yaml @@ -9,10 +9,15 @@ on: branches: - main workflow_dispatch: - +permissions: + id-token: write + contents: read jobs: test-ocd-scenarios: runs-on: ubuntu-latest + # This is needed in order to obtain OIDC tokens to sign this pipeline into + # the testing subscription for any branch in this repository. + environment: ScenarioTesting steps: - uses: actions/checkout@v2 - name: Build all targets. @@ -20,17 +25,20 @@ jobs: make build-all make test-all - name: Sign into Azure - uses: azure/actions/login@v1 + uses: azure/login@v1 with: - creds: ${{ secrets.AZURE_CREDENTIALS }} + client-id: ${{ secrets.AZURE_CLIENT_ID }} + tenant-id: ${{ secrets.AZURE_TENANT_ID }} + subscription-id: ${{ secrets.AZURE_SUBSCRIPTION }} - name: Run all one click deployment scenarios. uses: azure/CLI@v1 if: github.event_name != 'pull_request' with: + azcliversion: 2.53.0 inlineScript: | - apk add --no-cache make - make test-scenarios SUBSCRIPTION=${{ secrets.AZURE_SUBSCRIPTION }} + apk add --no-cache make git openssh openssl helm curl jq + make test-upstream-scenarios SUBSCRIPTION=${{ secrets.AZURE_SUBSCRIPTION }} - name: Display ie.log file - if: github.event_name != 'pull_request' + if: (success() || failure()) && github.event_name != 'pull_request' run: | cat ie.log diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..955d6436 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "upstream-scenarios"] + path = upstream-scenarios + url = https://github.com/MicrosoftDocs/executable-docs diff --git a/Makefile b/Makefile index 32e312bf..f0beeac8 100644 --- a/Makefile +++ b/Makefile @@ -35,14 +35,30 @@ test-all: SUBSCRIPTION ?= 00000000-0000-0000-0000-000000000000 SCENARIO ?= ./README.md +WORKING_DIRECTORY ?= $(PWD) test-scenario: @echo "Running scenario $(SCENARIO)" - $(IE_BINARY) test $(SCENARIO) --subscription $(SUBSCRIPTION) + $(IE_BINARY) test $(SCENARIO) --subscription $(SUBSCRIPTION) --working-directory $(WORKING_DIRECTORY) test-scenarios: @echo "Testing out the scenarios" for dir in ./scenarios/ocd/*/; do \ - $(MAKE) test-scenario SCENARIO="$${dir}README.md" SUBCRIPTION="$(SUBSCRIPTION)"; \ + ($(MAKE) test-scenario SCENARIO="$${dir}README.md" SUBCRIPTION="$(SUBSCRIPTION)") || exit $$?; \ + done + +test-upstream-scenarios: + @echo "Pulling the upstream scenarios" + @git config --global --add safe.directory /home/runner/work/InnovationEngine/InnovationEngine + @git submodule update --init --recursive + @echo "Testing out the upstream scenarios" + for dir in ./upstream-scenarios/scenarios/*/; do \ + if ! [ -f $${dir}README.md ]; then \ + continue; \ + fi; \ + if echo "$${dir}" | grep -q "CreateContainerAppDeploymentFromSource"; then \ + continue; \ + fi; \ + ($(MAKE) test-scenario SCENARIO="$${dir}README.md" SUBCRIPTION="$(SUBSCRIPTION)" WORKING_DIRECTORY="$${dir}") || exit $$?; \ done # ------------------------------- Run targets ---------------------------------- diff --git a/cmd/ie/commands/execute.go b/cmd/ie/commands/execute.go index b6075928..d9890c9b 100644 --- a/cmd/ie/commands/execute.go +++ b/cmd/ie/commands/execute.go @@ -123,7 +123,7 @@ var executeCommand = &cobra.Command{ err = innovationEngine.ExecuteScenario(scenario) if err != nil { logging.GlobalLogger.Errorf("Error executing scenario: %s", err) - fmt.Printf("Error executing scenario: %s", err) + fmt.Printf("Error executing scenario: %s\n", err) os.Exit(1) } }, diff --git a/cmd/ie/commands/test.go b/cmd/ie/commands/test.go index f1ddcd8c..35259bb4 100644 --- a/cmd/ie/commands/test.go +++ b/cmd/ie/commands/test.go @@ -16,6 +16,8 @@ func init() { Bool("verbose", false, "Enable verbose logging & standard output.") testCommand.PersistentFlags(). String("subscription", "", "Sets the subscription ID used by a scenarios azure-cli commands. Will rely on the default subscription if not set.") + testCommand.PersistentFlags(). + String("working-directory", ".", "Sets the working directory for innovation engine to operate out of. Restores the current working directory when finished.") } var testCommand = &cobra.Command{ @@ -32,12 +34,14 @@ var testCommand = &cobra.Command{ verbose, _ := cmd.Flags().GetBool("verbose") subscription, _ := cmd.Flags().GetString("subscription") + workingDirectory, _ := cmd.Flags().GetString("working-directory") innovationEngine, err := engine.NewEngine(engine.EngineConfiguration{ - Verbose: verbose, - DoNotDelete: false, - Subscription: subscription, - CorrelationId: "", + Verbose: verbose, + DoNotDelete: false, + Subscription: subscription, + CorrelationId: "", + WorkingDirectory: workingDirectory, }) if err != nil { @@ -57,7 +61,11 @@ var testCommand = &cobra.Command{ os.Exit(1) } - innovationEngine.TestScenario(scenario) - + err = innovationEngine.TestScenario(scenario) + if err != nil { + logging.GlobalLogger.Errorf("Error testing scenario: %s", err) + fmt.Printf("Error testing scenario: %s\n", err) + os.Exit(1) + } }, } diff --git a/scenarios/ocd/AttachDataDiskLinuxVM/DataDisk_VM.md b/scenarios/ocd/AttachDataDiskLinuxVM/DataDisk_VM.md new file mode 100644 index 00000000..76922f29 --- /dev/null +++ b/scenarios/ocd/AttachDataDiskLinuxVM/DataDisk_VM.md @@ -0,0 +1,89 @@ +az login +az account set --subscription "XXXXXXX" +az account show + +#VARIABLE DECLARATION +export NETWORK_PREFIX="$(($RANDOM % 254 + 1))" +export RANDOM_ID="$(openssl rand -hex 3)" +export MY_RESOURCE_GROUP_NAME="myResourceGroup$RANDOM_ID" +export REGION="eastus" +export MY_VM_NAME="myVMName$RANDOM_ID" +export MY_VM_IMAGE='Ubuntu2204' +export MY_VM_USERNAME="azureuser" +export MY_VM_SIZE='Standard_DS2_v5' +export MY_VNET_NAME="myVNet$RANDOM_ID" +export MY_VNET_PREFIX="10.$NETWORK_PREFIX.0.0/16" +export MY_VM_NIC_NAME="myVMNicName$RANDOM_ID" +export MY_SN_NAME="mySN$RANDOM_ID" +export MY_SN_PREFIX="10.$NETWORK_PREFIX.0.0/24" +export MY_PUBLIC_IP_NAME="myPublicIP$RANDOM_ID" +export MY_DNS_LABEL="mydnslabel$RANDOM_ID" +export MY_NSG_NAME="myNSGName$RANDOM_ID" + +#CREATE A RESOURCE GROUP +az group create \ + --name $MY_RESOURCE_GROUP_NAME \ + --location $REGION -o JSON + +#SET UP VM NETWORK +az network vnet create \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --name $MY_VNET_NAME \ + --location $REGION \ + --address-prefix $MY_VNET_PREFIX \ + --subnet-name $MY_SN_NAME \ + --subnet-prefix $MY_SN_PREFIX -o JSON + +#CREATE STATIC PUBLIC IP +az network public-ip create \ + --name $MY_PUBLIC_IP_NAME \ + --location $REGION \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --dns-name $MY_DNS_LABEL \ + --sku Standard \ + --allocation-method static \ + --version IPv4 \ + --zone 1 2 3 -o JSON + +#CREATE NSG +az network nsg create \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --name $MY_NSG_NAME \ + --location $REGION -o JSON + +#CREATE NSG RULES +az network nsg rule create \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --nsg-name $MY_NSG_NAME \ + --name $MY_NSG_SSH_RULE \ + --access Allow \ + --protocol Tcp \ + --direction Inbound \ + --priority 100 \ + --source-address-prefix '*' \ + --source-port-range '*' \ + --destination-address-prefix '*' \ + --destination-port-range 22 80 443 -o JSON + +#CREATE NIC +az network nic create \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --name $MY_VM_NIC_NAME \ + --location $REGION \ + --ip-forwarding false \ + --subnet $MY_SN_NAME \ + --vnet-name $MY_VNET_NAME \ + --network-security-group $MY_NSG_NAME \ + --public-ip-address $MY_PUBLIC_IP_NAME -o JSON + +#CREATE LINUX VM +az vm create \ + --resource-group $MY_RESOURCE_GROUP_NAME \ + --name $MY_VM_NAME \ + --image $MY_VM_IMAGE \ + --admin-username $MY_VM_USERNAME \ + --generate-ssh-keys \ + --assign-identity $MY_VM_ID \ + --size $MY_VM_SIZE \ + --custom-data cloud-init-nginx.txt \ + --nics $MY_VM_NIC_NAME \ No newline at end of file diff --git a/scenarios/ocd/CreateLinuxVMSSwithAppGW/README.md b/scenarios/ocd/CreateLinuxVMSSwithAppGW/README.md new file mode 100644 index 00000000..de17dce6 --- /dev/null +++ b/scenarios/ocd/CreateLinuxVMSSwithAppGW/README.md @@ -0,0 +1,838 @@ +# Create a Virtual Machine Scale Set with Application Gateway with Linux image + +## Define Environment Variables + +The First step in this tutorial is to define environment variables. + +```bash + +export RANDOM_ID="$(openssl rand -hex 3)" +export MY_RESOURCE_GROUP_NAME="myVMSSResourceGroup$RANDOM_ID" +export REGION=EastUS +export MY_VMSS_NAME="myVMSS$RANDOM_ID" +export MY_USERNAME=azureuser +export MY_VM_IMAGE="Ubuntu2204" +export MY_VNET_NAME="myVNet$RANDOM_ID" +export NETWORK_PREFIX="$(($RANDOM % 254 + 1))" +export MY_VNET_PREFIX="10.$NETWORK_PREFIX.0.0/16" +export MY_VM_SN_NAME="myVMSN$RANDOM_ID" +export MY_VM_SN_PREFIX="10.$NETWORK_PREFIX.0.0/24" +export MY_APPGW_SN_NAME="myAPPGWSN$RANDOM_ID" +export MY_APPGW_SN_PREFIX="10.$NETWORK_PREFIX.1.0/24" +export MY_APPGW_NAME="myAPPGW$RANDOM_ID" +export MY_APPGW_PUBLIC_IP_NAME="myAPPGWPublicIP$RANDOM_ID" + +``` +# Login to Azure using the CLI + +In order to run commands against Azure using the CLI you need to login. This is done, very simply, though the `az login` command: + +# Create a resource group + +A resource group is a container for related resources. All resources must be placed in a resource group. We will create one for this tutorial. The following command creates a resource group with the previously defined $MY_RESOURCE_GROUP_NAME and $REGION parameters. + +```bash +az group create --name $MY_RESOURCE_GROUP_NAME --location $REGION -o JSON +``` + +Results: + + +```json +{ + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx", + "location": "eastus", + "managedBy": null, + "name": "myVMSSResourceGroupxxxxxx", + "properties": { + "provisioningState": "Succeeded" + }, + "tags": null, + "type": "Microsoft.Resources/resourceGroups" +} +``` + +# Create Network Resources + +You need to create network resources before you proceed the VMSS steps. In this step you're going to create a VNET, 2 subnets 1 for Application Gateway and 1 for VMs. You also need to have a public IP to attach your Application Gateway to be able to reach your web application from internet. + + +#### Create Virtual Network (VNET) and VM Subnet + +```bash +az network vnet create --name $MY_VNET_NAME --resource-group $MY_RESOURCE_GROUP_NAME --location $REGION --address-prefix $MY_VNET_PREFIX --subnet-name $MY_VM_SN_NAME --subnet-prefix $MY_VM_SN_PREFIX -o JSON +``` + +Results: + + +```json +{ + "newVNet": { + "addressSpace": { + "addressPrefixes": [ + "10.X.0.0/16" + ] + }, + "enableDdosProtection": false, + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxxx", + "location": "eastus", + "name": "myVNetxxxxxx", + "provisioningState": "Succeeded", + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "resourceGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "subnets": [ + { + "addressPrefix": "10.X.0.0/24", + "delegations": [], + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxxx/subnets/myVMSNxxxxxx", + "name": "myVMSNxxxxxx", + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "provisioningState": "Succeeded", + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/virtualNetworks/subnets" + } + ], + "type": "Microsoft.Network/virtualNetworks", + "virtualNetworkPeerings": [] + } +} +``` + +### Create Application Gateway Resources + +Azure Application Gateway requires a dedicated subnet within your virtual network. The below command creates a subnet named $MY_APPGW_SN_NAME with specified address prefix named $MY_APPGW_SN_PREFIX in your VNET $MY_VNET_NAME + + +```bash +az network vnet subnet create --name $MY_APPGW_SN_NAME --resource-group $MY_RESOURCE_GROUP_NAME --vnet-name $MY_VNET_NAME --address-prefix $MY_APPGW_SN_PREFIX -o JSON +``` + +Results: + + +```json +{ + "addressPrefix": "10.66.1.0/24", + "delegations": [], + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxxx/subnets/myAPPGWSNxxxxxx", + "name": "myAPPGWSNxxxxxx", + "privateEndpointNetworkPolicies": "Disabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "provisioningState": "Succeeded", + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/virtualNetworks/subnets" +} +``` +The below command creates a standard, zone redundant, static, public IPv4 in your resource group. + +```bash +az network public-ip create --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_APPGW_PUBLIC_IP_NAME --sku Standard --location $REGION --allocation-method static --version IPv4 --zone 1 2 3 -o JSON + ``` + +Results: + + +```json +{ + "publicIp": { + "ddosSettings": { + "protectionMode": "VirtualNetworkInherited" + }, + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/publicIPAddresses//myAPPGWPublicIPxxxxxx", + "idleTimeoutInMinutes": 4, + "ipAddress": "X.X.X.X", + "ipTags": [], + "location": "eastus", + "name": "/myAPPGWPublicIPxxxxxx", + "provisioningState": "Succeeded", + "publicIPAddressVersion": "IPv4", + "publicIPAllocationMethod": "Static", + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "resourceGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "sku": { + "name": "Standard", + "tier": "Regional" + }, + "type": "Microsoft.Network/publicIPAddresses", + "zones": [ + "1", + "2", + "3" + ] + } +} +``` + +In this step you create an Application Gateway that you're going to integrate with your Virtual Machine Scale Set. In this example we create a zone redundant Application Gateway with Standard_v2 SKU and enable Http communication for the Application Gateway. The public IP $MY_APPGW_PUBLIC_IP_NAME that we created in previous step attached to the Application Gateway. + +```bash +az network application-gateway create --name $MY_APPGW_NAME --location $REGION --resource-group $MY_RESOURCE_GROUP_NAME --vnet-name $MY_VNET_NAME --subnet $MY_APPGW_SN_NAME --capacity 2 --zones 1 2 3 --sku Standard_v2 --http-settings-cookie-based-affinity Disabled --frontend-port 80 --http-settings-port 80 --http-settings-protocol Http --public-ip-address $MY_APPGW_PUBLIC_IP_NAME --priority 1001 -o JSON + ``` + + +```json +{ + "applicationGateway": { + "backendAddressPools": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/backendAddressPools/appGatewayBackendPool", + "name": "appGatewayBackendPool", + "properties": { + "backendAddresses": [], + "provisioningState": "Succeeded", + "requestRoutingRules": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/requestRoutingRules/rule1", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ] + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/backendAddressPools" + } + ], + "backendHttpSettingsCollection": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/backendHttpSettingsCollection/appGatewayBackendHttpSettings", + "name": "appGatewayBackendHttpSettings", + "properties": { + "connectionDraining": { + "drainTimeoutInSec": 1, + "enabled": false + }, + "cookieBasedAffinity": "Disabled", + "pickHostNameFromBackendAddress": false, + "port": 80, + "protocol": "Http", + "provisioningState": "Succeeded", + "requestRoutingRules": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/requestRoutingRules/rule1", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "requestTimeout": 30 + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/backendHttpSettingsCollection" + } + ], + "backendSettingsCollection": [], + "frontendIPConfigurations": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/frontendIPConfigurations/appGatewayFrontendIP", + "name": "appGatewayFrontendIP", + "properties": { + "httpListeners": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/httpListeners/appGatewayHttpListener", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "privateIPAllocationMethod": "Dynamic", + "provisioningState": "Succeeded", + "publicIPAddress": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/publicIPAddresses/myAPPGWPublicIPxxxxxx", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/frontendIPConfigurations" + } + ], + "frontendPorts": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/frontendPorts/appGatewayFrontendPort", + "name": "appGatewayFrontendPort", + "properties": { + "httpListeners": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/httpListeners/appGatewayHttpListener", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "port": 80, + "provisioningState": "Succeeded" + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/frontendPorts" + } + ], + "gatewayIPConfigurations": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/gatewayIPConfigurations/appGatewayFrontendIP", + "name": "appGatewayFrontendIP", + "properties": { + "provisioningState": "Succeeded", + "subnet": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxxx/subnets/myAPPGWSNxxxxxx", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/gatewayIPConfigurations" + } + ], + "httpListeners": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/httpListeners/appGatewayHttpListener", + "name": "appGatewayHttpListener", + "properties": { + "frontendIPConfiguration": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/frontendIPConfigurations/appGatewayFrontendIP", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + }, + "frontendPort": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/frontendPorts/appGatewayFrontendPort", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + }, + "hostNames": [], + "protocol": "Http", + "provisioningState": "Succeeded", + "requestRoutingRules": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/requestRoutingRules/rule1", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "requireServerNameIndication": false + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/httpListeners" + } + ], + "listeners": [], + "loadDistributionPolicies": [], + "operationalState": "Running", + "privateEndpointConnections": [], + "privateLinkConfigurations": [], + "probes": [], + "provisioningState": "Succeeded", + "redirectConfigurations": [], + "requestRoutingRules": [ + { + "etag": "W/\"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\"", + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/requestRoutingRules/rule1", + "name": "rule1", + "properties": { + "backendAddressPool": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/backendAddressPools/appGatewayBackendPool", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + }, + "backendHttpSettings": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/backendHttpSettingsCollection/appGatewayBackendHttpSettings", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + }, + "httpListener": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxxxx/httpListeners/appGatewayHttpListener", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + }, + "priority": 1001, + "provisioningState": "Succeeded", + "ruleType": "Basic" + }, + "resourceGroup": "myVMSSResourceGroupxxxxxx", + "type": "Microsoft.Network/applicationGateways/requestRoutingRules" + } + ], + "resourceGuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx", + "rewriteRuleSets": [], + "routingRules": [], + "sku": { + "capacity": 2, + "family": "Generation_1", + "name": "Standard_v2", + "tier": "Standard_v2" + }, + "sslCertificates": [], + "sslProfiles": [], + "trustedClientCertificates": [], + "trustedRootCertificates": [], + "urlPathMaps": [] + } +} + ``` + + +# Create Virtual Machine Scale Set + +The below command creates a zone redundant Virtual Machine Scale Set (VMSS) within your resource group $MY_RESOURCE_GROUP_NAME. We integrate the Application Gateway that we created previous step. This command creates 2 Standard_DS2_v2 SKU Virtual Machines with public IP in subnet $MY_VM_SN_NAME. A ssh key will be created during the below step you may want to save the key if you need to login your VMs via ssh. + +```bash +az vmss create --name $MY_VMSS_NAME --resource-group $MY_RESOURCE_GROUP_NAME --image $MY_VM_IMAGE --admin-username $MY_USERNAME --generate-ssh-keys --public-ip-per-vm --orchestration-mode Uniform --instance-count 2 --zones 1 2 3 --vnet-name $MY_VNET_NAME --subnet $MY_VM_SN_NAME --vm-sku Standard_DS2_v2 --upgrade-policy-mode Automatic --app-gateway $MY_APPGW_NAME --backend-pool-name appGatewayBackendPool -o JSON + ``` + +Results: + + +```json +{ + "vmss": { + "doNotRunExtensionsOnOverprovisionedVMs": false, + "orchestrationMode": "Uniform", + "overprovision": true, + "platformFaultDomainCount": 1, + "provisioningState": "Succeeded", + "singlePlacementGroup": false, + "timeCreated": "20xx-xx-xxTxx:xx:xx.xxxxxx+00:00", + "uniqueId": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx", + "upgradePolicy": { + "mode": "Automatic", + "rollingUpgradePolicy": { + "maxBatchInstancePercent": 20, + "maxSurge": false, + "maxUnhealthyInstancePercent": 20, + "maxUnhealthyUpgradedInstancePercent": 20, + "pauseTimeBetweenBatches": "PT0S", + "rollbackFailedInstancesOnPolicyBreach": false + } + }, + "virtualMachineProfile": { + "networkProfile": { + "networkInterfaceConfigurations": [ + { + "name": "myvmsa53cNic", + "properties": { + "disableTcpStateTracking": false, + "dnsSettings": { + "dnsServers": [] + }, + "enableAcceleratedNetworking": false, + "enableIPForwarding": false, + "ipConfigurations": [ + { + "name": "myvmsa53cIPConfig", + "properties": { + "applicationGatewayBackendAddressPools": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGW7xxxxx/backendAddressPools/appGatewayBackendPool", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "privateIPAddressVersion": "IPv4", + "publicIPAddressConfiguration": { + "name": "instancepublicip", + "properties": { + "idleTimeoutInMinutes": 10, + "ipTags": [], + "publicIPAddressVersion": "IPv4" + } + }, + "subnet": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxx/subnets/myVMSN7xxxxx", + "resourceGroup": "myVMSSResourceGroupxxxxxxx" + } + } + } + ], + "primary": true + } + } + ] + }, + "osProfile": { + "adminUsername": "azureuser", + "allowExtensionOperations": true, + "computerNamePrefix": "myvmsa53c", + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "enableVMAgentPlatformUpdates": false, + "provisionVMAgent": true, + "ssh": { + "publicKeys": [ + { + "keyData": "ssh-rsa xxxxxxxx", + "path": "/home/azureuser/.ssh/authorized_keys" + } + ] + } + }, + "requireGuestProvisionSignal": true, + "secrets": [] + }, + "storageProfile": { + "diskControllerType": "SCSI", + "imageReference": { + "offer": "0001-com-ubuntu-server-jammy", + "publisher": "Canonical", + "sku": "22_04-lts-gen2", + "version": "latest" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "diskSizeGB": 30, + "managedDisk": { + "storageAccountType": "Premium_LRS" + }, + "osType": "Linux" + } + }, + "timeCreated": "20xx-xx-xxTxx:xx:xx.xxxxxx+00:00" + }, + "zoneBalance": false + } +} +``` + +### Install ngnix with VMSS extensions + +The below command uses VMSS extension to run custom script. For testing purposes, here we install ngnix and publish a page that shows the hostname of the Virtual Machine that your HTTP requests hits. We use this custom script for this pusposes : https://raw.githubusercontent.com/Azure-Samples/compute-automation-configurations/master/automate_nginx.sh + + +```bash +az vmss extension set --publisher Microsoft.Azure.Extensions --version 2.0 --name CustomScript --resource-group $MY_RESOURCE_GROUP_NAME --vmss-name $MY_VMSS_NAME --settings '{ "fileUris": ["https://raw.githubusercontent.com/Azure-Samples/compute-automation-configurations/master/automate_nginx.sh"], "commandToExecute": "./automate_nginx.sh" }' -o JSON +``` + +Results: + + +```json +{ + "additionalCapabilities": null, + "automaticRepairsPolicy": null, + "constrainedMaximumCapacity": null, + "doNotRunExtensionsOnOverprovisionedVMs": false, + "extendedLocation": null, + "hostGroup": null, + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/Microsoft.Compute/virtualMachineScaleSets/myVMSSxxxxx", + "identity": null, + "location": "eastus", + "name": "myVMSSxxxx", + "orchestrationMode": "Uniform", + "overprovision": true, + "plan": null, + "platformFaultDomainCount": 1, + "priorityMixPolicy": null, + "provisioningState": "Succeeded", + "proximityPlacementGroup": null, + "resourceGroup": "myVMSSResourceGroupxxxxx", + "scaleInPolicy": null, + "singlePlacementGroup": false, + "sku": { + "capacity": 2, + "name": "Standard_DS2_v2", + "tier": "Standard" + }, + "spotRestorePolicy": null, + "tags": {}, + "timeCreated": "20xx-xx-xxTxx:xx:xx.xxxxxx+00:00", + "type": "Microsoft.Compute/virtualMachineScaleSets", + "uniqueId": "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxx", + "upgradePolicy": { + "automaticOsUpgradePolicy": null, + "mode": "Automatic", + "rollingUpgradePolicy": { + "enableCrossZoneUpgrade": null, + "maxBatchInstancePercent": 20, + "maxSurge": false, + "maxUnhealthyInstancePercent": 20, + "maxUnhealthyUpgradedInstancePercent": 20, + "pauseTimeBetweenBatches": "PT0S", + "prioritizeUnhealthyInstances": null, + "rollbackFailedInstancesOnPolicyBreach": false + } + }, + "virtualMachineProfile": { + "applicationProfile": null, + "billingProfile": null, + "capacityReservation": null, + "diagnosticsProfile": null, + "evictionPolicy": null, + "extensionProfile": { + "extensions": [ + { + "autoUpgradeMinorVersion": true, + "enableAutomaticUpgrade": null, + "forceUpdateTag": null, + "id": null, + "name": "CustomScript", + "protectedSettings": null, + "protectedSettingsFromKeyVault": null, + "provisionAfterExtensions": null, + "provisioningState": null, + "publisher": "Microsoft.Azure.Extensions", + "settings": { + "commandToExecute": "./automate_nginx.sh", + "fileUris": [ + "https://raw.githubusercontent.com/Azure-Samples/compute-automation-configurations/master/automate_nginx.sh" + ] + }, + "suppressFailures": null, + "type": null, + "typeHandlerVersion": "2.0", + "typePropertiesType": "CustomScript" + } + ], + "extensionsTimeBudget": null + }, + "hardwareProfile": null, + "licenseType": null, + "networkProfile": { + "healthProbe": null, + "networkApiVersion": null, + "networkInterfaceConfigurations": [ + { + "deleteOption": null, + "disableTcpStateTracking": false, + "dnsSettings": { + "dnsServers": [] + }, + "enableAcceleratedNetworking": false, + "enableFpga": null, + "enableIpForwarding": false, + "ipConfigurations": [ + { + "applicationGatewayBackendAddressPools": [ + { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/Microsoft.Network/applicationGateways/myAPPGWxxxx/backendAddressPools/appGatewayBackendPool", + "resourceGroup": "myVMSSResourceGroupxxxxxx" + } + ], + "applicationSecurityGroups": null, + "loadBalancerBackendAddressPools": null, + "loadBalancerInboundNatPools": null, + "name": "myvmsdxxxIPConfig", + "primary": null, + "privateIpAddressVersion": "IPv4", + "publicIpAddressConfiguration": null, + "subnet": { + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/Microsoft.Network/virtualNetworks/myVNetxxxxx/subnets/myVMSNxxxxx", + "resourceGroup": "myVMSSResourceGroupaxxxxx" + } + } + ], + "name": "myvmsxxxxxx", + "networkSecurityGroup": null, + "primary": true + } + ] + }, + "osProfile": { + "adminPassword": null, + "adminUsername": "azureuser", + "allowExtensionOperations": true, + "computerNamePrefix": "myvmsdxxx", + "customData": null, + "linuxConfiguration": { + "disablePasswordAuthentication": true, + "enableVmAgentPlatformUpdates": false, + "patchSettings": null, + "provisionVmAgent": true, + "ssh": { + "publicKeys": [ + { + "keyData": "ssh-rsa xxxxxxxx", + "path": "/home/azureuser/.ssh/authorized_keys" + } + ] + } + }, + "requireGuestProvisionSignal": true, + "secrets": [], + "windowsConfiguration": null + }, + "priority": null, + "scheduledEventsProfile": null, + "securityPostureReference": null, + "securityProfile": null, + "serviceArtifactReference": null, + "storageProfile": { + "dataDisks": null, + "diskControllerType": "SCSI", + "imageReference": { + "communityGalleryImageId": null, + "exactVersion": null, + "id": null, + "offer": "0001-com-ubuntu-server-jammy", + "publisher": "Canonical", + "sharedGalleryImageId": null, + "sku": "22_04-lts-gen2", + "version": "latest" + }, + "osDisk": { + "caching": "ReadWrite", + "createOption": "FromImage", + "deleteOption": null, + "diffDiskSettings": null, + "diskSizeGb": 30, + "image": null, + "managedDisk": { + "diskEncryptionSet": null, + "securityProfile": null, + "storageAccountType": "Premium_LRS" + }, + "name": null, + "osType": "Linux", + "vhdContainers": null, + "writeAcceleratorEnabled": null + } + }, + "userData": null + }, + "zoneBalance": false, + "zones": [ + "1", + "2", + "3" + ] +} +``` + + +# Define an autoscale profile + +To enable autoscale on a scale set, you first define an autoscale profile. This profile defines the default, minimum, and maximum scale set capacity. These limits let you control cost by not continually creating VM instances, and balance acceptable performance with a minimum number of instances that remain in a scale-in event. +The following example sets the default, and minimum, capacity of 2 VM instances, and a maximum of 10: + +```bash +az monitor autoscale create --resource-group $MY_RESOURCE_GROUP_NAME --resource $MY_VMSS_NAME --resource-type Microsoft.Compute/virtualMachineScaleSets --name autoscale --min-count 2 --max-count 10 --count 2 +``` + + +Results: + + +```json +{ + "enabled": true, + "id": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxx/providers/microsoft.insights/autoscalesettings/autoscale", + "location": "eastus", + "name": "autoscale", + "namePropertiesName": "autoscale", + "notifications": [ + { + "email": { + "customEmails": [], + "sendToSubscriptionAdministrator": false, + "sendToSubscriptionCoAdministrators": false + }, + "webhooks": [] + } + ], + "predictiveAutoscalePolicy": { + "scaleLookAheadTime": null, + "scaleMode": "Disabled" + }, + "profiles": [ + { + "capacity": { + "default": "2", + "maximum": "10", + "minimum": "2" + }, + "fixedDate": null, + "name": "default", + "recurrence": null, + "rules": [] + } + ], + "resourceGroup": "myVMSSResourceGroupxxxxx", + "systemData": null, + "tags": {}, + "targetResourceLocation": null, + "targetResourceUri": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Compute/virtualMachineScaleSets/myVMSSxxxxxx", + "type": "Microsoft.Insights/autoscaleSettings" +} +``` + +# Create a rule to autoscale out + +The Following command creates a rule that increases the number of VM instances in a scale set when the average CPU load is greater than 70% over a 5-minute period. When the rule triggers, the number of VM instances is increased by three. + +```bash +az monitor autoscale rule create --resource-group $MY_RESOURCE_GROUP_NAME --autoscale-name autoscale --condition "Percentage CPU > 70 avg 5m" --scale out 3 +``` + +Results: + + +```json +{ + "metricTrigger": { + "dimensions": [], + "dividePerInstance": null, + "metricName": "Percentage CPU", + "metricNamespace": null, + "metricResourceLocation": null, + "metricResourceUri": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Compute/virtualMachineScaleSets/myVMSSxxxxxx", + "operator": "GreaterThan", + "statistic": "Average", + "threshold": "70", + "timeAggregation": "Average", + "timeGrain": "PT1M", + "timeWindow": "PT5M" + }, + "scaleAction": { + "cooldown": "PT5M", + "direction": "Increase", + "type": "ChangeCount", + "value": "3" + } +} +``` + +# Create a rule to autoscale in + +Create another rule with az monitor autoscale rule create that decreases the number of VM instances in a scale set when the average CPU load then drops below 30% over a 5-minute period. The following example defines the rule to scale in the number of VM instances by one. + +```bash +az monitor autoscale rule create --resource-group $MY_RESOURCE_GROUP_NAME --autoscale-name autoscale --condition "Percentage CPU < 30 avg 5m" --scale in 1 +``` + +Results: + + +```json +{ + "metricTrigger": { + "dimensions": [], + "dividePerInstance": null, + "metricName": "Percentage CPU", + "metricNamespace": null, + "metricResourceLocation": null, + "metricResourceUri": "/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/myVMSSResourceGroupxxxxxx/providers/Microsoft.Compute/virtualMachineScaleSets/myVMSSxxxxxx", + "operator": "LessThan", + "statistic": "Average", + "threshold": "30", + "timeAggregation": "Average", + "timeGrain": "PT1M", + "timeWindow": "PT5M" + }, + "scaleAction": { + "cooldown": "PT5M", + "direction": "Decrease", + "type": "ChangeCount", + "value": "1" + } +} +``` + + +### Test the page + +The below command shows you the public IP of your Application Gateway. You can paste the IP adress to a browser page for testing. + +```bash +az network public-ip show --resource-group $MY_RESOURCE_GROUP_NAME --name $MY_APPGW_PUBLIC_IP_NAME --query [ipAddress] --output tsv +``` + + + +# References + +* [VMSS Documentation](https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/overview) +* [VMSS AutoScale](https://learn.microsoft.com/en-us/azure/virtual-machine-scale-sets/tutorial-autoscale-cli?tabs=Ubuntu) + diff --git a/scripts/install_from_release.sh b/scripts/install_from_release.sh index 3b8ac8ad..cf261837 100644 --- a/scripts/install_from_release.sh +++ b/scripts/install_from_release.sh @@ -1,9 +1,25 @@ +# Script to install scenarios file. Pass in language code parameter for a particular language, such as it-it for Italian. set -e +# Define the language parameter +LANG="$1" +SCENARIOS="" + +# Map the language parameter to the corresponding scenarios file +# If no parameter, download the scenarios from IE +if [ "$LANG" = "" ]; then + SCENARIOS='https://github.com/Azure/InnovationEngine/releases/download/latest/scenarios.zip' +# Otherwise, download the scenarios from Microsoft Docs in the appropriate langauge +elif [ "$LANG" = "en-us" ]; then + SCENARIOS='https://github.com/MicrosoftDocs/executable-docs/releases/download/v1.0.1/scenarios.zip' +else + SCENARIOS="https://github.com/MicrosoftDocs/executable-docs/releases/download/v1.0.1/$LANG-scenarios.zip" +fi + # Download the binary from the latest echo "Installing IE & scenarios from the latest release..." wget -q -O ie https://github.com/Azure/InnovationEngine/releases/download/latest/ie > /dev/null -wget -q -O scenarios.zip https://github.com/Azure/InnovationEngine/releases/download/latest/scenarios.zip > /dev/null +wget -q -O scenarios.zip "$SCENARIOS" > /dev/null # Setup permissions & move to the local bin chmod +x ie > /dev/null diff --git a/upstream-scenarios b/upstream-scenarios new file mode 160000 index 00000000..8c715328 --- /dev/null +++ b/upstream-scenarios @@ -0,0 +1 @@ +Subproject commit 8c7153283e160569577589d923449fd940ea0ef1