第七色在线视频,2021少妇久久久久久久久久,亚洲欧洲精品成人久久av18,亚洲国产精品特色大片观看完整版,孙宇晨将参加特朗普的晚宴

為了賬號安全,請及時綁定郵箱和手機(jī)立即綁定

如何優(yōu)化我的DotNet Docker實例?

標(biāo)簽:
Docker 容器 .NET

关于如何改进Docker和DotNet构建的讨论和想法

我们公司的项目管理软件专注于Gantt图表和工作流。我们有四个主要的DotNet服务,我们的开发人员可以将这些服务在Docker中运行,也可以直接在他们的电脑上运行。

一般来说,我发现我的开发者更喜欢从他们选择的IDE内直接运行代码。这让他们能在编辑器里设置断点并重新构建代码;而作为管理者,我倾向于做出相反的选择。我发现快速切换不同的分支并在Docker中快速重建所有内容非常方便,例如,我可以通过一条命令 docker compose up -d --build 来快速重建所有内容。

这对我非常有用,因为我经常需要检查一些东西——帮助验证修复的有效性,或者检查某个组件或测试的行为。我不总是保持我的IDE加载;电话会议花的时间更多,而不是写代码。任何能加速Docker构建或减小Docker容器大小的方法都会帮助我更好地与团队合作。

咱们试试看能做点什么让我的Docker容器更实用一些。

让我们试着抛光我们的DotNet Docker容器,直到它们发光(来自Romina Campos在flickr上的照片)(Romina Campos, flickr)

在Docker容器中快速构建.NET应用

首先,我注意到的是,我想减少Docker容器中的文件数量。创建一个.dockerignore文件这一点很重要:这有助于我排除不需要上传到Docker容器的文件。这是我选择的。

忽略文件和文件夹
.git  注释:Git版本控制系统文件夹
.idea  
.sonaqube  注释:SonarQube质量管理系统文件夹
.vs  
.vscode  
**/[b|B]in  注释:匹配所有路径下的bin文件夹(不区分大小写)
**/[o|O]bj  注释:匹配所有路径下的obj文件夹(不区分大小写)
out  注释:输出文件夹
**/node_modules  
**/packages  
**/TestResults  注释:测试结果文件夹

我为什么选择这些被忽略的代码片段?

  • 将文件夹从我们的IDE和源代码控制系统中排除了。
  • 不需要复制如packagesnode_modules这样的包下载;我的构建过程会重新生成这些内容。
  • 我排除了本地构建的产物如binobj;因为我同时使用Windows和Mac机器,所以我需要排除大写和小写版本。
  • 当然,也不必复制测试结果。

这有帮助,但下一步更为关键:我不得不将所有的源代码仓库整理到有限数量的文件夹中。我们将仓库整理成两个文件夹,分别是 ApplicationsCommon。这意味着在 Dockerfile 中,我无需执行 COPY . .,而是用两条语句,只复制相关的文件。

    FROM mcr.microsoft.com/dotnet/sdk:9.0  # 从微软镜像拉取 .NET SDK 9.0 版本
    WORKDIR /src  # 设置工作目录到/src
    COPY ./Applications /src/Applications  # 复制 ./Applications 到 /src/Applications
    COPY ./Common /src/Common  # 复制 ./Common 到 /src/Common

这大大加快了我的构建流程。现在 Docker 只跟踪 ApplicationCommon 文件夹。如果有人提交了不同文件夹的内容,例如“数据库脚本”和“测试”文件夹,Docker 现在知道不需要重新构建容器,因为这些更改对容器无关紧要。

使用 DotNet-Subset(一个子集工具)简化包恢复过程

接下来的性能改进步骤是避免不必要的 dotnet restore 语句。运行恢复命令会让 DotNet 下载所有包依赖,这可能需要花费不少时间。幸运的是,有一个 dotnet-subset 工具,可以让 Docker 跟踪 .csproj 文件的变更,从而确保仅当这些文件发生变化时才需要运行 dotnet restore。

去年我写了关于使用DotNet-Subset的文章使用DotNet-Subset加速你的Docker构建,从那以后我一直都很满意。

这里最大的挑战在于,dotnet-subset能否加快我的构建速度?添加后,虽然构建步骤增加了,但是更多的步骤被缓存了。我对自己得到的结果感到满意。

但我发现下一步是,我的容器太大了!我的笔记本电脑运行这些容器时,占用了超过一半的可用内存。我还有什么办法可以让容器更小一些吗?

使用较小的容器模板开发DotNet

DotNet 提供了一种简化的容器形式,称为 精简容器。虽然 DotNet 团队说 精简容器可以显著减少你的依赖项,但我却发现它们有不少问题。很多代码无法运行起来,我花了太多时间去弄清楚精简容器缺少了哪些我需要的功能。

相反,我选择使用微软的Alpine Linux实例。这些是更小、更精简的Linux发行版,主要针对无头服务器安装。它们比普通的DotNet容器镜像要小……不过,它们也有一些问题。

在我的情况下,问题主要集中在SQL Server组件的构建过程中。这些组件无法构建,直到我在我的Dockerfile中添加了一些额外的逻辑来构建用于全球化的Unicode数据。我还发现我的代码中有些部分需要处理时区的问题,而.NET DateTime类的某些功能只有添加了tzdata后才可用。

    # 使用全球化支持用于 DotNet alpine 实例  
    # https://github.com/dotnet/dotnet-docker/blob/main/samples/enable-globalization.md#alpine-images  
    # 同时安装 "tzdata" 用于提供时区信息  
    ENV \  
        DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \  
        LC_ALL=en_US.UTF-8 \  
        LANG=en_US.UTF-8  

    RUN apk add --no-cache \  
        icu-data-full \  
        icu-libs \  
        tzdata  # 时区数据

现在我的代码终于完成了!但用了好多内存。到底是哪里出了问题?

工作站垃圾回收 vs 服务器垃圾回收

当你在你的电脑上运行一个DotNet应用程序时,DotNet会选择使用所谓的'工作站模式'垃圾回收。在工作站模式下,DotNet运行速度会稍微慢一些,但在不再需要内存时会快速释放。这让你的电脑感觉更流畅。

但当你在一个 Docker 容器中运行一个 .NET 应用程序时,你会发现你正在使用的是服务器模式垃圾回收。在服务器模式下,.NET 假设它可以完全使用机器上的所有资源,并且只有在必要时才会释放内存。

哎呀!瞧!如果我在我的桌面计算机上运行四个独立的dotnet容器,每个容器都将使用服务器垃圾回收模式,并认为可以无限占用内存。你可以在你的Dockerfile中添加以下内容来切换容器到工作站垃圾回收模式。

# 禁用工作站垃圾收集器  
# (关于垃圾收集器的更多信息,请参阅此链接)  
# https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector  
ENV \  
    DOTNET_gcServer=0  
把所有的东西整合起来

我的Dockerfile会经历三个关键阶段。

  • 首先,第一个构建阶段执行 dotnet-subset 以保存还原文件
  • 其次,第二个构建阶段执行 dotnet restoredotnet publish
  • 最后,第三个构建阶段仅复制可执行文件和最终运行时

我的Dockerfile 和这个非常相似:

    # 构建阶段一 - 选择仓库中的部分文件以执行 dotnet restore 操作
    FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS restore-env  
    ENV PATH="${PATH}:/root/.dotnet/tools"  
    RUN dotnet tool install --global --no-cache dotnet-subset  
    WORKDIR /restore  
    COPY ./Applications /restore/Applications  
    COPY ./Common /restore/Common  
    RUN dotnet subset restore Applications/MyApp/MyApp.csproj \  
      --root-directory /restore --output restore_subset/  

    # 构建阶段二 - 执行恢复(如果未检测到变更,则恢复将被缓存)并构建
    FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build-env  
    WORKDIR /src  
    COPY --from=restore-env /restore/restore_subset .  
    RUN dotnet restore /src/Applications/MyApp/MyApp.csproj  
    COPY ./Applications Applications  
    COPY ./Common Common  
    RUN dotnet publish Applications/MyApp/MyApp.csproj -c Release -o out  

    # 构建运行时镜像  
    FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine  

    # 添加全球化支持并配置工作站垃圾回收模式  
    # 参见 https://github.com/dotnet/dotnet-docker/blob/main/samples/enable-globalization.md#alpine-images  
    # 和 https://learn.microsoft.com/en-us/dotnet/core/runtime-config/garbage-collector  
    ENV \  
        DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false \  
        DOTNET_gcServer=0 \   
        LC_ALL=en_US.UTF-8 \  
        LANG=en_US.UTF-8  

    RUN apk add --no-cache \  
        icu-data-full \  
        icu-libs \  
        tzdata  

    # 启动服务  
    WORKDIR /app  
    COPY --from=build-env /src/out .  
    ENTRYPOINT ["/app/MyApp"]

结合我的SQL Server 全文搜索 Docker 容器后,我拥有一套技术栈,可以通过执行 docker compose up -d --build 命令随时重建。

但如果是我想重新初始化我的SQL服务器怎么办?当我测试代码时,我经常需要在不同的分支之间切换,而分支A有一个SQL脚本,而分支B却没有,反之亦然。

我接下来的一个大挑战是快速简单地完成从零开始重建SQL Server。

为 SQL Server 容器自动化的重建流程

我的基本 SQL Server (微软的数据库管理系统) Dockerfile 如下:

    FROM mcr.microsoft.com/mssql/server:2022-latest  

    # 切换到 root 用户以安装 full-text search - apt-get 不切换用户无法工作!  
    USER root  

    # 安装依赖 - 这些依赖项用于下面对 apt-get 的更改  
    RUN apt-get update && \  
        apt-get install -yq gnupg gnupg2 gnupg1 curl apt-transport-https && \  
    # 安装 SQL Server 包链接 - 为什么这些包链接未内置在镜像中?这真奇怪。  
        curl https://packages.microsoft.com/keys/microsoft.asc -o /var/opt/mssql/ms-key.cer && \  
        apt-key add /var/opt/mssql/ms-key.cer && \  
        curl https://packages.microsoft.com/config/ubuntu/22.04/mssql-server-2022.list -o /etc/apt/sources.list.d/mssql-server-2022.list && \  
        apt-get update && \  
    # 安装 SQL Server 全文搜索功能 - 只有添加上述包链接才能安装  
        apt-get install -y mssql-server-fts && \  
    # 清理 - 删除之前添加的 Microsoft 包的链接  
        apt-get clean && \  
        rm -rf /var/lib/apt/lists  

    # 启动 SQL Server 服务  
    ENTRYPOINT [ "/opt/mssql/bin/sqlservr" ]

我在Docker Compose中这样用它:

      # 主数据库  
      sqlserver:  
        container_name: sqlserver  
        build:  
          dockerfile: sqlserver/sqlserver-fulltext.Dockerfile  
        ports:  
          - 1433:1433  
        environment:  
          # 在Linux环境下使用Docker来运行SQL Server时,密码的复杂度要求比较低  
          # 我们需要修改默认密码以符合该复杂度要求,或者在启动之后再改密码  
          SA_PASSWORD: "SomeStrongPassword123!@#"   
          ACCEPT_EULA: "Y"

我发现,当使用我们公司默认的本地数据库密码时,SQL Server 的 Docker 启动过程会失败,所以我需要在容器启动后按照以下方法更改密码。

#
# 启动 SQL Server 后运行的 PowerShell 脚本
#

# 将密码重置为旧 .NET 框架项目中使用的不安全值
docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd \
  -S localhost -U sa -Q \
  "ALTER LOGIN sa WITH check_policy=OFF, password='other-password'" \
  -P "SomeStrongPassword123!@#" -C
$success = $success

# 完成了吗?
if (-not $success) {
    Write-Output "密码更改失败。"
} else {
    Write-Output "sa 密码已成功替换。"
}

我选择用PowerShell来编写这个脚本,因为它可以在Mac上安装PowerShell。我在使用WindowsSubsystemforLinux在Mac和Windows上编写可在Mac和Windows上相同运行的bash脚本时,并没有取得很大的成功,所以选择了PowerShell。

接下来的任务是,我应该如何把数据库重置到一个已知的状态?我决定将其拆分为几个单独的步骤。

  • 从备份中恢复SQL Server实例
  • 使用 shell 脚本执行 SQL 脚本

这里有一个PowerShell脚本,它会解压一个备份文件,然后在Docker中恢复这个备份文件。举个例子,文件restoredb.sql中包含了用于从备份恢复SQL Server所需的所有语句。

    #
    # 启动 SQL Server 后运行的脚本
    #

    # 使用 NewDb.zip 从备份恢复数据库
    if ($IsWindows) {
        $basePath = "c:/temp/restoredb";
    } else {
        $basePath = "/tmp/restoredb";
    }

    # 清理 $($basePath) 文件夹以确保干净
    if (Test-Path -Path $basePath) {
        Write-Output "清理 $($basePath) 文件夹"
        Remove-Item -path $basePath -force -recurse
    } else {
        Write-Output "不清理 $($basePath) 文件夹"
    }
    mkdir $basePath
    Expand-Archive ../../DBScript/NewDB/NewDB.zip -DestinationPath $basePath

    # 将脚本复制到 Docker,执行,然后清理
    docker cp $basePath sqlserver:/var/opt/mssql/restoredb
    docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost \
      -U sa -i /var/opt/mssql/restoredb/RestoreDB.sql -P other-password -C
    docker exec -it sqlserver rm -rf /var/opt/mssql/restoredb

    # 删除临时解压文件夹
    Remove-Item -path $basePath -force -recurse

    # 完成了!
    Write-Output "您现在有了一个全新的数据库实例!"

运行 SQL 脚本很简单;只需复制脚本文件并通过 sqlcmd 执行它。

    # 将脚本文件从本地复制到SQL Server容器内
    docker cp ../../DBScript/myscript.sql sqlserver:/var/opt/mssql/myscript.sql  
    # 在SQL Server容器内执行SQLCMD命令,以交互模式和终端连接到SQL服务器
    docker exec -it sqlserver /opt/mssql-tools18/bin/sqlcmd -S localhost \  
      -U sa -i /var/opt/mssql/fix-pmadmin.sql -P other-password -C  # 注意:other-password是占位符,请替换为实际的密码
    # 上述命令中的“-it”参数表示交互模式和分配终端
    # 文件路径“/var/opt/mssql/myscript.sql”和“/var/opt/mssql/fix-pmadmin.sql”分别指向需要复制和执行的脚本文件

注意:在命令中使用了标准的中文术语“SQLCMD”和“SQL服务器”。

有了这些脚本,我可以在几分钟内彻底删除SQL Server的Docker容器,并快速重启一个全新的干净实例。

还有哪些值得考虑的改进呢?

Docker 容器设计的难点在于我想要减小磁盘使用、内存使用以及构建时间。如果我只是复制所有文件而不进行多阶段构建,构建时间就会更快;但是这样每个镜像都会占用大量磁盘空间。

我一直在考虑的一个选择是——我是否应该只用一个构建实例?这样会是这样的。

  • 复制我所有的 dotnet 文件、项目和解决方案
  • 一次性重建所有
  • 只复制我需要的应用程序到每个微服务容器中

这种方法的好处是,每次修改文件后只需重建一次;问题是每次修改任何文件时都必须重建整个解决方案。

我最终决定不采用这种方法,而是让这四个容器各自独立。也就是说,如果我只需要处理容器A,我只需重建容器A,以此类推,其他容器也一样。但如果有需要从一个大模块跳到另一个大模块,我可能需要重建所有四个容器。还没找到完美的平衡,但会继续尝试调整。

祝你好运,也请告诉我你都用哪些技巧来使用你的 DotNet 容器!

泰德·斯彭斯在ProjectManager.com担任工程主管(Head of Engineering),并在贝尔维尤学院授课。如果你对软件工程和商业分析感兴趣的话,欢迎通过MastodonLinkedIn与我交流。

點擊查看更多內(nèi)容
TA 點贊

若覺得本文不錯,就分享一下吧!

評論

作者其他優(yōu)質(zhì)文章

正在加載中
  • 推薦
  • 評論
  • 收藏
  • 共同學(xué)習(xí),寫下你的評論
感謝您的支持,我會繼續(xù)努力的~
掃碼打賞,你說多少就多少
贊賞金額會直接到老師賬戶
支付方式
打開微信掃一掃,即可進(jìn)行掃碼打賞哦
今天注冊有機(jī)會得

100積分直接送

付費專欄免費學(xué)

大額優(yōu)惠券免費領(lǐng)

立即參與 放棄機(jī)會
微信客服

購課補貼
聯(lián)系客服咨詢優(yōu)惠詳情

幫助反饋 APP下載

慕課網(wǎng)APP
您的移動學(xué)習(xí)伙伴

公眾號

掃描二維碼
關(guān)注慕課網(wǎng)微信公眾號

舉報

0/150
提交
取消