如何将 SSL 添加到 Azure 容器实例应用程序?

How to add SSL to Azure Container Instance App?

正如标题所说,我需要为托管在 Azure 容器实例中的应用程序设置 SSL,但是,我不太确定需要从哪里开始。

我在地址 http://myApp.northamerica.azurecontainer.io 有一个通过 Azure 容器实例托管的容器化应用程序。此地址被 http://api.myApp.com 处的 'official' 地址屏蔽。

为什么我不能将 SSL 添加到表面域 @ http://api.myApp.com,重定向到真实域 @ http://myApp.northamerica.azurecontainer.io?还是我需要为两个域都添加 SSL?

此外,如果我需要使用 SSL 保护两个域,是否需要为每个域获取单独的证书?

Azure 提供 SSL 证书服务,但我只需要知道最佳途径。谢谢

据我所知,目前仍然没有内置支持在 Azure 容器实例上启用 SSL,请参阅

但是,您可以有多种选择来为您的 ACI 应用程序启用 SSL 连接。

如果您在 Azure virtual network 中部署容器组,您可以考虑其他选项来为后端容器实例启用 SSL 端点,包括:

标准 SSL 证书映射到一个唯一的域名,因此您需要为每个域单独的证书。

您可以开始将 Nginx 设置为 sidecar 容器中的 SSL 提供程序,并且您需要域 api.myApp.com 的 SSL 证书。如果你想单独安全访问域 myApp.northamerica.azurecontainer.io,你可以在 Nginx 配置文件中配置额外的服务器块。参考configuring HTTPS server in Nginx.

server {
    listen              443 ssl;
    server_name         www.example.com;
    ssl_certificate     www.example.com.crt;
    ssl_certificate_key www.example.com.key;
    ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ...
}

在经历了围绕这个问题的痛苦研究之后,我们终于想出了如何使用 Caddy Docker 图像作为 sidecar 将 SSL 添加到容器实例。 Caddy 使自动续订和验证所有权以颁发 SSL 变得容易。

我们写了一个博客 post 来帮助其他有同样问题的人。希望这可以帮助。

https://www.antstack.io/blog/how-to-enable-tls-for-hasura-graphql-engine-in-azure-caddy/

在这里写了一篇文章:https://dev.to/kedzior_io/net-core-api-in-azure-container-instances-secured-with-https-using-caddy2-32jm

这里是 copy/paste:

  1. 创建您的 Web API 项目我们称它为 MyApp.Image.Api 假设它依赖于另一个项目 MyApp.Core
  2. Dockerfile 添加到您的 MyApp.Image.Api 项目
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
ARG ENVIRONMENT

ENV ASPNETCORE_URLS http://*:5000
ENV ENVIRONMENT_NAME "${ENVIRONMENT}"

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src

# copy project dependencies 
COPY ["src/MyApp.Core/MyApp.Core.csproj", "src/MyApp.Core/"]
COPY ["src/MyApp.Image.Api/MyApp.Image.Api.csproj", "src/MyApp.Image.Api/"]

RUN dotnet restore "src/MyApp.Image.Api/MyApp.Image.Api.csproj"
COPY . .

RUN dotnet build "src/MyApp.Image.Api/MyApp.Image.Api.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "src/MyApp.Image.Api/MyApp.Image.Api.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "MyApp.Image.Api.dll"]
  1. 接下来在同一项目中创建名为 Proxy 的目录,您将需要 4 个文件:

  2. Caddyfile.development - 这将在 运行 在本地使用自签名证书

    时创建代理
{
    email artur@isready.io
}

https://localhost {
    reverse_proxy localhost:5000
}

  1. Caddyfile.staging
{
    email artur@isready.io
}

https://myapp-image-api-staging.eastus.azurecontainer.io {
    reverse_proxy localhost:5000
}

  1. Caddyfile.production
{
    email artur@isready.io
}

https://myapp-image-api.eastus.azurecontainer.io {
    reverse_proxy localhost:5000
}

  1. Dockerfile - 我们将在执行之前设置环境
FROM caddy:latest
ARG ENVIRONMENT

COPY "src/MyApp.Image.Api/Proxy/Caddyfile.${ENVIRONMENT}" /etc/caddy/Caddyfile

*如果您没有多个环境,您知道要跳过什么:-)

  1. 现在您需要将docker-compose.yml添加到项目
version: '3.4'

services:

  proxy:
    image: myapp-image-api-proxy:${CONTAINER_VERSION}-${ENVIRONMENT}
    build:
      context: ../../
      args:
        ENVIRONMENT: ${ENVIRONMENT}
      dockerfile:  src/MyApp.Image.Api/Proxy/Dockerfile
  api:
    image:  myapp-image-api:${CONTAINER_VERSION}-${ENVIRONMENT}
    depends_on:
      - proxy
    build:
        context: ../../
        args:
          ENVIRONMENT: ${ENVIRONMENT}
        dockerfile: src/MyApp.Image.Api/Dockerfile

你应该得到这个结构:

  1. 现在您可以开始了!我使用那个 powershell 脚本来构建我的图像。如果你只是在本地构建 运行 这个:
# set azure environment
$env:ENVIRONMENT = 'development'
$env:CONTAINER_VERSION = 'latest'

# builds images
docker-compose build

如果您想构建映像并将其推送到 Azure 容器注册表,请执行以下命令,但在确保您已创建注册表之前:

az provider register --namespace Microsoft.ContainerInstance`
az acr create --resource-group EastUS--name myapp --sku Basic // *enable admin user in azure portal once Azure Container Registry is created
# logs to azure
az login

# set azure environment
$env:ENVIRONMENT = 'staging'
$env:CONTAINER_VERSION = 'latest'

# builds images
docker-compose build

# logs to Azure Container Registry
az acr login --name myapp

# tag images
docker tag myapp-image-api-proxy:latest-staging myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker tag myapp-image-api:latest-staging myapp.azurecr.io/myapp-image-api:latest-staging

# push images
docker push myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker push myapp.azurecr.io/myapp-image-api:latest-staging

# clean up
docker rmi myapp-image-api-proxy:latest-staging
docker rmi myapp-image-api:latest-staging
docker rmi myapp.azurecr.io/myapp-image-api-proxy:latest-staging
docker rmi myapp.azurecr.io/myapp-image-api:latest-staging

##酷!你准备好启动它了!##

  1. 您将需要两个包:

Microsoft.Azure.Management.ContainerInstance.Fluent Microsoft.Azure.Management.Fluent

  1. 让我们从使用 C# Fluent 连接到 Azure 开始API
private IAzure GetAzureContext()
{
    Log.Information("[Container] Getting Service Principal");
    var creds = new AzureCredentialsFactory().FromServicePrincipal(
        _config["AppSettings:ImageApi:ServicePrincipalClientId"],
        _config["AppSettings:ImageApi:ServicePrincipalSecretId"],
        _config["AppSettings:ImageApi:ServicePrincipalTenat"],
        AzureEnvironment.AzureGlobalCloud);

    Log.Information("[Container] Getting subscribtion");
    var azure = Microsoft.Azure.Management.Fluent.Azure.Authenticate(creds).WithSubscription(_config["AppSettings:ImageApi:SubscribtionId"]);

    return azure;
}

您从哪里获得这些服务主体 ID?

在 Azure CLI 中执行:

az ad sp create-for-rbac --name myapp-containers --sdk-auth > my.azureauth

该文件将包含所需的所有服务主体数据。

  1. 现在主要部分:
private string CreateContainer()
{
    Log.Information("[Container] Authenticating with Azure");

    var azureContext = GetAzureContext();`
    
    // Get azure container registry for my resource group named 'EastUS' and registry 'myapp'
    var azureRegistry = azureContext.ContainerRegistries.GetByResourceGroup(
        _config["AppSettings:ImageApi:ResourceGroupName"],
        _config["AppSettings:ImageApi:ContainerRegistryName"]);

    var acrCredentials = azureRegistry.GetCredentials();

    // Get container group for my resource group named 'EastUS' and container group i.e 'myapp-image-api-staging'
    var containerGroup = azureContext.ContainerGroups.GetByResourceGroup(
        _config["AppSettings:ImageApi:ResourceGroupName"],
        _config["AppSettings:ImageApi:ContainerGroupName"]
        );

    if (containerGroup is null)
    {
        Log.Information("[Container] Creating with fluent API");

        // ContainerGroupName = 'myapp-image-api-staging'
        // ResourceGroupName = 'EastUS'
        // VolumeName = 'image-api-volume'
        // FileShare = 'containers' 
        //      # yes you need to have storage account with file share, we need it so that proxy (caddy2) can store Let's Encrypt certs in there
        //      az storage share create --name myapp-staging-containers-share --account-name myappstaging
        // 
        // StorageAccountName = 'myappstaging'
        // StorageAccountKey = 'well-that-key-here'
        // ProxyContainerName = 'image-api-proxy'
        // ProxyImageName = 'myapp.azurecr.io/myapp-image-api-proxy:latest-staging'
        // VolumeMountPath = '/data/'
        // ApiContainerName 'image-api'
        // ApiImageName 'myapp.azurecr.io/myapp-image-api:latest-staging'

        containerGroup = azureContext.ContainerGroups.Define(_config["AppSettings:ImageApi:ContainerGroupName"])
                .WithRegion(Region.USEast)
                .WithExistingResourceGroup(_config["AppSettings:ImageApi:ResourceGroupName"])
                .WithLinux()
                .WithPrivateImageRegistry(azureRegistry.LoginServerUrl, acrCredentials.Username, acrCredentials.AccessKeys[AccessKeyType.Primary])
                .DefineVolume(_config["AppSettings:ImageApi:VolumeName"])
                    .WithExistingReadWriteAzureFileShare(_config["AppSettings:ImageApi:FileShare"])
                    .WithStorageAccountName(_config["AppSettings:ImageApi:StorageAccountName"])
                    .WithStorageAccountKey(_config["AppSettings:ImageApi:StorageAccountKey"])
                    .Attach()
                .DefineContainerInstance(_config["AppSettings:ImageApi:ProxyContainerName"])
                    .WithImage(_config["AppSettings:ImageApi:ProxyImageName"])
                    .WithExternalTcpPort(443)
                    .WithExternalTcpPort(80)
                    .WithCpuCoreCount(1.0)
                    .WithMemorySizeInGB(0.5)
                    .WithVolumeMountSetting(
                        _config["AppSettings:ImageApi:VolumeName"],
                        _config["AppSettings:ImageApi:VolumeMountPath"])
                    .Attach()
                .DefineContainerInstance(_config["AppSettings:ImageApi:ApiContainerName"])
                    .WithImage(_config["AppSettings:ImageApi:ApiImageName"])
                    .WithExternalTcpPort(5000)
                    .WithCpuCoreCount(1.0)
                    .WithMemorySizeInGB(3.5)
                    .Attach()
                .WithDnsPrefix(_config["AppSettings:ImageApi:ContainerGroupName"])
                .WithRestartPolicy(ContainerGroupRestartPolicy.Always)
                .Create();
    }

    Log.Information("[Container] created {fqdn}", containerGroup.Fqdn);
    return containerGroup.Fqdn;
}

容器 运行ning!

它们完全可用大约需要 2:50 分钟。

点击上面代码中给出的 fqdn 会得到我的 swagger 索引页: