歡迎光臨
每天分享高質量文章

用 docker-compose 啟動 WebApi 和 SQL Server

本系列文章所要做出的演示架構基於 .NET Core Web Api、MSSQL、Skywalking 和 nginx ,這些都會通過docker-compose一鍵創建/啟動容器,然後用 Azure DevOps 發佈上線。

所以本系列文章重點並不是如何寫好.NET Core,而是圍繞著 .NET Core 的容器化架構加上一部分 DevOps 的實踐

系列大綱

本系列文章會分為四個部分,大家可以通過大綱大概瞭解一下我們會涉及到哪些內容,是不是你想瞭解想學習的東西。

  1. 用 docker-compose 啟動WebApiSQL Server
  2. 在容器中集成SkywalkingAPM
  3. 通過nginx-proxy對 ESSkywalkingWebApi實現自動反向代理和HTTPS
  4. 通過Azure DevOps進行CI/CD和藍綠發佈

前提條件

本系列文章不會對.NET CoreDocker等做零基礎講解,如果遇到不會建專案,不懂某條命令一類的問題需要你多谷歌一下,遇到問題可以先看文章結尾的幫助信息,會寫一些對排錯有幫助的內容。

然後 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,然後為了演示方便,我們修改一下ValuesControllerGet方法。


[HttpGet("{id}")]
public ActionResult<string> Get(int id)
{
    return "您輸入的是:" + id;
}

然後修改一下launchSettings.json,將httpsUrl移除掉:

...
        "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.csCreateWebHostBuilder方法:

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

一個標準的應用當然不能少了資料庫,一個簡明扼要的教程就需要EFCoreCode 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在容其中將webapiSQL 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: '[email protected]#'
    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: '[email protected]#'
    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: "[email protected]#"
    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是什麼,能做什麼。

赞(0)

分享創造快樂