必讀
本文總結了博主在使用持續集成 ASP.NET Core & Docker & Jenkins 的經驗,里面有些章節講的是 Docker Compose,這是由於早期版本的Visual Studio 2017添加的Docker支持采用的Docker Compose方式,15.8 版本以后,就放棄了Docker Compose方式,直接采用Dockerfile,簡單、方面。比如第二小節中出現的 docker-compose.override.yml
這個就是以前版本的VS2017自動生成的Docker Compose 文件,特此說明。持續集成教程鏈接:《ASP.NET Core & Docker 零基礎持續集成 》
一.前言
最近一直在研究和實踐ASP.NET Core、Docker、持續集成。在ASP.NET Core 和 Dcoker結合下遇到了一些坑,在此記錄和分享,希望對大家有一些幫助。
二.中間鏡像
多階段構建產生的中間鏡像,鏡像緩存層等
我前面寫過一個 《ASP.NET Core & Docker 零基礎持續集成 》的教程。里面我們通過持續集成工具Jenkins構建Docker鏡像並運行容器,采用的是Docker Compose
來進行編排構建運行的(Visual Studio 2017添加Docker支持是采用的Docker Compose
)。細心的朋友可能會發現,每次構建完畢以后通過docker images
命令查詢,可以發現多了許多沒有名稱(<none>)的鏡像。這些都是構建過程中的中間鏡像,我們可以在構建完成以后 進行統一刪除。
刪除所有無名稱鏡像:
docker rmi $(docker images -f "dangling=true" -q)
此命令應當加在構建的最后一步,示例:
echo ---------------Remove-Orphans------------------
docker-compose -f src/docker-compose.yml -f src/docker-compose.override.yml -p alipaydemo down --rmi local --remove-orphans
echo ---------------Publishing...------------------
docker-compose -f "src/docker-compose.yml" -f "src/docker-compose.override.yml" -p alipaydemo up -d --build
echo ---------------Clear-Images...------------------
docker rmi $(docker images -f "dangling=true" -q)
執行之后會看到以下效果:
這是非常有必要的,因為如果每次構建都殘留一些中間鏡像,會額外消耗我們的磁盤空間的。
三.固定容器外部端口 (Docker Compose)
這里主要講的是在自動化構建的過程中,通過docker compose來運行容器的外部端口,而不是直接通過docker run命令來指定。
我們通過Visual Studio 2017添加Docker支持(Docker Compose
),通過Docker Compose
編排構建運行容器,我們會發現每次構建以后,運行的容器的外部端口都不是固定的,比如32774、32775、32776等等。這對於我們設置了Nginx反向代理和API Gateway等配置的肯定是十分不方便的,我們每次構建完畢以后還要去改這些配置,不是扯淡嗎。所以我們需要固定我們容器運行的外部端口,我們可以通過改變docker compose的yml文件來固定容器的外部端口。
Visual Studio 2017 添加的Docker支持所生成的文件有如下結構:
我是用的版本為VS2017 15.6.5。如果是更早的版本添加Docker支持可能會多出一個
docker-compose.ci.build.yml
文件,其實這一步沒必要,目前的最新的VS2017已經移除了該文件。
我們固定容器外部端口需要修改的是docker-compose.override.yml
文件,我們需要修改的是ports。默認為:
ports:
- "80"
這個80端口只是容器的內部端口,我們進行如下修改來知道容器運行時映射到服務器的端口也就是外部端口:
ports:
- "32775:80"
通過上面的設置,我們將容器的外部端口指定為32775,這樣我們在構建完成以后,容器運行以后的外部端口都將會為32775,無需再次修改Nginx反向代理等配置。
四.設置鏡像版本 (Docker Compose)
我們的應用程序具有不同的版本號,我們不同版本的應用程序構建出來的鏡像應該也是具有不同的版本的,我們可以通過設置鏡像的Tag來表示不同的版本:
我們同樣可以在docker compose的yml里面進行設置,這次修改的是docker-compose.yml
文件,我們直接在鏡像的名稱后面設置Tag,語法為:
image: <鏡像名稱>:<Tag>
比如我設置一個名為alipaydemo的鏡像Tag為V1:
image: alipaydemo:v1
完整的配置分享:
version: '3'
services:
alipay.demo.pcpayment:
image: alipaydemopcpayment:v1
container_name: alipaydemocontainer
build:
context: .
dockerfile: Alipay.Demo.PCPayment/Dockerfile
五.設置容器名稱 (Docker Compose)
我們在通過docker compose運行的容器將會被指定一個默認的容器名稱,如果是第四節的配置,那么容器的默認名稱為alipaydemopcpayment.alipay.demo.pcpayment.build_1
,具有非常一長串,此時我們可以自己來指定這個容器的名稱,同樣我們需要修改docker-compose.yml
文件,設置容器名稱的命令格式為:
container_name: <容器名稱>
完整的配置分享:
version: '3'
services:
alipay.demo.pcpayment:
image: alipaydemopcpayment:v1
container_name: alipaydemocontainer
build:
context: .
dockerfile: Alipay.Demo.PCPayment/Dockerfile
通過上面的配置我們將容器名稱設置為了alipaydemocontainer
六.設置容器重啟策略 (Docker Compose)
某一天我對服務器進行了重啟,重啟以后發現通過Docker運行的服務無法訪問了,然后查看原因,發現Docker服務沒有開機自啟,啟動了Docker以后發現容器又沒有自動啟動。
1.設置Docker開機自啟
我們可以直接通過chkconfig命令來設置Docker開啟自啟:
chkconfig docker
執行成功如下:
2.設置容器隨Docker啟動
我們要讓容器隨Docker啟動,就必須設置容器的重啟策略為always,我們通過docker compose來運行容器時可以在yml里面指定。打開docker-compose.override.yml
文件,添加配置:
restart: always
完整的配置示例:
version: '3'
services:
alipay.demo.pcpayment:
restart: always
environment:
- ASPNETCORE_ENVIRONMENT=Production
ports:
- "32775:80"
七.Docker的重啟策略
1.設置容器重啟策略
這里講的設置容器重啟策略主要是通過命令來進行交互,並非第六節講的通過docker compose來設置重啟策略,是對第六節的一個擴展閱讀。
我們可以在使用docker run
命令時通過--restart
參數來設置重啟策略:
docker run -d --restart=always alipaydemo
docker run -d --restart=on-failure:10 alipaydemo
第一條命令代表容器退出時總是重啟容器,第二條代表在容器非正常退出時重啟容器,最多重啟10次。
對於已經運行的容器可以通過docker update
命令來指定:
docker update --restart=always alipaydemo
2.Docker容器的重啟策略
Docker容器的重啟策略是面向生產環境的一個啟動策略,在開發過程中可以忽略該策略。
Docker容器的重啟都是由Docker守護進程完成的,因此與守護進程息息相關。
Docker容器的重啟策略如下:
- no,默認策略,在容器退出時不重啟容器
- on-failure,在容器非正常退出時(退出狀態非0),才會重啟容器
- on-failure:3,在容器非正常退出時重啟容器,最多重啟3次
- always,在容器退出時總是重啟容器
- unless-stopped,在容器退出時總是重啟容器,但是不考慮在Docker守護進程啟動時就已經停止了的容器
3.Docker容器的退出狀態碼
docker run的退出狀態碼如下:
-
0,表示正常退出
-
非0,表示異常退出(退出狀態碼采用chroot標准)
- 125,Docker守護進程本身的錯誤
- 126,容器啟動后,要執行的默認命令無法調用
- 127,容器啟動后,要執行的默認命令不存在
-
其他命令狀態碼,容器啟動后正常執行命令,退出命令時該命令的返回狀態碼作為容器的退出狀態碼
參考資料:https://blog.csdn.net/taiyangdao/article/details/73076019
八.設置自動交互
我們在編寫Dockerfile時,可以通過RUN
命令來運行一些命令,由此我們可以通過運行apt-get
等命令,將一些必要的組件安裝到我們的鏡像之中,比如lsof等。我們有如下配置:
RUN apt-get install lsof
但是我們在安裝一個組件時,非常有可能遇到交互操作,比如“Dou you want to xxx?[y/n]”等,遇到這種將會中斷我們的Docker鏡像構建過程,那么如何解決呢?我們可以給命令指定--assume-yes
來實現自動交互:
RUN apt-get install lsof --assume-yes
持續集成自動化構建過程中,如若不設置自動交互將會被中斷:
使用apt-get install命令以前,最好使用apt-get update更新一下,避免出現問題。
九.ASP.NET Core 生成圖片問題
大家應該知道目前.NET Core(2.0)還是沒有System.Drawing
程序集,如果我們要使用Image
等對象來完成生成圖片驗證碼、圖片二維碼等操作只有通過第三方編寫的組件,ZKWeb.System.Drawing
便是其中一個,我們使用它以后,我們在windows上運行良好,無需其他額外的操作。但是我們一到Linux運行或者使用Docker(dotnet鏡像使用的是ubantu環境)運行時,會發現程序無法正常生成圖片,會出現異常,這是因為我們的zk在Linux/Docker下運行需要安裝一個名為 libgdiplus
的組件,我們在構建Docker鏡像的時候可以通過RUN
命令使用apt-get命令進行安裝:
RUN apt-get update
RUN apt-get install libgdiplus --assume-yes
RUN cd /usr/lib
RUN ln -s libgdiplus.so gdiplus.dll
apt-get update
是非常有必要的
完整的Dockerfile配置提供給大家參考:
FROM microsoft/aspnetcore:2.0 AS base
WORKDIR /app
EXPOSE 80
FROM microsoft/aspnetcore-build:2.0 AS build
WORKDIR /src
COPY Alipay.Demo.PCPayment.sln ./
COPY Alipay.Demo.PCPayment/Alipay.Demo.PCPayment.csproj Alipay.Demo.PCPayment/
RUN dotnet restore -nowarn:msb3202,nu1503
COPY . .
WORKDIR /src/Alipay.Demo.PCPayment
RUN dotnet build -c Release -o /app
FROM build AS publish
RUN dotnet publish -c Release -o /app
FROM base AS final
WORKDIR /app
COPY --from=publish /app .
RUN apt-get update
RUN apt-get install libgdiplus --assume-yes
RUN cd /usr/lib
RUN ln -s libgdiplus.so gdiplus.dll
ENTRYPOINT ["dotnet", "Alipay.Demo.PCPayment.dll"]
十.寫在最后
本文乃是我在實踐ASP.NET Core & Docker & 持續集成過程中遇到的問題和解決之道,完完全全的實戰經驗、總結,希望能幫助到大家。關於實踐ASP.NET Core & Docker 構建持續集成大家可以看我的這一系列文章:《ASP.NET Core & Docker 零基礎持續集成》。本文所說的實戰是我在實踐我的開源項目:
https://github.com/dotnetcore/Alipay.AopSdk.Core
此項目及其演示項目通過持續集成自動化發布到Nuget和通過Docker運行。這個項目是一個支持.NET Core的支付寶的服務端SDK項目,不僅僅提供支付能力,還提供支付寶生活號、服務窗、行業合作等開發。覺得好的希望能給一個Star支持(手動滑稽)。
最后分享給大家一個高效寫博客的方法,本文亦是用此來進行編寫和發布:http://www.cnblogs.com/stulzq/p/9043632.html