新手指南:透過 Docker 在 Linux 上託管 .NET Core
這篇文章基於我之前的文章 .NET Core 入門。首先,我把 RESTful API 從 .NET Core RC1 升級到了 .NET Core 1.0,然後,我增加了對 Docker 的支援並描述瞭如何在 Linux 生產環境裡託管它。
我是首次接觸 Docker 並且距離成為一名 Linux 高手還有很遠的一段路程。因此,這裡的很多想法是來自一個新手。
安裝
按照 https://www.microsoft.com/net/core 上的介紹在你的電腦上安裝 .NET Core 。這將會同時在 Windows 上安裝 dotnet 命令列工具以及最新的 Visual Studio 工具。
原始碼
你可以直接到 GitHub 上找最到最新完整的原始碼。
轉換到 .NET CORE 1.0
自然地,當我考慮如何把 API 從 .NET Core RC1 升級到 .NET Core 1.0 時想到的第一個求助的地方就是谷歌搜尋。我是按照下面這兩條非常全面的指導來進行升級的:
當你遷移程式碼的時候,我建議仔細閱讀這兩篇指導,因為我在沒有閱讀第一篇指導的情況下又嘗試瀏覽第二篇,結果感到非常迷惑和沮喪。
我不想描述細節上的改變因為你可以看 GitHub 上的提交。這兒是我所作改變的總結:
- 更新
global.json
和project.json
上的版本號 - 刪除
project.json
上的廢棄章節 - 使用輕型
ControllerBase
而不是Controller
, 因為我不需要與 MVC 檢視相關的方法(這是一個可選的改變)。 - 從輔助方法中去掉
Http
字首,比如:HttpNotFound
->NotFound
LogVerbose
->LogTrace
- 名字空間改變:
Microsoft.AspNetCore.*
- 在
Startup
中使用SetBasePath
(沒有它appsettings.json
將不會被發現) - 透過
WebHostBuilder
來執行而不是透過WebApplication.Run
來執行 - 刪除 Serilog(在寫文章的時候,它不支援 .NET Core 1.0)
唯一令我真正頭疼的事是需要移動 Serilog。我本可以實現自己的檔案記錄器,但是我刪除了檔案記錄功能,因為我不想為了這次操作在這件事情上花費精力。
不幸的是,將有大量的第三方開發者扮演追趕 .NET Core 1.0 的角色,我非常同情他們,因為他們通常在休息時間還堅持工作但卻依舊根本無法接近靠攏微軟的可用資源。我建議閱讀 Travis Illig 的文章 .NET Core 1.0 釋出了,但 Autofac 在哪兒?這是一篇關於第三方開發者觀點的文章。
做了這些改變以後,我可以從 project.json
目錄恢復、構建並執行 dotnet,可以看到 API 又像以前一樣工作了。
透過 Docker 執行
在我寫這篇文章的時候, Docker 只能夠在 Linux 系統上工作。在 Windows 系統和 OS X 上有 beta 支援 Docker,但是它們都必須依賴於虛擬化技術,因此,我選擇把 Ubuntu 14.04 當作虛擬機器來執行。如果你還沒有安裝過 Docker,請按照指導來安裝。
我最近閱讀了一些關於 Docker 的東西,但我直到現在還沒有真正用它來幹任何事。我假設讀者還沒有關於 Docker 的知識,因此我會解釋我所使用的所有命令。
HELLO DOCKER
在 Ubuntu 上安裝好 Docker 之後,我所進行的下一步就是按照 https://www.microsoft.com/net/core#docker 上的介紹來開始執行 .NET Core 和 Docker。
首先啟動一個已安裝有 .NET Core 的容器。
docker run -it microsoft/dotnet:latest
-it
選項表示互動,所以你執行這條命令之後,你就處於容器之內了,可以如你所希望的那樣執行任何 bash 命令。
然後我們可以執行下面這五條命令來在 Docker 內部執行起來微軟 .NET Core 控制檯應用程式示例。
mkdir hwapp
cd hwapp
dotnet new
dotnet restore
dotnet run
你可以透過執行 exit
來離開容器,然後執行 Docker ps -a
命令,這會顯示你建立的那個已經退出的容器。你可以透過上執行命令 Docker rm <container_name>
來清除容器。
掛載原始碼
我的下一步驟是使用和上面相同的 microsoft/dotnet
映象,但是將為我們的應用程式以資料卷的方式掛載上原始碼。
首先簽出有相關提交的倉庫:
git clone https://github.com/niksoper/aspnet5-books.git
cd aspnet5-books/src/MvcLibrary
git checkout dotnet-core-1.0
現在啟動一個容器來執行 .NET Core 1.0,並將原始碼放在 /book
下。注意更改 /path/to/repo
這部分檔案來匹配你的電腦:
docker run -it \
-v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \
microsoft/dotnet:latest
現在你可以在容器中執行應用程式了!
cd /books
dotnet restore
dotnet run
作為一個概念性展示這的確很棒,但是我們可不想每次執行一個程式都要考慮如何把原始碼安裝到容器裡。
增加一個 DOCKERFILE
我的下一步驟是引入一個 Dockerfile,這可以讓應用程式很容易在自己的容器內啟動。
我的 Dockerfile 和 project.json
一樣位於 src/MvcLibrary
目錄下,看起來像下面這樣:
FROM microsoft/dotnet:latest
# 為應用程式原始碼建立目錄
RUN mkdir -p /usr/src/books
WORKDIR /usr/src/books
# 複製原始碼並恢復依賴關係
COPY . /usr/src/books
RUN dotnet restore
# 暴露埠並執行應用程式
EXPOSE 5000
CMD [ "dotnet", "run" ]
嚴格來說,RUN mkdir -p /usr/src/books
命令是不需要的,因為 COPY
會自動建立丟失的目錄。
Docker 映象是按層建立的,我們從包含 .NET Core 的映象開始,新增另一個從原始碼生成應用程式,然後執行這個應用程式的層。
新增了 Dockerfile 以後,我透過執行下面的命令來生成一個映象,並使用生成的映象啟動一個容器(確保在和 Dockerfile 相同的目錄下進行操作,並且你應該使用自己的使用者名稱)。
docker build -t niksoper/netcore-books .
docker run -it niksoper/netcore-books
你應該看到程式能夠和之前一樣的執行,不過這一次我們不需要像之前那樣安裝原始碼,因為原始碼已經包含在 docker 映象裡面了。
暴露併發布埠
這個 API 並不是特別有用,除非我們需要從容器外面和它進行通訊。 Docker 已經有了暴露和釋出埠的概念,但這是兩件完全不同的事。
據 Docker 官方文件:
EXPOSE
指令通知 Docker 容器在執行時監聽特定的網路埠。EXPOSE
指令不能夠讓容器的埠可被主機訪問。要使可被訪問,你必須透過-p
標誌來釋出一個埠範圍或者使用-P
標誌來發布所有暴露的埠
EXPOSE
指令只是將後設資料新增到映象上,所以你可以如文件中說的認為它是映象消費者。從技術上講,我本應該忽略 EXPOSE 5000
這行指令,因為我知道 API 正在監聽的埠,但把它們留下很有用的,並且值得推薦。
在這個階段,我想直接從主機訪問這個 API ,因此我需要透過 -p
指令來發布這個埠,這將允許請求從主機上的埠 5000 轉發到容器上的埠 5000,無論這個埠是不是之前透過 Dockerfile 暴露的。
docker run -d -p 5000:5000 niksoper/netcore-books
透過 -d
指令告訴 docker 在分離模式下執行容器,因此我們不能看到它的輸出,但是它依舊會執行並監聽埠 5000。你可以透過 docker ps
來證實這件事。
因此,接下來我準備從主機向容器發起一個請求來慶祝一下:
curl http://localhost:5000/api/books
它不工作。
重複進行相同 curl
請求,我看到了兩個錯誤:要麼是 curl: (56) Recv failure: Connection reset by peer
,要麼是 curl: (52) Empty reply from server
。
我返回去看 docker run 的文件,然後再次檢查我所使用的 -p
選項以及 Dockerfile 中的 EXPOSE
指令是否正確。我沒有發現任何問題,這讓我開始有些沮喪。
重新振作起來以後,我決定去諮詢當地的一個 Scott Logic DevOps 大師 - Dave Wybourn(也在這篇 Docker Swarm 的文章裡提到過),他的團隊也曾遇到這個實際問題。這個問題是我沒有配置過 Kestral,這是一個全新的輕量級、跨平臺 web 伺服器,用於 .NET Core 。
預設情況下, Kestrel 會監聽 http://localhost:5000
。但問題是,這兒的 localhost
是一個迴路介面。
據維基百科:
在計算機網路中,localhost 是一個代表本機的主機名。本地主機可以透過網路迴路介面訪問在主機上執行的網路服務。透過使用迴路介面可以繞過任何硬體網路介面。
當執行在容器內時這是一個問題,因為 localhost
只能夠在容器內訪問。解決方法是更新 Startup.cs
裡的 Main
方法來配置 Kestral 監聽的 URL:
public static void Main(string[] args)
{
var host = new WebHostBuilder()
.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseUrls("http://*:5000") // 在所有網路介面上監聽埠 5000
.UseIISIntegration()
.UseStartup<Startup>()
.Build();
host.Run();
}
透過這些額外的配置,我可以重建映象,並在容器中執行應用程式,它將能夠接收來自主機的請求:
docker build -t niksoper/netcore-books .
docker run -d -p 5000:5000 niksoper/netcore-books
curl -i http://localhost:5000/api/books
我現在得到下面這些相應:
HTTP/1.1 200 OK
Date: Tue, 30 Aug 2016 15:25:43 GMT
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
[{"id":"1","title":"RESTful API with ASP.NET Core MVC 1.0","author":"Nick Soper"}]
在產品環境中執行 KESTREL
Kestrel 可以很好的處理來自 ASP.NET 的動態內容,然而,網路服務部分的特性沒有如 IIS,Apache 或者 Nginx 那樣的全特性伺服器那麼好。反向代理伺服器可以讓你不用去做像處理靜態內容、快取請求、壓縮請求、SSL 端點這樣的來自 HTTP 伺服器的工作。
因此我需要在我的 Linux 機器上把 Nginx 設定成一個反向代理伺服器。微軟介紹瞭如何釋出到 Linux 生產環境下的指導教程。我把說明總結在這兒:
- 透過
dotnet publish
來給應用程式產生一個自包含包。 - 把已釋出的應用程式複製到伺服器上
- 安裝並配置 Nginx(作為反向代理伺服器)
- 安裝並配置 supervisor(用於確保 Nginx 伺服器處於執行狀態中)
- 安裝並配置 AppArmor(用於限制應用的資源使用)
- 配置伺服器防火牆
- 安全加固 Nginx(從原始碼構建和配置 SSL)
這些內容已經超出了本文的範圍,因此我將側重於如何把 Nginx 配置成一個反向代理伺服器。自然地,我透過 Docker 來完成這件事。
在另一個容器中執行 NGINX
我的目標是在第二個 Docker 容器中執行 Nginx 並把它配置成我們的應用程式容器的反向代理伺服器。
我使用的是來自 Docker Hub 的官方 Nginx 映象。首先我嘗試這樣做:
docker run -d -p 8080:80 --name web nginx
這啟動了一個執行 Nginx 的容器並把主機上的 8080 埠對映到了容器的 80 埠上。現在在瀏覽器中開啟網址 http://localhost:8080
會顯示出 Nginx 的預設登入頁面。
現在我們證實了執行 Nginx 是多麼的簡單,我們可以關閉這個容器。
docker rm -f web
把 NGINX 配置成一個反向代理伺服器
可以透過像下面這樣編輯位於 /etc/nginx/conf.d/default.conf
的配置檔案,把 Nginx 配置成一個反向代理伺服器:
server {
listen 80;
location / {
proxy_pass http://localhost:6666;
}
}
透過上面的配置可以讓 Nginx 將所有對根目錄的訪問請求代理到 http://localhost:6666
。記住這裡的 localhost
指的是執行 Nginx 的容器。我們可以在 Nginx容器內部利用捲來使用我們自己的配置檔案:
docker run -d -p 8080:80 \
-v /path/to/my.conf:/etc/nginx/conf.d/default.conf \
nginx
注意:這把一個單一檔案從主機對映到容器中,而不是一個完整目錄。
在容器間進行通訊
Docker 允許內部容器透過共享虛擬網路進行通訊。預設情況下,所有透過 Docker 守護程式啟動的容器都可以訪問一種叫做“橋”的虛擬網路。這使得一個容器可以被另一個容器在相同的網路上透過 IP 地址和埠來引用。
你可以透過監測容器來找到它的 IP 地址。我將從之前建立的 niksoper/netcore-books
映象中啟動一個容器並監測它:
docker run -d -p 5000:5000 --name books niksoper/netcore-books
docker inspect books
我們可以看到這個容器的 IP 地址是 "IPAddress": "172.17.0.3"
。
所以現在如果我建立下面的 Nginx 配置檔案,並使用這個檔案啟動一個 Nginx 容器, 它將代理請求到我的 API :
server {
listen 80;
location / {
proxy_pass http://172.17.0.3:5000;
}
}
現在我可以使用這個配置檔案啟動一個 Nginx 容器(注意我把主機上的 8080 埠對映到了 Nginx 容器上的 80 埠):
docker run -d -p 8080:80 \
-v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \
nginx
一個到 http://localhost:8080
的請求將被代理到應用上。注意下面 curl
響應的 Server
響應頭:
DOCKER COMPOSE
在這個地方,我為自己的進步而感到高興,但我認為一定還有更好的方法來配置 Nginx,可以不需要知道應用程式容器的確切 IP 地址。另一個當地的 Scott Logic DevOps 大師 Jason Ebbin 在這個地方進行了改進,並建議使用 Docker Compose。
概況描述一下,Docker Compose 使得一組透過宣告式語法互相連線的容器很容易啟動。我不想再細說 Docker Compose 是如何工作的,因為你可以在之前的文章中找到。
我將透過一個我所使用的 docker-compose.yml
檔案來啟動:
version: '2'
services:
books-service:
container_name: books-api
build: .
reverse-proxy:
container_name: reverse-proxy
image: nginx
ports:
- "9090:8080"
volumes:
- ./proxy.conf:/etc/nginx/conf.d/default.conf
這是版本 2 語法,所以為了能夠正常工作,你至少需要 1.6 版本的 Docker Compose。
這個檔案告訴 Docker 建立兩個服務:一個是給應用的,另一個是給 Nginx 反向代理伺服器的。
BOOKS-SERVICE
這個與 docker-compose.yml
相同目錄下的 Dockerfile 構建的容器叫做 books-api
。注意這個容器不需要釋出任何埠,因為只要能夠從反向代理伺服器訪問它就可以,而不需要從主機作業系統訪問它。
REVERSE-PROXY
這將基於 nginx 映象啟動一個叫做 reverse-proxy
的容器,並將位於當前目錄下的 proxy.conf
檔案掛載為配置。它把主機上的 9090 埠對映到容器中的 8080 埠,這將允許我們在 http://localhost:9090
上透過主機訪問容器。
proxy.conf
檔案看起來像下面這樣:
server {
listen 8080;
location / {
proxy_pass http://books-service:5000;
}
}
這兒的關鍵點是我們現在可以透過名字引用 books-service
,因此我們不需要知道 books-api
這個容器的 IP 地址!
現在我們可以透過一個執行著的反向代理啟動兩個容器(-d
意味著這是獨立的,因此我們不能看到來自容器的輸出):
docker compose up -d
驗證我們所建立的容器:
docker ps
最後來驗證我們可以透過反向代理來控制該 API :
curl -i http://localhost:9090/api/books
怎麼做到的?
Docker Compose 透過建立一個新的叫做 mvclibrary_default
的虛擬網路來實現這件事,這個虛擬網路同時用於 books-api
和 reverse-proxy
容器(名字是基於 docker-compose.yml
檔案的父目錄)。
透過 docker network ls
來驗證網路已經存在:
你可以使用 docker network inspect mvclibrary_default
來看到新的網路的細節:
注意 Docker 已經給網路分配了子網:"Subnet": "172.18.0.0/16"
。/16
部分是無類域內路由選擇(CIDR),完整的解釋已經超出了本文的範圍,但 CIDR 只是表示 IP 地址範圍。執行 docker network inspect bridge
顯示子網:"Subnet": "172.17.0.0/16"
,因此這兩個網路是不重疊的。
現在用 docker inspect books-api
來確認應用程式的容器正在使用該網路:
注意容器的兩個別名("Aliases"
)是容器識別符號(3c42db680459
)和由 docker-compose.yml
給出的服務名(books-service
)。我們透過 books-service
別名在自定義 Nginx 配置檔案中來引用應用程式的容器。這本可以透過 docker network create
手動建立,但是我喜歡用 Docker Compose,因為它可以乾淨簡潔地將容器建立和依存捆綁在一起。
結論
所以現在我可以透過幾個簡單的步驟在 Linux 系統上用 Nginx 執行應用程式,不需要對主機作業系統做任何長期的改變:
git clone https://github.com/niksoper/aspnet5-books.git
cd aspnet5-books/src/MvcLibrary
git checkout blog-docker
docker-compose up -d
curl -i http://localhost:9090/api/books
我知道我在這篇文章中所寫的內容不是一個真正的生產環境就緒的裝置,因為我沒有寫任何有關下面這些的內容,絕大多數下面的這些主題都需要用單獨一篇完整的文章來敘述。
- 安全考慮比如防火牆和 SSL 配置
- 如何確保應用程式保持執行狀態
- 如何選擇需要包含的 Docker 映象(我把所有的都放入了 Dockerfile 中)
- 資料庫 - 如何在容器中管理它們
對我來說這是一個非常有趣的學習經歷,因為有一段時間我對探索 ASP.NET Core 的跨平臺支援非常好奇,使用 “Configuratin as Code” 的 Docker Compose 方法來探索一下 DevOps 的世界也是非常愉快並且很有教育意義的。
如果你對 Docker 很好奇,那麼我鼓勵你來嘗試學習它 或許這會讓你離開舒適區,不過,有可能你會喜歡它?
via: http://blog.scottlogic.com/2016/09/05/hosting-netcore-on-linux-with-docker.html
作者:Nick Soper 譯者:ucasFL 校對:wxy
相關文章
- Linux透過 Docker 可以託管 .NET Core啦!LinuxDocker
- 翻譯 - ASP.NET Core 託管和部署 - 在 Linux 上使用 Nginx 託管 ASP.NET Core 網站ASP.NETLinuxNginx網站
- 管中窺豹----.NET Core到.NET 8 託管堆的變遷
- windows 服務中託管asp.net coreWindowsASP.NET
- ASP.NET Core 託管和部署(一)【Kestrel】ASP.NET
- 在 NGINX 上託管 Angular 應用程式的終極指南NginxAngular
- ASP.NET Core 託管和部署(二)【HTTP.sys】ASP.NETHTTP
- .NET 6學習筆記(3)——在Windows Service中託管ASP.NET Core並指定埠筆記WindowsASP.NET
- 在玩客雲上透過docker部署transmissionDocker
- 【.Net Core】分析.net core在linux下記憶體佔用過高問題Linux記憶體
- 在玩客雲(armbian)上透過docker部署jellyfinDocker
- 關於 .NET Core(.NET Core 指南)
- ASP.NET Core託管執行Quartz.NET作業排程詳解ASP.NETquartz
- .NET平臺系列27:在 Linux 上安裝 .NET Core/.NET5/.NET6Linux
- Linux Docker 部署 ASP.NET Core應用LinuxDockerASP.NET
- 監管再次收緊!SEC新透過的“託管規則”有何用意?
- 搭建自己的harbor(docker託管)Docker
- 在 Arch Linux 中使用 .NET Core SDKLinux
- 在玩客雲上透過docker部署zabbix(PostgreSQL資料庫)DockerSQL資料庫
- Linux伺服器使用Docker部署.net Core專案Linux伺服器Docker
- 終極自託管解決方案指南
- .NET Core容器化(Docker)Docker
- 透過Docker啟動Solace,並在Spring Boot透過JMS整合SolaceDockerSpring Boot
- Asp.Net Core WebAPI+PostgreSQL部署在Docker中ASP.NETWebAPISQLDocker
- 託管在GitHub上的流行/超讚影片遊戲列表Github遊戲
- Linux環境下透過docker安裝mysqlLinuxDockerMySql
- 10 款你可以透過 Wine 在 Linux 上玩的遊戲Linux遊戲
- 在 Ubuntu 上透過命令列改變 Linux 系統語言Ubuntu命令列Linux
- Docker & ASP.NET Core (5):Docker ComposeDockerASP.NET
- Docker Desktop 現在可以在 Linux 上使用DockerLinux
- 託管在GitHub上的流行/超讚視訊遊戲列表Github遊戲
- Docker 之 GitLab 區域網程式碼託管DockerGitlab
- Advanced .Net Debugging 7:託管堆與垃圾收集
- linux 上 jenkins 透過節點服務在 windows 執行指令碼LinuxJenkinsWindows指令碼
- webapi透過docker部署到Linux的兩種方式WebAPIDockerLinux
- Linux | Ubuntu 16.04.4 透過docker安裝單機FastDFSLinuxUbuntuDockerAST
- 在Oracle Linux 7.1上安裝DockerOracleLinuxDocker
- .NET平臺系列26:在 Windows 上安裝 .NET Core/.NET5/.NET6Windows
- Docker結合.Net Core初步使用Docker