将 ASP.Net Core WebApi 应用打包至 Docker 镜像

运行环境为 Windows 10 专业版 21H1, Docker Desktop 3.6.0(67351),Docker Engine 20.10.8

1. ASP.Net Core Runtime 还是 .Net Core Runtime

在这里首先要区分一下 SDK 和 Runtime 的区别。SDK (Software Development Kit)主要是在开发过程中使用的,而 Runtime 是在实际运行的时候使用的(类似于 JDK 和 JRE 的关系)。所以对于我们这种发布后运行的情况,Runtime 就足够了。

一开始没有分清楚 ASP.Net Core Runtime,和 .Net Core Runtime 的区别,导致自己的网站项目虽然拷贝进了镜像但一直提示缺少运行时。ASP 的全称是 Active Server Pages,顾名思义,是用于动态网页的,所以网站应用要使用 ASP.Net Core Runtime;而 .NET Core Runtime 一般是用于控制台应用的;还有一个类似的 .NET Desktop Runtime 一般是用于 Windows 桌面应用的。详细可以参见微软的 .NET 下载页面

2. ASP.NET Core WebApi 应用的编译

虽然前两天 .NET 6.0 发布了,也是 LTS,但此处还是使用 .NET Core 3.1 哈

新建一个 ASP.NET Core WebApi 应用,在 Controllers 文件夹里面添加一个 HelloWorldController,并且在 appSettings.json 中添加一个配置项 WelcomeStr

HelloWorldController.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration; namespace HelloWorldWebApplication.Controllers
{
[ApiController]
[Route("[controller]/[action]")]
public class HelloWorldController
{
private readonly IConfiguration Configuration; public HelloWorldController(IConfiguration configuration)
{
this.Configuration = configuration;
} [HttpGet]
public string Hello()
{
return Configuration.GetSection("WelcomeStr").Value;
}
}
}

appSettings.json

{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"WelcomeStr": "Hello ASP.NET Core 3.1!" # 新增部分
}

功能也比较简单,返回一个在配置文件中定义的欢迎语。控制器中的依赖注入相关内容在此处就不细讲了。运行一下看一下本地的效果:

接下来就是本地发布了,右键项目选择发布,选择发布到文件夹。配置项的 Debug 和 Release、目标框架 自不用多说。关于“部署模式”,框架依赖 的意思是,发布的内容必须要在安装了相应运行环境的机器上才能运行(正是我们打包至Docker想要的),而独立 的意思是,即使机器没有安装相应的运行环境,也可以运行。对于目标运行时,如果我们要打包到 Docker 的 Linux x64 镜像中,应当选择 linux-x64。点击“发布”,在项目的根目录中,依次进入 bin\Release\netcoreapp3.1\publish,这里面的文件就是发布后的文件,我们要将它们打包进镜像。

3. Dockerfile 的创建与打包

准备工作的最后一步了。注意文件名称一定要是 Dockerfile,鄙人一开始使用 VS Code 新建了一个 .dockerfile 文件,在使用 docker 构建命令时一直提示没有构建文件。

Dockerfile

FROM mcr.microsoft.com/dotnet/aspnet:3.1-focal
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
RUN mkdir /HelloWorldWebApplication
COPY ./ /HelloWorldWebApplication
EXPOSE 80
WORKDIR /HelloWorldWebApplication
CMD ["/bin/bash","-c","./HelloWorldWebApplication"]

这里面的注意点就比较多了,我们逐个来说

第 1 行,使用 From 选择基础镜像。所有的选项可以参见 ASP.NET Core Runtime 的 DockerHub 页面。此处选用的是基于 Ubuntu、 运行时版本是 3.1 的镜像。后面的 focal 其实对应的操作系统的代号,即 Ubuntu 20.04 Focal Fossa(类似的 Buster 是 Debian 10 的代号)。如果希望镜像尽可能的小,可以使用 alpine 镜像(这里选用 Ubuntu 镜像主要是为了个人的操作方便,比如按下别名 ll 就可以执行对应命令 ls -l,alpine 镜像虽然小,但很多常用命令、功能都没有)

第 2 ~ 3 行设置时区。如果使用的是 alpine 镜像,需要手动拷贝时区文件

第 4 行,在容器中创建目录。一开始的时候没写这行,导致后面的 COPY 找不到文件夹了。这可不像是 docker cp 可以自动在容器中创建文件夹啊。

第 5 行,使用 COPY 命令复制。切记在 源路径中不要使用 *,如 COPY * /HelloWorldWebApplication,因为这会忽略第一层的文件夹。假设我们当前目录是这样的(多了wwwroot):

│ appsettings.Development.json

│ appsettings.json

│ Dockerfile

│ HelloWorldWebApplication

│ HelloWorldWebApplication.deps.json

│ HelloWorldWebApplication.dll

│ HelloWorldWebApplication.pdb

│ HelloWorldWebApplication.runtimeconfig.json

│ web.config



└─wwwroot

                  index.html

复制进去后,wwwroot 文件夹已经没了:

root@f659a7e407e1:/HelloWorldWebApplication# ll

total 264

drwxr-xr-x 1 root root 4096 Nov 12 02:54 ./

drwxr-xr-x 1 root root 4096 Nov 12 02:55 ../

-rwxr-xr-x 1 root root 216 Nov 12 02:54 Dockerfile*

-rwxr-xr-x 1 root root 90680 Nov 12 02:24 HelloWorldWebApplication*

-rwxr-xr-x 1 root root 114406 Nov 12 02:24 HelloWorldWebApplication.deps.json*

-rwxr-xr-x 1 root root 8704 Nov 12 02:24 HelloWorldWebApplication.dll*

-rwxr-xr-x 1 root root 19932 Nov 12 02:24 HelloWorldWebApplication.pdb*

-rwxr-xr-x 1 root root 311 Nov 12 02:24 HelloWorldWebApplication.runtimeconfig.json*

-rwxr-xr-x 1 root root 162 Nov 12 01:49 appsettings.Development.json*

-rwxr-xr-x 1 root root 236 Nov 12 01:56 appsettings.json*

-rwxr-xr-x 1 root root 0 Nov 12 02:47 index.html*

-rwxr-xr-x 1 root root 545 Nov 12 02:24 web.config*

第 6 行,使用 EXPOSE 指明应当映射的端口。为什么这个端口一定是 80 呢,我就是想使用 8001 呢,这是在 ASP.NET Core 3.1 Runtime 镜像中定义的环境变量。可以进入容器,使用 env 命令查看,可以发现:

所以如果想修改端口,在 Dockerfile 添加如下指令即可

ENV ASPNETCORE_URLS=http://+:5000

第 7 行,使用 WORKDIR 切换工作路径。如果不切换工作路径,则会找不到配置文件。如修改 Dockerfile,删除该行,并修改最后一行(需要指定执行文件的路径)为:

CMD ["/bin/bash","-c","./HelloWorldWebApplication/HelloWorldWebApplication"]

再次访问,可以看到没有获取到配置文件的内容:

第 8 行,使用 CMD 执行命令。我们可以直接执行 ./HelloWorldWebApplication,可别忘了是在 shell 中,所以要指定 /bin/bash 或 /bin/sh(和使用的具体操作系统镜像有关,在 Ubuntu 中是 /bin/bash)。也尝试过使用 alpine,似乎直接执行并不奏效,需要执行的命令为 /bin/sh dotnet ./HelloWorldWebApplication.dll。

4. 运行

终于到最后的执行步骤了,在 Windows 的终端中切换目录到项目的 publish 文件夹中,执行 docker build -t helloworld_web:v1 . 生成最终的镜像。使用 docker run -itd -p 5002:80 helloworld_web:v1 创建并运行容器(映射到 5002 端口),可以看到得到了与本地同样的效果:

后记:

这一路操作下来,虽然主要的思路没毛病,但细节的东西还是不少的,终于把之前的 Docker 内容实际运用上了。

Docker Desktop 的功能也越来越完备了,现在可以查看构建 image 的 Dockerfile 文件:

另外无意间看到了 Visual Studio 2019 中可以直接使用发布到 Docker 的功能, 有空尝试下。

参考:

.NET Core 官方下载

https://dotnet.microsoft.com/download/dotnet/3.1

Asp.net core在docker容器中的端口问题

https://www.cnblogs.com/RandyField/p/13059985.html

Linux 下执行本目录的可执行文件(命令)为什么需要在文件名前加“./”

https://www.cnblogs.com/fortunel/p/8663669.html

Docker COPY 复制文件夹的诡异行为

https://www.jianshu.com/p/9b7da9aacd8a

Dockerfile复制时如何保留子目录的结构

https://www.pkslow.com/archives/dockerfile-copy-keep-subdirectory-structure

linux的显示全部环境变量

https://blog.csdn.net/weixin_39880318/article/details/116864978

ASP.NET Core 2.1 使用Docker运行

https://www.cnblogs.com/stulzq/p/9201830.html

将 ASP.Net Core WebApi 应用打包至 Docker 镜像的更多相关文章

  1. 将 ASP.NET Core 1.0 应用作为 docker 镜像发布 (Linux版)

    var appInsights=window.appInsights||function(config){ function r(config){t[config]=function(){var i= ...

  2. Asp.Net Core WebAPI+PostgreSQL部署在Docker中

     PostgreSQL是一个功能强大的开源数据库系统.它支持了大多数的SQL:2008标准的数据类型,包括整型.数值值.布尔型.字节型.字符型.日期型.时间间隔型和时间型,它也支持存储二进制的大对像, ...

  3. 【Azure Developer】已发布好的.NET Core项目文件如何打包为Docker镜像文件

    问题描述 在博文([Azure App Service For Container]创建ASP.NET Core Blazor项目并打包为Linux镜像发布到Azure应用服务)中我们通过VS 201 ...

  4. 记一次使用Asp.Net Core WebApi 5.0+Dapper+Mysql+Redis+Docker的开发过程

    #前言 我可能有三年没怎么碰C#了,目前的工作是在全职搞前端,最近有时间抽空看了一下Asp.net Core,Core版本号都到了5.0了,也越来越好用了,下面将记录一下这几天以来使用Asp.Net ...

  5. asp.net core webapi 使用ef 对mysql进行增删改查,并生成Docker镜像构建容器运行

    1.构建运行mysql容器,添加数据库user 参考Docker创建运行多个mysql容器,地址 http://www.cnblogs.com/heyangyi/p/9288402.html 添加us ...

  6. 品尝阿里云容器服务:初步尝试ASP.NET Core Web API站点的Docker自动化部署

    部署场景是这样的,我们基于 ASP.NET Core 2.0 Preview 1 开发了一个用于管理缓存的 Web API ,想通过阿里云容器服务基于 Docker 部署为内网服务. 在这篇博文中分享 ...

  7. asp.net core webapi之跨域(Cors)访问

    这里说的跨域是指通过js在不同的域之间进行数据传输或通信,比如用ajax向一个不同的域请求数据,或者通过js获取页面中不同域的框架中(iframe)的数据.只要协议.域名.端口有任何一个不同,都被当作 ...

  8. ASP.NET Core WebAPI 开发-新建WebAPI项目

    ASP.NET Core WebAPI 开发-新建WebAPI项目, ASP.NET Core 1.0 RC2 即将发布,我们现在来学习一下 ASP.NET Core WebAPI开发. 网上已经有泄 ...

  9. Asp.net Core WebApi 使用Swagger做帮助文档,并且自定义Swagger的UI

    WebApi写好之后,在线帮助文档以及能够在线调试的工具是专业化的表现,而Swagger毫无疑问是做Docs的最佳工具,自动生成每个Controller的接口说明,自动将参数解析成json,并且能够在 ...

随机推荐

  1. eval(input())

    看到一段代码,判读输入的数字,用的是eval(input()),查了一下,原来input()会把所有输入值,包括数字,视为字符串,而eval()会去掉字符串最外层的引号,然后当做Python语句执行[ ...

  2. P6091-[模板]原根

    正题 题目链接:https://www.luogu.com.cn/problem/P6091 题目大意 给出一个数\(p\),求出它的所有在\([0,p]\)的原根. 解题思路 原根的定义,\(\de ...

  3. CF618F-Double Knapsack【结论】

    正题 题目链接:https://www.luogu.com.cn/problem/CF618F 题目大意 给出大小为\(n\),值域为\([1,n]\)的两个可重集合\(A,B\) 需要你对它们各求出 ...

  4. Python日常Bug集

    1.TypeError: 'int' object is not iterable: 场景示例: data = 7 for i in data: print(i) # 原因:直接对int数据进行迭代造 ...

  5. JavaScript数组 几个常用方法

    前言 数组方法有太多了,本文记录一些本人容易记错的一些数组方法,用于巩固及复习. 后续会慢慢的将其他数组方法添加进来. 善用数组方法可以使数据处理变的优雅且简单. 那下面让我们开始吧: filter( ...

  6. Java基础之(八):顺序结构与选择结构

    顺序结构 Java的基本结构就是顺序结构 顺序结构是最简单的算法结构 它是任何一个算法都离不开的一种基本算法结构. 例子: public static void main(String[] args) ...

  7. 洛谷2151[SDOI2009]HH去散步(dp+矩阵乘法优化)

    一道良好的矩阵乘法优化\(dp\)的题. 首先,一个比较\(naive\)的想法. 我们定义\(dp[i][j]\)表示已经走了\(i\)步,当前在点\(j\)的方案数. 由于题目中限制了不能立即走之 ...

  8. Bloom Filter算法

    Bloom Filter算法详解 什么是布隆过滤器 布隆过滤器(Bloom Filter)是 1970 年由布隆提出的.它实际上是一个很长的二进制向量和一系列随机映射函数 (下面详细说),实际上你也可 ...

  9. 【转-Andrew_qian】stm32中断嵌套全攻略

    断断续续学习STM32一学期了,时间过的好快,现在对STM32F103系列单片机的中断嵌套及外部中断做一个总结,全当学习笔记.废话不多说,ARM公司的Cortex-m3 内核,支持256个中断,其中包 ...

  10. 【数据结构】<栈的应用>回文判断

    通过栈与队列相关内容的学习,我们知道,栈是"先进后出"的线性表,而队列是"先进先出"的线性表.可以通过构造栈与队列来实现在这一算法.将要判断的字符序列依次压栈和 ...