Docker Compose 部署 ASP.NET Core

引言 Dockerfile 构建 ASP.NET Core 在添加Docker支持之前,先看本人仓库目录结构: 注意:下方目录结构为本人已经添加完 Docker支持之后的,所以有 Dockerfile文件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
│  .dockerignore			# .dockerignore 放在仓库根目录
│ .gitattributes
│ .gitignore
│ CHANGELOG.md
│ deploy-docs.sh
│ LICENSE
│ package-lock.json
│ package.json
│ README.md
│ SimCaptcha.sln # 解决方案文件放在仓库根目录

├─.github
│ └─workflows

├─docs
│ │ README.md
│ │
│ ├─...

├─examples # 示例源代码单独放到 examples 文件夹
│ ├─AspNetCoreClient
│ │ │ AspNetCoreClient.csproj
│ │ │ ...
│ │ │
│ │ ├─...
│ │
│ ├─AspNetCoreService
│ │ │ AspNetCoreService.csproj
│ │ │ ...
│ │ │
│ │ ├─...
│ │
│ └─EasyAspNetCoreService
│ │ Dockerfile # Dockerfile 放在 Project 根目录
│ │ EasyAspNetCoreService.csproj
│ │ ...
│ │
│ ├─...

├─node_modules
│ ├─...

└─src # 所有主要源代码放到 src 文件夹
├─SimCaptcha
│ │ SimCaptcha.csproj
│ │ SimCaptcha.nuspec
│ │ ...
│ │
│ ├─...

└─SimCaptcha.AspNetCore
│ SimCaptcha.AspNetCore.csproj
│ ...

├─...
补充: 生成此路径树使用以下命令(Powershell):
1
tree /f > tree.txt
/f 表示显示每个目录中的文件名 这里不加 /f 也行,加了 /f ,因为有node_modules会导致花费很多时间。

使用 Visual Studio 2019 为 Project 添加 Docker 支持

右键单击目标 Project,点击 Add 选择 Linux,确定完成,生成 Dockerfile如下:
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging.

FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
WORKDIR /src
COPY ["examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj", "examples/EasyAspNetCoreService/"]
COPY ["src/SimCaptcha.AspNetCore/SimCaptcha.AspNetCore.csproj", "src/SimCaptcha.AspNetCore/"]
COPY ["src/SimCaptcha/SimCaptcha.csproj", "src/SimCaptcha/"]
RUN dotnet restore "examples/EasyAspNetCoreService/EasyAspNetCoreService.csproj"
COPY . .
WORKDIR "/src/examples/EasyAspNetCoreService"
RUN dotnet build "EasyAspNetCoreService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "EasyAspNetCoreService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "EasyAspNetCoreService.dll"]
默认此 Dockerfile 就会生成在此Project根目录 注意: 很多同类文章认为 Visual Studio 生成的 Dockerfile 其中的路径不正确,其实不然,只是需要对构建命令做修改, 而不是通常的 docker build -t yiyungent/simcaptcha . ,也不是在此 Project 执行命令, 你应当在根目录执行命令,这样镜像构建上下文(Context)才能复制所有依赖的Project,同时通过指定Dockerfile文件路径的方式构建镜像 注意: 当前由于环境变量的影响,只会监听 80 端口, 郁闷,由于 有 EXPOSE 443 导致误导以为监听了443,结果没有,下面:
1
ENTRYPOINT ["dotnet", "EasyAspNetCoreService.dll",  "--urls", "http://*:80;https://*:443"]
参考:ASP.Net Core 中设置 urls 的方式 · 语雀 注意: WORKDIR /src 与 仓库文件夹 src 意义不同。 补充: 可以看到,Visual Studio 生成的Dockerfile 中,会自动解析Project依赖关系,从而判断哪些需要复制,但无法解析.csproj中的if方法解析的依赖,所以它都会算作需要依赖,如下:
xxx.csproj
1
2
3
4
5
6
7
<!-- 方便开发debug,与发布到nuget -->
<ItemGroup Condition="'$(Configuration)' == 'Release'">
<PackageReference Include="SimCaptcha.AspNetCore" Version="0.2.0" />
</ItemGroup>
<ItemGroup Condition="'$(Configuration)' == 'Debug'">
<ProjectReference Include="..\..\src\SimCaptcha.AspNetCore\SimCaptcha.AspNetCore.csproj" />
</ItemGroup>
上面可看出,实际Release时,不需要对 SimCaptcha.AspNetCore 对Project依赖,但Dockerfile中还是生成了。 当你添加Docker支持的Project有Project依赖时,你应保证,所有Project依赖都被复制,并保持相对路径不变。 补充: 上面的这种构建方式名为多阶段构建。

构建镜像

注意:此命令在 仓库根目录执行(即解决方案同级目录)
1
docker build -t yiyungent/simcaptcha -f examples/EasyAspNetCoreService/Dockerfile .
补充: 其实关键在于构建上下文,因此,如果在 Project 根目录执行构建的话,下方命令也可以: 记住,默认 -f 指定Dockerfile位置,为 构建上下文同级目录Dockerfile
1
docker build -t yiyungent/simcaptcha -f Dockerfile ../..
其实,在 Visual Stuido 中添加 Docker 支持时,你会发现会修改 xxx.csproj,如下:
xxx.csproj
1
2
3
4
5
6
7
8
9
10
11
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<AssemblyName>EasyAspNetCoreService</AssemblyName>
<UserSecretsId>1955bcda-6f7b-491c-9fab-0e7e7a2cdb45</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<DockerfileContext>..\..</DockerfileContext>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.9" />
</ItemGroup>
其中,<DockerfileContext>..\..</DockerfileContext> 中就是对 上下文的设置, 如果需要在Visual Studio 中Debug Docker, 那么请务必使用Visual Studio 添加Docker支持,而不是自己写Dockerfile, 因为 Visual Stuido 添加Docker 支持不仅仅是添加了Dockerfile,还有 launchSettings.json,EasyAspNetCoreService.csproj,.dockerignore Properties/launchSettings.json
Properties/launchSettings.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"EasyAspNetCoreService": {
"commandName": "Project",
"launchBrowser": true,
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5003;http://localhost:5004"
},
"Docker": {
"commandName": "Docker",
"launchBrowser": true,
"launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}",
"publishAllPorts": true,
"useSSL": true
}
}
}
添加HTTPS,参考: 通过 HTTPS 在 Docker 上宿主 ASP.NET Core 映像 | Microsoft Docs 补充

简单版 Dockerfile 构建 ASP.NET Core

一般一个解决方案下有多个项目(Project),可能不止一个项目(Project)需要使用Dockerfile构建镜像,所以不建议放在仓库根目录(与解决方案文件同级),而 .dockerignore 文件用于忽略不需要加入构建镜像的文件,默认需要与 Dockerfile同级目录。 注意:应保证复制完当前Project所有 ProjectReference(PackageReference不需要,因为会从网络下载),并保证相对路径不变 下面使用标准的 ASP.NET Core Runtime,以及 SDK 构建镜像 Dockerfile 放在项目文件(EasyAspNetCoreService.csproj)同级目录
Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build
WORKDIR /src
COPY ["EasyAspNetCoreService.csproj", "./"]
RUN dotnet restore "./EasyAspNetCoreService.csproj"
COPY . .
WORKDIR "/src/."
RUN dotnet build "EasyAspNetCoreService.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "EasyAspNetCoreService.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "EasyAspNetCoreService.dll"]
补充: 之所以可以像这样做,是因为此 Project对于Release而言,无Project依赖,只需此Project源代码即可构建 此种方式,直接在此Project执行构建镜像命令即可。 .dockerignore 放在与 Dockerfile 同级目录,用于忽略不需要加入构建镜像的文件
.dockerignore
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
**/.classpath
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
上方内容由 Visual Studio 2019 自动生成,其实当放在Project级别目录时,很多忽略内容都不再需要,例如:**/.git,但这里还是保留,以备不时之需。

docker 命令中环境变量指定urls

参考: ASP.NET Core Web 主机 | Microsoft Docs
1
docker run -d -p 5004:80 -p 5003:443 -e ASPNETCORE_URLS="http://*:80;https://*:443" --name simcaptcha-container yiyungent/simcaptcha
值得注意的是,使用HTTPS,需要证书:

Docker下 | System.Net.WebException: Cannot assign requested address

源自笔者项目:https://github.com/yiyungent/SimCaptcha/issues/8 初步判定是因为容器之间彼此隔离,无法通信导致 客户端容器 无法 HTTP 请求 验证码服务端容器, 参考: docker如何进行同主机下容器间的互相通信?(初学菜鸟)? - 知乎 docker中容器之间通信方式_谦190的博客-CSDN博客_docker 容器之间通信 Docker之容器互联实现容器间通信-Hello,World!-51CTO博客 docker容器之间的通信 - 豆浆D - 博客园 访问docker主机时java.net.SocketException - IT屋-程序员软件开发技术分享社区 System.Net.WebException: Cannot assign requested address
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
System.Net.WebException: Cannot assign requested address Cannot assign requested address
---> System.Net.Http.HttpRequestException: Cannot assign requested address
---> System.Net.Sockets.SocketException (99): Cannot assign requested address
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(String host, Int32 port, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean allowHttp2, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.GetHttpConnectionAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.FinishSendAsyncUnbuffered(Task`1 sendTask, HttpRequestMessage request, CancellationTokenSource cts, Boolean disposeCts)
at System.Net.HttpWebRequest.SendRequest()
at System.Net.HttpWebRequest.GetResponse()
--- End of inner exception stack trace ---
at SimCaptcha.Common.HttpAide.HttpPost(String url, String postDataStr, StringBuilder responseHeadersSb, String[] headers, WebProxy proxy) in /src/src/SimCaptcha/Common/HttpAide.cs:line 184
at SimCaptcha.SimCaptchaClient.Verify(String ticket, String userId, String userIp) in /src/src/SimCaptcha/SimCaptchaClient.cs:line 84
解决:
将两个容器处于同一 network(bridge)即可,再通过容器名访问,例如: simcaptcha-container:80

Docker下 System.Drawing.Image.FromFile 不可用

源自笔者项目:https://github.com/yiyungent/SimCaptcha/issues/6 参考: Asp.Net Core使用System.Drawing.Common部署到docker报错问题 - SunnyTrudeau - 博客园 .NET Core使用System.Drawing.Common时找不到libgdiplus的问题_已解决_博问_博客园 asp.net core 2.1 容器中使用 System.Drawing.Common 的问题_已解决_博问_博客园 .NET Core使用skiasharp文字头像生成方案(基于docker发布) - OMango - 博客园
1
2
3
4
5
6
7
8
9
10
11
System.TypeInitializationException: The type initializer for 'Gdip' threw an exception.
---> System.DllNotFoundException: Unable to load shared library 'libgdiplus' or one of its dependencies. In order to help diagnose loading problems, consider setting the LD_DEBUG environment variable: liblibgdiplus: cannot open shared object file: No such file or directory
at System.Drawing.SafeNativeMethods.Gdip.GdiplusStartup(IntPtr& token, StartupInput& input, StartupOutput& output)
at System.Drawing.SafeNativeMethods.Gdip..cctor()
--- End of inner exception stack trace ---
at System.Drawing.SafeNativeMethods.Gdip.GdipLoadImageFromFile(String filename, IntPtr& image)
at System.Drawing.Image.FromFile(String filename, Boolean useEmbeddedColorManagement)
at System.Drawing.Image.FromFile(String filename)
at SimCaptcha.AspNetCore.AspNetCoreVCodeImage.Create(String code, Int32 width, Int32 height) in /src/src/SimCaptcha.AspNetCore/AspNetCoreVCodeImage.cs:line 54
at SimCaptcha.SimCaptchaService.CreateVCodeImg() in /src/src/SimCaptcha/SimCaptchaService.cs:line 484
at SimCaptcha.SimCaptchaService.VCode() in /src/src/SimCaptcha/SimCaptchaService.cs:line 408
文件位置:
src/SimCaptcha.AspNetCore/AspNetCoreVCodeImage.cs:line 54
1
var imageStream = Image.FromFile(randomImgFile);
解决:
Dockerfile
1
2
3
# 解决 Linux 下缺少 'libgdiplus'
RUN apt-get update
RUN apt-get install -y --no-install-recommends libgdiplus libc6-dev

Docker下 中文无法显示 | 中文乱码

源自: https://github.com/yiyungent/SimCaptcha/issues/7 参考: .net core在linux下图片中文乱码 - 没有星星的夏季 - 博客园 解决: 微软 base 镜像问题,自己构建了个base镜像就好了: https://github.com/yiyungent/dotnet-docker/blob/main/aspnetcore-runtime-3.1.Dockerfile
aspnetcore-runtime-3.1.Dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# https://docs.microsoft.com/zh-cn/dotnet/core/install/linux-ubuntu#2004-

FROM ubuntu:20.04 AS base

LABEL maintainer="yiyun <yiyungent@gmail.com>"

# 设置国内阿里云镜像源
COPY etc/apt/aliyun-ubuntu-20.04-focal-sources.list /etc/apt/sources.list

RUN apt-get update
RUN apt-get install -y wget

# 将 Microsoft 包签名密钥添加到受信任密钥列表,并添加包存储库
RUN wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb
RUN dpkg -i packages-microsoft-prod.deb
# 安装运行时
RUN apt-get update
RUN apt-get install -y apt-transport-https
RUN apt-get update
RUN apt-get install -y aspnetcore-runtime-3.1

# 时区设置
ENV TZ=Asia/Shanghai

Windows下 Docker --network host 无效

参考: windows run docker with --network=host and access with 127.0.0.1 - Stack Overflow networking - How to connect to docker host from container on Windows 10 (Docker for Windows) - Stack Overflow Networking using the host network | Docker Documentation The host networking driver only works on Linux hosts, and is not supported on Docker for Mac, Docker for Windows, or Docker EE for Windows Server.

Docker Ubuntu 安装 ping

1
docker exec -it <容器ID> bash
1
apt-get update
1
apt-get install -y iputils-ping
1
ping 容器名

CentOS 安装 Git

1

1
yum install -y git

Nginx 反向代理

nginx.conf
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {
listen 80;
server_name captcha.moeci.com; # 绑定的域名,例如:www.example.com。多个域名用空格分开。
access_log off;

# 反向代理: 将请求转发到 ASP.NET Core 端口 5004
location / {
proxy_pass http://localhost:5004;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}

server {
listen 80;
server_name captcha-client.moeci.com; # 绑定的域名,例如:www.example.com。多个域名用空格分开。
access_log off;

# 反向代理: 将请求转发到 ASP.NET Core 端口 5002
location / {
proxy_pass http://localhost:5002;
proxy_redirect off;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
参考 感谢帮助! docker部署ASP.NET Core、Nginx、MySQL - kasnti - 博客园 Docker & ASP.NET Core (5):Docker Compose - 软件工艺师 - 博客园 docker方式安装 - Wiki - Gitee.com Hosting ASP.NET Core Images with Docker over HTTPS | Microsoft Docs