ASP.NET Core应用程序容器化、持续集成与Kubernetes集群部署(一)

上个月15日,上海MVP做了一次线下的技术分享活动,我分享的主题是《快速构建容器化的ASP.NET Core应用程序》,有关这次活动的简报,可以参考这里。另外,我的主题分享的PPT也可以点击这里下载。由于线下活动时间紧迫,没有办法把所有的内容完全仔细地讲解一遍,最后使用一个小时左右的时间做了一个tasklist的案例演示,但也是走马观花,很多细节没有覆盖到。因此,特撰此文,将之前分享的内容再细化一下,希望能够给关注这方面内容的读者带来帮助。

我会尽量将细节问题解释清楚,于是,文章篇幅会比较长,因此,我会分三个部分进行介绍:

  1. ASP.NET Core应用程序容器化需要注意的内容

  2. 持续集成、持续部署与Azure DevOps

  3. Azure Kubernetes Service介绍

ASP.NET Core应用程序的构建

构建ASP.NET Core应用程序的方式有很多种,你可以使用Visual Studio 2017的项目模板直接创建,也可以在安装了.NET Core SDK之后,使用dotnet new命令创建,具体步骤在此也就不再细表,我仍然使用Visual Studio 2017的ASP.NET Core项目模板进行创建。在新建项目对话框中,我们可以选择启用Docker容器支持,这样的话,Visual Studio会在新建的ASP.NET Core项目中添加Dockerfile文件,同时会在解决方案中增加一个Docker Compose的项目,用以实现容器编排。然而,我并不太喜欢使用这一功能,虽然它能够带来很多方便,原因主要有二。首先,一个复杂的应用程序解决方案,项目往往不止一个,各项目的运行环境和配置都会有所不同,使用项目模板创建的Dockerfile和Docker Compose文件有可能还是需要进行修改,甚至重写;其次,我们需要对IDE自动生成的代码了如指掌,这样才能理解并在实际项目中正确使用,与其如此,不如自己根据实际需要自己编写,这样可以让自己对整个项目的各个技术细节都有着深刻的理解和认识。

640?wx_fmt=png

新建ASP.NET Core项目之后,就可以开始编写代码来实现我们的业务逻辑了。有关Visual Studio 2017开发ASP.NET Core应用程序的详细步骤在这里就不多介绍了,作为这次线下活动的演示案例,我开发了一个简单的App:tasklist,这个App使用Angular 6作为前端框架,TypeScript进行前端编程,后端使用ASP.NET Core Web API构建,基于MongoDB数据库,完整的代码可以在https://github.com/daxnet/tasklist找到。该案例项目使用MIT许可协议开源。

Tasklist的业务非常简单,就是允许用户能够增加、删除任务项目,它的界面如下:

640?wx_fmt=png

在这个界面中,用户可以在文本框中输入需要完成的任务项目,点击“新增”按钮可以将任务项目添加到列表,也可以在列表中点击“删除”按钮删除指定的项目,文本框下方列出了所有已添加的任务项目。整个后端ASP.NET Core Web API解决方案中各项目的依赖关系如下:

640?wx_fmt=png

具体的代码实现部分就不多介绍了,这里重点介绍一下ASP.NET Core应用程序容器化时需要注意的几点问题。

ASP.NET Core应用程序容器化所需注意的问题

应用程序的配置信息

容器化的应用程序往往都是在容器启动的过程中,将所需的配置信息通过环境变量注入容器,此时运行于容器中的应用就可以读取环境变量来获得运行参数。比如,使用docker run命令启动容器时,就可以使用-e参数来指定环境变量。因此,理解ASP.NET Core应用程序的配置系统是非常重要的,它有助于应用程序配置体系的设计。在《ASP.NET Core应用程序的参数配置及使用》一文中,我已经简要介绍过ASP.NET Core应用程序的配置系统,可供参考。

在此需要注意的一点是,ASP.NET Core配置系统通常使用冒号(:)来分隔配置数据模型中不同层次的名称。比如,有如下配置数据模型:

1
2
3
4
5
6
7
"mongo": {
  "server": {
    "host": "localhost",
    "port": 27017
  },
  "database": "tasklist"
}

如果在C#代码中要访问host,那么就需要使用下面的代码:

1
var mongoServerHost = Configuration["mongo:server:host"]

然而,如果应用程序需要运行在容器中,这个配置就需要写在容器的编排文件里,比如docker-compose.yml文件。但是,有些容器的编排系统,例如Kubernetes,就不支持在环境变量设置时出现冒号这样的“非法字符”,为此,ASP.NET Core的配置也支持使用双下划线分隔。比如:

640?wx_fmt=png

这样的话,不仅ASP.NET Core应用程序在容器中能够获得环境变量的配置,而且诸如Kubernetes这样的系统也能在启动容器时,将配置信息设置到环境变量中。

端口侦听

ASP.NET Core应用程序的端口侦听设置也是一个在容器化过程中非常重要的内容。通常,在开发阶段,我们偏向于在Main方法中通过代码的方式指定应用程序所侦听的端口号,比如:

1
2
3
4
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .UseUrls("http://*:8087")
        .UseStartup<Startup>();

但是这种方法缺点是很明显的:我们无法在部署应用程序的时候动态设置需要侦听的端口。对于ASP.NET Core应用程序而言,常见的做法有两种:通过命令行参数指定侦听端口,或者使用环境变量。通过命令行参数,只需要在启动应用程序时,指定—server.urls参数即可:

640?wx_fmt=png

或者,如果是使用环境变量,只需要配置ASPNETCORE_URLS变量即可,如下:

640?wx_fmt=png

因此,事实上我们并不需要在Main函数中去显式地指定侦听端口,只需要在最终部署的时候,设置ASPNETCORE_URLS环境变量即可。现在,让我们看看tasklist代码库中,docker-compose.yml文件中有关后端服务的环境变量配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
service:
  image: daxnet/tasklist-service
  build:
    context: service/tasklist
    dockerfile: TaskList.Service/Dockerfile
  links:
    - db
  depends_on:
    - db
  ports:
    - 9020:9020
  environment:
    - ASPNETCORE_ENVIRONMENT=Production
    - ASPNETCORE_URLS=http://*:9020
    - mongo__server__host=tasklist-db
    - mongo__server__port=27017
    - mongo__database=tasklist
  container_name: tasklist-service

在上面的配置中:

  • ASPNETCORE_ENVIRONMENT:指定ASP.NET Core应用程序运行环境,该参数将决定应用程序配置信息的读取方式

  • ASPNETCORE_URLS:指定ASP.NET Core应用程序的侦听端口

  • mongo__server__host:MongoDB的服务器名称

  • mongo__server__port:MongoDB的侦听端口

  • mongo__database:MongoDB的数据库名称

ASP.NET Core的容器版本

微软官方发布了.NET Core/ASP.NET Core的docker容器镜像,可以在https://hub.docker.com/r/microsoft/dotnet/中找到。开发人员需要根据不同的场景来选用不同的tag。比如:

  • 2.1-sdk:包含了.NET Core 2.1 SDK

  • 2.1-aspnetcore-runtime:包含了ASP.NET Core 2.1的运行库

  • 2.1-runtime:包含了.NET Core 2.1的运行库

此外,在这个repo下,还有一些预览版的tag,可以在https://hub.docker.com/r/microsoft/dotnet/tags/页面找到所有的tag。就ASP.NET Core而言,在2.0(含)之前,需要使用microsoft/aspnetcore这个docker容器镜像,而从2.1开始,则需要使用上面提到的microsoft/dotnet这个容器镜像。总之,对于容器镜像和tag的选择需要慎重,否则有可能出现一些奇奇怪怪的问题。

docker镜像构建上下文(Build Context)与Dockerfile的配套使用

在上面的docker-compose.yml片段中,我们指定了ASP.NET Core应用程序的docker镜像构建上下文,为service/tasklist目录,于是,接下来所有与构建docker镜像相关的操作,都会基于这个构建上下文来执行。首先,通过dockerfile指定了Dockerfile的位置是:service/tasklist/TaskList.Service/Dockerfile(注意这里已经将构建上下文路径带入进来);然后,我们了解一下Dockerfile的具体内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM microsoft/dotnet:2.1-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 9020
FROM microsoft/dotnet:2.1-sdk AS publish
WORKDIR /src
COPY . .
RUN dotnet restore
WORKDIR "/src/TaskList.Service"
RUN dotnet publish "TaskList.Service.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
CMD ["dotnet", "TaskList.Service.dll"]

这个Dockerfile分成三个部分:第一部分指定运行时会采用microsoft/dotnet:2.1-aspnetcore-runtime这个tag,运行目录为/app目录,并会向外界暴露9020端口;第二部分就是应用程序的编译部分,这里采用microsoft/dotnet:2.1-sdk作为编译环境,先设置容器中的工作目录为/src,然后,将service/tasklist目录下的所有内容全部复制到容器中的/src目录(注意,虽然COPY指令后面是两个点号,但由于我们已经指定了镜像构建上下文,因此,第一个点号就表示service/tasklist目录,第二个点号就表示容器中的当前目录,也就是/src目录),接着就是标准的dotnet restore命令,然后就是进入到/src/TaskList.Service目录,执行dotnet publish指令,从而编译整个项目,并将编译结果输出到/app目录;到了第三部分,将第二部分的输出结果复制到第一部分容器中的/app目录(也就是最后那个点号所指定的目录),然后执行dotnet命令启动服务。

事实上,如果你在创建ASP.NET Core应用程序时,启用了docker支持,那么Visual Studio会在你的项目中添加一个Dockerfile,内容与上面的Dockerfile类似,不过需要注意的是,使用这个自动生成的Dockerfile之前,需要弄清楚镜像构建上下文,否则直接通过docker build命令是无法正常完成镜像构建的。

在容器化ASP.NET Core应用程序方面,我暂时先介绍这些内容;接下来看看前端部分需要做些什么。

前端应用:nginx的反向代理

在tasklist案例中,前端我采用的是Angular 6框架,使用TypeScript编写。由于是一个单页面应用,因此,我没有选择相对比较重的Jetty、Tomcat、IIS等Web容器,而是选择使用了比较轻量的nginx。当然,前端通过http请求访问ASP.NET Core Web API应用程序所提供的RESTful API接口,那么这里就有一个访问URL的问题。使用过Angular框架的开发者都知道,通过environment.ts(或者environment.prod.ts)代码文件,可以针对不同的运行环境(Development, Staging或者Production)来选择设置不同的配置数据,那么,后端服务的URL地址又该如何设置呢?

  1. 使用绝对路径:这不是个好的做法,这就要求将后端API的全路径都写死(Hard Code)在environment.prod.ts里,显然不是一种合理的做法

  2. 使用相对路径:这种做法会使得前端App调用后端API时,产生一个错误的URL。比如,假设前端运行在localhost:80,而后端是localhost:9020,那么如果我们指定API的URL是相对路径/api/service,那么当前端程序运行时,它请求的API地址就成了http://localhost/api/service,而不是http://localhost:9020/api/service

在tasklist中,我选择了使用相对路径,然后更改nginx的配置,设置了一条反向代理规则:

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
events {
    worker_connections      4096;
}
http {
     
    server {
      listen    80;
      server_name   localhost;
       
      include  /etc/nginx/mime.types;
      location / {
        root /usr/share/nginx/html;
        index  index.html  index.htm;
      }
      location /api {
        proxy_pass http://tasklist-service;
      }
    }
    upstream tasklist-service {
      server tasklist-service:9020;
    }
}

在这里,当前端页面请求/api路径时,nginx会自动重定向到http://tasklist-service:9020/api,此时就能正确完成RESTful API调用。注意:这里的tasklist-service是ASP.NET Core应用程序的运行机器名,请参考docker-compose.yml文件中service配置部分的container_name设置。

同样,基于docker镜像构建上下文,我们可以使用容器来编译和运行前端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 基于node 8容器作为编译环境
FROM node:8 AS build
# 首先安装Angular CLI
RUN npm install -g @angular/cli@6.1.5
# 然后将源代码复制到容器中
WORKDIR /src
COPY . .
# 执行npm install以及Angular的编译
RUN npm install
RUN ng build --prod
# 基于nginx容器作为运行环境
FROM nginx AS final
# 将nginx.conf配置文件复制到容器指定目录
COPY nginx.conf /etc/nginx/nginx.conf
# 将Angular编译输出复制到nginx的指定目录
COPY --from=build /src/dist/tasklist /usr/share/nginx/html

在容器中运行整个应用程序

在此,我选择使用Docker for Windows来运行整个tasklist应用程序。首先启动Docker for Windows,然后打开Windows命令行工具,进入到tasklist目录,执行:

1
docker-compose up --build

经过一段漫长时间的构建过程之后,所有的服务都会启动:

640?wx_fmt=png

在浏览器中打开我们的应用:

640?wx_fmt=png

总结

本文为ASP.NET Core应用程序容器化、持续集成、持续部署话题的第一部分,重点介绍了ASP.NET Core应用程序容器化时需要注意的地方,并展示了整个案例的运行效果。下文会接着讨论基于Azure DevOps的持续集成,看看如何使用Azure DevOps的服务来完成项目的自动编译。

原文地址: http://sunnycoding.cn/2018/10/07/dockerize-aspnetcore-cicd-with-azure-devops-and-kubernetes-part1


.NET社区新闻,深度好文,欢迎访问公众号文章汇总 http://www.csharpkit.com

640?wx_fmt=jpeg

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

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

相关文章

牛客练习赛 63 F-牛牛的树行棋

F-牛牛的树行棋 大佬题解 对于每一个棋子来说&#xff0c;都是独立的&#xff0c;因此当前局面的 SG 值就是每一枚棋子的 SG 值的异或和。若一枚棋子的往子树内最多可以走 kkk 步&#xff0c;它的 SG 值为 kkk。然后就可以dfs求出整个局面的SG值。 仔细再思考一步&#xff1a…

【DP】小学生语文题(jzoj 5102)

正题 jzoj 5102 题目大意 给你两个串A,B&#xff0c;字母个数相等&#xff0c;可以把B的一个字符移到前面某个位置&#xff0c;问你最少移多少次可以使A,B相等 解题思路 设fi,jf_{i,j}fi,j​为A匹配了i-n&#xff0c;B用了j-n 1.当i,j匹配时fi,jfi1,j1f_{i,j}f_{i1,j1}fi,j…

AT3949-[AGC022D]Shopping【贪心】

正题 题目链接:https://www.luogu.com.cn/problem/AT3949 题目大意 长度为LLL的坐标轴上&#xff0c;给出nnn个点&#xff0c;每个点xix_ixi​需要购物tit_iti​的时间&#xff0c;一辆车在0∼L0\sim L0∼L折返跑&#xff0c;求从000出发购物完回到000的最短时间。 n∈[1,310…

现代软件工程的《构建之法》

要想了解世界&#xff0c;就必须亲自来打造它。—— 帕韦泽&#xff08;Cesare Pavese&#xff09;国庆假日期间&#xff0c;我重读了邹欣老师的《构建之法》一书。我从事软件行业相关工作超过15年&#xff0c;每每在被问到给在校计算机专业大学生的学习建议时&#xff0c;我都…

牛客练习赛 62

A.牛妹的游戏 Ramsey定理&#xff1a;人话解释任意六个人中要么至少三个人认识&#xff0c;要么至少三个不认识。 结论简要证明: 假设 666 个据点分别为 A,B,C,D,E,FA,B,C,D,E,FA,B,C,D,E,F那么在 A 连向其它据点的控制链中&#xff0c;必然至少有 333条链被同一方控制&#x…

【图论】【斜率优化】前往大都会(loj 2769)

正题 jzoj 7181 题目大意 给你由若干铁路组成的图&#xff08;一个铁路上有若干点&#xff09;&#xff0c;问你从1到n在最短路径的前提下&#xff0c;乘坐的每一条铁路所花费时间的平方和的最大值 解题思路 先用dij跑出最短路图&#xff08;即长度等于最短路的所有路径&…

AT4995-[AGC034E] Complete Compress【树形dp】

正题 题目链接:https://www.luogu.com.cn/problem/AT4995 题目大意 nnn个点的一棵树&#xff0c;上面有一些棋子&#xff0c;每次可以选择两个棋子移动到他们之间的路径上相邻的点上&#xff0c;求最少多少步能移动到一个点上。 n∈[1,2000]n\in[1,2000]n∈[1,2000] 解题思路 …

【每日一题】8月10日题目精讲—排座椅

来源&#xff1a;牛客网&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 51200K&#xff0c;其他语言102400K 64bit IO Format: %lld题目描述 上课的时候总有一些同学和前后左右的人交头接耳&#xff0c;这是令小学班主任十分头疼的…

使用Consul做服务发现的若干姿势

从2016年起就开始接触Consul&#xff0c;使用的主要目的就是做服务发现&#xff0c;后来逐步应用于生产环境&#xff0c;并总结了少许使用经验。最开始使用Consul的人不多&#xff0c;为了方便交流创建了一个QQ群&#xff0c;这两年微服务越来越火&#xff0c;使用Consul的人也…

【费用流】【线性规划】志愿者招募(luogu 3980)

正题 luogu 3980 题目大意 有n个时刻&#xff0c;第i个时刻需要aia_iai​个志愿者&#xff0c;有m类志愿者&#xff0c;第j类可以从ljl_jlj​做到rjr_jrj​&#xff0c;代价为wjw_jwj​&#xff0c;数量无限&#xff0c;问你使所有时刻志愿者个数都足够的最小代价 解题思路 …

YbtOJ#643-机器决斗【贪心,李超树】

正题 题目链接:https://www.ybtoj.com.cn/problem/643 题目大意 nnn个机器人&#xff0c;第iii个攻击力为AiA_iAi​&#xff0c;防御为DiD_iDi​。 然后你每次可以对一个机器人造成AtkAtkAtk点伤害&#xff0c;之后所有机器人对你进行一次攻击。 开局可以删除两个机器人&…

【每日一题】8月11日题目精讲—矩阵消除游戏

来源&#xff1a;牛客网&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld题目描述 牛妹在玩一个名为矩阵消除的游戏&#xff0c;矩阵的大小是n行m列&#xff0c;第i行第…

牛客练习赛 61(待补F-点分治?)

A. 打怪 先求出每次打死一只怪需要掉多少血&#xff0c;然后就直接算出能够打死多少只。 #define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0) #pragma GCC optimize(2) #include<iostream> #include<algorithm> using namespace std; int main() {IO…

asp.net core集成CAP(分布式事务总线)

一、前言感谢杨晓东大佬为社区贡献的CAP开源项目&#xff0c;传送门在此&#xff1a;.NET Core 事件总线,分布式事务解决方案&#xff1a;CAP 以及 如何在你的项目中集成 CAP【手把手视频教程】&#xff0c;之前也在工作中遇到分布式数据一致性的问题&#xff0c;也一直都是基于…

Ybt#452-序列合并【期望dp】

正题 题目链接:https://www.ybtoj.com.cn/contest/113/problem/2 题目大意 一个空序列&#xff0c;每次往末尾加入一个[1,m][1,m][1,m]中的随机一个数。如果末尾两个数相同都为xxx且(x<t)(x<t)(x<t)&#xff0c;那么将它们合并成x1x1x1。 如果序列长度为nnn且无法合…

【Trie】【费用流】管道监控(loj 3026)

正题 loj 3026 题目大意 给你一棵树&#xff0c;和若干匹配串&#xff0c;如果一个节点向下的某条链构成了匹配串i&#xff0c;则可以花费这w_i匹配这条链&#xff0c;问你匹配完所有点的最小代价 解题思路 这题可以理解为树上树上的线性规划 先对于每个匹配串倒着建trie&a…

【每日一题】8月12日题目精讲 Mr. Kitayuta, the Treasure Hunter

来源&#xff1a;牛客网&#xff1a; 时间限制&#xff1a;C/C 1秒&#xff0c;其他语言2秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld题目描述 The Shuseki Islands are an archipelago of 30001 small islands in the Yutampo Se…

.Net Core中利用TPL(任务并行库)构建Pipeline处理Dataflow

在学习的过程中&#xff0c;看一些一线的技术文档很吃力&#xff0c;而且考虑到国内那些技术牛人英语都不差的&#xff0c;要向他们看齐&#xff0c;所以每天下班都在疯狂地背单词&#xff0c;博客有些日子没有更新了&#xff0c;见谅见谅 什么是TPL?Task Parallel Library (T…

AT3950-[AGC022E]Median Replace【贪心,dp】

正题 题目链接:https://www.luogu.com.cn/problem/AT3950 题目大意 一个包含?,0,1?,0,1?,0,1的长度为奇数的序列&#xff0c;把???替换为0/10/10/1。每次可以选择三个数变成它们的中位数&#xff0c;求有多少种替换方案使得能够把序列最终变为一个111。 1≤∣S∣≤3105…

【excrt】屠龙勇士(luogu 4774)

正题 luogu 4774 题目大意 有n条龙&#xff0c;第i条血量为aia_iai​&#xff0c;回血量为bib_ibi​&#xff0c;杀死后掉落伤害为DiD_iDi​的刀&#xff0c;初始有若干刀 杀第i条龙要用现有伤害比aia_iai​小的刀中伤害最大的&#xff08;如果没有就用伤害最小的&#xff0…