通过Julie Lerman |2019 年 5 |获取代码
在上个月的专栏中 (msdn.com/magazine/mt833405),我创建了要在 Docker 容器中运行的 ASP.NET Core API 项目。使用 EF Core 的项目保留的数据。在该专栏中,我开始使用 SQL Server LocalDB,但它的出现时间运行的 Docker 容器内部的应用,我遇到了阻碍,因为当前的安装程序假定 SQL Server LocalDB 在容器内已存在。作为快速解决方案,我切换到使用 SQLite,在容器内生成了新项目时将安装。使用此方法,即会在调试期间成功的完整解决方案。
切换到 SQLite 是只需一个使即时地满足需求。让我们继续有关正确发现解决方案的发布的 API 中的 Docker 映像时,针对 SQL Server 的学习之旅。此列将重点介绍面向始终可用的 Azure SQL 数据库,可从任何位置。
API 指向 Azure SQL 数据库
我的起点是专栏的解决方案,因为我在我以前末尾保留。如果你尚未阅读,您可能想要从这里开始上下文。在整篇文章中,我将改进该解决方案按位。
尽管 Azure SQL 数据库上,EF Core 可以为您创建一个数据库,必须预先存在 SQL Server。我已有几个 SQL 服务器上设置 Azure 中,因此我将使用其中一种此列的演示。
首先,我将添加名为 MagsConnectionAzSql 新的连接字符串-指向该服务器,到 appsettings.json,其中已包含我在第 1 部分中使用的连接字符串。尽管 JSON 不兼容返回,我已包装长行以提高可读性。我还会包含假凭据:
"ConnectionStrings": {"MagsConnectionMssql":"Server=(localdb)\\mssqllocaldb; Database=DP0419Mags;Trusted_Connection=True;","MagsConnectionSqlite":"Filename=DP0419Mags.db;","MagsConnectionAzSql": "Server=tcp:msdnmaglerman.database.windows.net,1433; Initial Catalog=DP0419Mags;Persist Security Info=False; User ID=lerman;Password=eiluj; MultipleActiveResultSets=False;Encrypt=True; TrustServerCertificate=False;Connection Timeout=30;"
接下来,我将更改启动文件的 ConfigureServices 方法,以使用 SQL Server 提供程序和此连接字符串中的 DbContext 配置:
services.AddDbContext(options => options.UseSqlServer( Configuration.GetConnectionString("MagsConnectionAzSql")));
这样,EF Core 迁移,以便在设计时查找连接字符串,因此我可以运行任何所需的命令。在运行时,应用将能够找到连接字符串。事实上,由于我最后的工作是使用 SQLite,我需要重置迁移,这在我的演示程序意味着删除 Migrations 文件夹并运行添加迁移 initSqlAz,若要获取数据库的正确描述的迁移。
迁移存储后,我将在运行应用使用 Docker 配置文件。这将证明对我来说,在 program.cs 中的迁移方法能够在云中创建数据库和控制器就可以对其进行查询,都是在 Docker 容器 — 按预期方式。第一次应用程序在运行迁移,必须创建数据库,会有短暂的延迟。完成后,不仅不会在浏览器中继三个杂志与我设定种子的数据库 (在以前的专栏),作为图 1所示,但我可以看到列出了在 Azure 门户和 Visual Studio SQL Server 中的数据库对象资源管理器。
图 1API 的输出列出使用种子设定在 DbContext 中定义的三个杂志
请注意,EF Core 创建的数据库的默认配置设置的定价层: 标准 S0:10 个 Dtu。您可以进行更改以便在门户中或对于生产应用使用 Azure CLI 时。事实上,对于生产环境,你可能想要显式创建数据库,以确保其设置对齐到你的需求。然后可以使用 EF Core 迁移来管理架构。
有关处理机密的注意事项
尽管这得非常漂亮,它还不是生产的工作。有几个问题需要考虑。
首先,连接字符串和机密是硬编码到 appsettings.json 文件。很容易地修改而无需重新编译该项目,因此"硬编码"则有点夸大之辞的 JSON 文件中的该连接字符串。但它不是动态 Docker 是而言,因为 appsettings 文件将"内置"到 Docker 映像。可能需要更好地控制用于开发、 暂存、 测试和生产连接字符串。这不是新问题和各种解决方案已存在于相当长的时间。相对于保密,ASP.NET Core 中的新机密管理工具可帮助在开发过程。请参阅上的文档bit.ly/2HeVgVr有关详细信息。此外,appsettings.json 文件是文本,任何人都可以读取它从源代码管理如果你意外地将其推送到公共存储库以及应用程序。
容器化解决方案的更好地路径是使用 Docker 环境变量,该应用可以读取在运行时,同时继续将无法在设计时运行迁移命令。这还使您灵活地动态提供对这些变量的值。
下面是计划:在设计时,以及用于测试的应用"裸机"; 我将使用 SQL Server LocalDB也就是说,当 Kestrel 或 IIS 服务器上进行调试。同时,LocalDB 不需要凭据,因为我无需担心其连接字符串中的机密。用于运行和本地调试在 Docker 中,我可以切换到 SQL Server 上我的网络或指向 Azure SQL 数据库。最后,为容器化应用程序的生产版本,我会确保指向 Azure SQL 数据库。在整个,您将看到如何使用 Windows 和 Docker 环境变量,以确保你的密码的安全。
而以下是一些极大地方便:由于所有这些方法使用某个版本的 SQL Server,因此我可以始终使用相同的提供程序,Microsoft.EntityFrameworkCore.SqlServer。
移动到开发设置开发连接字符串
ASP.NET Core 将默认为 appsettings。运行时从 Visual Studio 中,但在生产环境中其默认值为 appsettings.json Development.json 设置文件。
我将从 appsettings.json 中删除整个 connectionStrings 部分和,而是将 LocalDB 连接字符串添加到 appsettings。Development.json。如果展开解决方案资源管理器中的 appsettings.json 旁边的箭头标志符号,你会发现此文件:
"ConnectionStrings": {"MagsConnectionMssql": "Server=(localdb)\\mssqllocaldb;Database= DP0419Mags;Trusted_Connection=True;" }
因为我希望应用程序能够读取在设计时此连接字符串和环境变量在运行时提供的 Dockerfile,我需要使用不同的语法为 UseSqlServer 选项。当前 (和最常),使用 Configuration.GetConnectionString 来从 appsettings 文件中读取字符串。这不会起作用,但是,对于环境变量,无论他们是从 Windows 或 Docker。GetConnectionString 是一种帮助器方法,它可以取代直接引用的属性名称。
但是,我可以阅读的 appsettings 值和环境的任何值作为键 / 值对使用此语法:
services.AddDbContext(options => options.UseSqlServer( Configuration["ConnectionStrings:MagsConnectionMssql"]));
让我们验证 EF Core 迁移可以在 appsettings 中找到连接字符串。Development.json,可以通过运行 PowerShell 迁移命令 Get DbContext 执行此操作。这强制 EF Core 执行相同的工作与任何其他迁移命令,并会导致显示提供程序名称、 数据库名称和数据源的输出:
providerName databaseName dataSource options
------------ ------------ ---------- -------
Microsoft.EntityFrameworkCore.SqlServer DP0419Mags (localdb)\mssqllocaldb None
对于只有 Docker 的自己创建的连接字符串
因此,现在我可以在设计时知道 appsettings 的工作原理。如何允许 Docker 容器运行,而无需修改 startup.cs 如来回跳转时查找其替代连接字符串?
最好知道 CreateWebHostBuilder 调用该方法在 program.cs 调用 AddEnvironmentVariables,它将读取可用的环境变量并将其存储为配置数据中的键 / 值对。
Docker 有一个名为允许您设置的键-值对的 ENV 命令。首先,我将通过硬编码这到 Dockerfile。我可以将具有相同名称的新密钥设置为 JSON 文件,它是什么需要 UseSqlServer 配置中的一个。我甚至可以在该密钥的名称包含冒号。 创建生成映像之前,我将我 ENV 变量放在文件中。图 2演示 Dockerfile,包括新的环境变量。如果需要返回以复习一下,我介绍了本系列的第 1 部分中的此文件的内容。请注意,我缩简为便于阅读此处的连接字符串。
图 2 中的位置的连接字符串具有 DataAPIDocker 项目 Dockerfile
FROM microsoft/dotnet:2.2-aspnetcore-runtime AS base
WORKDIR /app
EXPOSE 80
ENV ConnectionStrings:MagsConnectionMssql=
"Server=tcp:msdnmaglerman.database.windows.net ...”
FROM microsoft/dotnet:2.2-sdk AS build
WORKDIR /src
COPY ["DataAPIDocker/DataAPIDocker.csproj", "DataAPIDocker/"]
RUN dotnet restore "DataAPIDocker/DataAPIDocker.csproj"
COPY . .
WORKDIR "/src/DataAPIDocker"
RUN dotnet build "DataAPIDocker.csproj" -c Release -o /app
FROM build AS publish
RUN dotnet publish "DataAPIDocker.csproj" -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
ENTRYPOINT ["dotnet", "DataAPIDocker.dll"]
我们来看这有什么影响。务必调试配置文件所指向 Docker,让我们实际调试这一次。我只是在 Startup.cs 和调试,在 AddDbContext 命令之后设置断点,然后检查配置"ConnectionStrings:MagsConnectionMssql"的值。我可以看到它现在返回 Azure SQL 数据库的连接字符串,不是 LocalDb 连接字符串。调试到所有可用的配置数据,我可以看到 appsettings 连接字符串还加载到配置对象。但通过 Randy Patterson 解释在他的博客文章bit.ly/2F5jfE8,最后一个设置将替代早期设置。和 appsettings 后才读取环境变量。因此,即使有两个 ConnectionStrings:MagsConnectionMssql 值,指定在 Dockerfile 中的一个是正在使用的一个。如果在 Kestrel 或 IIS 中运行此数据,不会执行 Dockerfile 和其环境变量不会存在于配置。
创建机密的占位符
但 ENV 变量不是变量。因为它是硬编码,它只是静态稍后再试。此外,请记住,它仍包含机密 (登录名和密码)。而不是让它们在连接字符串中,我将首先提取到其自己的环境变量的以下两个密码。对于占位符名称中,我将使用 ENVID 和 ENVPW。然后我将在 Dockerfile 中的用户 ID 和密码创建两个环境变量,并作为第一次传递中,我将指定其值直接:
ENV ConnectionStrings:MagsConnectionAzSql=
"Server=tcp:msdnmaglerman.database.windows.net,1433;
Initial Catalog=DP0419Mags;
User ID=ENVID;Password=ENVPW; [etc...]
ENV DB_UserId="lerman"
ENV DB_PW="eiluj"
返回在 Startup.cs ConfigureServices,我将读取所有三个环境变量,生成使用其凭据的连接字符串:
varconfig =newStringBuilder (Configuration[“ConnectionStrings:MagsConnectionMssql”]);stringconn = config.Replace(“ENVID”, Configuration[“DB_UserId”]) .Replace(“ENVPW”, Configuration[“DB_PW”]) .ToString();services.AddDbContext(options => options.UseSqlServer(conn));
这有效轻松,因为所有所需的值都是在 Dockerfile 中。但仍变量是静态的在 Dockerfile 中公开的机密。
移出 Dockerfile 和 Docker Compose 的机密
Docker 容器只能访问在容器内的环境变量,因为我当前的安装程序不提供任何方式来定义密码或其他 Dockerfile 中指定的环境变量的值。但没有一种方法,允许您走近进一步在 Docker 专业知识。Docker 提供名为 Docker Compose,它可用于处理包含多个映像的功能。这由 docker 控制-compose 指令文件,可以触发并运行一个或多个 Dockerfiles,与每个控制其自己的 Docker 映像的 Dockerfile。目前,我有单个图像,我将坚持使用的。但我仍然可以利用的 Docker Compose 来将值传递到图像的环境变量。
为什么这是很重要?它允许我确保我的密码超出 Dockerfile 和传出的映像。容器实例启动,以及任何动态配置信息,例如不同的连接字符串时,我可以传入机密。有关于本主题是非常不错的文章bit.ly/2Uuhu8F,我找到非常有用。
使用 docker-compose 文件来协调多个容器称为容器业务流程。这可以帮助 Visual Studio 的 Docker 工具。右键单击该项目在解决方案资源管理器,然后选择添加,并选择容器业务流程协调程序支持。系统将显示一个下拉列表,从中你应选择 Docker Compose,出现提示时,选择 Linux 作为目标 OS。因为 Dockerfile 已存在,系统将要求是否想要将其重命名并创建新的 Dockerfile。回答这个问题否保持不变的现有 Dockerfile。接下来,您将会要求你想要覆盖隐藏的.dockerignore 文件。由于还未接触该文件,其中任何一个选项是确定。
此操作完成后,您将看到两个文件与 docker compose 的新解决方案文件夹名为:.dockerignore 和 docker-compose.yml。Yml 扩展指的是 YAML 语言 (yaml.org),依赖于缩进来表达的文件架构以非常简洁雅致的文本格式。
工具创建中的以下 docker compose.yml:
version: '3.4'
services:
dataapidocker:
image: ${DOCKER_REGISTRY-} dataapidocker
build:
context: .
dockerfile: DataAPIDocker/Dockerfile
它具有只有一个服务: dataapidocker 项目。它会通知您的映像名称是 dataapidocker 和时即可生成该映像的 docker 文件的位置。
有很多种方法可使用 docker compose 可以将环境变量传递到的容器 (dockr.ly/2TwfZub)。我将首先将该变量置于直接在 docker-compose.yml 文件中。首先,我将添加一个环境部分 dataapidocker 服务内的,同一级别作为映像和生成。然后,在新的节中,我将定义 DP_PW 变量使用特定的格式如下所示:
version: '3.4'
services:
dataapidocker :
image: ${DOCKER_REGISTRY-}dataapidocker
build:
context: .
dockerfile: DataAPIDocker/Dockerfile
environment:
- DB_PW=eiluj
别忘了从 Dockerfile 中完全删除 DB_PW 变量。Docker compose 将确保该变量传递到正在运行的容器,但它不会存在于图像本身。
现在,若要运行项目,你将需要确保 docker compose 解决方案文件夹设置为启动项目。请注意,调试按钮设置为 Docker Compose。若要查看神奇才,代码建立的连接字符串启动中放置一个断点和调试应用程序。你应看到配置"DB_PW"确实能够找到从 docker-compose 中传递的值。
和最后,移动机密值超出 Docker Compose
但我仍有 docker-compose 文件中的机密和你知道和我知道,在某一时刻我将其推送到我公共源代码管理意外。在我的计算机,而不是在 Docker 映像上运行的 docker compose。表示 docker compose 可以访问主机上的信息。我可以创建一个环境变量来存储密码,并允许 docker compose 我开发计算机上发现它。您甚至可以在 Visual Studio 包管理器控制台中创建临时环境变量。但是,Docker 提供了用于读取.env 文件支持更好的选择。
默认情况下,docker compose 将读取名为".env。"的文件 为文件名称,只是".env。"没有基 还有可能要.env 文件名称和在 docker compose,请使用 env_file 映射来指定服务说明中。请参阅我的博客bit.ly/2CR40x3的名为的.env 文件的详细信息。
可以使用这些存储变量,如连接字符串,例如,dev.env、 test.env 或 production.env;或机密。当我使用此工具来添加容器业务流程时,此工具创建的 docker.ignore 文件列出了.env 文件,以便那些不会意外地推送到源代码管理。
Visual Studio 不会让你将文件添加到 docker-compose 项目,所以,我解决此问题,获得了: 将新的文本文件添加到解决方案项的文件夹,然后将其移到 docker-compose 项目。
我会将我的机密放入.env 文件和文件内容只是:
DB_PW=eiluj
图 3显示了我最终的解决方案如下所示。
图 3 包括新的.env 文件的最终解决方案
这样一来,我可以只需设置密码,而我将开发和调试应用程序并不担心有可能会共享任何文件中。此外,我可以提供其他变量的配置信息的选项。
我的密码仍是纯文本,但是,,这样做也可以在我的计算机上。可能需要在生产中,这些不过加密。Elton Stoneman 提供此指南在他的著作,"Docker 上 Windows,Second Edition"(Packt 发布,2019 年 2 月)。
后续步骤
为我的一个明显的下一步是将容器部署和了解如何获取我的 Azure SQL 数据库到容器实例使用的密码的环境变量。这一挑战需要付出大量的读取和试验和在我已运行对于本专栏的空间不足时,我已发表有关执行此操作在此完全在 Azure bit.ly/2FHdbAM。我还编写了有关发布到 Docker 和驻留在 Azure 虚拟机中的 Docker 博客。我将这篇文章的联机版本的 url 更新为,当其可用。
此多篇连载专栏的下一期中的计划是转换到从目标 Azure SQL 数据库到其自己的容器中的 SQL Server 数据库。这将合并什么已了解到目前为止 docker-使用从以前的专栏获得的教训编写 ("动态上的 SQL Server 使用 Docker" msdn.com/magazine/mt784660)。发布映像和在云中运行的容器将介绍两个引用的博客文章。
Julie Lerman 住在佛蒙特州的丘陵地区,担任 Microsoft 区域主管、Microsoft MVP、软件团队导师和顾问。可以在全球的用户组和会议中看到她对数据访问和其他主题的介绍。她的博客地址是 thedatafarm.com/blog。她是“Entity Framework 编程”及其 Code First 和 DbContext 版本(全都出版自 O’Reilly Media)的作者。请通过 Twitter 关注她 (@julielerman),并观看她在 bit.ly/PS-Julie 上的 Pluralsight 课程。
衷心感谢以下技术专家对本文的审阅:Steven 绿色和 Mike Morton (Microsoft)、 Elton Stoneman (Docker)
Elton Stoneman 是 Pluralsight 的作者,Microsoft MVP 和 Docker 开发大使。他已构建和交付自 2000 年,最近的大数据和在 Azure 中,API 实现和使用 Docker 的分布式应用程序的成功与 Microsoft 技术的解决方案。
目前他已对感 Microsoft 堆栈的演变探索提供的大好机会来更新运行的.NET Framework 应用使用 Docker,并在 Windows 和 Linux 容器中的新.NET Core 应用程序运行它们。
他是在会议上发表的正则表示器和研讨会主机。他一直幸运在 DockerCon、 NDC、 DevSum、 ProgNet、 SDD、 容器 Camp 和未来解码。您会经常看到我在用户组太-Docker 伦敦、 伦敦 DevOps 和 WinOps 是我局部变量。
In the second in a series on using EF Core and Docker together to build a containerized app and add a data persistence mechanism, Julie Lerman looks at production-worthy solutions for targeting SQL Server when publishing an API inside of a Docker ima...
5月 1, 2019