47 asp.net core mvc集成CI CD时,update database的时机选择 - xiaoxin01/Blog GitHub Wiki

使用 docker 来部署 ASP.NET CORE MVC,在部署到正式环境的时候,如何根据 migrations 更新数据库,是一个需要仔细考虑的问题。

方法大致有3个:

  1. 直接使用 aspnetcore-build 镜像来构建项目、更新数据库、运行项目,参考:Compose and ASP.NET Core with SQL Server
  2. 通过命令将 migrations 导出为通用 sql script,然后通过脚本更新 database,参考:How to best run Database Migrations
  3. 在 MVC app 中更新 database,参考:Razor Pages with EF Core in ASP.NET Core - Migrations

该如何选择呢?

直接使用 aspnetcore-build 镜像

Dockerfile如下:

FROM microsoft/aspnetcore-build:lts
COPY . /app
WORKDIR /app
RUN ["dotnet", "restore"]
RUN ["dotnet", "build"]
EXPOSE 80/tcp
RUN chmod +x ./entrypoint.sh
CMD /bin/bash ./entrypoint.sh

entrypoint.sh 脚本如下:

#!/bin/bash

set -e
run_cmd="dotnet run --server.urls http://*:80"

until dotnet ef database update; do
>&2 echo "SQL Server is starting up"
sleep 1
done

>&2 echo "SQL Server is up - executing command"
exec $run_cmd

可以看到,这种方法虽然可以达到效果,但是有两个问题:

  1. 集群部署时,每个单独的app都会执行 database update,可能会带来冲突
  2. 使用 aspnetcore-build 镜像,使得最终运行环境镜像浪费大量硬盘空间。

aspnetcore-build 和 aspnetcore 镜像大小对比:

https://hub.docker.com/r/microsoft/aspnetcore/tags/ https://hub.docker.com/r/microsoft/aspnetcore-build/tags/

微软官网提醒内容:

We recommend production apps should not call Database.Migrate at application startup. Migrate shouldn't be called from an app in server farm. For example, if the app has been cloud deployed with scale-out (multiple instances of the app are running).

Database migration should be done as part of deployment, and in a controlled way. Production database migration approaches include:

  • Using migrations to create SQL scripts and using the SQL scripts in deployment.
  • Running dotnet ef database update from a controlled environment.

导出为通用 sql script

在github上有人也问道,最好的更新数据库的方式是什么,最后他的解决方案是,通过命令将 migrations 导出为通用 sql script,然后用sql command更新数据库。

命令如下:

dotnet ef migrations script -o migrate.sql --idempotent

这种方法可以达到效果,且符合微软官网推荐,但是在实际的执行过程中,有如下问题:

  • 更新数据库的sql command,不同的 database 不同。
  • 需要为sql command额外维护一套 db connection

还是不太理想。

在 MVC app 中更新 database

有没有办法解决上面方法遇到的问题呢?

  • 不用研究不同的database需要如何执行sql command来导入数据
  • 不用额外维护一套 db connection

有,如果可以在部署的时候,让 mvc app 运行一次,专门并更新数据库,完成之后退出,则可以完美解决。

步骤如下:

  1. 修改 mvc app 可以读取 arguments,如果收到 updata dabase 命令,则专门更新数据库

Program.cs

public static IWebHost BuildWebHost(string[] args) =>
    WebHost.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostingContext, config)=>{
            config.AddCommandLine(args);
        })

Startup.cs

public void Configure(IApplicationBuilder app, IHostingEnvironment env, IApplicationLifetime appLifetime,
    DbContext dbContext)
{
    if (Configuration["updatedb"] == "true")
    {
        dbContext.Database.Migrate();
        appLifetime.StopApplication();
    }
}
  1. 建立一个 update database 的yml文件:

docker-compose.updatedb.yml

version: '3'

services:
  web:
    entrypoint:
      - dotnet
      - mvc.dll
      - --updatedb
      - 'true'
  1. 建立 update database 的 script:
#!/bin/bash

update_db_check_container_name="_mvc_"

echo "update database"
docker-compose \
    -f docker-compose.yml \
    -f docker-compose.development.yml \
    -f docker-compose.postgres.yml \
    -f docker-compose.updatedb.yml \
    up -d

# wait for database update
while [ "$(docker ps --quiet --filter name=$update_db_check_container_name)" ]
do
    echo "wait for update database"
    sleep 1
done
⚠️ **GitHub.com Fallback** ⚠️