在Docker中運行.NET 應用,初學者教程-在Docker容器中運行NancyFx

xiuhua 8年前發布 | 40K 次閱讀 Docker .NET

來自: http://dockone.io/article/999

【編者的話】本文詳細講解了如何在Docker容器中運行.NET應用,這也說明了容器技術的一種趨勢,即使像微軟這樣的大企業也在逐步向Docker靠攏。

在安靜的圣誕節期間來研究在我列表上有一段時間了的新技術和最近趨勢是一個好時機。這個圣誕節我花時間學習了最新的ASP.NET框架,尤其是通過CoreCLR將ASP.NET 5應用部署在linux操作系統上,還有通過mono在docker 容器中運行一個普通的.net 4.x的web框架。后一種技術是我今天這篇文章的主題。

什么是docker

我假設你對docker有一個基本的認識,了解docker革新了我們在云中托管軟件的技術,清楚容器比虛擬機的好處。如果對這些并不理解,那么我強烈推薦你先熟悉容器的基本概念,知道為什么在容器中部署應用是值得的。

下面是一些讓你開始的資源:

  • Docker Training
  • Docker Docs
  • Docker Deep Dive on Pluralsight
  • Awesome Docker (list of useful Docker resources)

在windows上安裝docker

首先我在本地安裝docker,這樣我可以在開發環境調試應用。幸運的是這對我們來說非常容易。我需要做的就是下載windows版的Docker Toolbox,然后根據指示安裝它。

Docker Toolbox

在我安裝完成后我會安裝以下三個軟件

  • VirtualBox
  • Kitematic
  • Docker Quickstart Terminal

如果你已經安裝了VirtualBox,這部可以跳過了。最重要的是VirtualBox提供了一個對外接口,來方便其他應用自動管理虛擬機。這也是Docker Machine的做法。它會在VirtualBox中創建一個新的虛擬機,這個鏡像里面包含了啟動Docker需要的一切。因為這一切都是自動的,所以你并不需要擔心VirtualBox。

Kitematic是一個關于Docker Machine的圖形化客戶端。它的功能非常有限,所以你也許并不需要它。

最后一個應用就是Docker Terminal,我們用它來做的唯一一件事就是在本地環境啟動和管理Docker容器。

在終端中啟動你的第一個Docker命令

在成功安裝之后讓我們執行第一條Docker命令來看看是否奏效。在你第一次打開終端的時候它會在VirtualBox中初始化虛擬機。這會花費一些時間,但是最后會以這樣的屏幕來結束:

你不需要打開Kitematic或Virtual來運行它。正如我在前面說的,你可以開心的忽略這兩個軟件,然而,如果你仔細的話,打開VirtualBox你會看到虛擬機正如期望的那樣運行著。

這是一個通過boot2docker.iso啟動的linux鏡像。

返回到終端,我們可以通過輸入命令docker ersion來看到關于Docker客戶端和服務端應用的一些基礎的版本信息。

如果看到上面的信息,說明docker運行正常。

這里有一個信息需要著重說明一下,那就是在Docker終端里面的初始化信息。

在終端中顯示的IP地址是這篇文章中你獲取你自己應用的節點。

為Docker創建一個NancyFx的web應用

現在是時候創建一個可以在Mono上運行的.Net web應用了。

首先我用一個常規的cosole application模版來新建一個工程,應用到 .NET Framework 4.6.1。

這個工程除了Program.cs這個文件以外其他都是空的:

class Program

{

static void Main(string[] args)

{

}

}</pre>

下面我會安裝3個NuGet packages:

Install-Package Nancy

Install-Package Nancy.Hosting.Self

Install-Package Mono.Posix</pre>

第一個package安裝NancyFx web框架。Nancy是一個用于創建Http基礎服務的輕量級.NET框架。你可以把它當作ASP.NET,但是它和ASP.NET、IIS和System.Web namespace無關。

你依然可以將Nancy應用部署在IIS上面,但是對應的你可以在某處像管理終端應用一樣管理它。這正是我們將要做的,也是我們安裝Nancy.Hosting.Self作為第二個安裝包的原因。

第三個包安裝了POSIX interface for Mono and .NET。

在安裝完Nancy包后我現在可以配置一個節點,并啟動一個新的Nancy.Hosting.Self.NancyHost

using System;

using Nancy.Hosting.Self;

class Program

{

static void Main(string[] args)

{

const string url = "http://localhost:8888";



var uri = new Uri(url);

var host = new NancyHost(uri);



host.Start();

}

}</pre>

這個終端應用會在啟動后馬上結束,因此我需要添加一些東西來保持它的運行狀態,比如Console.ReadLine()命令。另外我想在應用關閉前停止宿主機:

host.Start();

Console.ReadLine();

host.Stop();</pre>

如果我想將這個程序運行在windows上,那我已經完成了,但是在linux上我想用Unix的終端信號來替代。

下面是用一種幫助方法來檢測應用是否在Mono上運行:

private static bool IsRunningOnMono()

{

return Type.GetType("Mono.Runtime") != null;

}</pre>

另一種方法是暴露Unix的中斷信號

private static UnixSignal[] GetUnixTerminationSignals()

{

return new[]

{

new UnixSignal(Signum.SIGINT),

new UnixSignal(Signum.SIGTERM),

new UnixSignal(Signum.SIGQUIT),

new UnixSignal(Signum.SIGHUP)

};

}</pre>

我把兩種方法都加在了我的Program class,為了同時支持Windows和Unix中斷,修改了Main方法。

host.Start();

if (IsRunningOnMono())

{

var terminationSignals = GetUnixTerminationSignals();

UnixSignal.WaitAny(terminationSignals);

}

else

{

Console.ReadLine();

}

host.Stop();</pre>

這是最終方法的樣子:

using System;

using Nancy.Hosting.Self;

using Mono.Unix;

using Mono.Unix.Native;

class Program

{

static void Main(string[] args)

{

const string url = "http://localhost:8888";



Console.WriteLine($"Starting Nancy on {url}...");



var uri = new Uri(url);

var host = new NancyHost(uri);

host.Start();



if (IsRunningOnMono())

{

    var terminationSignals = GetUnixTerminationSignals();

    UnixSignal.WaitAny(terminationSignals);

}

else

{

    Console.ReadLine();

}



host.Stop();

}

private static bool IsRunningOnMono()

{

return Type.GetType("Mono.Runtime") != null;

}

private static UnixSignal[] GetUnixTerminationSignals()

{

return new[]

{

    new UnixSignal(Signum.SIGINT),

    new UnixSignal(Signum.SIGTERM),

    new UnixSignal(Signum.SIGQUIT),

    new UnixSignal(Signum.SIGHUP)

};

}

}</pre>

現在我只缺少一個服務Http請求的Nancy模塊。通過從Nancy.NancyModule中實現一個新的模塊,并且將它注冊到至少一個路由可以實現。我在/目錄下建立了一個“Nancy: Hello World”消息,并在/os節點下建立了一個操作系統版本的消息。

using System;

using Nancy;

public class IndexModule : NancyModule

{

public IndexModule()

{

Get["/"] = _ => "Nancy: Hello World";

Get["/os"] = _ => Environment.OSVersion.ToString();

}

}</pre>

如果我編譯并運行這個應用,我會在訪問 http://localhost:8888 時看到hello world 信息,在訪問 http://localhost:8888/os 時看到系統的版本信息:

在Docker容器中運行NancyFx

這個應用很簡單但是足夠在Docker容器中運行第一個版本。

創建Dockerfile

首先我需要構建一個包含應用和它所有依賴的Docker鏡像。為了這個目的,我要創建一個定義了在鏡像中運行的應用程序的方法。這個方法就是Dockerfile,這是一個包含如何組成鏡像的可讀指令集合。重要的是完全按顯示命名,不要包含文件擴展和大寫的“D”。

將Dockfile加入到你的工程是一個很好的練習,因為當你的工程改變時,Dockfile也要跟著更改。

我想將Dockerfile加入到應用的輸出中,所以我需要將“Build Action”設置為“Content”,將“Copy to Output Directory”設置為“Copy always”。

Visual Studio 2015 在創建文件時默認使用 UTF-8-BOM 編碼。這會在創建的文件首行添加一個不可見的BOM字符,這在通過Dockerfile構建鏡像時會引起錯誤。最簡單的解決辦法是通過 Notepad 打開文件,并將編碼格式修改為UTF-8。

你也可以通過設置Visual Studio來改變保存文件的編碼。

現在我可以定義并排序生成步驟。

每個Dockerfile文件都以FROM指令開始。這定義了基本鏡像開始的位置。Dokcer鏡像輕量的一個原因就是Docker使用了layering system。你可以在Docker Hub上找到很多官方鏡像來開始。

幸運的是,這里已經有一個官方的Mono倉庫可供我們使用。在我寫作的時候最新的鏡像是4.2.1.102。正如你看到的一樣,Mono鏡像使用Debian的官方倉庫中的debian:wheezy image作為它的基本鏡像。這個Debian鏡像使用空的scratch鏡像作為它的基礎。當我們使用Mono鏡像的時候我們需要在目錄的頂端重新構建一個層次結構:

scratch

___ debian:wheezy

___ mono:4.2.1.102

   \___ {our repository}:{tag}</pre> 

如果你觀察Mono的官方倉庫你會看到最新的Mono鏡像有很多標簽:

通過你的用例來選擇最合適你應用的版本。盡管它們都是由同一個Dockerfile構建的,但是只有4.2.1.102是始終保證完全相同的構建。

從個人角度來說我會選擇這個版本作為我的生產應用:

FROM mono:4.2.1.102

下面的兩個命令非常明了。我想要創建一個名字為/app的文件夾,將所有與執行這個應用相關的文件復制進去。記住Dockerfile會被復制到輸出文件夾下。這意味著同一目錄下的所有文件都需要被拷貝到/app文件夾下。

RUN mkdir /app

COPY . /app</pre>

我的Nancy應用配置為監聽8888端口。通過使用EXPOSE命令提示容器監聽指定端口:

EXPOSE 8888

最后我將應用部署到Mono上:

CMD ["mono", "/app/DockerDemoNancy.Host.exe", "-d"]

最后的Dockerfile如下所示:

FROM mono:4.2.1.102

RUN mkdir /app

COPY . /app

EXPOSE 8888

CMD ["mono", "/app/DockerDemoNancy.Host.exe", "-d"]</pre>

利用Dockerfile你還可以進行很多操作。參考Dockerfile reference查看完整的指令列表。

構建Docker鏡像

構建一個Docker鏡像非常簡單。通過Docker終端查看我的Nancy應用下面的/bin/Realease目錄:

cd /c/github/docker-demo-nancy/dockerdemonancy.host/bin/release

下面我運行 docker build命令,并用-t給鏡像添加標識

docker build -t docker-demo-nancy:0.1.0 .

不要忘了以句號結尾。這指明了包含Dockerfile的目錄。因為我已經在/bin/Realease目錄下,所以我只是以句號結尾。

構建的過程會執行每個指令,并且在執行之后會生成新的目錄結構。在你第一次執行命令的時候你的硬盤上不會有mono:4.2.1.102鏡像,Docker會自動從公有倉庫(Docker Hub)下載下來。

正如你看到的,FROM指令會下載六個不同的鏡像,這事因為mono:4.2.1.102鏡像和它的繼承鏡像(debian:wheezy)一共有六條指令,所以產生了六層鏡像。

將這些過程可視化的方法是監控我們自己的鏡像。

一旦構建完成,我們可以通過docker image命令來查看鏡像列表:

通過docker history {image-id}可以查看鏡像的全部歷史記錄,包括這個鏡像在哪一層,執行過哪些指令。

這樣非常智能。無論如何,我們剛剛創建了我們的第一個Docker鏡像。

如果你想將鏡像上傳到Docker Hub或者其他私有倉庫,你可以使用docker tag給已有的鏡像打上標簽,然后利用docker push命令將它上傳到倉庫。

創建并運行第一個Docker應用

運行一個Docker容器并不容易。用docker run 命令來創建并運行一個容器:

docker run -d -p 8888:8888 docker-demo-nancy:0.1.0

-d參數說明Docker以獨立模式運行,-p 8888:8888參數將容器的8888端口與主機8888端口對應起來。

然后你可以用命令docker ps來查看正在運行的容器:

非常好,現在將{docker-ip}:8888粘貼到瀏覽器地址欄,你將會看到hello world信息:

瀏覽{docker-ip}:8888/os會看到“Unix 4.1.13.2”:

這非常棒。我們毫不費力的在Docker容器中通過Mono部署了一個 Nancy .Net應用。

提示:將你Docker的IP地址映射到一個友好的DNS

你可以通過更改Windows的hosts文件將Dokcer的IP地址映射到DNS:

1、以管理員的身份打開C:\Windows\System32\drivers\etc\hosts

2、映射IP地址

192.168.99.100  docker.local

3、保存文件

現在你可以在地址欄輸入docker.local:8888來直接獲取返回信息

通過Docker來配置指定的環境設置

在這篇文章的最后我想要說明如何在Docker容器中控制環境變量。

我認為當你遷移你的Docker容器時,你不能修改你的Docker鏡像。這意味著在Docker鏡像中的app.config文件在每個環境都是一樣的。

人們習慣傳換配置文件。這種習慣需要停止,Docker使得在容器啟動時配置參數變得容易。

讓我們對Nancy的IndexModule做出一點小的改變:

public IndexModule()

{

var secret = Environment.GetEnvironmentVariable("Secret");

Get["/"] = _ => "Nancy: Hello World";

Get["/os"] = _ => Environment.OSVersion.ToString();

Get["/secret"] = _ => secret jQuery2110766658102395013_1454232198306 "not set";

}</pre>

這是個很明顯的改變。我將一個名為“Secret”的參數加入到環境變量中,并將它公開出來。

環境變量可以是任何數據,但通常來說他包含著加密密匙、數據庫連接串和諸如錯誤日志路徑這樣的敏感數據。

這個例子的目的只是說明可以將環境變量公開。

現在我需要運行之前的指令來重新編譯并構建應用。我將新的鏡像標記為docker-demo-nancy:0.2.0。

為了防止端口沖突,我需要將之前運行的Docker容器先停下來,盡管我很希望能同時運行這兩個容器。

docker run -d -p 8888:8888 -e Secret=S3cReT docker-demo-nancy:0.2.0

docker run命令通過-e 參數可以指定很多環境變量。還有很多不同的方法可以指定環境變量,但是你可能最想用到是通過--env-file參數來導入額外的變量。

這樣做有以下幾個好處:

1、你可以輕易地傳遞環境變量

2、你可以輕易地提供很多環境變量

3、敏感數據不會出現在日志文件中

4、日志文件存在指定目錄可以在生產環境中更好的安排執行計劃。

在加載了secret變量的容器啟動之后,我可以通過運行docker inspect {container-id} 命令來獲取容器相關的大量數據。下面是容器加載的變量信息:

瀏覽 docker.local:8888/secret 會看到環境變量的數據:

概述

我第一篇關于在Docker中運行.NET應用就這樣結束了。我希望我能撇去Docker的基礎知識,將關注點放在如何更快、更容易的通過Docker部署.NET應用上。

在這個例子中我使用Nancy框架來部署一個web應用,但是我同時可以使用一個可以在Mono或者ASP.NET 5上部署的普通.NET應用,這是可以跨平臺的。

在這篇文章中還有很多在Docker中運行.NET應用的知識點我沒有提到。這些事包括在Docker容器中調試應用,通過你的CI來構建Docker鏡像,在生產環境中管理容器等。請關注我下面的系列文章,我會在這系列的文章中深入的探討這些問題。

完整的示例代碼可以在GItHub上找到。鏡像也可以在我的Docker Hub倉庫上下載下來。

原文鏈接 Running NancyFx in a Docker container, a beginner's guide to build and run .NET applications in Docker

(翻譯:邢毅勛)

譯者介紹

邢毅勛,亞信研發工程師,熱愛開源好青年

</div>

 本文由用戶 xiuhua 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!