.NET遇上Docker - 使用Docker Compose組織Ngnix和.NETCore運行


本文工具准備:

  • Docker for Windows
  • Visual Studio 2015 與 Visual Studio Tools for Docker
  • 或 Visual Studio 2017 需要在安裝時選擇“容器開發支持”,如圖:

Docker的思想是將不同的應用放在不同的容器中分開運行,如運行.NetCore Web的典型組合Nginx+.NETCore(kestrel),我們應該使用一個容器運行Nginx,另一個容器運行.NETCore App。

之前還陷入一個誤區,一直在研究如何將dotnet與Nginx配置實現到一個Dockerfile中,后來了解到Docker Compose才知道這兩者應該分開到不同的容器。

服務器端安裝Docker與Docker Compose

此文

DotnetCore的Dockerfile

一般來說通過Visual Studio 2015 Tools for Docker給項目添加Docker支持后,項目中就會有Dockerfile與docker-compose.xml的初始模板。只需要修改其中的內容適應我們的項目即可。
Visual Studio的2017可以在新建項目時,或建立項目以后選擇添加Docker支持。

Visual Studio 2017稍有不同的是其將docker-compose.yml文件作為解決方案級文件來管理。這對於組合多個項目是很有幫助的。如圖:

本文最初編寫時項目使用的Visual Studio 2015,所以docker-compose.yml還都是在Web項目中。但下文的設置對這兩種組織方式都支持,稍微調整一下路徑即可。

首先是默認Dockerfile的文件,我們將其配置為運行dotnet

FROM microsoft/aspnetcore:1.0
ENTRYPOINT ["dotnet", "orgname.projname.WebApi.dll"]
ARG source=.
WORKDIR /app
EXPOSE 5000
COPY $source /app

aspnetcore這個Iamge有1.0和1.1兩個版本,根據項目所使用的.NETCore版本自行更換

單獨測試下這個Dockerfile的image生成

docker rmi orgname/projname.core:test
docker build --rm -t orgname/projname.core:test -f Dockerfile .

測試下鏡像作為容器運行:

docker run --name projname.core.inst -p 5000:5000 orgname/projname.core:test

測試完成后,把所有測試產物,如鏡像和容器,都刪除。

docker stop projname.core.inst
docker rm -f projname.core.inst
docker rmi orgname/projname.core:test

在剛開始編寫Dockerfile打包鏡像時,可能會反復進行生成,運行容器的步驟。為了方便測試,樓主把這些腳本整合成了一個buildtest.bat,如下。

@echo on

SET /p app=.NETCoreWeb(d)Nginx(n)
SET /p job=生成並運行(r)清理(c)
SET /p mode=交互運行(i)后台運行(b)


if "%app%"=="d" (
SET contName=projname.core.inst
SET imagName=orgname/projname.core:test
SET file=Dockerfile
)
if "%app=%"=="n" (
SET contName=projname.core.pub.inst
SET imagName=orgname/projname.core.pub:test
SET file=Dockerfile-Nginx
)

if "%mode%"=="i" (
SET operate=-it
)
if "%mode%"=="b" (
SET operate=-d
)

if "%job%"=="r" (
GOTO Build
)
if "%job%"=="c" (
GOTO Clear
)

:Build
docker stop %contName%
docker rm -f %contName%
docker rmi %imagName%
docker build --rm -t %imagName% -f %file% .
docker run %operate% --name %contName% -p 5000:5000 -e "ASPNETCORE_ENVIRONMENT=Staging"  %imagName% 
GOTO End

:Clear
docker stop %contName%
docker rm -f %contName%
docker rmi %imagName%
docker ps -a
docker images
GOTO End

:End

樓主一般用Docker for Windows做測試,自然也就寫了批處理的腳本,后來(這篇文章慢慢的攢了一段時間才最終完成)為了調試方便轉投PowerShell。
包括這個腳本在內的本文提供的幾個腳本都非常好用,誰用誰知道。

Nginx的Dockerfile

首先是Nginx的配置文件,這是比較重要的一個配置,Nginx的Docker Image生成到時候會復制這個文件到Nginx Docker Image內。

本文介紹普通的80端口的轉發,配置如下:

server {
    listen 8081;

      location / {
          proxy_pass http://core-app:5000;
          proxy_http_version 1.1;
          proxy_set_header Upgrade $http_upgrade;
          proxy_set_header Connection keep-alive;
          proxy_set_header Host $host;
          proxy_cache_bypass $http_upgrade;
      }
    }

注意 配置中的轉發地址,不再是localhost,而是需要根據link所指定的服務/容器的別名來確定。這個link參數,在下面有示例。
這個涉及到Docker網絡,這是個非常復雜的話題。

注意nginx.conf需要ANSI編碼,且換行為Linux格式,否則導入容器中可能會報錯。(可以使用Notepad++編輯,使用VS編輯可能會出問題)

還可以配置Nginx使用HTTPS,或者使用Nginx配置簡單的負載均衡,如果以后樓主有機會研究的話會再寫文章分享。

測試下Nginx的Docker Image的生成:

docker build --rm -t orgname/projname.core.pub:test -f Dockerfile-Nginx .

啟動容器來測試:

docker run --name projname.core.pub.inst -p 8081:8081 --link projname.core.inst:core-app orgname/projname.core.pub:test

注意:link即指示鏈接到的之前創建的運行.net core的容器,冒號后面部分是指定別名。這個別名就是前文Ngnix配置文件中,所轉發的地址。

如果啟動Nginx容器后,可以通過Nginx訪問.NET Core Web App,說明到此為止的配置都是正確的。
可以繼續配置docker compose了。

清理測試產物:

docker stop projname.core.pub.inst
docker rm -f projname.core.pub.inst
docker rmi orgname/projname.core.pub:test

將docker相關文件添加到項目發布文件

(VS2015)編輯project.json文件中publishOptions-include的數組,將Dockerfile、Dockerfile-Nginx、nginx.conf及docker-compose.yml添加到其中。

一般來說,按照之前步驟添加“Docker Support”后,插件會自動將相關文件添加到project.jsonpublishOptions中,這一步進行確認就好。

(VS2017)在解決方案資源管理器中將Dockerfile、Dockerfile-Nginx、nginx.conf及docker-compose.yml包含在項目中。“復制到輸出目錄”選擇“如果較新則復制”。

發布項目

使用下面命令發布項目

dotnet publish --framework netcoreapp1.0 --configuration release --output publish.linux

可以將這條命令保存為一個批處理文件,如publish.linux.bat,放到項目根目錄下

編輯Docker Compose配置文件

docker compose配置文件基本上就之前用到的docker build和docker run命令的另一種表述形式

version: '3'

services:
  orgname.projname.webapi:
    image: orgname/projname.core:${TAG}
    build:
      context: .
      dockerfile: Dockerfile
    expose:
      - "5000"
    container_name: projname.core.inst
    environment:
      - ASPNETCORE_ENVIRONMENT=Development
    volumes: 
      - ./log:/app/log:rw
    deploy:
      restart_policy:
        condition: always
  orgname.projname.webapi.pub:
    image: orgname/projname.core.pub:${TAG}
    build:
      context: .
      dockerfile: Dockerfile-Nginx
    ports: 
      - "8081:8081"
    links: 
      - orgname.projname.webapi:core-app
    container_name: projname.core.pub.inst

volumes:
  orgname.projname.webapi:

注意,服務orgname.projname.webapi中,使用expose來暴露端口,因為我們不需要將端口暴露給docker外部。另外我們也將日志輸出到掛在到Docker的主機目錄,這樣方便查看日志。

提示 強烈推薦使用version3版本的compose文件。version3中新增deploy選項,可以實現docker run --restart選項的作用控制容器在失敗等情況下自動重啟,從而保證服務的無人值守運行。compose選項詳見此文檔

構建Image

項目發布完成后,進入發布文件夾publish,執行下面的命令生成相關鏡像

docker-compose build

提示,可以在使用docker compose生成之前,分別使用docker build單獨生成dotnet core和nginx的鏡像進行測試,就像介紹Dockerfile那部分所述。

啟動docker compose

確定compose生成好image后,就可以啟動服務(容器)了:

docker-compose start

可以使用下面的命令將Build、Start一起完成。

docker-compose up

服務啟動后,可以通過Nginx服務訪問.NET Core App。
上面的命令會在前台運行並打印日志到控制台。
如果需要在后台運行“服務”,使用-d參數:

docker-compose up -d

docker compose啟動的也是普通的容器,通過docker ps也可以看到compose啟動的容器。

如果需要停止docker compose啟動的服務,使用:

docker-compose down

注意:服務的容器將被刪除,所有容器中的數據(非主機掛載到容器目錄下)將丟失。

這一小節介紹的方式是在Docker for Windows中使用docker compose運行服務,而實際情況下我們需要在服務器去運行docker compose,具體方式后文有介紹。

其它相關命令:

查看compose相關服務運行狀態:docker-compose ps
重啟compose中的服務:docker-compose restart

環境

在程序開發中,在不同環境下使用不同的配置是很常見的情況。
如.NET Core就定義了三種默認的環境 - Development、Staging和Production。
體現在配置文件上,有appsettings.jsonappsettings.Staging.jsonappsettings.Production.json等文件
樓主一般將其分別用作開發、測試和生產環境的配置,相信大部分人也都是這么干的。
.NET Core Web應用會根據一個名為ASPNETCORE_ENVIRONMENT的環境變量來判斷應用所處的環境。所以我們需要做的就是在生成鏡像時將這個變量傳入
docker compose的environment就是做這個用的,比如我們將docker-compose.yml文件中orgname.projname.webapi這個服務的定義改為:

environment:
    - ASPNETCORE_ENVIRONMENT=Staging

則我們的.NETCore應用將以Staging環境運行,並使用appsettings.Staging.json這個配置文件。
然后我們利用VS Tool for Docker創建的其它兩個文件(docker-compose.dev.debug.ymldocker-compose.dev.release.yml)來實現不同環境的配置分離(VS2017中這兩個文件中的dev改為vs)。

其實那兩個文件是用於VS集成的Docker調試和發布用的,不過我這里不打算依賴工具,而是通過命令行的形式完成工作。所以刪除兩個文件中原有的內容,並改為我們自己所需。

要使兩個配置文件共同工作,最主要的還是靠docker compose對多配置文件的支持。其-f選項可以設置多次,docker compose會把其中的選項疊加。
如:

docker-compose.exe -f docker-compose.yml -f docker-compose.dev.debug.yml build

下面來分別看下用於不同環境的配置文件
首先是docker-compose.dev.debug.yml

version: '3'
 
services:
  orgname.projname.webapi:
    environment:
      - ASPNETCORE_ENVIRONMENT=Staging
    env_file:
      - staging.env

其中的env_file指定的staging.env用於演示,由於我們的例子需要配置的環境變量很少,所以無需使用這個文件。文件的格式很簡單,就是鍵值對的格式:

ENV=VAL

注意,這個文件要使用ANSI編碼,不然會因為編碼問題導致實際定義的變量和期望定義的變量不一致。

提示:可以使用下面的命令,確認compose執行時的配置

docker-compose.exe -f docker-compose.yml -f docker-compose.dev.debug.yml config

這對於檢查環境變量等設置是否正確很有幫助。在確認無誤后再進行buildup操作。上面說得env文件的編碼問題,就是通過config命令查出來的。(下文的composebuild.bat集成了這個檢查的功能)

然后是docker-compose.dev.release.yml,內容也差不多:

version: '3'
 
services:
  orgname.projname.webapi:
    environment:
      - ASPNETCORE_ENVIRONMENT=Production

另外我們還要給測試和生產環境的image打上不同的tag。這個需要修改一下之前編輯的docker-compose.yml中服務定義中image那個配置:

image: orgname/projname.core:${TAG}

image: orgname/projname.core.pub:${TAG}

將它們的版本號都改為插值計算的方式。
docker compose可以由當前執行的環境中獲取這些值的定義。比如shell中EXPORT的變量,或者cmd中SET的變量。
在Windows下,我們借助如下的批處理(composebuild.bat)來幫助定義這個變量,並執行docker compose命令:

@echo off

SET /p mode=測試(t)發布(p)
SET /p job=檢查(c)生成(b)運行(u)清理(r)

if "%job%"=="c" (
SET operate=config
)
if "%job%"=="b" (
SET operate=build
)
if "%job%"=="u" (
SET operate=up
)


if "%mode%"=="t" (
SET TAG=test
)
if "%mode%"=="p" (
SET TAG=1.0.0
)
if "%job%"=="r" (
docker rmi orgname/projname.core.pub:%TAG%
docker rmi orgname/projname.core:%TAG%
GOTO End
)

if "%mode%"=="t" (
SET fileyml=docker-compose.dev.debug.yml
GOTO Build
)
if "%mode%"=="p" (
SET fileyml=docker-compose.dev.release.yml
GOTO Build
)
:Build
docker-compose -f docker-compose.yml -f %fileyml% %operate%

:End

這個腳本有一個小問題,在后續章節,會介紹將Image推送到注冊中心,而推送到注冊中心需要一個tag操作。而這個腳本刪除這些被tag的Image,只會解除tag,而不刪除Image,最終導致本地殘留很多無tag的Image。可以全部工作結束后使用下面的PowerShell命令,統一刪除:

Get-ContainerImage | ? {$_.RepoTags -eq $null} | foreach { Remove-ContainerImage $_.ID }

運行這個命令需要安裝Docker-PowerShell,可參見此文

運行這個批處理,選一下需要運行的選項便可以通過docker compose在不同的環境下生成鏡像或啟動服務。

注意:所有這些文件都記得加入project.json中(VS2015),包含到項目並復制到輸出目錄(VS2017)

添加到鏡像倉庫

這是可選步驟,也可以在把Dockerfile和發布文件上傳到服務器並在服務器上生成鏡像。見總結。

可以使用Harbor構建一個私有Docker倉庫。Harbor相對於Docker官方庫就像GitLab相對於GitHub。Harbor的安裝和基本使用參見此文

Docker的Image進行分層存儲,所以第一次push到私有倉庫的上傳量比較大,之后將只是推送改變的層。數據傳輸量比較小。
push操作的操作的基本部分在那篇介紹Docker的文章中有介紹。這里我們只是給出腳本,可以按照提示執行即可,嗯,這是一個PowerShell腳本(ImagesPush.ps1),不再是批處理了。

$reghost="register-host"
$regport="register-port"
$dockerregister="$($reghost):$($regport)"

echo "測試(t)發布(p)"
$operation=Read-Host
Switch($operation)
{
	"t" {$tag="test"}
	"p" {$tag="1.0.0"}
}

#docker login $dockerregister

docker tag orgname/projname.core:$tag $dockerregister/orgnameprojname/projname.core:$tag
docker tag orgname/projname.core.pub:$tag $dockerregister/orgnameprojname/projname.core.pub:$tag

docker push $dockerregister/orgnameprojname/projname.core:$tag
docker push $dockerregister/orgnameprojname/projname.core.pub:$tag

從鏡像倉庫獲取並啟動服務

通過Docker啟動服務,docker-compose.yml是必備的。(我們執行docker-compose命令的目錄下必須有這個文件,不然分分鍾報錯。)
我們之前那個docker-compose.yml文件所創建容器的鏡像是通過Build Dockerfile來得到的,這里我們要新建這樣的一個docker-compose.yml,其使用一個現成的Image來啟動容器。
我們新建一個名為docker-compose.server.yml的文件(不要懷疑名字錯了)。

version: '3'

services:
  orgname.projname.webapi:
    image: {REG}/orgnameprojname/projname.core:{TAG}
    expose:
      - "5000"
    container_name: projname.core.inst
    environment:
      - ASPNETCORE_ENVIRONMENT={ENV}
    volumes: 
      - ./log:/app/log:rw
    deploy:
      restart_policy:
        condition: always
  orgname.projname.webapi.pub:
    image: {REG}/orgnameprojname/projname.core.pub:{TAG}
    ports: 
      - "8081:8081"
    links: 
      - orgname.projname.webapi:core-app
    container_name: projname.core.pub.inst

volumes:
  orgname.projname.webapi:

與之前文件的最大不同,這個配置沒有了build這個小節。
而鏡像就需要從注冊中心拉取,直接上腳本(ImagesPull.ps1),然后稍作解釋:

$reghost="register-host"
$regport="register-port"
$dockerregister="$($reghost):$($regport)"

echo "Test(t) Publish(p)"
$operation=Read-Host
Switch($operation)
{
	"t" {$tag="test";$envstr="Staging"}
	"p" {$tag="1.0.0";$envstr="Production"}
}
docker login $dockerregister

docker pull $dockerregister/orgnameprojname/projname.core:$tag
docker pull $dockerregister/orgnameprojname/projname.core.pub:$tag

ni docker-compose.yml -ItemType File -Force
(get-content docker-compose.server.yml) -replace "{TAG}","$($tag)" -replace "{REG}","$($dockerregister)" -replace "{ENV}","$($envstr)" | set-content docker-compose.yml

# remove old container
docker-compose down 

docker-compose up

是的,這是一個PowerShell腳本,PowerShell現在也可以用Linux,雖然還處在測試狀態。在CentOS7.3中有一堆顯示方面的bug,中文支持也沒有(所以上面的腳本沒有中文),主要功能方面還是很正常的。
Linux上安裝PowerShell可見此文檔
腳本中,首先登錄鏡像倉庫並下載鏡像,然后按照用戶輸入將docker-compose.server.yml進行插值得到需要的docker-compose.yml文件,最后啟動docker compose。

網絡

網絡方面,Docker-Conpose為應用創建一個子網絡,Docker-Compose將每個Service作為一個容器實例,並加入到這個網絡中,網絡中的容器可以彼此訪問。
容器間以容器名稱作為主機名來互相訪問。
Docker網絡這塊水很深,樓主對這個了解幾乎沒有。

總結

借用此部分牆裂推薦Cmder這個神奇,雖然時不時的會有光標錯位,中文重疊等問題,但絕對是取代自帶控制台的必備工具。

最后總結一下,在上面的所有配置文件准備妥當后,每次修改得到新版本后只需要執行下列步驟:

方式1:

  1. 執行publish.linux.bat生成項目發布文件
  2. 運行buildtest.bat驗證Dockerfile正常工作
  3. 運行composebuild.bat根據提示選擇在測試環境還是發布環境下驗證配置、生成鏡像
  4. 使用ImagesPush.ps1將生成的鏡像發布到Harbor
  5. 在服務器上使用ImagesPull.ps1拉取鏡像並使用compose啟動服務。(先要把docker-compose.server.yml放到與ImagesPull.ps1服務器端相同目錄下)

方式2:

  1. 執行publish.linux.bat生成項目發布文件,將所有文件上傳到服務器
  2. 在服務器端使用composebuild.bat來運行服務。(目前樓主沒有使用這種方法,所以沒有將這個腳本用shell或者powershell重寫)

如果您有更好的實現方法,歡迎評論區指點一二。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM