Tye - 使用 Tye 管理 ASP.NET Core 專案

21 June 2020 — Written by Sky Chang
#Tye#K8S#AKS#Container#Docker#SQLServer

前言

好吧,我知道 Dapr 文章還欠著 QQ ( 謎之聲 : GitHub Action 也還欠著喔~~ 很多都欠著喔 ),但因為某些因素,被抓來研究這個東西..所以就又開了新的分支...( 感覺線已經收不完了... )

Tye

在 2020 年的 Build 大會,提到了一個滿有趣的新工具,而這個東西就是今天的主題 Tye (念音為 泰~~),目前這個專案,還非常非常的新,屬於實驗性質的東西,而根據官網敘述,目前專案最快到 2020/11 結束,等等,沒看錯吧,11 月就結束了!?,依據官方給的時程來說是這樣沒錯,但官網也說了,最快是到 11 月結束,但如果成效不錯的話,就會繼續下去。而目前看起來,大家關注度也不錯 ( Star 也破 1.6k 了,小弟也貢獻了一顆 ),所以小弟自己推測,後續應該還是會持續發展。但後續會繼續使用 Tye 這個名詞,或是直接整併到 dotnet Tool 就看後續官方怎麼決定了。

那到底什麼事 Tye 呢,其實他的目的,主要是簡化未來眾多 Service 開發過程中,會不好處理的一些相關的事物,你可以把他想像成類似 Docker-Compose,可以透過一個指令,就把所有的 Service Run 起來,但 Docker-Compose 則是關注於 Container,而 Tye 則可以讓你同時使用 Dotnet Project 和 Container。( 目前屬於 dotnet 的專屬工具 )

除此之外,他也提供了,讓你快速部署到不同環境的功能與設定,可以一鍵部署到 K8S,AKS 等等,而這一篇,我們先來看看要如何使用 Tye。

安裝

目前 Tye 必須要 .NET Core 3.1 的版本,所以請記得先裝完 .NET Core 3.1。

安裝的步驟很簡單,我們只要透過 dotnet cli 就可以搞定,如下。

dotnet tool install -g Microsoft.Tye --version "0.3.0-alpha.20319.3"

而小弟的環境是 MacOS,且使用 zsh,所以,需要加上 .zprofile,讓 Tye 能在環境變數裡面,隨時都可以使用 tye 這個指令。

如果是 Windows 的使用者,可以加入到 PATH 的環境變數裡面。

cat << \EOF >> ~/.zprofile
# Add .NET Core SDK tools
export PATH="$PATH:/Users/skychang/.dotnet/tools"
EOF

使用 zsh ,加入 .zprofile 後,要使用底下指令重新讀取。

zsh -l

如果以前有安裝過,可以用底下指令升級。

dotnet tool update -g Microsoft.Tye --version "0.3.0-alpha.20319.3"

實際執行

接下來,我們就打算直接執行試試看,這邊,你可以下載 Study4 的 CrudAspNetCore 專案,或是自己建立一個新的 ASP.NET Core 專案都可以。

以 CrudAspNetCore 為案例,這個專案,主要用途是提供給前端用的後端 API,可以透過 api/test 來取得 Hello World 字串,很適合這邊使用。

當然,你也可以建立一個新的 ASP.NET Core 的 Web API,都可以。

完成專案後,我們進入到有 csproj 這個專案檔案的位置,執行底下指令,就可以讓 tye run 起來。

如前面提到的,這是 dotnet 專用的工具,所以別的語言可能就沒辦法使用。

tye run CrudAspNetCore.Api.csproj 

如果沒有指定專案,他也會自動搜尋這個目錄底下的 csproj,所以未來其實只要輸入底下指令就可以了。

tye run

執行完畢後,他預設會開一個 8000 port 的 Dashboard 給您使用,並且會亂數提供 ASP.NET Core 的 Port。請注意,這個 Port 是由 tye 給的,和我們自己透過 launchSettings.json 的無關,簡單的說,tye 會忽略 launchSettings 的設定,直接使用他自己的 Port 設定。

2020 06 21 15 15 46

連線到 localhost:8000 後,就可以看到 Dashboard,從這個 Dashboard 上,就可以看到,目前專案的位置,名稱,和他的網址。

2020 06 21 15 17 07

所以點下這個網址後,加上 /api/test 就可以看到 CrudAspNetCore 的回應。( /api/test 不是 tye 產生的,這是我們自己寫的 api,在 CrudAspNetCore 裡面,小弟已經先寫好這個 API,如果是自己建立的 API,請替換成自己的 API 位置來進行測試。)

2020 06 21 15 17 53

如果從 Dashboard 按下 Log,也可以看到目前 ASP.NET Core 的 Log 資訊,算是滿方便的一件事情。( 如果有顏色就更好了 QQ,不過現在 Tye 本來就是測試階段,就先多包涵了 )

2020 06 21 15 19 37

初始化 Tye

完成第一個後,我們想到的第一個問題就是,Port 是由 Tye 亂數跳動,很麻煩,因為有時候,開發階段的時候,希望固定的 Port,這樣就不用一直重開瀏覽器。而這要怎麼做??

其實 Tye 也提供了 yaml 的設定檔案,我們可以在 sln 的目錄下輸入底下指令,讓他產生 tye.yaml 這個檔案。( 通常,會放到 sln 下,而不是在 csproj 的目錄下,就如同開發 ASP.NET Core 的習慣。)

tye init

完成之後,他會產生底下的 yaml,其實就可以看到,目前我們的專案與名稱如下。

name: crudaspnetcore
services:
- name: crudaspnetcore-api
  project: CrudAspNetCore.Api/CrudAspNetCore.Api.csproj

接下來,我們就在底下加上 bindings,這代表我們希望透過綁定的方式,把 http 和 https 的 port 綁上去,這樣 Port 就會固定在 5000 (http) 和 5001(https) 了。

name: crudaspnetcore
services:
- name: crudaspnetcore-api
  project: CrudAspNetCore.Api/CrudAspNetCore.Api.csproj
  bindings:
    - name: http
      protocol: http
      port: 5000
    - name: https
      protocol: https
      port: 5001

加入 SQL Server Container

有了後端,沒有資料庫怎麼行!?,所以接下來,我們要處理 DB 的部分,在 Windows 底下,我們預設有 localdb 可以使用,但在 MacOS 底下,就只能依靠 SQL Server for Linux Container 了。而這邊,我們就打算使用 Tye 加上 SQL Server for Linux Container 一起整合開發。

接下來,我們一樣修改 tye.yaml,但這次我們加上的是 image,也就是 Docker 位置,這是一個 SQL Server for Linux Container 的影像檔。而如果有使用過 SQL Server Linux Container 的朋友,因該就會知道,要建立起 SQL Server Linux Container,需要給一些參數,這些參數,一樣也可以放在 Tye.yaml 裡面。

name: crudaspnetcore
services:
- name: crudaspnetcore-api
  project: CrudAspNetCore.Api/CrudAspNetCore.Api.csproj
  bindings:
    - name: http
      protocol: http
      port: 5000
    - name: https
      protocol: https
      port: 5001
- name: sky-hr-db
  image: mcr.microsoft.com/mssql/server:2017-latest
  env:
    - name: SA_PASSWORD
      value: "P@ssw0rd"
    - name: ACCEPT_EULA
      value: "Y"
  bindings:
  - port: 1433
    connectionString: "Server=${host},${port};Database=SkyHRDB;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=true"

此外,眼尖的大家,應該有注意到,最底下那一行,就是 ConnectionString,而這邊我們會先將 ConnectionString 設定好,到時候可以讓 ASP.NET Core 應用程式去抓這個 ConnectionString。( 沒錯,抓 tye.yaml 的 ConnectionString,而不是抓 appsettings.json 裡面的 )。

這邊,我們用了 ${host} 和 ${port} 這兩個變數,這兩個變數也代表著這台 SQL Server 到時候的 Host Name 和 Port。用這種方式,未來 Deploy 的時候,也會比較方便,因為就不用去管,在 ASK、K8S、Local 的時候,機器名稱是什麼了。

ASP.NET Core 取得 tye.yaml 連線字串

當然,設定完後,ASP.NET Core 並不會聰明到,自動就會使用 tye.yaml 的 ConnectionString。所以這邊要裝一個套件,稱為 Microsoft.Tye.Extensions.Configuration

我們可以使用 dotnet cli 去新增。

dotnet add 你的.csproj package Microsoft.Tye.Extensions.Configuration  --version "0.3.0-*"

但小弟通常記不起指令 XDDDD,所以都是去偷改 csproj.....

2020 06 22 17 28 45

完成後,我們就可以使用底下指令,取得 ConnectionString,其中 sky-hr-db 就是我們在 tye.yaml 定義的 SQL Server 的名稱,他透過名稱,就會找到 tye.yaml 的 ConnectionString

Configuration.GetConnectionString("sky-hr-db");

所以,我的 Startup.cs 就會變成類似這樣。當然,這樣的配置,在正式開發上面,可能還不夠,但我們這邊主要講 Tye,所以只會提到,如何抓到 tye.yaml 的 ConnectionString,而要怎麼在不同環境,都抓到不同的 ConnectionString 這件事情,我們這邊就先暫時不提。

2020 06 22 17 33 00

除了 DB ConnectionString 外,我們也可以使用底下指令,取得 Service 的 Uri,這種情境很適合 HttpClient 這種服務。

Configuration.GetServiceUri("backend");

完成後,基本上,我們的專案,就可以使用 tye.yaml 裡面定義的 ConnectionString。當然,不想使用 tye.yaml 裡面定義的 ConnectionString 也沒差,像以前可能會透過 ASP.NET Core 的環境變數,來調整 appsettings ,也是可以的,但 tye.yaml 有這個功能,就在這邊提一下了。

加入前端

接下來,我們要加入前端,大家一樣可以透過 GitHub 下載 CrudBlazorWasm 這個專案,這個專案是使用 Blazor Web Assembly 撰寫的,裡面會會有簡單的 Crud 去呼叫我們後面的 Web API 應用。

當然,你可以和官網一樣,將前後端都放到同一個 sln 底下,或是像我這邊,將前後端分成不同的 sln,這部分就看大家怎麼做。小弟這邊因為 GitHub 上的專案項目,是切成兩個的,所以就用兩組 sln 當作範例,大家也可以當作有一個前端工程師,和一個後端工程師在合作開發。( 難怪小弟都要人格分裂了,每天都在充當不同的角色.. )。

放在同一個 sln 回比較單純,但如果有前後端的工程師而言,有時候就會比較不適合,一來是前端有的時候會端單純寫 JS,並不會想裝 dotnet sdk,所以放到同一個專案的情況下,就變成前端工程師一定要裝 dotnet sdk 了,同樣的,後端工程師也可能要裝 node.js。

而分開的好處,都是各自獨立的,雖然在這邊的範例,還是用兩個專案,但其實我們可以使用上述的技巧,將前端或後端寫好的應用,包成 Container,這樣,前後端都不需要額外裝自己的 SDK 了。

而這邊,我們用分開當作範例。

因為是分開,所以我們前端一樣要有自己的 tye.yaml,所以我們可以進行初始化 ( 若從 GitHub 下載下來的,就不需要初始化了 )。

接下來,我們一樣要編輯前端的 tye.yaml,其實內容和後端很像,但現在一個 Project 是前端自己 ( CrudBlazorWasm.csproj ),Port 為 4000,另外再多增加一個後端 ( /Users/skychang/source/study4.github.io/CrudAspNetCore/CrudAspNetCore.Api/CrudAspNetCore.Api.csproj ),Port 為 5000。因為兩個不是放在同一個目錄,所以這邊就直接使用絕對路徑。

當然,如果後端有 Docker Image,也可以改用 Docker image。

name: crudblazorwasm
services:
- name: crudblazorwasm-web
  project: CrudBlazorWasm.csproj
  bindings:
    - name: http
      protocol: http
      port: 4000
    - name: https
      protocol: https
      port: 4001
- name: crudaspnetcore-api
  project: /Users/skychang/source/study4.github.io/CrudAspNetCore/CrudAspNetCore.Api/CrudAspNetCore.Api.csproj
  bindings:
    - name: http
      protocol: http
      port: 5000
    - name: https
      protocol: https
      port: 5001
- name: sky-hr-db
  image: mcr.microsoft.com/mssql/server:2017-latest
  env:
    - name: SA_PASSWORD
      value: "P@ssw0rd"
    - name: ACCEPT_EULA
      value: "Y"
  bindings:
  - port: 1433
    connectionString: "Server=${host},${port};Database=SkyHRDB;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=true"

這樣,前端工程師就可以使用 tye 來進行執行。

Note : 等等,你不是說前端不需要下載後端 Source Code 嗎,為什麼這邊還要使用後端的 Porject?,對,沒錯,這邊只是為了 Demo,所以我們前後端還是都用 Source Code,而且,小弟也希望讓大家知道,就算是不同的 sln,還是能在 tye.yaml 進行設定的。如果在真實環境,真的要隔離,可以採用 Docker Image 的做法,所以,以這篇來說,沒錯,雖然人格分裂成前後端工程師,但對於筆電上,你還是要安裝 sdk,不過因為前端用 Blazor,所以也只需要安裝 .NET Core

那對於後端的 tye.yaml,也要修改一下,其實也只是加入前端專案而已。

name: crudaspnetcore
services:
- name: crudaspnetcore-api
  project: CrudAspNetCore.Api/CrudAspNetCore.Api.csproj
  bindings:
    - name: http
      protocol: http
      port: 5000
    - name: https
      protocol: https
      port: 5001
- name: crudaspnetcore-web
  project: /Users/skychang/source/study4.github.io/CrudBlazorWasm/CrudBlazorWasm.csproj
  bindings:
    - name: http
      protocol: http
      port: 4000
    - name: https
      protocol: https
      port: 4001
- name: sky-hr-db
  image: mcr.microsoft.com/mssql/server:2017-latest
  env:
    - name: SA_PASSWORD
      value: "P@ssw0rd"
    - name: ACCEPT_EULA
      value: "Y"
  bindings:
  - port: 1433
    connectionString: "Server=${host},${port};Database=SkyHRDB;User Id=sa;Password=P@ssw0rd;MultipleActiveResultSets=true"

完成設定後,現在大家一定有一個問題,那我的前端,怎麼知道後端的位置?,沒錯,這就可以使用上面提到的 Configuration.GetServiceUri("後端名稱") 這個方法。但,理想是美好的,現實是殘酷的。如果你想使用這個方法在 Blazor Web Assambly 上,你可能就會和小弟一樣,浪費一整個晚上可以打鋼彈的時間,然後取到的值,永遠是 null。

Why? 為什麼不能用,目前官方找不到此 issue,但小弟看官方的文件,目前的推測,這種 GetServiceUri 的方法,是透過環境變數的設定,來取得。但 Blazor Web Assambly,只要是在瀏覽器上執行,也因此可能在 Tye 的執行週期,沒辦法設定環境變數,也可能在 Browser 裡面取不到環境變數,也可能官方根本沒想到 Blazor Web Assambly...

所以,目前前端的 baseAddress,還是使用預設寫死的方式。( localhost:5000 )

雖然有點不足,但其實也無傷大雅,畢竟人家專案才開始,後續還有很多調整空間。

後記

到目前為止,我們把 Tye 實際的運用到一個簡單的 Sample 上,原則上,小弟蠻滿意的,已經開始想把 Docker Compose 的設定移除了 XDDD,而這篇沒提到 Deploy,後面有機會,再聊談談 Deploy 上去。

最後,如果使用 Tye,想做 CI 的朋友,可以看看這篇

參考資料

Sky & Study4.TW