0

I need to make a setup where I can read and write to an external sql db from a python script residing in a azure container instance. I order to make this work I need to assign a static ip to the container.

As I cannot associate a container instance with a dedicated ip I have had to make a setup that use the following resources: a vnet, a gateway and a public IP.

I have partially borrowed the setup from https://godatadriven.com/blog/azure-container-instance-example/ where the setup is drawn as follows:

enter image description here

I have made a dev-ops build and release pipeline. I use an ARM template to create the release (the resources of the template are below):

  "resources": [
    {
      "type": "Microsoft.Network/virtualNetworks",
      "name": "[parameters('vnetName')]",
      "apiVersion": "2019-07-01",
      "location": "[parameters('location')]",
      "properties": {
        "addressSpace": {
          "addressPrefixes": [
            "[parameters('vnetAddressPrefix')]"
          ]
        },
        "subnets": [
          {
            "name": "[parameters('subnet2Name')]",
            "properties": {
              "addressPrefix": "[parameters('subnet2AddressPrefix')]",
              "privateEndpointNetworkPolicies": "Enabled",
              "privateLinkServiceNetworkPolicies": "Enabled"
            }
          },
          {
            "name": "[parameters('subnetName')]",
            "properties": {
              "addressPrefix": "[parameters('subnetAddressPrefix')]",
              "delegations": [
                {
                  "name": "DelegationService",
                  "properties": {
                    "serviceName": "Microsoft.ContainerInstance/containerGroups"
                  }
                }
              ],
              "privateEndpointNetworkPolicies": "Enabled",
              "privateLinkServiceNetworkPolicies": "Enabled"
            }
          }
        ]
      }
    },
    {
      "apiVersion": "2018-07-01",
      "type": "Microsoft.Network/publicIPAddresses",
      "name": "[variables('publicIPAddressName')]",
      "location": "[parameters('location')]",
      "sku": {
        "name": "Standard",
        "tier": "Regional"
      },
      "properties": {
        "publicIPAddressVersion": "IPv4",
        "publicIPAllocationMethod": "Static",
        "idleTimeoutInMinutes": 4,
         "dnsSettings": {
          "domainNameLabel": "[parameters('dnsName')]"
        }
      }
    },
    {
      "apiVersion": "2019-08-01",
      "name": "[variables('applicationGatewayName')]",
      "type": "Microsoft.Network/applicationGateways",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/virtualNetworks/', parameters('vnetName'))]",
        "[resourceId('Microsoft.Network/publicIPAddresses/', variables('publicIPAddressName'))]",
        "[resourceId('Microsoft.ContainerInstance/containerGroups/', parameters('containerInstanceName'))]"
      ],
      "properties": {
        "sku": {
          "name": "[parameters('skuName')]",
          "tier": "Standard_v2",
          "capacity": "[variables('capacity')]"
        },
        "gatewayIPConfigurations": [
          {
            "name": "appGatewayIpConfig",
            "properties": {
              "subnet": {
                "id": "[variables('subnetRef')]"
              }
            }
          }
        ],
        "frontendIPConfigurations": [
          {
            "name": "appGatewayFrontendIP",
            "properties": {
              "privateIPAllocationMethod": "Dynamic",
              "PublicIPAddress": {
                "id": "[variables('publicIPRef')]"
              }
            }
          }
        ],
        "frontendPorts": [
          {
            "name": "appGatewayFrontendPort",
            "properties": {
              "Port": 80
            }
          }
        ],
        "backendAddressPools": [
          {
            "name": "appGatewayBackendPool",
            "properties": {
              "backendAddresses": [
                {
                  "IpAddress": "[parameters('backendIP')]"
                }
              ]
            }
          }
        ],
        "backendHttpSettingsCollection": [
          {
            "name": "appGatewayBackendHttpSettings",
            "properties": {
              "Port": 80,
              "Protocol": "Http",
              "CookieBasedAffinity": "Disabled"
            }
          }
        ],
        "httpListeners": [
          {
            "name": "appGatewayHttpListener",
            "properties": {
              "FrontendIPConfiguration": {
                "Id": "[resourceId('Microsoft.Network/applicationGateways/frontendIPConfigurations', variables('applicationGatewayName'), 'appGatewayFrontendIP')]"
              },
              "FrontendPort": {
                "Id": "[resourceId('Microsoft.Network/applicationGateways/frontendPorts', variables('applicationGatewayName'), 'appGatewayFrontendPort')]"
              },
              "Protocol": "Http",
              "SslCertificate": null
            }
          }
        ],
        "requestRoutingRules": [
          {
            "Name": "rule1",
            "properties": {
              "RuleType": "Basic",
              "httpListener": {
                "id": "[resourceId('Microsoft.Network/applicationGateways/httpListeners', variables('applicationGatewayName'), 'appGatewayHttpListener')]"
              },
              "backendAddressPool": {
                "id": "[resourceId('Microsoft.Network/applicationGateways/backendAddressPools', variables('applicationGatewayName'), 'appGatewayBackendPool')]"
              },
              "backendHttpSettings": {
                "id": "[resourceId('Microsoft.Network/applicationGateways/backendHttpSettingsCollection', variables('applicationGatewayName'), 'appGatewayBackendHttpSettings')]"
              }
            }
          }
        ]
      }
    },
    {
      "name": "[parameters('networkProfileName')]",
      "type": "Microsoft.Network/networkProfiles",
      "apiVersion": "2018-07-01",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/virtualNetworks', parameters('vnetName'))]"
      ],
      "properties": {
        "containerNetworkInterfaceConfigurations": [
          {
            "name": "[variables('interfaceConfigName')]",
            "properties": {
              "ipConfigurations": [
                {
                  "name": "[variables('interfaceIpConfig')]",
                  "properties": {
                    "subnet": {
                      "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('vnetName'), parameters('subnetName'))]"
                    }
                  }
                }
              ]
            }
          }
        ]
      }
    },
    {
      "name": "[parameters('containerInstanceName')]",
      "type": "Microsoft.ContainerInstance/containerGroups",
      "apiVersion": "2018-10-01",
      "location": "[parameters('location')]",
      "dependsOn": [
        "[resourceId('Microsoft.Network/networkProfiles', parameters('networkProfileName'))]"
      ],
      "properties": {
        "containers": [
          {
            "name": "[parameters('containerName')]",
            "properties": {
              "image": "[parameters('registryImageUri')]",
              "ports": [{
                "port": "[variables('port')]"
              }],
              "resources": {
                "requests": {
                  "cpu": "[variables('cpuCores')]",
                  "memoryInGb": "[variables('memoryInGb')]"
                }
              }
            }
          }
        ],
        "imageRegistryCredentials": [
          {
            "server": "[parameters('registryLoginServer')]",
            "username": "[parameters('registryUserName')]",
            "password": "[parameters('registryPassword')]"
          }
        ],
        "diagnostics": {
          "logAnalytics": {
          "workspaceId": "[parameters('LogAnalyticsID')]",
          "workspaceKey": "[parameters('LogAnalyticsKEY')]"
         }
        },
        "networkProfile": {
          "Id": "[resourceId('Microsoft.Network/networkProfiles', parameters('networkProfileName'))]"
        },
        "osType": "Linux",
        "ipAddress": {
            "ports": [{
                "protocol": "tcp",
                "port": 80
            }],
            "type": "private",
            "ip": "[parameters('backendIP')]"
        },
        "restartPolicy": "[parameters('restartPolicy')]"
      }
    }
  ]

The release works, but when I run I try to run the container instance, it use a different ip each time.

What am I doing wrong?

Martin Petri Bagger
  • 2,187
  • 4
  • 17
  • 20
  • which IP are you referring that is changing ? – djsly May 26 '20 at 13:03
  • also, is your "external sql db" in Azure using their SaaS offering ? – djsly May 26 '20 at 13:04
  • Also, are you rerunning this whole ARM template every time you run your pipeline ? – djsly May 26 '20 at 13:06
  • @djsly , the ip which is changing is the externally facing ip. I connect to the sql db using odbc (pyodbc) in the script within the container, and when I run the container I get an error stating "Client with IP address 'xx.xx.xxx.xxx' is not allowed to access the server.". I have been running the ARM template multiple times, but the ip changes when I try to start my container instance (without rerunning the pipeline). – Martin Petri Bagger May 26 '20 at 21:37
  • Are you using azure sql service ? – djsly May 26 '20 at 22:29
  • The “external sql db” is SaaS (Azure SQL DB). – Martin Petri Bagger May 27 '20 at 06:10
  • thanks Martin, I provided you with a possible solution leveraging private VNET – djsly May 27 '20 at 12:51

2 Answers2

1

From the things you did, I think you misunderstanding the network of the Azure Container Instance. The Public or the Private type for the ACI is only available for the inbound traffic, not for the outbound. Even when you use the private type, the instance also can access the Internet without any other resource, but in this type, you cannot access it from the Internet.

Unfortunately, when you use the public type, the public IP address for the inbound and outbound may be even no the same. And for Azure Container Instance, we cannot control the IP address which we can use. So when you want to use a static public IP address to access the SQL DB, the Azure Container Instance is not suitable, I would recommend the VM, it's more controllable and appropriate.

Charles Xu
  • 29,862
  • 2
  • 22
  • 39
1

Since you are using an Azure provided SQL, I would recommend to leverage the private VNET offering that Azure provides.

you should look at configuring your ACI with a private subnet https://learn.microsoft.com/en-us/azure/container-instances/container-instances-vnet

and also setup a vnet rule for your SQL server

https://learn.microsoft.com/en-us/azure/sql-database/sql-database-vnet-service-endpoint-rule-overview

Virtual network rules are one firewall security feature that controls whether the database server for your single databases and elastic pool in Azure SQL Database or for your databases in Azure Synapse Analytics accepts communications that are sent from particular subnets in virtual networks.

it's important that you enable the SQL service endpoint for SQL on the ACI subnet as well.

This will avoid you having to manage outbound IP whitelisting in your SQL firewall.

djsly
  • 1,522
  • 11
  • 13