小學二年級的女兒學到乘法時,隨手寫的練習程式。 由於工作關係,三年多沒碰前端,順便練習寫一下 ReactJS;這是我第一次寫 ReactJS,寫得不好的地方請多指教。
這是免費的線上九九乘法數學練習小程式,程式碼也公開在 GitHub 上給需要的人自取。
之後會隨著女兒及兒子的課業內容不定期更新,如果有改善建議歡迎在下面留言處跟我說。
或是有能力開發的人,也歡迎發 MR 給我,底下有 GitHub 連結。
由左到右為:
由左到右為:
由左到右為:
1 | System.ObjectDisposedException: Cannot access a disposed object. |
前一刻才剛跑過所有的測試,突然間就死一片,如圖:
主要原因是本機整合測試時,為了要使用 IServiceProvider
,所以建立出 IHostBuilder
實體。
但很不幸的是這個 Host
死在背景,而且站著資源放不掉,所以重跑測試時一直無法 Build 出新的 Host,導致 IServiceProvider
不能被使用。
可透過以下指令找出在背景的 dotnet process,在強制刪除,如下:
1 | # 找到死在背景的 dotnet process |
刪除後又回復正常了。
]]>以下介面作為範例:
1 | public enum WalletType |
用相同的介面,註冊不同的實作類別:
1 | public class Startup |
在 Constructor Injection 時用 IEnumerable<T>
注入,便可取得相同介面的全部實例,再依照使用情境選擇要用的實例。範例如下:
1 | using System.Collections.Generic; |
示意圖如下:
注意!
通常只建議 Singleton 類型的服務這樣使用,因為使用 Transient 或 Scoped 類型的服務,注入時會new
新的實例,若沒用到的話,就變成不必要的效能耗損。
如果服務註冊用相同的介面,註冊不同的實作類別,Constructor Injection 時不是用 IEnumerable<T>
注入,就只會得到最後一個註冊的類別,如上例會得到 LinePayService
的實例。
示意圖如下:
]]>BuildServiceProvider
建置出 Service Provider。BuildServiceProvider
建置 Service Provider,以及要注意的地方。通常會在 Startup.ConfigureServices
方法中,註冊 Services,待 Host 成立實體後,即可透過 Constructor Injection,提供 Services。
Host 建立實體前,若要使用 Service Provider,就需要透過 IServiceCollection 的擴充方法 BuildServiceProvider
建置,範例如下:
1 | using Microsoft.AspNetCore.Builder; |
Host 實例使用的 Service Provider 與自行建立的 Service Provider 是完全不同的實體,而 services.AddSingleton
所註冊的 Services 僅在 Service Provider 實體中 Singleton,不同的 Service Provider 實體會各自存在自己的 Services。
以上例來說,雖然 Sample 是使用 AddSingleton
註冊,但實際啟動時,會有兩個 Sample 的實體。圖如:
實際運行上例程式碼,Log Output 的 Sample 實例 HashCode 與 Controller 注入的 Sample 實例 HashCode 並不一樣,表示這兩個式不同的實例,範例執行結果:
Sample 類別的範例程式:
1 | public interface ISampleSingleton |
範例 Controllers\HomeController.cs:
1 | using Microsoft.AspNetCore.Mvc; |
在 ASP.NET Core 的起手式,都會透過靜態類別 Host
或 WebHost
建立 HostBuilder,再透過 HostBuilder 建置出 Host 的實例,如下:
1 | using Microsoft.AspNetCore.Hosting; |
在 HostBuilder 建置出 Host 的實例之前,需要透過 ConfigureServices
先宣告 DI Services,當 HostBuilder 建置出 Host 實例時,就會依照 ConfigureServices
註冊的配置告知 Service Provider,讓 Service Provider 可以提供請求者 Services。
而 Service Provider 的實例會一直存在 Host 的實例之中。
為了方便說明,用之前ASP.NET Core 3 系列 - 依賴注入 (Dependency Injection) 的 Sample 類別當作範例:
1 | public interface ISample |
在 Main
方法 HostBuilder 建置出的 Host 實例,再從 Service Provider 中獲取需要的服務。
Singleton 及 Transient 類型的服務,都可以直接透過 Service Provider 取出,但 Scoped 類型的服務,必須先建立出 Service Scope,才可以在該 Scope 內的 Service Provider 中取出服務。
範例如下:
1 | public class Program |
在沒有使用 DI Framework 的情況下,假設在 UserController 要呼叫 UserLogic,會直接在 UserController 實例化 UserLogic,如下:
1 | public class UserLogic { |
xxxLogic 邏輯層分層命名,有興趣可以參考這篇:軟體分層架構模式
以上程式基本上沒什麼問題,但程式相依性就差了點。UserController 必須 要依賴 UserLogic 才可以運作,就算拆出介面改成:
1 | public interface IUserLogic { |
UserController 與 UserLogic 的相依關係只是從 Action 移到建構子,依然還是很強的相依關係。
ASP.NET Core 透過 DI 容器,切斷這些相依關係,實例的產生不會是在使用方(指上例 UserController 建構子的 new
),而是在 DI 容器。
DI 容器的註冊方式也很簡單,在 ConfigureServices
註冊。Startup.cs 範例如下:
1 | // ... |
services 就是一個 DI 容器。
此例把 MVC 的服務註冊到 DI 容器,等到需要用到 MVC 服務時,才從 DI 容器取得物件實例。
基本上要注入到 Service 的類別沒什麼限制,除了靜態類別。
以下範例程式就只是一般的 Class 繼承 Interface:
1 | public interface ISample |
要注入的 Service 需要在 ConfigureServices
中註冊實做類別。Startup.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
ASP.NET Core 3 開始,建議透過 Generic Host 建立 Web Host,所以也能用 HostBuilter 中的 ConfigureServices
方法註冊:
1 | using Microsoft.AspNetCore.Hosting; |
注意!重複註冊,會產生出重複的結果。
ASP.NET Core 的 DI 是採用 Constructor Injection,也就是說會把實例化的物件從建構子傳入。
如果要取用 DI 容器內的物件,只要在建構子加入相對的 Interface 即可。例如 Controllers\HomeController.cs:
1 | using Microsoft.AspNetCore.Mvc; |
輸出內容如下:
1 | [ISample] |
ASP.NET Core 實例化 Controller 時,發現建構子有 ISample 這個類型的參數,就把 Sample 的實例注入給該 Controller。
每個 Request 都會把 Controller 實例化,所以 DI 容器會從建構子注入 ISample 的實例,把 sample 存到欄位 _sample 中,就能確保 Action 能夠使用到被注入進來的 ISample 實例。
注入實例過程,情境如下:
註冊在 DI 容器的 Service 有分三種生命週期:
new
一個新的實例。new
一個新的實例,同一個 Request 不管經過多少個 Pipeline 都是用同一個實例。上例所使用的就是 Scoped。小改一下 Sample 類別的範例程式:
1 | public interface ISample |
在 Startup.ConfigureServices
中註冊三種不同生命週期的服務。如下:
1 | public class Startup |
如果有特殊需求,也可以透過委派的方式註冊。如下:
1 | public class Startup |
只要是透過 WebHost 產生實例的類別,都可以在建構子定義型態注入。
所以 Controller、View、Filter、Middleware 或自訂的 Service 等都可以被注入。
此篇我只用 Controller、View、Service 做為範例。
在 HomeController 中注入上例的三個 Services,範例 Controllers\HomeController.cs:
1 | public class HomeController : Controller |
Views\Home\Index.cshtml:
1 | <table border="1"> |
輸出內容如下:
從左到又打開頁面三次,可以發現 Singleton 的 Id 及 HashCode 都是一樣的,此例還看不太出來 Transient 及 Scoped 的差異。
Service 實例產生方式:
圖例說明:
View 注入 Service 的方式,直接在 *.cshtml
使用 @inject
,如範例 Views\Home\Index.cshtml:
1 | @using MyWebsite |
輸出內容如下:
從左到又打開頁面三次,Singleton 的 Id 及 HashCode 如前例是一樣的。
Transient 及 Scoped 的差異在這次就有明顯差異,Scoped 在同一次 Request 的 Id 及 HashCode 都是一樣的,如紅綠籃框。
簡單建立一個 CustomService,注入上例三個 Service,程式碼類似 HomeController。如下 Services\CustomService.cs:
1 | public class CustomService |
註冊 CustomService:
1 | public class Startup |
第一個泛型也可以是類別,不一定要是介面。
缺點是使用方以 Class 作為相依關係,變成強關聯的依賴。
在 Views\Home\Index.cshtml 注入 CustomService:
1 | @using MyWebsite |
輸出內容如下:
從左到又打開頁面三次:
在 Middleware 的 Invoke 可以獲取到 HttpContext,其中會包含 Request 的內容,包含 URL、Header 等。
Request Body 是 Stream 型別,要取出內容,可以透過 StreamReader 如下:
1 | using System; |
注意!
Seek(0, SeekOrigin.Begin)
非常重要,如果把 Stream 讀完後,不把 Stream Position 還原,之後的 Pipeline、Action 在取得 Request Body 時,會從 Stream 的結尾開始取資料,意味著取出來都是空資料。
Middleware 取得 Response Body 相較於 Request 麻煩很多,因為 Response.Body 的 Stream 並不允許被讀取讀取,但可以被替換。
所以在 Response.Body 開始被寫入之前,先抽換成 MemoryStream;這樣之後的 Pipeline 在寫入 Response.Body 時,實際上都是寫入到被抽換的 MemoryStream 之中。
等到下層 Pipeline 都做完的時候,就可以讀取 MemoryStream 的資料,讀完後再把 MemoryStream 寫到真實的 Response.Body。
範例如下:
1 | using System; |
上例 Seek(0, SeekOrigin.Begin)
被呼叫了兩次,原因:
_next(context)
會寫入內容到 fakeResponseBody,導致 Stream Position 會被指到結尾,為了讀取 fakeResponseBody 內容,所以要把 Stream Position 指回起始位置。執行流程如下:
ASP.NET Core 在 Middleware 的官方說明中,使用了 Pipeline 這個名詞,意旨 Middleware 像水管一樣可以串聯在一起,所有的 Request 及 Response 都會層層經過這些水管。
用圖例可以很容易理解,如下圖:
Middleware 的註冊方式是在 Startup.cs 的 Configure
對 IApplicationBuilder
使用 Use
方法註冊。
大部分擴充的 Middleware 也都是以 Use 開頭的方法註冊,例如:
一個簡單的 Middleware 範例。Startup.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
用瀏覽器打開網站任意連結,輸出結果:
1 | First Middleware in. |
在 Pipeline 的概念中,註冊順序是很重要的事情。資料經過的順序一定是先進後出。
Request 流程如下圖:
Middleware 也可以作為攔截使用,Startup.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
輸出結果:
1 | First Middleware in. |
在 Second Middleware 中,因為沒有達成條件,所以封包也就不在往後面的水管傳送。流程如圖:
Run
是 Middleware 的最後一個行為,以上面圖例來說,就是最末端的 Action。
它不像 Use
能串聯其他 Middleware,但 Run
還是能完整的使用 Request 及 Response。
Map
是能用來處理一些簡單路由的 Middleware,可依照不同的 URL 指向不同的 Run
及註冊不同的 Use
。
新增一個路由,Startup.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
開啟網站任意連結,會顯示:
1 | First Middleware in. |
開啟網站 http://localhost:5000/second
,則會顯示:
1 | First Middleware in. |
如果 Middleware 全部都寫在 Startup.cs,程式碼應該很難維護,所以應該把自製的 Middleware 邏輯獨立出來。
建立 Middleware 類別不需要額外繼承其它類別或介面,一般的類別即可,FirstMiddleware.cs 範例如下:
1 | using System.Threading.Tasks; |
在 Startup.cs 的 Configure
註冊 Middleware 就可以套用到所有的 Request。如下:
1 | using Microsoft.AspNetCore.Builder; |
Middleware 也可以只套用在特定的 Controller 或 Action。註冊方式如 Controllers\HomeController.cs:
1 | // .. |
大部分擴充的 Middleware 都會用一個靜態方法包裝,如:UseRouting()
、UseRewriter()
等。
自製的 Middleware 當然也可以透過靜態方法包,範例 CustomMiddlewareExtensions.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
註冊 Extension Middleware 的方式如下:
1 | using Microsoft.AspNetCore.Builder; |
HttpApplication
做為網站開始的進入點。.NET Core 把 Web 及 Console 專案都變成一樣的啟動方式,預設從 Program.cs 的 Main()
做為程式進入點,再從程式進入點把 Kestrel 實例化。
透過 .NET Core CLI 建置的 Program.cs 內容大致如下:
1 | using Microsoft.AspNetCore.Hosting; |
Main()
透過 CreateHostBuilder 方法宣告需要相依的相關服務,並設定 WebHost 啟動後要執行 Startup
類別。
.NET Core 3.0 官方建議的方式是透過 Generic Host 建立 Web Host。
但如果真的不想透過 Generic Host 建立 Web Host,可改成以下方式:
1 | using Microsoft.AspNetCore.Hosting; |
Host 建置時,WebHost 會呼叫 UseStartup
泛型類別的 ConfigureServices 方法。
Host 啟動後,WebHost 會呼叫 UseStartup
泛型類別的 Configure 方法。
透過 .NET Core CLI 建置的 Startup.cs 內容大致如下:
1 | using Microsoft.AspNetCore.Builder; |
Configure
方法的參數並不固定,參數的實例都是從 WebHost 注入進來,可依需求增減需要的參數。對 WebHost 來說 Startup.cs 並不是必要存在的功能。
可以試著把 Startup.cs 中的兩個方法,都改成在 WebHost Builder 設定,變成啟動的前置準備。Program.cs 如下:
1 | using Microsoft.AspNetCore.Builder; |
把 ConfigureServices
及 Configure
都改到 WebHost Builder 註冊,網站的執行結果會是一樣的。
若是透過 Generic Host 建立 Web Host,也可以在 HostBuilder 用 ConfigureServices
註冊 Services。
除了程式進入點外,WebHost 的停起也是網站事件很重要一環,ASP.NET Core 不像 ASP.NET MVC 用繼承的方式補捉啟動及停止事件。 而是透過 IHostApplicationLifetime
來補捉 WebHost 的停啟事件。
IHostApplicationLifetime
有三個註冊監聽事件及終止網站事件可以觸發。如下:
1 | public interface IHostApplicationLifetime |
透過 Console 輸出執行的過程,Program.cs 範例如下:
1 | using System; |
Startup.cs:
1 | using System.Threading; |
1 | [2019/10/24 01:24:59] [Program] Start |
輸出內容少了 [Program] webBuilder.Configure - Called,因為
Configure
只能有一個,後註冊的Configure
會把之前註冊的蓋掉。
執行流程如下:
開發 .NET Core 必需要安裝 .NET Core SDK,所以先到官網下載 .NET Core SDK 的安裝檔,官網下載位置點此。
.NET Core 是跨作業系統的框架,不再像 .NET Framework 要依附在 Windows 的作業系統才能執行,所以可以依照各平台版本進行下載及安裝。
文中範例用的截圖大部分是 Windows 作業系統,但本系列教學都會是以指令為主,並不受限於 Windows 平台。
(安裝軟體步驟太簡單,除了按下一步以外,幾乎沒什麼好解說的,所以不介紹怎麼安裝軟體。)
安裝完成後,可以透過 .NET Core CLI (Command-Line Interface) 確認 .NET Core SDK 安裝的版本,指令如下:
1 | dotnet --version |
先建立一個專案資料夾 MyWebsite
,然後在該資料夾執行 .NET Core CLI 建置網站的指令:
1 | # 建立專案資料夾 |
.NET Core CLI 會在該資料夾,建立一個空的 ASP.NET Core 專案,內容如下:
1 | obj/ # 專案暫存目錄 |
.NET Core 3.0 之後,
*.csproj
專案檔變得很乾淨俐落,如上圖。
當宣告<Project Sdk="Microsoft.NET.Sdk.Web">
就會預設參考Microsoft.AspNetCore.App
套件。
建立完成後,就可以用 .NET Core CLI 啟動網站了。啟動網站指令:
1 | dotnet run |
.NET Core CLI 預設會起一個http://localhost:5000/
的站台,用瀏覽器打開此連結就可以看到 ASP.NET Core 網站了。如下:
.NET Core 都已經跨作業系統了,開發工具當然也就不再限制於 Visual Studio IDE (Visual Studio 2019/2017 等)。基本上純文字編輯器搭配 .NET Core CLI 就可以開發 ASP.NET Core 了,但沒有中斷點除錯或 Autocomplete 開發有些辛苦。
VS Code 是一套可安裝擴充套件的文字編輯器,有支援 Windows、Mac 及 Linux 版本,極輕量又免費。
只要安裝擴充套件就變成了 IDE,並且支援多種不同的程式語言。下載位置點此。
範例選擇用 VS Code 最主要的原因是免費且跨平台。
打開 VS Code 可以在左邊看到五個 Icon,點選最下面的那個 Extensions 圖示,並在 Extensions 搜尋列輸入 C# ,便可以找到 C#
的擴充套件安裝。如下圖:
VS Code 跟一般文字編輯器有些不同,它是以資料夾為工作區域,開啟一個目錄,就等通於是開啟一個專案。從上方工具列 File -> Open Folder 選擇 ASP.NET Core 專案目錄,大概隔幾秒後,VS Code 會提示是否要幫此專案加入 Build/Debug 的設定。如下圖:
如果沒有自動提示加入 Build/Debug 設定,可以在左邊 Icon,點選倒數第二個 Debug 圖示,手動加入 Build/Debug 設定。如下步驟:
設定完成後,VS Code 會自動建立 .vscode 目錄及設定檔 launch.json、tasks.json。
目錄結構如下:
1 | .vscode/ # VS Code 設定檔目錄 |
如果 VS Code 自動建立失敗,那就手動新增 launch.json 及 tasks.json 吧…
內容如下:
launch.json:
1 | { |
tasks.json:
1 | { |
在程式碼行號左邊點擊滑鼠就可以下中斷點了,跟一般 IDE 差不多。然後在 Debug 側欄啟動偵錯:
當執行到該中斷點後,就會停下來,並在 Debug 側欄顯示當前變數狀態等,也可以用滑鼠移到變數上面檢視該變數的內容。如下:
偵錯方式跟大部分的 IDE 都差不多,可以 Step over、Step in/out 等。
如此一來就可以用 VS Code 輕鬆開發 ASP.NET Core。
如果沒有 SonarQube 環境的話,可透過以下 docker 指令,快速啟動 SonarQube:
1 | docker run --name SonarQube -p 9000:9000 sonarqube |
啟動後,用瀏覽器打開 http://localhost:9000/ 就能看到 SonarQube 站台。
預設帳號/密碼為:admin
/ admin
登入後,可新增 SonarQube 專案,步驟如下:
分析報告要上傳到 SonarQube Server 時,需要用到
Project Key
及步驟 8 的Token
。
build-unit-test.dockerfile
1 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2 |
Docker Image 建置指令:
1 | docker build -f build-unit-test.dockerfile . |
前一篇文章 Docker 教學 - .NET Core 測試報告 (Coverlet + ReportGenerator) 有基本介紹一下 Coverlet,本篇就不再贅述。
Project Key
是必填欄位。Token
,錯誤的話無法成功上傳到 SonarQube Server。,
隔開指定多個 Patterns。*.js
、*.ts
及 *.css
的檔案,如果要分析 *.js
、*.ts
或 *.css
,還需要在 Dockerfile
安裝 node.js
。,
隔開匯入多個檔案。,
隔開指定多個 Patterns。Token
,錯誤的話無法成功上傳到 SonarQube Server。dotnet cli 提供的 dotnet test
指令,並沒有支援測試覆蓋率,可透過第三方套件分析程式碼覆蓋率。
Coverlet 是一套支援 .NET Core 且跨平台的程式碼覆蓋率分析工具。
可透過 dotnet cli 的 dotnet tool
安裝,指令如下:
1 | dotnet tool install --global coverlet.console |
除了安裝 dotnet tool
外,測試專案也需要安裝 NuGet 套件 coverlet.msbuild
,安裝指令:
1 | dotnet add package coverlet.msbuild |
安裝完成後,就可透過 dotnet test
指令,附帶參數執行程式碼覆蓋率分析:
1 | dotnet test /p:CollectCoverage=true \ |
json
、lcov
、opencover
、cobertura
、teamcity
。coverage
資料夾產生 coverage.opencover.xml
檔案。dotnet test
程式碼覆蓋率分析完成會輸出如下畫面:
上面步驟透過 Coverlet 產生的 coverage.opencover.xml
檔案並不適合閱讀,所以透過 ReportGenerator 這套工具,把 opencover 格式的測試報告轉換成 HTML,再透過瀏覽器打開,呈現圖形化介面報告。
ReportGenerator 支援多種測試報告格式轉換,官方資料:
輸入格式 | 輸出格式 |
---|---|
|
|
安裝方式一樣可透過 dotnet cli 的 dotnet tool
安裝,指令如下:
1 | dotnet tool install --global dotnet-reportgenerator-globaltool |
安裝完成後,就可透過 reportgenerator
指令,將測試報告格式轉換:
1 | reportgenerator \ |
轉換完成就會生成一大堆的 HTML 檔案。如下:
用瀏覽器開啟
index.htm
就可以看到圖形化的測試報告進入點。
build-unit-test.dockerfile
1 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2 AS dotnet-test-env |
Docker Image 建置指令:
1 | docker build -f build-unit-test.dockerfile -t my-project-coverage . |
以上 Dockerfile
共分為兩個階段:
Coverlet
及 ReportGenerator
建置完成後,就可以用 docker run
把測試報告的 Docker Image 執行起來了。
指令:
1 | docker run -p 8080:80 my-project-coverage |
用瀏覽器開啟 http://localhost:8080/ 就可以看到圖形化的測試報告了。
近期產品是上到 GCP 跟阿里雲,本篇硬體規格會以雲端服務的 Server 規格做為參考的基準。
本範例產品的參考資料:
算是一個不大的小系統,用 Kibana 查詢近期一週的 ES Index 用量。如圖:
由於經費有限,所以只有使用兩台 ELK 做 HA,用 Master/Slave 架構,沒有做到 Cluster。
兩台 Server 分別裝載著 ELK 三個服務,架構如下:
此範例幾個月前從 GCP 轉移到阿里雲,在這兩個平台使用的 VM 等級如下:
Master/Slave 的機器規格都是一樣的。
產品運行超過半年,CPU 大約都落在 30% 左右,依照上述使用量,在阿里雲其中一台 ELK Server,近期一週的監控資訊:
找不到當時在 GCP 用量的截圖。
首先要評估預計要放到 ELK 的 Log 量,資料筆數及資料大小。
再來就是 Log Parsing 的規則,如果 Parsing 很複雜 CPU 就會佔用較高的資源。
ELK 三項服務,分別佔用資源情況:
服務 | CPU | RAM | Disk |
---|---|---|---|
Elasticsearch | 中高 | 高 | 極高 |
Logstash | 中 | 低 | 低 |
Kibana | 低 | 低 | 低 |
基本上可以完全不用考慮 Kibana 消耗資源。
主要高耗能的就是 Elasticsearch 跟 Logstash。
CPU 是比較難評估部分,因為 Log Parsing 的複雜度以及查詢 ES 的條件,都會強烈影響 CPU 的使用量。
ES 查詢所消耗的 CPU,阿里雲提供參考:
每個 vCPU core 大約可處理 20~40 GB 查詢資料。
(依據本例使用情境,CPU 消耗偏高一些,但也沒落差太多。)
Logstash 依照上述的情境,Log 每秒也才 500 筆左右,分配 vCPU * 1,其實綽綽有餘了。
建議每處理 1500 筆資料,就分一個 vCPU core。
ES 使用記憶體有兩個條件限制:
如果條件允許,就直上 64 GB 記憶體,然後把一半分給 ES。
ES 查詢很吃記憶體,尤其是大區間的查詢,根據阿里雲提供的參考:
每 1 GB RAM,大約可處理 10 GB 查詢資料。
Logstash 的記憶體用於緩存消化不完的資料,CPU 不夠力的情況下就會需要比較高的記憶體。
不過還是要依照實際使用量調整,在預估資源分配上,不用考慮太高的比重。
基本上 1 GB 以內都夠用,甚至 128 MB 都夠用。
ELK 實際存資料的是 ES,所以評估時就不考慮 Logstash 跟 Kibana。
系統碟分配 20 GB 基本上就很足夠了,甚至可以更低。
這邊只單純討論 ES 存資料的空間計算。
估計 ES 硬碟空間要注意的項目:
注意!儲存的資料大小,不等於原始資料的大小。
如果對資料加工不熟悉的話,建議在估計時* 1.5 倍
當作彈性倍率。
- 原始資料若透過 Logstash 加工,產生了很多欄位,且保留原始內容,實際存放的大小就會比原始資料大很多,可能會多到一倍。
- 若加工的欄位建的好,且加工完就拋棄原始內容,實際存放就會比原始資料更小。如圖:
預估硬碟空間公式如下:
主要資料 = 原始資料 * 1.5(彈性倍率)
節點硬碟需求 = 主要資料 * 1.15(保留空間) * (1 + 副本數量) / ES節點數量
假設預估每週產生 10 GB 的 Log,希望可以留 4 週,每個 ES 節點所需的硬碟空間:
10 GB * 4 * 1.5 * 1.15 * (1 + 0) / 1 ≒ 69 GB
10 GB * 4 * 1.5 * 1.15 * (1 + 1) / 2 ≒ 70 GB
10 GB * 4 * 1.5 * 1.15 * (1 + 1) / 3 ≒ 46 GB
10 GB * 4 * 1.5 * 1.15 * (1 + 3) / 5 ≒ 55 GB
1 | 無法連上這個網站 |
如圖:
瀏覽器因為安全性問題,不讓 ASP.NET Core 站台使用 HTTPS,可以透過以下指令把開發憑證清除,再重建:
1 | sudo dotnet dev-certs https --clean |
回想一下原因,應該是上週將 MacOS 系統更新後,同時更新一些軟體等造成。(好像有更新 Chrome)
HTTPS on macOS does not work running from the default ASP.NET Core Web App (MVC) template
]]>最近在一個特殊的網路環境下,因流量限制及安全性考量,產生了這個特別的需求,網路架構如下圖:
開發環境跟生產環境都可以連到外網,但兩邊的網路互不相通,只能透過 Cleanroom 存取兩邊的網路環境。
但上圖紅線部分,Cleanroom 連到生產環境有以下問題:
若直接將 Docker Images 從 Cleanroom 推上生產環境,每次佈署都會產生驚人的費用。
生產環境雖然可以連外網,但因安全性考量不能在外網建立私有 Docker Registry,所以只能在開發環境跟生產環境內部架設私有 Docker Registry。
在建置 Docker Image 時,可利用 Docker Layer 的特性,將非敏感資料及敏感資料分層,把敏感資料的 Layer Size 降至最低,透過 Cleanroom 傳送到生產環境,其餘則直接退送到外部公開的 Docker Registry。
Docker Image 分為以下三個階段,分別建置出三個 Docker Image,再拼裝成可直接被運行的 Docker Image。
此範例主要是以 ASP.NET Core 作為建置範例,但此範例不侷限於 ASP.NET Core 專案。
ASP.NET Core 編譯出來的專案,會依照 namespase 區分出對應的 DLL,例如:
而外部參考的第三方套件,會有自己所屬名稱的 DLL,利用此特性將內部開發的 DLL 及外部參考的 DLL 分離。
首先建立一個專門編譯的 Container,將編譯後的結果依照名稱特性,區分到不同目錄:
檔案 artifacts.dockerfile
:
1 | FROM mcr.microsoft.com/dotnet/core/sdk:2.2 |
建置 Docker Image,並打上一個自訂的 Tag 名稱:
1 | docker build -f artifacts.dockerfile -t artifacts:latest . |
artifacts.dockerfile
所產出的 Docker Image 包含的原始碼及編譯後的 DLL,只用在以下兩個 Docker Image 建置時使用。
使用完畢應立即刪除,最好不要推上 Docker Registry。
製作 ASP.NET Core Runtime 環境的 Docker Image,並以 Artifacts Image 作為檔案來源:
檔案 third-party.dockerfile
:
1 | FROM artifacts:latest as artifacts |
建置 Third-Party DLL 的 Docker Image,作為基底 Image:
1 | docker build -f third-party.dockerfile -t third-party:latest . |
third-party.dockerfile
產出的 Docker Image,是基於微軟官方所提供,隨處可得的公開資料內容;
而從 Artifacts 所複製進來的檔案,也都是第三方套件,並沒有什麼敏感性資訊。
由於此 Docker Image 不帶有敏感資訊,所以推上 Docker Hub 也無所謂。
最後以 third-party.dockerfile
為運行環境的基底,做出最後可被執行的 Docker Image。
檔案 app.dockerfile
:
1 | FROM artifacts:latest as artifacts |
建置運行環境的 Docker Image:
1 | docker build -f app.dockerfile -t my-project:latest . |
app.dockerfile
產出的 Docker Image,就是 Artifacts 當初編譯後的結果,只是將資料分在兩個 Dockre Image。
將以上 Docker Image 準備好後,就可以開始時實作此範例流程了:
1 | # 1. 把 my-project 推上開發環境的 Registry |
步驟 6 在推送的時候,會先比對目標 Docker Registry 使否已經有 Image 相依 Layers,如果有就不會傳送。
而 Layers 的比較依據是 sha,所以 Docker Image 名稱或 Tag 改變,也不會受影響。
dev-private-registry
、prod-private-registry
及public-registry
請記得換上實際的 Docker Registry URL。
新增一個檔案 setup-aspnet-core.sh
內容如下:
1 |
|
透過以下指令執行安裝腳本,便會自動安裝 ASP.NET Core Runtime 及 Nginx。
1 | sudo sh setup-aspnet-core.sh |
在 /etc/systemd/system/<自訂名稱>.service
新增一個服務,把 ASP.NET Core 的停起都透過系統服務控制。
例 /etc/systemd/system/my-website.service
內容如下:
1 | [Unit] |
注意!
dotnet
CLI 的路徑可能不一樣,有可能如上例在 /bin/dotnet 也有可能在 /usr/bin/dotnet
建議先用指令which dotnet
查看dotnet
CLI 的路徑。
服務相關指令:
1 | # 開啟,開機自動啟動服務 |
執行啟動指令後,再執行查看服務狀態確認是否執行成功。
新增檔案 /etc/nginx/conf.d/default_proxy_settings
,以便其他設定重複使用:
1 | proxy_http_version 1.1; |
ASP.NET Core Proxy 設定 /etc/nginx/conf.d/my-website.conf
:
1 | upstream portal { |
修改完成後,執行以下指令檢查及套用:
1 | # 檢查 Nginx 的設定是否有誤 |
套用以上設定後,架構如下圖:
.dockerignore
及 docker
指令參數等小技巧,讓專案目錄整理得比較乾淨。這個範例有使用到前端及後端的專案,目錄結構大致長成這樣:
1 | build/ # 存放建置 docker image 所需的檔案 |
大部分的 Dockerfile
範例,會把這個檔案放在方案根目錄;
但此檔跟建置相關,放到 build
目錄會比較是適合,同時改一下檔案名稱,比較容易識別。如:
build/build-image.dockerfile
內容如下:
1 | ### Build Stage |
- 檔案名稱可以自訂,附檔名也沒有任何限制;但可透過修改 IDE 自動偵測附檔名,把
*.dockerfile
都以Dockerfile
的格式開啟會比較方便。- ASPNET_PROJECT_NAME
名稱從外部帶入有個好處,就是大部分的 dotnet core 專案都適用這個Dockerfile
建置 Docker Image。
建置指令:
1 | docker build -f [DOCKERFILE_PATH] -t [IMAGE_NAME]:[TAG] --build-arg project_name=[ASPNET_PROJECT_NAME] . |
以上 Dockerfile
共分為兩個階段:
src
複製到 dotnet-build-env,執行 dotnet publish
把建置的結果放到 /publish
目錄。mcr.microsoft.com/dotnet/core/sdk:2.2
是拿來編譯用的,大小約 1.74 GB。/publish
目錄內的檔案,全部複製到最終階段 Container 的 /app
目錄,並指定 Docker 啟動時要執行的指令。mcr.microsoft.com/dotnet/core/aspnet:2.2
是執行階段用的,大小約 260 MB。alpine
版本。建置流程如下:
此範例除了有 ASP.NET Core 專案,同時也包含了前端專案在裡面,前端專案如果是用 TypeScript 編寫或其他需要 Webpack 打包等動作,都會需要 node.js。
因此,可以透過另一個暫存 Container,負責打包前端專案。
build/build-image.dockerfile
內容如下:
1 | ### Build Stage - dotnet |
- npm-build-env 先判斷專案目錄內是否有
package.json
檔案,若存在才會執行 npm build。這樣做的好處是,不管 dotnet core 專案是否有前端專案,都可共用此Dockerfile
建置 Docker Image。- 以此範例來說,
src/XXXX.WebPortal/wwwroot
目錄為前端專案打包的結果,ASP.NET Core 執行根路徑的wwwroot
目錄為靜態檔案的位置。若實作上的目錄名稱不同,需自行更改位置。
以上 Dockerfile
分為三個階段,在中間又插入了 npm run build
的動作,然後把最終結果複製到 Publish Stage。
建置流程如下:
從 Host 複製 src
目錄到 Container 時,可能會複製到不必要的檔案,如開發階段所產生的目錄: bin
、obj
及 node_modules
等。
為了加速複製檔案,我們可以在方案根目錄新增 .dockerignore
,告知 COPY 忽略這些目錄或檔案,例:
1 | .git/ |
Dockerfile
執行建置後,只會把最後一個 Container 賦予名稱當作最終結果,其他的 Stage 並不會消失,若執行 docker images
指令,會發現有一大堆顯示為 <none> 的 Docker Image。
可透過以下指令快速移除:
1 | # Linux / MacOS |
之前介紹過 ASP.NET Core 用 Open XML SDK 匯出 Excel 的功能,但沒介紹匯入 Excel。
被網友提問後,馬上補了這篇介紹 ASP.NET Core 利用 Open XML SDK 匯入 Excel 的基本用法。
Open XML SDK 這個套件支援 .NET 操作 Word、Excel、PowerPoint。
打開 NuGet 找到 DocumentFormat.OpenXml
並安裝。
1 | <form method="post" enctype="multipart/form-data" action="/api/import"> |
要操作 Excel 檔案,主要是透過 SpreadsheetDocument
物件。SpreadsheetDocument
可以讀取檔案,也可以直接讀取 Stream
。
此範例是從 ASP.NET Core 的 Request 取得 FileStream
,進而讀取 Excel 檔案:
1 | using System.Linq; |
DisableFormValueModelBinding
及Request.StreamFile
擴充方法請參考 [鐵人賽 Day23] ASP.NET Core 2 系列 - 上傳/下載檔案
Open-XML-SDK
Read Excel Files Using Open XML SDK In ASP.NET C#
How to create an Excel file in .NET using OpenXML – Part 1: Basics
歡迎使用 Open XML SDK 2.5 for Office
最近把 ASP.NET Core 專案從 2.1 升級到 2.2,原本正常的 Integration Test 跑不過了;
追根究底後才發現是,ASP.NET Core 2.2 的 Bug,用到注入 IHttpContextAccessor
發生 HttpContext
是 null
。
又踩到雷,還是踩到大地雷,搞了我好幾天!!!
在本機開發,直接運行 Integration Test 是正常的,但只要放到 Docker 裡運行 Integration Test 就跑不過;
Deploy 出去服務也都正常,想破頭都想不出來,覺得怎麼會這樣,還暫時改 Build Flow 跳過 Integration Test。
最後只好進到 Docker 下一大堆 Log 除錯,終於找到原因,DI 的 IHttpContextAccessor
,取用 HttpContext
竟然是 null
,導致拿不到 Session。
不查就算了,一查差點嘔了幾十兩血,沒想到是 dotnet core 2.2 的 BUG…
我們的情境偏偏只在 Docker + WebApplicationFactory 才會發生。雷阿~~~
不要注入 IHttpContextAccessor,改成注入 ISession。如圖:
在 CentOS 啟動 Kubernetes 遇到 Nodes NotReady 的問題。 使用 kubectl get nodes
查詢 Node 狀態,顯示 NotReady,如下:
1 | NAME STATUS ROLES AGE VERSION |
透過 journalctl -f -u kubelet
指令查詢 log,一直重複顯示以下訊息:
1 | Mar 07 19:57:32 k8s-master.xxxxxx.xxx kubelet[5454]: W0307 19:57:32.340979 5454 cni.go:203] Unable to update cni config: No networks found in /etc/cni/net.d |
kubelet 參數多了 network-plugin=cni
,但卻沒安裝 cni,所以打開設定檔把 network-plugin=cni
的參數移除。
可能在以下兩個檔案中的其中一個:
以 v1.13.4 的版本為例:
1 | #KUBELET_KUBEADM_ARGS=--cgroup-driver=cgroupfs --network-plugin=cni --pod-infra-container-image=k8s.gcr.io/pause:3.1 |
改完之後重啟服務:
1 | systemctl daemon-reload |
再次使用 kubectl get nodes
查詢 Node 狀態,顯示 Ready 囉,如下:
1 | NAME STATUS ROLES AGE VERSION |