本系列文章所要做出的演示架構基於 .NET Core Web Api、MSSQL、Skywalking 和 nginx ,這些都會透過docker-compose
一鍵建立/啟動容器,然後用 Azure DevOps
釋出上線。
所以本系列文章重點並不是如何寫好.NET Core
,而是圍繞著 .NET Core 的容器化架構加上一部分 DevOps 的實踐。
系列大綱
本系列文章會分為四個部分,大家可以透過大綱大概瞭解一下我們會涉及到哪些內容,是不是你想瞭解想學習的東西。
- 用
docker-compose
啟動WebApi
和SQL Server
- 在容器中整合
Skywalking
APM - 透過
nginx-proxy
對ES
、Skywalking
、WebApi
實現自動反向代理和HTTPS
- 透過
Azure DevOps
進行CI/CD
和藍綠釋出
前提條件
本系列文章不會對.NET Core
、Docker
等做零基礎講解,如果遇到不會建專案,不懂某條命令一類的問題需要你多谷歌一下,遇到問題可以先看文章結尾的幫助資訊,會寫一些對排錯有幫助的內容。
然後 Azure DevOps
部分會要求你有一個 Linux 伺服器,沒有的可以用信用卡去 Google Cloud Platform、Azure、AWS 等自己免費擼一個,我推薦 Google Cloud Platform。
環境條件
- NET Core 2.2 SDK
- Docker 最新版(目前是 18.09.2)
- SQL Server
接下來我們開始系列第一篇。
建立 .NET Core WebApi 專案
首先我們建立一個.NET Core WebApi
專案,將其命名為Core.API
,然後為了演示方便,我們修改一下ValuesController
的Get
方法。
[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
return "您輸入的是:" + id;
}
然後修改一下launchSettings.json
,將https
的Url
移除掉:
...
"Core.API": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "api/values",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
//"applicationUrl": "https://localhost:5001;http://localhost:5000"
"applicationUrl": "http://localhost:5000"
}
...
然後點選這裡執行看一下效果:
接下來,為了讓WebApi
在容器中被順利訪問,我們需要讓Kestrel
監聽外網流量,修改Program.cs
的CreateWebHostBuilder
方法:
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup()
.UseUrls("http://0.0.0.0:5000");
建立 Dockerfile
找到我們的解決方案檔案夾,建立一個名為Dockerfile
的檔案。
然後將以下內容複製進去:
FROM mcr.microsoft.com/dotnet/core/sdk:2.2-stretch AS publish
WORKDIR /src
COPY . .
RUN dotnet publish "/src/Core.API/Core.API.csproj" -c Release -o /app
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2-stretch-slim
EXPOSE 5000
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "Core.API.dll"]
開啟你的命令列工具,指向到 API 專案中,將下列命令中的[yourusername]
替換為你的 Docker 使用者名稱,當然如果你沒有登入的話,你需要先執行docker login
登入一下,然後依次執行下列命令:
docker build -t [yourusername]/coreapi .
docker run -p 5000:5000 -it --rm --name coreapi [yourusername]/coreapi
不出意外等一段時間你應該會發現自己的 API 在容器裡活潑地蹦達了起來,我們這次訪問http://localhost:5000/api/values/5這個 Url,你應該能看到跟之前一樣的結果。
好,我們按下Ctrl+C
讓它冷靜下來,繼續教程。
配置 Entityframework Core
一個標準的應用當然不能少了資料庫,一個簡明扼要的教程就需要EFCore
的Code First
。
我們新建一個名為Core.Entity
的.NET Standard
類庫,開啟你的程式包管理控制檯,將預設專案指向Core.Entity
,安裝以下依賴:
Install-Package Microsoft.EntityFrameworkCore -Version 2.2.4
Install-Package Microsoft.EntityFrameworkCore.SqlServer -Version 2.2.4
Install-Package Microsoft.EntityFrameworkCore.Design -Version 2.2.4
Install-Package Microsoft.Extensions.Configuration.Json -Version 2.2.0
Install-Package Microsoft.Extensions.Configuration.FileExtensions -Version 2.2.0
新建兩個類檔案,一個叫CoreContext
,作為我們的資料庫背景關係:
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System;
using System.IO;
namespace Core.Entity
{
public class CoreContext : DbContext
{
public CoreContext() { }
public CoreContext(DbContextOptions options)
: base(options)
{
}
public virtual DbSet Post { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
var config = new ConfigurationBuilder()
.SetBasePath(Path.Combine(Directory.GetCurrentDirectory()))
.AddJsonFile("appsettings.Development.json", optional: false).Build();
optionsBuilder.UseSqlServer(config["ConnectionString"]);
}
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.HasAnnotation("ProductVersion", "2.2.0-rtm-35687");
#region Seeds
string[] titles = new string[] {
"本教程由Siegrain傾情奉獻?️",
"感謝大家關註~",
"部落格地址為 http://siegrain.wang",
"本教程Github地址為 https://github.com/Seanwong933/.NET-Core-with-Docker"
};
for (var i = 0; i < titles.Length; i++)
{
builder.Entity().HasData(new Post
{
Id = i + 1,
Title = titles[i],
Content = Guid.NewGuid().ToString()
});
}
#endregion
}
}
}
一個叫Post
,作為我們的示例物體:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Core.Entity
{
public class Post
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string Content { get; set; }
}
}
接下來在`Core.API`中新增`Core.Entity`的專案依賴,然後在程式包控制檯執行以下命令:
add-migration initial
update-database
它會建立你的初始遷移檔案,然後將其更新到資料庫中。
![](Untitled-c6fc13c0-281f-434a-a86b-9dcc4a2059cd.png)
如果報錯了,檢查一下`appsettings.Development.json`中的連結字串是否正確。
接下來,我們需要在`Startup.cs`的`ConfigureServices`和`Configure`方法中進行一些修改,並設定`EFCore`在發現沒有資料庫或資料庫過期時自動遷移。
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddScoped();
services.AddDbContext(options =>
{
options.UseSqlServer(Configuration["ConnectionString"]);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseHsts();
}
using (var serviceScope = app.ApplicationServices.GetService().CreateScope())
{
var context = serviceScope.ServiceProvider.GetRequiredService();
context.Database.Migrate();
}
app.UseHttpsRedirection();
app.UseMvc();
}
然後修改一下ValuesController
,好讓其獲取我們想要的資料:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
private readonly CoreContext _context;
public ValuesController(CoreContext context)
{
_context = context;
}
[HttpGet]
public IActionResult Get()
{
return Ok(_context.Post.ToList());
}
[HttpGet("{id}")]
public IActionResult Get(int id)
{
var entity = _context.Post.Find(id);
if (entity == null) return NotFound();
return Ok(entity);
}
}
我們再次執行看下是否能夠正確獲取了。
建立 docker-compose 檔案
接下來,我們要用docker-compose
在容其中將webapi
跟SQL Server
一併執行起來。
在解決方案檔案夾下建立 docker-compose.yml
檔案:
貼上以下內容:
version: '3.3'
services:
coreapi:
container_name: coreapi
image: siegrainwong/coreapi:latest
ports:
- 5000:5000
depends_on:
- sqlserver
links:
- sqlserver
volumes:
- ./Core.API/appsettings.docker.json:/app/appsettings.json:ro
restart: always
sqlserver:
image: mcr.microsoft.com/mssql/server:2017-latest
container_name: sqlserver
restart: always
environment:
ACCEPT_EULA: Y
MSSQL_PID: Developer
SA_PASSWORD: 'NetCore123!@#'
ports:
- 1433
然後在當前目錄執行docker-compose up -d
這時候再看下是不是正常運行了。
但此時SQL Server
的資料也是隻在容器中的,意味著容器被移除後資料就會丟失,所以我們要透過掛載volume
的方式持久化資料。
在docker-compose
檔案中新增箭頭處的程式碼:
version: '3.3'
services:
coreapi:
container_name: coreapi
image: siegrainwong/coreapi:latest
ports:
- 5000:5000
depends_on:
- sqlserver
links:
- sqlserver
volumes:
- ./Core.API/appsettings.docker.json:/app/appsettings.json:ro
restart: always
sqlserver:
image: mcr.microsoft.com/mssql/server:2017-latest
container_name: sqlserver
restart: always
environment:
ACCEPT_EULA: Y
MSSQL_PID: Developer
SA_PASSWORD: 'NetCore123!@#'
volumes:
- coredata:/var/opt/mssql # ports:
- 1433
volumes:
coredata: #
此時我們可以先將容器用docker rm -f coreapi
移除,再跑起來後用docker logs coreapi
命令看看有沒有EFCore
的遷移日誌就知道資料有沒有被持久化了。
幫助資訊
實用的 docker 命令
docker ps
docker images
docker rm -f [container_name]
docker exec [container_name] [commands]
docker exec -it [container_name] bash
docker logs [container_name]
帶註釋的docker-compose.yml
檔案
因為編碼問題帶中文註釋的不太容易被識別,所以沒有放進原始碼中
version: "3.3"
services:
coreapi:
# 容器名
container_name: coreapi
# 映象 由 使用者名稱/映象名:版本 組成
image: siegrainwong/coreapi:latest
ports:
# 宿主機對映:容器內部對映
- 5000:5000
depends_on:
# 在 sqlserver 容器啟動後啟動
- sqlserver
links:
# 允許連線到 sqlserver 容器
- sqlserver
# 將 appsettings.docker.json 掛載到容器中的 appsettings.json 檔案並設為只讀:ro
volumes:
- ./Core.API/appsettings.docker.json:/app/appsettings.json:ro
# 總是重啟
restart: always
sqlserver:
image: mcr.microsoft.com/mssql/server:2017-latest
container_name: sqlserver
restart: always
# 環境變數
environment:
ACCEPT_EULA: Y
# SQL Server 版本
MSSQL_PID: Developer
# SA 密碼
SA_PASSWORD: "NetCore123!@#"
ports:
# 不需要透過宿主機連線容器的 sqlserver,所以不需要主動指定外部埠
- 1433
案例參考
這是我正在開發的一個開源部落格系統,該系列文章的這套架構就出自這個專案,覺得不錯的話希望可以幫我點顆星鼓勵一下;如果你是從 https://siegrain.wang 看見這篇文章的,那麼你現在所看見的就是這個專案: )
https://github.com/siegrainwong/ancorazor
專案原始碼
https://github.com/Seanwong933/.NET-Core-with-Docker/tree/master/Part1
本篇到此為止,下一篇文章將帶領大家在容器中整合skywalking
監控webapi
,不瞭解的同學可以先去瞭解一下skywalking
是什麼,能做什麼。
朋友會在“發現-看一看”看到你“在看”的內容