docker

使用Docker中的Windows Container构建.NE

2022-10-07  本文已影响0人  冰麟轻武

0. 背景

鉴于目前公司的旧项目是.NET4.5开发的,为方便部署,打算使用docker的Windows Container做一个打包镜像

目前基于Windows Container的例子太少,所以也确实踩了不少坑,这里记录一下

1. 操作系统版本

Windows家庭版是可以安装Docker,启动并成功运行Linux Container。
但如果要切换到Windows Container将会卡在启动阶段,且不会有任何报错!

卡在启动阶段
由于刚换了笔记本,平时也没注意系统版本,这个问题卡了1天,中间尝试了各种命令,配置服务,注册表都无效;
最后切换到“Windows专业版”问题解决。

如果遇到卡在启动阶段一直无法成功启动的,不妨换系统版本试一试。

2. Git版本

因为目前的windows sdk镜像中不包含wingetchoco所以如果需要额外的软件支持是需要自己打包镜像的时候安装进去的。
当时下载了git官网最新的2.38.0,在本地跑的好好的一段脚本,在docker直接卡死无响应。

每当执行到`git pull`时就卡死
在网上搜了一圈,偶尔发现一篇文章,说是git版本太低导致命令卡死,但是,我下的是最新版本啊?,抱着试一试想法,把git版本退回本地版本2.34.1.windows.1,问题解决。

另外使用git还需要对git进行一些安全方面的配置,这个网上可以随便找到

@rem build.bat
# 设置git安全策略
RUN git config --global --add safe.directory "*"
RUN git config --system http.sslverify false

3. 设置nuget缓存

做过CI的同学肯定都知道nuget必须是要缓存的,否则每次构建都需要完整的下载全部nuget依赖,会很慢。
.net core可以通过dotnet restore --packages "..."的方式指定缓存文件夹,但dotnet restore命令在framework的项目中无法使用,只能通过nuget.exe的来还原,他们的设置方式不一样。
需要通过命令设置全局缓存文件夹:nuget config -set globalPackagesFolder=c:/nuget repositoryPath=c:/nuget,这一步可以放在Dockerfile中执行

4. 安装NodeJs

Web项目构建可能需要依赖NodeJs,但微软的SDK镜像中并不包含,需要自己安装。

缺少NodeJs
安装方式:
node-v16.17.1-x64.msi
###Dockerfile
COPY ./node-v16.17.1-x64.msi c:/bat/node-v16.17.1-x64.msi
# 安装nodejs
RUN msiexec /a "C:\bat\node-v16.17.1-x64.msi" /qb TARGETDIR="C:\nodejs"
# 设置环境变量
RUN setx path '%path%;C:/nodejs/nodejs'

5. NuGet私仓鉴权

这个问题我还不知道发生的原因,现象就是:

将nuget账号密码配置在Dockerfile阶段会失效,在编译时依然显示需要账号密码鉴权
目前解决方案是将配置操作放在编译阶段

### build.bat
nuget source remove -name mynuget
nuget source add -source http://mynuget/nuget-hosted/ -Name mynuget -Username mynuget -Password mynuget

6. NuGet还原失败1

这是一个已知的bug,它会阻止nuget restore的操作,显示全部项目已还原,但实际上并没有 具体看这里
解决方法是在 *.csproj 文件中删除类似下面这样的代码

 <!-- web.config -->
 <Target Name="EnsureNuGetPackageBuildImports" BeforeTargets="PrepareForBuild">
    <PropertyGroup>
      <ErrorText>这台计算机上缺少此项目引用的 NuGet 程序包。使用“NuGet 程序包还原”可下载这些程序包。有关更多信息,请参见 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。</ErrorText>
    </PropertyGroup>
    <Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.4.6.4\build\Microsoft.TypeScript.MSBuild.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.4.6.4\build\Microsoft.TypeScript.MSBuild.props'))" />
    <Error Condition="!Exists('..\packages\Microsoft.TypeScript.MSBuild.4.6.4\build\Microsoft.TypeScript.MSBuild.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\Microsoft.TypeScript.MSBuild.4.6.4\build\Microsoft.TypeScript.MSBuild.targets'))" />
    <Error Condition="!Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.props')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\EntityFramework.6.4.4\build\EntityFramework.props'))" />
    <Error Condition="!Exists('..\packages\EntityFramework.6.4.4\build\EntityFramework.targets')" Text="$([System.String]::Format('$(ErrorText)', '..\packages\EntityFramework.6.4.4\build\EntityFramework.targets'))" />
  </Target>

7. NuGet还原失败2

如果项目引用关系比较复杂,msbuild通过单个csproj文件分析项目关系还原依赖会出现问题;
nuget restore "%csproj%" -PackagesDirectory "packages" -NonInteractive -Recursive
之后可能会出现编译失败的情况
解决方案:通过sln项目文件还原依赖没事
nuget restore "%sln%" -PackagesDirectory "packages" -NonInteractive

8. Access is denied.

这个问题我也没搞懂是为什么,目前观察到的现象就是:

编译项目A,A引用了B,使用msbuild -r "A项目目录" -property:OutDir="输出目录";编译A项目,同时会将B项目也输出到输出目录,此时在docker中无法操作B项目输出的文件,删除,移动,重命名等操作均返回Access is denied

Access is denied

重新运行一个容器可以正常操作;
起初以为是有进程锁文件,但使用tasklist没有观察到异常进程;
再次怀疑是权限不足,使用takeown提升权限,显示成功,但依然无法操作。

这种情况在每次编译一个项目时并不会有什么太大的问题,但如果一次编译多个项目,且项目之间有引用关系时,编译第二个项目就会出现因为文件无权限操作而编译失败的情况。

目前的解决方案是,编译项目到各自项目的bin目录,最后再xcopy到发布目录

@rem build.bat
msbuild -r "%~1" -verbosity:n -property:WarningLevel=0;OutDir=.\bin\;Configuration=%MODE%
if exist "%~p1bin\_PublishedWebsites\%~n1" (
    call :Exec "xcopy /seyfrhq "%~p1bin\_PublishedWebsites\%~n1" C:\publish\%~n1"
) else (
    if exist "%~p1bin\_PublishedWebsites" (
        call :Exec "xcopy /seyfrhq "%~p1bin\_PublishedWebsites" C:\publish\%~n1"
    ) else (
        call :Exec "xcopy /seyfrhq "%~p1bin" C:\publish\%~n1"
    )
)

9. 多个分支并发构建

由于实际场景中项目较大,考虑到每次都从git拉取整个项目时间较长,所以选择挂载项目目录到docker容器中;
但这就出现了多个Runner同时构建不同分支时,必然会互相影响

解决方案是:将.git文件夹复制到docker容器内部,然后执行还原+切换分支,再进行构建,这样即使多进程同时构建也不会互相影响。

@rem build.bat
cd "c:\src"
mkdir C:\source\.git & xcopy /seyfrhqk "c:\src\.git" "c:\source\.git"
mkdir C:\source\packages & xcopy /seyfrhqk "c:\src\packages" "c:\source\packages
cd "c:\source"
git clean -dfx -q -e web.config -e packages & git reset --hard & git reset HEAD . & git checkout .
git checkout %BRANCH% && git pull origin %BRANCH% -q

10. build.bat

echo off

if exist "c:\bat\DEBUG" if "%1"=="" (
    echo "DEBUG MODE"
    cmd
    exit /b
)

call :Exec "rd /s /q c:\publish"
cd "c:\src"

if "%MODE%"=="" set MODE=DEBUG
if "%BRANCH%"=="" set BRANCH=master


echo "=================================================================="
echo "==== bat version 2022-10-07 ======================================"
echo "====  2022-10-07 14:54:48  ======================================"
echo "============ dir ================================================="
echo "BRANCH     = %BRANCH%"
echo "MODE       = %MODE%"
echo "SLN        = %SLN%"
echo "CSPROJ     = %CSPROJ%"
echo "=================================================================="

call :Exec "git fetch --all" 

call :Exec "mkdir C:\source\.git & xcopy /seyfrhqk "c:\src\.git" "c:\source\.git""
@REM call :Exec "mkdir C:\source\packages & xcopy /seyfrhqk "c:\src\packages" "c:\source\packages""
call :Exec "cd "c:\source""
call :Exec "git clean -dfx -q -e web.config -e packages & git reset --hard & git reset HEAD . & git checkout ."
call :Exec "git checkout %BRANCH% && git pull origin %BRANCH% -q" 

for /r c:\src %%i in (web.config) do (
    if exist %%i (
        call :CopyToSource %%i
    )
)

@REM 如果有私仓,这里进行配置
@REM call :Exec "nuget source list |findstr mynuget && nuget source remove -name mynuget"
@REM call :Exec "nuget source add -source http://mynuget/ -Name mynuget -Username mynuget -Password mynuget"
if "%SLN%"=="" (
    call :Exec "nuget restore "c:\source" -PackagesDirectory "packages" -NonInteractive"
) else (
    call :Exec "nuget restore "c:\source\%SLN%" -PackagesDirectory "packages" -NonInteractive"
)

call :ResolvePath %CSPROJ% CSPROJ
set entry=%CSPROJ%

:loop
for /f "tokens=1* delims=;" %%a in ("%entry%") do (
    echo %%a
    call :ResolvePath %%a entry
    call :Build %entry%
    set entry=%%b
)
if defined entry goto :loop

rem === Functions ===
exit /b

:ResolvePath
    set %2=%~dpfn1
    exit /b

:Exec
    echo "******************************************************************"
    echo "> %~1"
    call %~1
    exit /b

:Build
    mkdir "C:\publish\%~n1"
    @REM call :Exec "nuget restore "%~1" -PackagesDirectory "packages" -NonInteractive -Recursive"
    call :Exec "msbuild -r "%~1" -verbosity:n -property:WarningLevel=0;OutDir=.\bin\;Configuration=%MODE%"
    call :Exec "taskkill /f /im VBCSCompiler.exe /t"
    
    if exist "%~p1bin\_PublishedWebsites\%~n1" (
        call :Exec "xcopy /seyfrhq "%~p1bin\_PublishedWebsites\%~n1" C:\publish\%~n1"
    ) else (
        if exist "%~p1bin\_PublishedWebsites" (
            call :Exec "xcopy /seyfrhq "%~p1bin\_PublishedWebsites" C:\publish\%~n1"
        ) else (
            call :Exec "xcopy /seyfrhq "%~p1bin" C:\publish\%~n1"
        )
    )
    
:CopyToSource
    set src=%~1
    set dest=%src:c:\src=c:\source%
    echo F|xcopy /seyfrhqk "%src%" "%dest%"
    exit /b

11. Dockerfile

FROM mcr.microsoft.com/dotnet/framework/sdk:4.8-windowsservercore-ltsc2019

WORKDIR "C:/"
COPY ./git "C:/git"
COPY ./build.bat c:/bat/build.bat
COPY ./node-v16.17.1-x64.msi c:/bat/node-v16.17.1-x64.msi
# COPY ["./NETFramework", "C:/Program Files (x86)/Reference Assemblies/Microsoft/Framework/.NETFramework"]
RUN Write-Output 2022-10-07 >/imgver

# 创建基础文件夹
RUN "md c:/source/;md c:/publish/;md c:/nuget/"
# 安装nodejs
RUN msiexec /a "C:\bat\node-v16.17.1-x64.msi" /qb TARGETDIR="C:\nodejs"
# 设置环境变量
RUN setx path '%path%;C:/git/bin;C:/nodejs/nodejs'
# 设置nuget缓存文件夹
RUN nuget config -set globalPackagesFolder=c:/nuget repositoryPath=c:/nuget
ENV NUGET_HTTP_CACHE_PATH="c:/nuget"

# 设置git安全策略
RUN git config --global --add safe.directory "*"
RUN git config --system http.sslverify false

# 声明镜像变量
ENV MODE=DEBUG CSPROJ= BRANCH=main SLN=
# 启动脚本
ENTRYPOINT ["/bat/build.bat"]
ONBUILD RUN echo 'base image build time : 2022-10-07 14:53:22'

编译镜像
cls && docker rmi -f build && docker build . -t build

构建.NET项目

docker run -it --rm ^
    -v "D:/codes/dotnet/CSM":"c:/src" ^
    -v "D:/publish":"c:/publish" ^
    -v "D:/apps/.nuget":"c:/nuget" ^
    -e CSPROJ="WebUI/WebUI.csproj;Api/Api.csproj" ^
    -e MODE="DEBUG" ^
    -e BRANCH="release/v1.0" ^
    build
上一篇下一篇

猜你喜欢

热点阅读