新手指南:通過(guò)Docker在Linux上托管.NET Core
這篇文章基于我之前的文章 .NET Core 入門。首先,我把 RESTful API 從 .NET Core RC1 升級(jí)到了 .NET Core 1.0,然后,我增加了對(duì) Docker 的支持并描述了如何在 Linux 生產(chǎn)環(huán)境里托管它。
我是首次接觸 Docker 并且距離成為一名 Linux 高手還有很遠(yuǎn)的一段路程。因此,這里的很多想法是來(lái)自一個(gè)新手。
安裝
按照 https://www.microsoft.com/net/core 上的介紹在你的電腦上安裝 .NET Core 。這將會(huì)同時(shí)在 Windows 上安裝 dotnet 命令行工具以及最新的 Visual Studio 工具。
源代碼
你可以直接到 GitHub 上找最到最新完整的源代碼。
轉(zhuǎn)換到 .NET CORE 1.0
自然地,當(dāng)我考慮如何把 API 從 .NET Core RC1 升級(jí)到 .NET Core 1.0 時(shí)想到的第一個(gè)求助的地方就是谷歌搜索。我是按照下面這兩條非常全面的指導(dǎo)來(lái)進(jìn)行升級(jí)的:
當(dāng)你遷移代碼的時(shí)候,我建議仔細(xì)閱讀這兩篇指導(dǎo),因?yàn)槲以跊]有閱讀第一篇指導(dǎo)的情況下又嘗試瀏覽第二篇,結(jié)果感到非常迷惑和沮喪。
我不想描述細(xì)節(jié)上的改變因?yàn)槟憧梢钥?GitHub 上的提交。這兒是我所作改變的總結(jié):
- 更新 global.json 和 project.json 上的版本號(hào)
- 刪除 project.json 上的廢棄章節(jié)
- 使用輕型 ControllerBase 而不是 Controller, 因?yàn)槲也恍枰c MVC 視圖相關(guān)的方法(這是一個(gè)可選的改變)。
- 從輔助方法中去掉 Http 前綴,比如:HttpNotFound -> NotFound
- LogVerbose -> LogTrace
- 名字空間改變: Microsoft.AspNetCore.*
- 在 Startup 中使用 SetBasePath(沒有它 appsettings.json 將不會(huì)被發(fā)現(xiàn))
- 通過(guò) WebHostBuilder 來(lái)運(yùn)行而不是通過(guò) WebApplication.Run 來(lái)運(yùn)行
- 刪除 Serilog(在寫文章的時(shí)候,它不支持 .NET Core 1.0)
唯一令我真正頭疼的事是需要移動(dòng) Serilog。我本可以實(shí)現(xiàn)自己的文件記錄器,但是我刪除了文件記錄功能,因?yàn)槲也幌霝榱诉@次操作在這件事情上花費(fèi)精力。
不幸的是,將有大量的第三方開發(fā)者扮演追趕 .NET Core 1.0 的角色,我非常同情他們,因?yàn)樗麄兺ǔT谛菹r(shí)間還堅(jiān)持工作但卻依舊根本無(wú)法接近靠攏微軟的可用資源。我建議閱讀 Travis Illig 的文章 .NET Core 1.0 發(fā)布了,但 Autofac 在哪兒?這是一篇關(guān)于第三方開發(fā)者觀點(diǎn)的文章。
做了這些改變以后,我可以從 project.json 目錄恢復(fù)、構(gòu)建并運(yùn)行 dotnet,可以看到 API 又像以前一樣工作了。
通過(guò) Docker 運(yùn)行
在我寫這篇文章的時(shí)候, Docker 只能夠在 Linux 系統(tǒng)上工作。在 Windows 系統(tǒng)和 OS X 上有 beta 支持 Docker,但是它們都必須依賴于虛擬化技術(shù),因此,我選擇把 Ubuntu 14.04 當(dāng)作虛擬機(jī)來(lái)運(yùn)行。如果你還沒有安裝過(guò) Docker,請(qǐng)按照指導(dǎo)來(lái)安裝。
我最近閱讀了一些關(guān)于 Docker 的東西,但我直到現(xiàn)在還沒有真正用它來(lái)干任何事。我假設(shè)讀者還沒有關(guān)于 Docker 的知識(shí),因此我會(huì)解釋我所使用的所有命令。
HELLO DOCKER
在 Ubuntu 上安裝好 Docker 之后,我所進(jìn)行的下一步就是按照 https://www.microsoft.com/net/core#docker 上的介紹來(lái)開始運(yùn)行 .NET Core 和 Docker。
首先啟動(dòng)一個(gè)已安裝有 .NET Core 的容器。
- docker run -it microsoft/dotnet:latest
-it 選項(xiàng)表示交互,所以你執(zhí)行這條命令之后,你就處于容器之內(nèi)了,可以如你所希望的那樣執(zhí)行任何 bash 命令。
然后我們可以執(zhí)行下面這五條命令來(lái)在 Docker 內(nèi)部運(yùn)行起來(lái)微軟 .NET Core 控制臺(tái)應(yīng)用程序示例。
- mkdir hwapp
- cd hwapp
- dotnet new
- dotnet restore
- dotnet run
你可以通過(guò)運(yùn)行 exit 來(lái)離開容器,然后運(yùn)行 Docker ps -a 命令,這會(huì)顯示你創(chuàng)建的那個(gè)已經(jīng)退出的容器。你可以通過(guò)上運(yùn)行命令 Docker rm <container_name> 來(lái)清除容器。
掛載源代碼
我的下一步驟是使用和上面相同的 microsoft/dotnet 鏡像,但是將為我們的應(yīng)用程序以數(shù)據(jù)卷的方式掛載上源代碼。
首先簽出有相關(guān)提交的倉(cāng)庫(kù):
- git clone https://github.com/niksoper/aspnet5-books.git
- cd aspnet5-books/src/MvcLibrary
- git checkout dotnet-core-1.0
現(xiàn)在啟動(dòng)一個(gè)容器來(lái)運(yùn)行 .NET Core 1.0,并將源代碼放在 /book 下。注意更改 /path/to/repo 這部分文件來(lái)匹配你的電腦:
- docker run -it \
- -v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \
- microsoft/dotnet:latest
現(xiàn)在你可以在容器中運(yùn)行應(yīng)用程序了!
- cd /books
- dotnet restore
- dotnet run
作為一個(gè)概念性展示這的確很棒,但是我們可不想每次運(yùn)行一個(gè)程序都要考慮如何把源代碼安裝到容器里。
增加一個(gè) DOCKERFILE
我的下一步驟是引入一個(gè) Dockerfile,這可以讓應(yīng)用程序很容易在自己的容器內(nèi)啟動(dòng)。
我的 Dockerfile 和 project.json 一樣位于 src/MvcLibrary 目錄下,看起來(lái)像下面這樣:
- FROM microsoft/dotnet:latest
- # 為應(yīng)用程序源代碼創(chuàng)建目錄
- RUN mkdir -p /usr/src/books
- WORKDIR /usr/src/books
- # 復(fù)制源代碼并恢復(fù)依賴關(guān)系
- COPY . /usr/src/books
- RUN dotnet restore
- # 暴露端口并運(yùn)行應(yīng)用程序
- EXPOSE 5000
- CMD [ "dotnet", "run" ]
嚴(yán)格來(lái)說(shuō),RUN mkdir -p /usr/src/books 命令是不需要的,因?yàn)?COPY 會(huì)自動(dòng)創(chuàng)建丟失的目錄。
Docker 鏡像是按層建立的,我們從包含 .NET Core 的鏡像開始,添加另一個(gè)從源代碼生成應(yīng)用程序,然后運(yùn)行這個(gè)應(yīng)用程序的層。
添加了 Dockerfile 以后,我通過(guò)運(yùn)行下面的命令來(lái)生成一個(gè)鏡像,并使用生成的鏡像啟動(dòng)一個(gè)容器(確保在和 Dockerfile 相同的目錄下進(jìn)行操作,并且你應(yīng)該使用自己的用戶名)。
- docker build -t niksoper/netcore-books .
- docker run -it niksoper/netcore-books
你應(yīng)該看到程序能夠和之前一樣的運(yùn)行,不過(guò)這一次我們不需要像之前那樣安裝源代碼,因?yàn)樵创a已經(jīng)包含在 docker 鏡像里面了。
暴露并發(fā)布端口
這個(gè) API 并不是特別有用,除非我們需要從容器外面和它進(jìn)行通信。 Docker 已經(jīng)有了暴露和發(fā)布端口的概念,但這是兩件完全不同的事。
據(jù) Docker 官方文檔:
EXPOSE 指令通知 Docker 容器在運(yùn)行時(shí)監(jiān)聽特定的網(wǎng)絡(luò)端口。EXPOSE 指令不能夠讓容器的端口可被主機(jī)訪問(wèn)。要使可被訪問(wèn),你必須通過(guò) -p 標(biāo)志來(lái)發(fā)布一個(gè)端口范圍或者使用 -P 標(biāo)志來(lái)發(fā)布所有暴露的端口
EXPOSE 指令只是將元數(shù)據(jù)添加到鏡像上,所以你可以如文檔中說(shuō)的認(rèn)為它是鏡像消費(fèi)者。從技術(shù)上講,我本應(yīng)該忽略 EXPOSE 5000 這行指令,因?yàn)槲抑?API 正在監(jiān)聽的端口,但把它們留下很有用的,并且值得推薦。
在這個(gè)階段,我想直接從主機(jī)訪問(wèn)這個(gè) API ,因此我需要通過(guò) -p 指令來(lái)發(fā)布這個(gè)端口,這將允許請(qǐng)求從主機(jī)上的端口 5000 轉(zhuǎn)發(fā)到容器上的端口 5000,無(wú)論這個(gè)端口是不是之前通過(guò) Dockerfile 暴露的。
- docker run -d -p 5000:5000 niksoper/netcore-books
通過(guò) -d 指令告訴 docker 在分離模式下運(yùn)行容器,因此我們不能看到它的輸出,但是它依舊會(huì)運(yùn)行并監(jiān)聽端口 5000。你可以通過(guò) docker ps 來(lái)證實(shí)這件事。
因此,接下來(lái)我準(zhǔn)備從主機(jī)向容器發(fā)起一個(gè)請(qǐng)求來(lái)慶祝一下:
- curl http://localhost:5000/api/books
它不工作。
重復(fù)進(jìn)行相同 curl 請(qǐng)求,我看到了兩個(gè)錯(cuò)誤:要么是 curl: (56) Recv failure: Connection reset by peer,要么是 curl: (52) Empty reply from server。
我返回去看 docker run 的文檔,然后再次檢查我所使用的 -p 選項(xiàng)以及 Dockerfile 中的 EXPOSE 指令是否正確。我沒有發(fā)現(xiàn)任何問(wèn)題,這讓我開始有些沮喪。
重新振作起來(lái)以后,我決定去咨詢當(dāng)?shù)氐囊粋€(gè) Scott Logic DevOps 大師 - Dave Wybourn(也在這篇 Docker Swarm 的文章里提到過(guò)),他的團(tuán)隊(duì)也曾遇到這個(gè)實(shí)際問(wèn)題。這個(gè)問(wèn)題是我沒有配置過(guò) Kestral,這是一個(gè)全新的輕量級(jí)、跨平臺(tái) web 服務(wù)器,用于 .NET Core 。
默認(rèn)情況下, Kestrel 會(huì)監(jiān)聽 http://localhost:5000。但問(wèn)題是,這兒的 localhost 是一個(gè)回路接口。
據(jù)維基百科:
在計(jì)算機(jī)網(wǎng)絡(luò)中,localhost 是一個(gè)代表本機(jī)的主機(jī)名。本地主機(jī)可以通過(guò)網(wǎng)絡(luò)回路接口訪問(wèn)在主機(jī)上運(yùn)行的網(wǎng)絡(luò)服務(wù)。通過(guò)使用回路接口可以繞過(guò)任何硬件網(wǎng)絡(luò)接口。
當(dāng)運(yùn)行在容器內(nèi)時(shí)這是一個(gè)問(wèn)題,因?yàn)?localhost 只能夠在容器內(nèi)訪問(wèn)。解決方法是更新 Startup.cs 里的 Main 方法來(lái)配置 Kestral 監(jiān)聽的 URL:
- public static void Main(string[] args)
- {
- var host = new WebHostBuilder()
- .UseKestrel()
- .UseContentRoot(Directory.GetCurrentDirectory())
- .UseUrls("http://*:5000") // 在所有網(wǎng)絡(luò)接口上監(jiān)聽端口 5000
- .UseIISIntegration()
- .UseStartup<Startup>()
- .Build();
- host.Run();
- }
通過(guò)這些額外的配置,我可以重建鏡像,并在容器中運(yùn)行應(yīng)用程序,它將能夠接收來(lái)自主機(jī)的請(qǐng)求:
- docker build -t niksoper/netcore-books .
- docker run -d -p 5000:5000 niksoper/netcore-books
- curl -i http://localhost:5000/api/books
我現(xiàn)在得到下面這些相應(yīng):
- 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"}]
在產(chǎn)品環(huán)境中運(yùn)行 KESTREL
微軟的介紹:
Kestrel 可以很好的處理來(lái)自 ASP.NET 的動(dòng)態(tài)內(nèi)容,然而,網(wǎng)絡(luò)服務(wù)部分的特性沒有如 IIS,Apache 或者 Nginx 那樣的全特性服務(wù)器那么好。反向代理服務(wù)器可以讓你不用去做像處理靜態(tài)內(nèi)容、緩存請(qǐng)求、壓縮請(qǐng)求、SSL 端點(diǎn)這樣的來(lái)自 HTTP 服務(wù)器的工作。
因此我需要在我的 Linux 機(jī)器上把 Nginx 設(shè)置成一個(gè)反向代理服務(wù)器。微軟介紹了如何發(fā)布到 Linux 生產(chǎn)環(huán)境下的指導(dǎo)教程。我把說(shuō)明總結(jié)在這兒:
- 通過(guò) dotnet publish 來(lái)給應(yīng)用程序產(chǎn)生一個(gè)自包含包。
- 把已發(fā)布的應(yīng)用程序復(fù)制到服務(wù)器上
- 安裝并配置 Nginx(作為反向代理服務(wù)器)
- 安裝并配置 supervisor(用于確保 Nginx 服務(wù)器處于運(yùn)行狀態(tài)中)
- 安裝并配置 AppArmor(用于限制應(yīng)用的資源使用)
- 配置服務(wù)器防火墻
- 安全加固 Nginx(從源代碼構(gòu)建和配置 SSL)
這些內(nèi)容已經(jīng)超出了本文的范圍,因此我將側(cè)重于如何把 Nginx 配置成一個(gè)反向代理服務(wù)器。自然地,我通過(guò) Docker 來(lái)完成這件事。
在另一個(gè)容器中運(yùn)行 NGINX
我的目標(biāo)是在第二個(gè) Docker 容器中運(yùn)行 Nginx 并把它配置成我們的應(yīng)用程序容器的反向代理服務(wù)器。
我使用的是來(lái)自 Docker Hub 的官方 Nginx 鏡像。首先我嘗試這樣做:
- docker run -d -p 8080:80 --name web nginx
這啟動(dòng)了一個(gè)運(yùn)行 Nginx 的容器并把主機(jī)上的 8080 端口映射到了容器的 80 端口上?,F(xiàn)在在瀏覽器中打開網(wǎng)址 http://localhost:8080 會(huì)顯示出 Nginx 的默認(rèn)登錄頁(yè)面。
現(xiàn)在我們證實(shí)了運(yùn)行 Nginx 是多么的簡(jiǎn)單,我們可以關(guān)閉這個(gè)容器。
- docker rm -f web
把 NGINX 配置成一個(gè)反向代理服務(wù)器
可以通過(guò)像下面這樣編輯位于 /etc/nginx/conf.d/default.conf 的配置文件,把 Nginx 配置成一個(gè)反向代理服務(wù)器:
- server {
- listen 80;
- location / {
- proxy_pass http://localhost:6666;
- }
- }
通過(guò)上面的配置可以讓 Nginx 將所有對(duì)根目錄的訪問(wèn)請(qǐng)求代理到 http://localhost:6666。記住這里的 localhost 指的是運(yùn)行 Nginx 的容器。我們可以在 Nginx容器內(nèi)部利用卷來(lái)使用我們自己的配置文件:
- docker run -d -p 8080:80 \
- -v /path/to/my.conf:/etc/nginx/conf.d/default.conf \
- nginx
注意:這把一個(gè)單一文件從主機(jī)映射到容器中,而不是一個(gè)完整目錄。
在容器間進(jìn)行通信
Docker 允許內(nèi)部容器通過(guò)共享虛擬網(wǎng)絡(luò)進(jìn)行通信。默認(rèn)情況下,所有通過(guò) Docker 守護(hù)進(jìn)程啟動(dòng)的容器都可以訪問(wèn)一種叫做“橋”的虛擬網(wǎng)絡(luò)。這使得一個(gè)容器可以被另一個(gè)容器在相同的網(wǎng)絡(luò)上通過(guò) IP 地址和端口來(lái)引用。
你可以通過(guò)監(jiān)測(cè)(inspect)容器來(lái)找到它的 IP 地址。我將從之前創(chuàng)建的 niksoper/netcore-books 鏡像中啟動(dòng)一個(gè)容器并監(jiān)測(cè)(inspect)它:
- docker run -d -p 5000:5000 --name books niksoper/netcore-books
- docker inspect books
我們可以看到這個(gè)容器的 IP 地址是 "IPAddress": "172.17.0.3"。
所以現(xiàn)在如果我創(chuàng)建下面的 Nginx 配置文件,并使用這個(gè)文件啟動(dòng)一個(gè) Nginx 容器, 它將代理請(qǐng)求到我的 API :
- server {
- listen 80;
- location / {
- proxy_pass http://172.17.0.3:5000;
- }
- }
現(xiàn)在我可以使用這個(gè)配置文件啟動(dòng)一個(gè) Nginx 容器(注意我把主機(jī)上的 8080 端口映射到了 Nginx 容器上的 80 端口):
- docker run -d -p 8080:80 \
- -v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \
- nginx
一個(gè)到 http://localhost:8080 的請(qǐng)求將被代理到應(yīng)用上。注意下面 curl 響應(yīng)的 Server 響應(yīng)頭:
DOCKER COMPOSE
在這個(gè)地方,我為自己的進(jìn)步而感到高興,但我認(rèn)為一定還有更好的方法來(lái)配置 Nginx,可以不需要知道應(yīng)用程序容器的確切 IP 地址。另一個(gè)當(dāng)?shù)氐?Scott Logic DevOps 大師 Jason Ebbin 在這個(gè)地方進(jìn)行了改進(jìn),并建議使用 Docker Compose。
概況描述一下,Docker Compose 使得一組通過(guò)聲明式語(yǔ)法互相連接的容器很容易啟動(dòng)。我不想再細(xì)說(shuō) Docker Compose 是如何工作的,因?yàn)槟憧梢栽谥暗奈恼轮姓业健?/p>
我將通過(guò)一個(gè)我所使用的 docker-compose.yml 文件來(lái)啟動(dòng):
- 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 語(yǔ)法,所以為了能夠正常工作,你至少需要 1.6 版本的 Docker Compose。
這個(gè)文件告訴 Docker 創(chuàng)建兩個(gè)服務(wù):一個(gè)是給應(yīng)用的,另一個(gè)是給 Nginx 反向代理服務(wù)器的。
BOOKS-SERVICE
這個(gè)與 docker-compose.yml 相同目錄下的 Dockerfile 構(gòu)建的容器叫做 books-api。注意這個(gè)容器不需要發(fā)布任何端口,因?yàn)橹灰軌驈姆聪虼矸?wù)器訪問(wèn)它就可以,而不需要從主機(jī)操作系統(tǒng)訪問(wèn)它。
REVERSE-PROXY
這將基于 nginx 鏡像啟動(dòng)一個(gè)叫做 reverse-proxy 的容器,并將位于當(dāng)前目錄下的 proxy.conf 文件掛載為配置。它把主機(jī)上的 9090 端口映射到容器中的 8080 端口,這將允許我們?cè)?http://localhost:9090 上通過(guò)主機(jī)訪問(wèn)容器。
proxy.conf 文件看起來(lái)像下面這樣:
- server {
- listen 8080;
- location / {
- proxy_pass http://books-service:5000;
- }
- }
這兒的關(guān)鍵點(diǎn)是我們現(xiàn)在可以通過(guò)名字引用 books-service,因此我們不需要知道 books-api 這個(gè)容器的 IP 地址!
現(xiàn)在我們可以通過(guò)一個(gè)運(yùn)行著的反向代理啟動(dòng)兩個(gè)容器(-d 意味著這是獨(dú)立的,因此我們不能看到來(lái)自容器的輸出):
- docker compose up -d
驗(yàn)證我們所創(chuàng)建的容器:
- docker ps
最后來(lái)驗(yàn)證我們可以通過(guò)反向代理來(lái)控制該 API :
- curl -i http://localhost:9090/api/books
怎么做到的?
Docker Compose 通過(guò)創(chuàng)建一個(gè)新的叫做 mvclibrary_default 的虛擬網(wǎng)絡(luò)來(lái)實(shí)現(xiàn)這件事,這個(gè)虛擬網(wǎng)絡(luò)同時(shí)用于 books-api 和 reverse-proxy 容器(名字是基于 docker-compose.yml 文件的父目錄)。
通過(guò) docker network ls 來(lái)驗(yàn)證網(wǎng)絡(luò)已經(jīng)存在:
你可以使用 docker network inspect mvclibrary_default 來(lái)看到新的網(wǎng)絡(luò)的細(xì)節(jié):
注意 Docker 已經(jīng)給網(wǎng)絡(luò)分配了子網(wǎng):"Subnet": "172.18.0.0/16"。/16 部分是無(wú)類域內(nèi)路由選擇(CIDR),完整的解釋已經(jīng)超出了本文的范圍,但 CIDR 只是表示 IP 地址范圍。運(yùn)行 docker network inspect bridge 顯示子網(wǎng):"Subnet": "172.17.0.0/16",因此這兩個(gè)網(wǎng)絡(luò)是不重疊的。
現(xiàn)在用 docker inspect books-api 來(lái)確認(rèn)應(yīng)用程序的容器正在使用該網(wǎng)絡(luò):
注意容器的兩個(gè)別名("Aliases")是容器標(biāo)識(shí)符(3c42db680459)和由 docker-compose.yml 給出的服務(wù)名(books-service)。我們通過(guò) books-service 別名在自定義 Nginx 配置文件中來(lái)引用應(yīng)用程序的容器。這本可以通過(guò) docker network create 手動(dòng)創(chuàng)建,但是我喜歡用 Docker Compose,因?yàn)樗梢愿蓛艉?jiǎn)潔地將容器創(chuàng)建和依存捆綁在一起。
結(jié)論
所以現(xiàn)在我可以通過(guò)幾個(gè)簡(jiǎn)單的步驟在 Linux 系統(tǒng)上用 Nginx 運(yùn)行應(yīng)用程序,不需要對(duì)主機(jī)操作系統(tǒng)做任何長(zhǎng)期的改變:
- 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
我知道我在這篇文章中所寫的內(nèi)容不是一個(gè)真正的生產(chǎn)環(huán)境就緒的設(shè)備,因?yàn)槲覜]有寫任何有關(guān)下面這些的內(nèi)容,絕大多數(shù)下面的這些主題都需要用單獨(dú)一篇完整的文章來(lái)敘述。
- 安全考慮比如防火墻和 SSL 配置
- 如何確保應(yīng)用程序保持運(yùn)行狀態(tài)
- 如何選擇需要包含的 Docker 鏡像(我把所有的都放入了 Dockerfile 中)
- 數(shù)據(jù)庫(kù) - 如何在容器中管理它們
對(duì)我來(lái)說(shuō)這是一個(gè)非常有趣的學(xué)習(xí)經(jīng)歷,因?yàn)橛幸欢螘r(shí)間我對(duì)探索 ASP.NET Core 的跨平臺(tái)支持非常好奇,使用 “Configuratin as Code” 的 Docker Compose 方法來(lái)探索一下 DevOps 的世界也是非常愉快并且很有教育意義的。
如果你對(duì) Docker 很好奇,那么我鼓勵(lì)你來(lái)嘗試學(xué)習(xí)它 或許這會(huì)讓你離開舒適區(qū),不過(guò),有可能你會(huì)喜歡它?