--- title: 在 .NET 8 中實現容器化:Docker 應用與經驗分享 tags: [.Net 8,Container,blog] --- {%hackmd @siugar/blog-article-base-layout %} :arrow_left: [回到部落格首頁](/@siugar/blog-tw) :arrow_right: <a target="_blank" href="/@siugar/DotNetContainer">本文單篇連結</a> # 在 .NET 8 中實現容器化:Docker 應用與調試心得 [toc] ## 前言 我之前參與過的一個全新專案,需要運行在google的cloud run,這是我第一個容器化的案子,內部開發服務器以及本機則使用docker desktop來測試與運行。藉此分享一下,如何從無到有建立一個容器化的網站以及經驗分享。 ## 本文 ### 1. 怎麼讓專案支援container 其實方法很簡單,目前我是安裝Visual studio 2022 最新版本,這邊介紹一下如何建立一個支援Container的.Net 8專案 #### 1.1 專案建立時則立刻選擇專案要支援Container 建立專案時,只要如下圖在`Enable container support` 處勾選即可 ![CreateProject2](https://hackmd.io/_uploads/ry6hT5zsyl.png) #### 1.2 專案已經建立好了,但想事後支援Container - 1.2.1 對專案點選右鍵開啟選單->[Add]->[選取Docker Support] <div style="display: flex; justify-content: center; gap: 10px;"> <img src="https://hackmd.io/_uploads/Hk_51jzoyx.png" width="50%"> <span style="display: flex; align-items: center; font-size: 32px;">➡️</span> <img src="https://hackmd.io/_uploads/rkpqgoMjyl.png" width="55%"> </div> <br/> - 1.2.2 完成之後,系統就會自動安裝好IDE整合除錯需要的Package Microsoft.VisualStudio.Azure.Containers.Tools.Targets,並且產生一個預設版本的Dockerfile ![image](https://hackmd.io/_uploads/rJE2bozsJg.png) ### 2. 在IDE運行Container站台與中斷debug 基本上前一個步驟完成後,這邊和一般其他專案的debug是一樣的 點選IDE上方的Container(Dockerfile)啟動站台,畫面如下, ![image](https://hackmd.io/_uploads/SyhUNjfskl.png) 可以中斷在自己加的中斷點進行除錯,下方還會顯示當下運行container的一些資訊包含Logs ![image](https://hackmd.io/_uploads/SkJd4jMiJe.png) ### 3. 支援一次開啟多個container站台 要可以一次開啟多個container同時進行debug,需要搭配docker compose專案,透過下面的方法可以建立,並且編寫docker compose來描述多個站台的設定,並且同時開啟。 #### 3.1 首次建立 Docker Compose專案的方法 對專案點選右鍵開啟選單->[Add]->[Container Orchestrator Support] ![DockerCompose1](https://hackmd.io/_uploads/B1DJ6szjkl.png) #### 3.2 初次建立後的畫面 初次建立後的畫面,可以看到docker-compose.yml與launchSettings.json,需要透過這兩個搭配編寫出想要一次性開啟多個站台 ![DockerCompose2](https://hackmd.io/_uploads/H1FMpsfjJe.png) #### 3.3 編寫docker-compose.yml與launchSettings.json設定想要一次性開啟哪些站台 - 3.3.1 docker-compose.yml 用來描述每個站台或服務的內容,包含對應的Dockerfile,port,environment, network與volume設定等等..。 ```yaml services: ehs.etipetslivestream.cms: image: ${DOCKER_REGISTRY-}ehsetipetslivestreamcms build: context: . dockerfile: Ehs.EtipetsLiveStream.Cms/Dockerfile ports: - "50201:8080" environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:8080 networks: - mybridge volumes: - ${APPDATA}/Microsoft/UserSecrets:/home/app/.microsoft/usersecrets:ro - ${APPDATA}/ASP.NET/Https:/home/app/.aspnet/https:ro #- D:\EtPet_Image_To_Nas:/app/share privileged: true ehs.etipetslivestream.b2c1: container_name: ehs_etipetslivestream_b2c1 image: ${DOCKER_REGISTRY-}ehsetipetslivestreamb2c build: context: . dockerfile: Ehs.EtipetsLiveStream.B2C/Dockerfile #target: development ports: - "50101:8080" environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:8080 - FORCE_USE_LOCAL_REDIRECT=true networks: - mybridge ehs.etipetslivestream.b2c2: container_name: ehs_etipetslivestream_b2c2 image: ${DOCKER_REGISTRY-}ehsetipetslivestreamb2c build: context: . dockerfile: Ehs.EtipetsLiveStream.B2C/Dockerfile ports: - "50102:8080" environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://+:8080 - FORCE_USE_LOCAL_REDIRECT=true networks: - mybridge networks: mybridge: external: true ``` - 3.3.2 laucnSettings.json 描述IDE上方的啟動選單,用來控制不同的設定對應開啟哪幾個站台(或服務) ``` { "profiles": { "Docker Compose(Cms)": { "commandName": "DockerCompose", "commandVersion": "1.0", "composeLaunchAction": "LaunchBrowser", "composeLaunchServiceName": "cms", "composeLaunchUrl": "{Scheme}://localhost:{ServicePort}", "serviceActions": { "cms": "StartDebugging", "b2c1": "DoNotStart", "b2c2": "DoNotStart" } }, "Docker Compose(B2C)": { "commandName": "DockerCompose", "commandVersion": "1.0", "composeLaunchAction": "LaunchBrowser", "composeLaunchServiceName": "b2c1", "composeLaunchUrl": "{Scheme}://localhost:{ServicePort}", "serviceActions": { "b2c1": "StartDebugging", "b2c2": "DoNotStart", "cms": "DoNotStart" } }, "Docker Compose(B2CX2)": { "commandName": "DockerCompose", "commandVersion": "1.0", "composeLaunchAction": "LaunchBrowser", "composeLaunchServiceName": "b2c1", "composeLaunchUrl": "{Scheme}://localhost:{ServicePort}", "serviceActions": { "b2c1": "StartDebugging", "b2c2": "StartDebugging", "cms": "DoNotStart" } }, "Docker Compose(B2CX2+Cms)": { "commandName": "DockerCompose", "commandVersion": "1.0", "composeLaunchAction": "LaunchBrowser", "composeLaunchServiceName": "b2c1", "composeLaunchUrl": "{Scheme}://localhost:{ServicePort}", "serviceActions": { "b2c1": "StartDebugging", "b2c2": "StartDebugging", "cms": "StartDebugging" } } } } ``` ### 4. Dockerfile的重點介紹 #### 4.1 將一些開發需要的套件安裝進入 ```dockerfile RUN apt-get update && \ apt-get install -y curl && \ apt-get install -y iputils-ping && \ apt-get install -y telnet && \ apt-get install -y iproute2 && \ apt-get install -y cifs-utils && \ apt-get install -y sudo ``` #### 4.2 運用分層的方式來控制不同的需求 以下面範例來說,每個紅色的一行表示不同的一層,`FROM base AS development`表示宣告接下來的描述為`development`這一層的內容,並且上一層銜接自`base` <pre> <span style="color: red;">FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base</span> 中間截略 #################################################################### <span style="color: red;">FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS production_base</span> 中間截略 #################################################################### <span style="color: red;">FROM production_base AS production</span> WORKDIR /app COPY --from=publish /app/publish . ENV ASPNETCORE_ENVIRONMENT=Production ENTRYPOINT ["dotnet", "your-project.dll"] #################################################################### <span style="color: red;">FROM base AS development</span> WORKDIR /app COPY --from=publish /app/publish . ENV ASPNETCORE_ENVIRONMENT=Development RUN echo "From development" ENTRYPOINT ["dotnet", "your-project.dll"] </pre> 附上完整的Dockerfile ```dockerfile # See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. # This stage is used when running from VS in fast mode (Default for Debug configuration) FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER app WORKDIR /app USER root # 設置 DEBIAN_FRONTEND 為 noninteractive ENV DEBIAN_FRONTEND=noninteractive # 更新包列表並安裝 curl, ping 工具, 網芳套件,sudo RUN apt-get update && \ apt-get install -y curl && \ apt-get install -y iputils-ping && \ apt-get install -y telnet && \ apt-get install -y iproute2 && \ apt-get install -y cifs-utils && \ apt-get install -y sudo RUN echo "app ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers USER app # 建立 app/share 目錄 RUN mkdir -p /app/share ENV TZ=Asia/Taipei ########################################### FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS production_base USER root RUN apt-get update && apt-get install -y --no-install-recommends \ libicu-dev \ && rm -rf /var/lib/apt/lists/* ENV DOTNET_SYSTEM_GLOBALIZATION_INVARIANT=false USER app WORKDIR /app ENV TZ=Asia/Taipei EXPOSE 8080 EXPOSE 8081 ########################################### # This stage is used to build the service project FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["your-project-folder/your-project.csproj", "your-project-folder/"] RUN dotnet restore "./your-project-folder/your-project.csproj" COPY . . WORKDIR "/src/your-project.Cms" RUN dotnet build "./your-project.csproj" -c $BUILD_CONFIGURATION -o /app/build ########################################### # This stage is used to publish the service project to be copied to the final stage FROM build AS publish ARG BUILD_CONFIGURATION=Release RUN dotnet publish "./your-project.csproj" -c $BUILD_CONFIGURATION -o /app/publish /p:UseAppHost=false ########################################### # This stage is used in production or when running from VS in regular mode (Default when not using the Debug configuration) FROM production_base AS final WORKDIR /app COPY --from=publish /app/publish . ENV ASPNETCORE_ENVIRONMENT=Production ENTRYPOINT ["dotnet", "your-project.dll"] ########################################### FROM production_base AS production WORKDIR /app COPY --from=publish /app/publish . ENV ASPNETCORE_ENVIRONMENT=Production ENTRYPOINT ["dotnet", "your-project.dll"] ########################################### FROM base AS development WORKDIR /app COPY --from=publish /app/publish . ENV ASPNETCORE_ENVIRONMENT=Development RUN echo "From development" ENTRYPOINT ["dotnet", "your-project.dll"] ``` ### 5. image建立與遠端更新 #### 5.1 透過batch呼叫建立image 這邊會常用到`--target`,透過這個可以指定Dockerfile內的不同設定,來搭配專案不同的環境設定 ``` docker build --target development -t your.project.image.output.name:development -f .\your-project-path\Dockerfile . ``` #### 5.2 將image推到遠端 - 5.2.1 推到遠端的docker desktop ``` docker tag your.project.image.output.name:development your-remote-ip:5000/your.project.image.output.name:development docker push your-remote-ip:5000/your.project.image.output.name:development ``` - 5.2.2 推到遠端的cloud run ``` set PROJECT_NAME=your.project.name gcloud run deploy your-service-name --image asia-east1-docker.pkg.dev/your-gcp-project/mc/%PROJECT_NAME%:latest --platform managed --region asia-east1 --vpc-connector connector --allow-unauthenticated ``` ## 結論 1. Dockerfile的分層建議做一些設計 1.1 Vs產生的Dockerfile有些格式為整合IDE專有的,需要保留。 1.2 搭配Development與Production階段,建議做一些分層設計。 2. 搭配docker compose可以開啟一次多個站台方便除錯。 3. Visual Studio 2022對Container支援性已蠻方便 .Net支援container的功能,其實Visual Studio 2022已經支援的蠻方便了,除了在IDE運行container性能感覺比較差一些,如果還可以再優化會更好。