如何将 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 连接。
- Use SSL provider in a sidecar container---such as Ngnix or Caddy
如果您在 Azure virtual network 中部署容器组,您可以考虑其他选项来为后端容器实例启用 SSL 端点,包括:
- Azure Functions Proxies
- Azure API Management
- Azure Application Gateway - see a sample deployment template.
标准 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/
这里是 copy/paste:
- 创建您的 Web API 项目我们称它为
MyApp.Image.Api
假设它依赖于另一个项目 MyApp.Core
- 将
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"]
接下来在同一项目中创建名为 Proxy
的目录,您将需要 4 个文件:
Caddyfile.development
- 这将在 运行 在本地使用自签名证书
时创建代理
{
email artur@isready.io
}
https://localhost {
reverse_proxy localhost:5000
}
Caddyfile.staging
{
email artur@isready.io
}
https://myapp-image-api-staging.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Caddyfile.production
{
email artur@isready.io
}
https://myapp-image-api.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Dockerfile
- 我们将在执行之前设置环境
FROM caddy:latest
ARG ENVIRONMENT
COPY "src/MyApp.Image.Api/Proxy/Caddyfile.${ENVIRONMENT}" /etc/caddy/Caddyfile
*如果您没有多个环境,您知道要跳过什么:-)
- 现在您需要将
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
你应该得到这个结构:
- 现在您可以开始了!我使用那个 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
##酷!你准备好启动它了!##
- 您将需要两个包:
Microsoft.Azure.Management.ContainerInstance.Fluent
Microsoft.Azure.Management.Fluent
- 让我们从使用 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
该文件将包含所需的所有服务主体数据。
- 现在主要部分:
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 索引页:
正如标题所说,我需要为托管在 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 连接。
- Use SSL provider in a sidecar container---such as Ngnix or Caddy
如果您在 Azure virtual network 中部署容器组,您可以考虑其他选项来为后端容器实例启用 SSL 端点,包括:
- Azure Functions Proxies
- Azure API Management
- Azure Application Gateway - see a sample deployment template.
标准 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/
这里是 copy/paste:
- 创建您的 Web API 项目我们称它为
MyApp.Image.Api
假设它依赖于另一个项目MyApp.Core
- 将
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"]
接下来在同一项目中创建名为
Proxy
的目录,您将需要 4 个文件:
时创建代理Caddyfile.development
- 这将在 运行 在本地使用自签名证书
{
email artur@isready.io
}
https://localhost {
reverse_proxy localhost:5000
}
Caddyfile.staging
{
email artur@isready.io
}
https://myapp-image-api-staging.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Caddyfile.production
{
email artur@isready.io
}
https://myapp-image-api.eastus.azurecontainer.io {
reverse_proxy localhost:5000
}
Dockerfile
- 我们将在执行之前设置环境
FROM caddy:latest
ARG ENVIRONMENT
COPY "src/MyApp.Image.Api/Proxy/Caddyfile.${ENVIRONMENT}" /etc/caddy/Caddyfile
*如果您没有多个环境,您知道要跳过什么:-)
- 现在您需要将
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
你应该得到这个结构:
- 现在您可以开始了!我使用那个 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
##酷!你准备好启动它了!##
- 您将需要两个包:
Microsoft.Azure.Management.ContainerInstance.Fluent
Microsoft.Azure.Management.Fluent
- 让我们从使用 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
该文件将包含所需的所有服务主体数据。
- 现在主要部分:
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 索引页: