ASP.NET Core应用程序容器化、持续集成与Kubernetes集群部署(一)
上個月15日,上海MVP做了一次線下的技術分享活動,我分享的主題是《快速構建容器化的ASP.NET Core應用程序》,有關這次活動的簡報,可以參考這里。另外,我的主題分享的PPT也可以點擊這里下載。由于線下活動時間緊迫,沒有辦法把所有的內容完全仔細地講解一遍,最后使用一個小時左右的時間做了一個tasklist的案例演示,但也是走馬觀花,很多細節沒有覆蓋到。因此,特撰此文,將之前分享的內容再細化一下,希望能夠給關注這方面內容的讀者帶來幫助。
我會盡量將細節問題解釋清楚,于是,文章篇幅會比較長,因此,我會分三個部分進行介紹:
ASP.NET Core應用程序容器化需要注意的內容
持續集成、持續部署與Azure DevOps
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自動生成的代碼了如指掌,這樣才能理解并在實際項目中正確使用,與其如此,不如自己根據實際需要自己編寫,這樣可以讓自己對整個項目的各個技術細節都有著深刻的理解和認識。
新建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的業務非常簡單,就是允許用戶能夠增加、刪除任務項目,它的界面如下:
在這個界面中,用戶可以在文本框中輸入需要完成的任務項目,點擊“新增”按鈕可以將任務項目添加到列表,也可以在列表中點擊“刪除”按鈕刪除指定的項目,文本框下方列出了所有已添加的任務項目。整個后端ASP.NET Core Web API解決方案中各項目的依賴關系如下:
具體的代碼實現部分就不多介紹了,這里重點介紹一下ASP.NET Core應用程序容器化時需要注意的幾點問題。
ASP.NET Core應用程序容器化所需注意的問題
應用程序的配置信息
容器化的應用程序往往都是在容器啟動的過程中,將所需的配置信息通過環境變量注入容器,此時運行于容器中的應用就可以讀取環境變量來獲得運行參數。比如,使用docker run命令啟動容器時,就可以使用-e參數來指定環境變量。因此,理解ASP.NET Core應用程序的配置系統是非常重要的,它有助于應用程序配置體系的設計。在《ASP.NET Core應用程序的參數配置及使用》一文中,我已經簡要介紹過ASP.NET Core應用程序的配置系統,可供參考。
在此需要注意的一點是,ASP.NET Core配置系統通常使用冒號(:)來分隔配置數據模型中不同層次的名稱。比如,有如下配置數據模型:
| 1234567 | "mongo": {??"server": {????"host": "localhost",????"port": 27017??},??"database": "tasklist"} |
如果在C#代碼中要訪問host,那么就需要使用下面的代碼:
| 1 | var mongoServerHost = Configuration["mongo:server:host"] |
然而,如果應用程序需要運行在容器中,這個配置就需要寫在容器的編排文件里,比如docker-compose.yml文件。但是,有些容器的編排系統,例如Kubernetes,就不支持在環境變量設置時出現冒號這樣的“非法字符”,為此,ASP.NET Core的配置也支持使用雙下劃線分隔。比如:
這樣的話,不僅ASP.NET Core應用程序在容器中能夠獲得環境變量的配置,而且諸如Kubernetes這樣的系統也能在啟動容器時,將配置信息設置到環境變量中。
端口偵聽
ASP.NET Core應用程序的端口偵聽設置也是一個在容器化過程中非常重要的內容。通常,在開發階段,我們偏向于在Main方法中通過代碼的方式指定應用程序所偵聽的端口號,比如:
| 1234 | public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>????WebHost.CreateDefaultBuilder(args)????????.UseUrls("http://*:8087")????????.UseStartup<Startup>(); |
但是這種方法缺點是很明顯的:我們無法在部署應用程序的時候動態設置需要偵聽的端口。對于ASP.NET Core應用程序而言,常見的做法有兩種:通過命令行參數指定偵聽端口,或者使用環境變量。通過命令行參數,只需要在啟動應用程序時,指定—server.urls參數即可:
或者,如果是使用環境變量,只需要配置ASPNETCORE_URLS變量即可,如下:
因此,事實上我們并不需要在Main函數中去顯式地指定偵聽端口,只需要在最終部署的時候,設置ASPNETCORE_URLS環境變量即可。現在,讓我們看看tasklist代碼庫中,docker-compose.yml文件中有關后端服務的環境變量配置:
| 123456789101112131415161718 | 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的具體內容:
| 123456789101112131415 | FROM microsoft/dotnet:2.1-aspnetcore-runtime AS baseWORKDIR /appEXPOSE 9020FROM microsoft/dotnet:2.1-sdk AS publishWORKDIR /srcCOPY . .RUN dotnet restoreWORKDIR "/src/TaskList.Service"RUN dotnet publish "TaskList.Service.csproj" -c Release -o /appFROM base AS finalWORKDIR /appCOPY --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地址又該如何設置呢?
使用絕對路徑:這不是個好的做法,這就要求將后端API的全路徑都寫死(Hard Code)在environment.prod.ts里,顯然不是一種合理的做法
使用相對路徑:這種做法會使得前端App調用后端API時,產生一個錯誤的URL。比如,假設前端運行在localhost:80,而后端是localhost:9020,那么如果我們指定API的URL是相對路徑/api/service,那么當前端程序運行時,它請求的API地址就成了http://localhost/api/service,而不是http://localhost:9020/api/service
在tasklist中,我選擇了使用相對路徑,然后更改nginx的配置,設置了一條反向代理規則:
| 1234567891011121314151617181920212223242526 | 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鏡像構建上下文,我們可以使用容器來編譯和運行前端代碼:
| 123456789101112131415161718192021 | # 基于node 8容器作為編譯環境FROM node:8 AS build# 首先安裝Angular CLIRUN npm install -g @angular/cli@6.1.5# 然后將源代碼復制到容器中WORKDIR /srcCOPY . .# 執行npm install以及Angular的編譯RUN npm installRUN 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 |
經過一段漫長時間的構建過程之后,所有的服務都會啟動:
在瀏覽器中打開我們的應用:
總結
本文為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
總結
以上是生活随笔為你收集整理的ASP.NET Core应用程序容器化、持续集成与Kubernetes集群部署(一)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学习究竟是为了什么?
- 下一篇: asp.net core集成CAP(分布