多项目的.net core解决方案(项目间引用)如何使用Docker部署

        解决方案内部项目之间引用很正常,但我docker不是很熟,对一些基础命令含义还理解不深入,部署引用其他项目的项目总不成功。搜到了一篇非常适合初学者,从dockerfile命令讲解,到解决引用其他项目时如何docker部署的文章。原论文链接,以下为翻译。

        当你了解Docker命令的基础知识时,从有项目引用的dotnet解决方案创建Docker镜像很容易,但是对于初学者来说,编写适当的Dockerfile可能会很棘手。

        大多数示例展示了如何容器化.net项目,假设它没有本地依赖项。那么,让我们分析一下,当我们的项目引用了解决方案中的其他项目时,我们能做些什么。我们将首先深入研究一个没有依赖项的简单示例,以了解我们引入了哪些更改以及为什么要进行更改。

        如果你只是想跳到解决方案并复制粘贴它,当然你可以这样做,但不建议这样做,因为迟早你会因为不理解发生了什么而被另一个障碍所阻碍,结果你会浪费更多的时间。本文非常适合开始学习Docker指令和命令,因为所有内容都用简明易懂的语言进行了解释。我们将使用.net core 2.2,因为它在撰写本文时是当前版本。


        示例容器化dotnet core应用程序是可用的,在GitHub上,请随意使用它来满足您的需求。

官方Docker示例分析

        官方.net Core应用容器化的文章向我们展示了Dockerfile位于project文件夹(.csproj文件存储的地方):

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env
WORKDIR /app# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "PROJECT_NAME.dll"]

这两个命令,要从Dockerfile所在的项目文件夹中运行:(编者注,docker build是用于使用 Dockerfile 创建镜像,docker run是创建一个新的容器并运行一个命令)

docker build -t aspnetapp .
docker run -d -p 8080:80 --name myapp aspnetapp

Dockerfile FROM指令

        我们的Dockerfile以FROM指令开始:

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env

        这意味着我们的镜像基于官方Microsoft Dotnet Core SDK 2.2版本。我们现在使用SDK,而不是production runtime,因为我们将在构建镜像期间在Docker中编译我们的应用程序。所以你甚至不需要在你的主机上安装.net core SDK,这个Dockerfile是用一种你无需自己为Docker镜像编译应用程序的方式准备的——Docker会编译它。您可以使用在您的主机上构建的二进制文件,但这并不安全——由于兼容性问题,它可能无法工作。为什么有AS build-env 指令-我们将在后面(在Docker多阶段构建部分)讨论。

Dockerfile WORKDIR 指令

        在第二行,我们看到WORKDIR /app指令,这意味着,在我们的Dockerfile下面的RUN, CMD, ENTRYPOINT, COPY和ADD指令将在/app目录执行。如果它不存在,它将被创建(即使它不会被使用)。

Dockerfile COPY 指令

        接下来我们看到COPY *.csproj ./指令,这意味着Docker构建上下文中的所有csproj文件将被复制到Docker镜像中的workdir (/app)目录下。Docker构建命令将在后面解释,但简而言之,构建上下文是来自主机的目录,指向Docker构建命令。.路径指向执行命令的目录。因此,在我们的示例中,我们只复制一个csproj文件,因为我们运行构建命令时将项目目录设置为构建上下文。

Dockerfile RUN 指令

接下来是RUN dotnet restore指令,它只是在我们的工作目录(/app)中运行dotnet restore命令。此时,在我们的镜像的/app目录中,除了我们项目的.csproj之外什么都没有,因为我们在前面的步骤中只复制了它,但它足以还原nuget依赖。

Copy和compile应用程序源代码

        我们再次看到COPY指令——COPY . ./从我们的构建上下文复制所有内容——在我们的例子中,它指的是项目文件(.cs文件等),因为我们运行docker build命令时将项目目录设置为构建上下文。然后使用run指令- run dotnet publish -c Release -o out,我们只需在镜像内部的workdir (/app)目录中运行dotnet publish,并使用-c Release -o out参数。这个dotnet命令用发布配置编译我们的应用,并在out目录下发布结果(在我们的例子中是/app/out)。我们可以编译源代码,因为我们将镜像像基于开发人员的sdk。

Docker多阶段构建

        我们再一次看到FROM指令,它设置了我们的镜像基于哪个镜像…怎么可能再次指定它,使用不同的基?这是一个非常新的Docker特性(从Docker 17.05版本开始),称为多阶段构建。当我们再次使用FROM关键字时,我们的意思是上面指定的先前镜像是临时的,并且仅用于某些目的。在我们的示例中,它仅用于编译我们的应用程序—这就是为什么我们使用SDK作为基础映像。现在我们再次指定基本镜像,这次我们准备的是实际镜像——即将部署到生产环境中的镜像,这个镜像不基于SDK,只基于生产运行时,因此会产生较小的大小。我们将从临时镜像复制我们编译的应用程序。所以我们再次指定工作目录到/app目录,然后复制我们的二进制文件——copy --from=build-env /app/out。这意味着从build-env镜像(这就是为什么我们在第一行给它一个名字)的/app/out/目录复制文件到当前工作目录(/app)。

Dockerfile ENTRYPOINT 指令

        这个Dockerfile中的最后一个指令是ENTRYPOINT,它(简单地说)指定了一个在容器启动时将执行的命令。所以在我们的例子中- ENTRYPOINT ["dotnet", "PROJECT_NAME.dll"] - Docker将运行带有PROJECT_NAME.dll参数(当然应该替换为我们的项目名)的dotnet,以启动我们的应用程序。

Docker build命令

        有了这样的Dockerfile,我们被告知在项目目录(存放Dockerfile的地方)运行docker build -t aspnetapp .命令。选项:-t name(——tag name)不是强制性的——它允许标记镜像(给它命名,并有选择性地以' name:tag '格式给它一个标记),所以不要关注它,并以这种方式查看这个命令:docker build .,因为重要的事情是在之后的选项-构建上下文参数。构建上下文是主机上,Dockerfile指令构建镜像时可以访问的路径。在我们的例子中是 . 路径,这意味着我们运行该命令的目录将作为构建上下文传递。因为我们被告知要在项目目录(.csproj文件存储的地方)中运行这个命令,所以我们的项目文件被作为构建上下文传递。

Docker run命令

        Docker run命令从镜像创建容器。镜像是Docker的只读手册,用于创建容器,容器是我们应用程序所在的虚拟机。我们可以这样想:镜像就像面向对象编程中的一个类,容器就像从这个类创建的实例。因此,我们可以创建任意数量的容器(实例),而不会影响镜像(类)——镜像只是让Docker知道如何创建容器。我们被告知这样运行:
docker run -d -p 8080:80 --name myapp aspnetapp

        如果没有——detach选项(-d),我们将开始看到来自容器的应用控制台输出。使用——publish (-p)选项,我们将容器的端口绑定到主机(默认使用TCP,但您也可以指定UDP和SCTP)。使用——name选项,我们为容器指定一个名字(没有这个选项,Docker会为我们选择一些有趣的名字)。最后,我们传递镜像名称,Docker将读取它来创建容器。因为我们将图像命名为aspnetapp,所以这里使用这个名称。


解决方案

合适的Docker命令

        问题当然是,我们运行Docker构建命令从项目目录传递.路径作为构建上下文。这意味着,在构建映像期间,只能访问这个目录中的文件,而依赖于项目的文件当然在其他目录中。我们有几个选项来解决这个问题。我们可以将Dockerfile向上移动一级(到解决方案目录),并从那里运行docker build。但建议在项目目录中存Dockerfile,以便能够在解决方案中有多个Dockerfile(针对不同的项目)。您也可以像以前一样运行docker build(从项目目录),但将构建上下文路径更改为上一级(..)。在我看来,更优雅的是第三种解决方案-从解决方案目录运行docker构建,通过.作为构建上下文,并使用——file (-f)选项指定我们要读取哪个Dockerfile,如下所示:
docker build -f PROJECT_DIRECTORY/Dockerfile -t IMAGE_NAME .

如何调整Dockerfile

        接下来我们需要调整Dockerfile,因为官方示例假设我们用项目目录作为构建上下文。我的版本是这样的:

FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS build-env
WORKDIR /appCOPY . ./
RUN dotnet publish PROJECT_NAME -c Release -o outFROM mcr.microsoft.com/dotnet/core/aspnet:2.2
WORKDIR /app
COPY --from=build-env /app/PROJECT_NAME/out .ENTRYPOINT ["dotnet", "PROJECT_NAME.dll"]

        我跳过了恢复nuget包作为单一步骤来简化,恢复包含在dotnet发布中,如果由于nuget失败而失败,错误信息是清晰的。但是如果你有很多nuget依赖,你可能想要有单独的步骤,因为这样Docker将其视为不同的层,并在没有任何csproj文件更改的情况下重用它,这样可以缩短构建时间(我在另一篇文章中描述过)。在这个演示中,恢复nugets的速度足够快,可以跳过它,但如果您有很长的构建时间(特别是-在…时间内完成恢复),请记住这一点。

        所以我们将所有项目(因为构建上下文现在是解决方案目录)复制到容器内的/app目录。接下来,我们在/app workdir中运行dotnet publish命令,指定要编译的项目- run dotnet publish PROJECT_NAME -c Release -o out -这里的PROJECT_NAME是目录名,里面有.csproj文件。

        其他指令保持不变,只有一个小小的改变——在从临时镜像复制编译后的应用程序时,这次我们需要将项目名称传递给path:
COPY——from=build-env /app/PROJECT_NAME/out。


在哪里保存.dockerignore文件

        官方文章说,将.dockerignore文件添加到项目目录,以使构建上下文尽可能小,降低缓存无效的风险,这当然是合理的。但是Docker CLI在构建上下文的根目录中查找.dockerignore文件,所以现在我们需要将其移动到解决方案目录。但在我看来,这样更好,因为我们不需要为许多项目创建和维护许多.dockerignore文件,我们为所有项目保留一个。示例规则:

*/bin
*/obj
.dockerignore
.env
.git
.gitignore
.vs
.vscode
**/.toolstarget
.idea

总结

        当我第一次需要dockerize.net core应用程序时,我只是从上述文章中获取Dockerfile,复制粘贴Docker命令,当我遇到障碍时,我试图在不分析Docker如何工作的情况下解决它。在以这种方式浪费了一些时间之后,我又浪费了时间——试图从网上复制粘贴解决方案——同样没有分析我做了什么,也没有成功。然后我又一次明白了(在我的生命中……),匆匆忙忙并不会节省时间,反而会适得其反——浪费时间。因为我没有找到合适的文章或教程,所以我从官方文档和手册开始,这些文档和手册写得很好,但对于初学者来说有太多的细节。本文展示了分析的要点,并用通俗易懂的语言解释了基础知识。我希望这样可以很好地开始编写适当的Dockerfile,而不会遇到像这里所呈现的情况那样的麻烦- dotnet项目从解决方案中引用其他项目,也不会遇到任何其他障碍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/21121.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

JavaWeb_SpringBootWeb基础

先通过一个小练习简单了解以下SpringBootWeb。 小练习: 需求:使用SpringBoot开发一个Web应用,浏览器发起请求/hello后,给浏览器返回字符串"Hello World~"。 步骤: 1.创建SpringBoot项目,勾选We…

3-EMMC命令使用

在调试emmc的过程,我们需要用到命令读写emmc,烧录,查看emmc寄存器,设置寄存器等功能,所以uboot和linux下都有各自的命令可以使用。 1、 uboot下mmc命令 1.1、mmc信息 查看mmc信息:mmc info 描述了emmc的速…

epoll模型下的简易版code

epoll模型下的简易版code c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/epoll.h> #include <fcntl.h>#define MAX_EVENTS 10 #define NUM_DESCRIPTORS 5 // 模拟多个文件描述符// …

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56

【代码随想录训练营】【Day 38】【贪心-5】| Leetcode 435, 763, 56 需强化知识点 重叠区间系列 题&#xff0c; 763&#xff0c; 435 题目 435. 无重叠区间 左起点排序&#xff0c;记录重叠区间个数&#xff0c;总数相减即为结果&#xff0c;过程中维护右边界注意&#x…

工具类解决事务和过滤器解决事务

事务的四大特性ACID 原子性&#xff1a;强调事务的不可分割.多条语句要么都成功&#xff0c;要么都失败。 一致性&#xff1a;强调的是事务的执行的前后&#xff0c;数据要保持一致 隔离性&#xff1a;并发访问数据库时,一个事务的执行不应该受到其他事务的干扰. 持久性&#…

测试:ollama加载羊驼版本llama-3中文大模型

找了一个晚上各种模型&#xff0c;像极了当初找各种操作系统的镜像&#xff0c;雨林木风&#xff0c;深蓝、老毛桃…… 主要是官方的默认7B版本回答好多英文&#xff0c;而且回复的很慢&#xff0c;所以我是在ollama上搜索"chinese"找到了这个羊驼版本的&#xff0c…

使用javacv对摄像头视频转码并实现播放

要实现Java接受RTSP流解码&#xff0c;并推送给前端实现播放实时流&#xff0c;可以使用一些流媒体处理库&#xff0c;比如JavaCV或者FFmpeg等。以下是一个简单的示例代码&#xff1a; 1.控制层方面的 根据视频rtsp流链接打开转换&#xff0c;通过响应写出流到前台使用flvjs播…

go语言初学04

Go 语言近年来发展迅速&#xff0c;并且出现了许多优秀的开发框架和组件来支持各种不同的开发需求。以下是一些常用的 Go 语言开发框架和组件&#xff1a; Web 框架 Gin&#xff1a; URL: Gin简单、高效、易用&#xff0c;适合构建高性能的 Web 应用。 Echo&#xff1a; URL: …

crossover软件是干什么的 crossover软件安装使用教程 crossover软件如何使用

CrossOver 以其出色的跨平台兼容性&#xff0c;让用户在Mac设备上轻松运行各种Windows软件&#xff0c;无需复杂的设置或额外的配置&#xff0c;支持多种语言&#xff0c;满足不同国家和地区用户的需求。 CrossOver 软件是干嘛的 使用CrossOver 不必购买Windows 授权&#xf…

Winform ListView 嵌入组合框、布尔、图片等复杂控件

一、Winform ListView 显示复杂控件示例 以下展示了两种实现思路方案。最后修改日期 2024-05 surfsky 1.1 方案一&#xff1a;ListView 结合组合框进行模拟编辑 基本思路 在界面上放置一个lisview和一个combobox&#xff0c;combobox平时是隐藏的。点击listview&#xff0c…

ArrayList源码讲解

ArrayList 底层采用的是数组队列&#xff0c;相当于动态数组。 ArrayList内部使用一个可重新分配的Object数组来存储元素&#xff0c;这个数组会随着元素的添加自动增长以容纳更多的元素&#xff0c;这就是所谓的“动态数组”。 1.实现了RandomAccess接口&#xff0c;可以随机…

rust嵌入式开发之总结

我们用rust开发的新版产品刚刚交付&#xff0c;已经在海上安装测试完毕并顺利投产。终于松了口气&#xff0c;同时也有时间和精力来做个全面的总结了。 这个产品&#xff0c;目前差不多有三版&#xff1a; 第一个版本是用crt-thread写的&#xff0c;投产后出了一个内存泄露的…

521源码-源码论坛-宝塔面板操作日志是存放在哪里的? 如何删除部分日志记录

我们帮别人搭建或者登录了&#xff08;不是自己权属的宝塔面板&#xff09;&#xff0c;会留下登录及操作的日志&#xff0c;我们不想留下这些操作日志&#xff0c;可以通过下面的方法处理掉&#xff0c;以达到无痕迹访问操作的目的&#xff1a; 如图所示的面板操作日志&#…

Python-3.12.0文档解读-内置函数sum()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 sum(iterable, /, start0) 参数&#xff1a; 返回值&#xff1a; 注意事…

骑砍2霸主MOD开发(10)-游戏大地图(MapScene)制作

一.MapScene中初始化NavMeshFaceID与TerrainType public TerrainType GetFaceTerrainType(PathFaceRecord navMeshFace){switch (navMeshFace.FaceGroupIndex){case 1:return TerrainType.Plain;case 2:return TerrainType.Desert;case 3:return TerrainType.Snow;case 4:retur…

算法优化:空间与时间复杂度的权衡

引言 在软件开发中&#xff0c;算法的性能至关重要。算法的性能通常通过其时间复杂度和空间复杂度来衡量。时间复杂度指的是算法执行时间与输入规模的关系&#xff0c;而空间复杂度则关注算法执行过程中所占用的存储空间。本文将探讨如何权衡这两者&#xff0c;以实现算法的最…

排序方法大汇总

以下所有排序方法均以排升序为例 一.插入排序 1.直接插入排序 1>方法介绍&#xff1a;假定前n个数据有序&#xff0c;将第n1个数据依次与前n个数据相比&#xff0c;若比第i个数据小且比第i-1个数据大则插入两者之间 2>时间复杂度&#xff1a;O(N^2) 空间复杂度&#…

【JS】对象转变成数组

1、Object.keys() 方法&#xff1a; 将对象的键转换为数组 const a { name: aa,age: 18 }; const arr Object.keys(a); console.log(arr); // 输出 [name, age] 2、Object.values() 方法&#xff1a; 将对象的值转换为数组 const a { name: aa,age: 18 }; const arr Obj…

BUUCTF中的密码题目解密

BUUCTF 1.MD5 题目名称就是MD5&#xff0c;这个题目肯定和md5密码有关&#xff0c;下载题目&#xff0c;打开后发现这确实是一个md5加密的密文 Md5在线解密网站&#xff1a;md5在线解密破解,md5解密加密 经过MD5在线解密网站解密后&#xff0c;获取到flag为&#xff1a;flag{…

域名主机服务器配置失败的原因和解决方法

域名主机服务器配置失败的原因可能涉及多个方面&#xff0c;包括域名设置、DNS配置、服务器设置、网络问题等。以下是一些常见的原因和相应的解决方案&#xff1a; 1. DNS配置错误 原因&#xff1a; 域名解析错误&#xff1a;域名没有正确指向服务器的IP地址。 DNS记录未更新&a…