新手指南:通过 Docker 在 Linux 上托管 .NET Core

我是首次接触 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 的容器。


  1. docker run -it microsoft/dotnet:latest

-it 选项表示交互,所以你执行这条命令之后,你就处于容器之内了,可以如你所希望的那样执行任何 bash 命令。

然后我们可以执行下面这五条命令来在 Docker 内部运行起来微软 .NET Core 控制台应用程序示例。


  1. mkdir hwapp
  2. cd hwapp
  3. dotnet new
  4. dotnet restore
  5. dotnet run

你可以通过运行 exit 来离开容器,然后运行 Docker ps -a 命令,这会显示你创建的那个已经退出的容器。你可以通过上运行命令 Docker rm <container_name> 来清除容器。

挂载源代码

我的下一步骤是使用和上面相同的 microsoft/dotnet 镜像,但是将为我们的应用程序以数据卷的方式挂载上源代码。

首先签出有相关提交的仓库:


  1. git clone https://github.com/niksoper/aspnet5-books.git
  2. cd aspnet5-books/src/MvcLibrary
  3. git checkout dotnet-core-1.0

现在启动一个容器来运行 .NET Core 1.0,并将源代码放在 /book 下。注意更改 /path/to/repo 这部分文件来匹配你的电脑:


  1. docker run -it \
  2. -v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \
  3. microsoft/dotnet:latest

现在你可以在容器中运行应用程序了!


  1. cd /books
  2. dotnet restore
  3. dotnet run

作为一个概念性展示这的确很棒,但是我们可不想每次运行一个程序都要考虑如何把源代码安装到容器里。

增加一个 DOCKERFILE

我的下一步骤是引入一个 Dockerfile,这可以让应用程序很容易在自己的容器内启动。

我的 Dockerfile 和 project.json 一样位于 src/MvcLibrary 目录下,看起来像下面这样:


  1. FROM microsoft/dotnet:latest
  2. # 为应用程序源代码创建目录
  3. RUN mkdir -p /usr/src/books
  4. WORKDIR /usr/src/books
  5. # 复制源代码并恢复依赖关系
  6. COPY . /usr/src/books
  7. RUN dotnet restore
  8. # 暴露端口并运行应用程序
  9. EXPOSE 5000
  10. CMD [ "dotnet", "run" ]

严格来说,RUN mkdir -p /usr/src/books 命令是不需要的,因为 COPY 会自动创建丢失的目录。

Docker 镜像是按层建立的,我们从包含 .NET Core 的镜像开始,添加另一个从源代码生成应用程序,然后运行这个应用程序的层。

添加了 Dockerfile 以后,我通过运行下面的命令来生成一个镜像,并使用生成的镜像启动一个容器(确保在和 Dockerfile 相同的目录下进行操作,并且你应该使用自己的用户名)。


  1. docker build -t niksoper/netcore-books .
  2. docker run -it niksoper/netcore-books

你应该看到程序能够和之前一样的运行,不过这一次我们不需要像之前那样安装源代码,因为源代码已经包含在 docker 镜像里面了。

暴露并发布端口

这个 API 并不是特别有用,除非我们需要从容器外面和它进行通信。 Docker 已经有了暴露和发布端口的概念,但这是两件完全不同的事。

据 Docker 官方文档

EXPOSE 指令通知 Docker 容器在运行时监听特定的网络端口。EXPOSE 指令不能够让容器的端口可被主机访问。要使可被访问,你必须通过 -p 标志来发布一个端口范围或者使用 -P 标志来发布所有暴露的端口

EXPOSE 指令只是将元数据添加到镜像上,所以你可以如文档中说的认为它是镜像消费者。从技术上讲,我本应该忽略 EXPOSE 5000 这行指令,因为我知道 API 正在监听的端口,但把它们留下很有用的,并且值得推荐。

在这个阶段,我想直接从主机访问这个 API ,因此我需要通过 -p 指令来发布这个端口,这将允许请求从主机上的端口 5000 转发到容器上的端口 5000,无论这个端口是不是之前通过 Dockerfile 暴露的。


  1. docker run -d -p 5000:5000 niksoper/netcore-books

通过 -d 指令告诉 docker 在分离模式下运行容器,因此我们不能看到它的输出,但是它依旧会运行并监听端口 5000。你可以通过 docker ps 来证实这件事。

因此,接下来我准备从主机向容器发起一个请求来庆祝一下:


  1. 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:


  1. public static void Main(string[] args)
  2. {
  3. var host = new WebHostBuilder()
  4. .UseKestrel()
  5. .UseContentRoot(Directory.GetCurrentDirectory())
  6. .UseUrls("http://*:5000") // 在所有网络接口上监听端口 5000
  7. .UseIISIntegration()
  8. .UseStartup<Startup>()
  9. .Build();
  10. host.Run();
  11. }

通过这些额外的配置,我可以重建镜像,并在容器中运行应用程序,它将能够接收来自主机的请求:


  1. docker build -t niksoper/netcore-books .
  2. docker run -d -p 5000:5000 niksoper/netcore-books
  3. curl -i http://localhost:5000/api/books

我现在得到下面这些相应:


  1. HTTP/1.1 200 OK
  2. Date: Tue, 30 Aug 2016 15:25:43 GMT
  3. Transfer-Encoding: chunked
  4. Content-Type: application/json; charset=utf-8
  5. Server: Kestrel
  6. [{"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 生产环境下的指导教程。我把说明总结在这儿:

  1. 通过 dotnet publish 来给应用程序产生一个自包含包。
  2. 把已发布的应用程序复制到服务器上
  3. 安装并配置 Nginx(作为反向代理服务器)
  4. 安装并配置 supervisor(用于确保 Nginx 服务器处于运行状态中)
  5. 安装并配置 AppArmor(用于限制应用的资源使用)
  6. 配置服务器防火墙
  7. 安全加固 Nginx(从源代码构建和配置 SSL)

这些内容已经超出了本文的范围,因此我将侧重于如何把 Nginx 配置成一个反向代理服务器。自然地,我通过 Docker 来完成这件事。

在另一个容器中运行 NGINX

我的目标是在第二个 Docker 容器中运行 Nginx 并把它配置成我们的应用程序容器的反向代理服务器。

我使用的是来自 Docker Hub 的官方 Nginx 镜像。首先我尝试这样做:


  1. docker run -d -p 8080:80 --name web nginx

这启动了一个运行 Nginx 的容器并把主机上的 8080 端口映射到了容器的 80 端口上。现在在浏览器中打开网址http://localhost:8080 会显示出 Nginx 的默认登录页面。

现在我们证实了运行 Nginx 是多么的简单,我们可以关闭这个容器。


  1. docker rm -f web

把 NGINX 配置成一个反向代理服务器

可以通过像下面这样编辑位于 /etc/nginx/conf.d/default.conf 的配置文件,把 Nginx 配置成一个反向代理服务器:


  1. server {
  2. listen 80;
  3. location / {
  4. proxy_pass http://localhost:6666;
  5. }
  6. }

通过上面的配置可以让 Nginx 将所有对根目录的访问请求代理到 http://localhost:6666。记住这里的localhost 指的是运行 Nginx 的容器。我们可以在 Nginx容器内部利用卷来使用我们自己的配置文件:


  1. docker run -d -p 8080:80 \
  2. -v /path/to/my.conf:/etc/nginx/conf.d/default.conf \
  3. nginx

注意:这把一个单一文件从主机映射到容器中,而不是一个完整目录。

在容器间进行通信

Docker 允许内部容器通过共享虚拟网络进行通信。默认情况下,所有通过 Docker 守护进程启动的容器都可以访问一种叫做“桥”的虚拟网络。这使得一个容器可以被另一个容器在相同的网络上通过 IP 地址和端口来引用。

你可以通过监测inspect容器来找到它的 IP 地址。我将从之前创建的 niksoper/netcore-books 镜像中启动一个容器并监测inspect它:


  1. docker run -d -p 5000:5000 --name books niksoper/netcore-books
  2. docker inspect books

我们可以看到这个容器的 IP 地址是 "IPAddress": "172.17.0.3"

所以现在如果我创建下面的 Nginx 配置文件,并使用这个文件启动一个 Nginx 容器, 它将代理请求到我的 API :


  1. server {
  2. listen 80;
  3. location / {
  4. proxy_pass http://172.17.0.3:5000;
  5. }
  6. }

现在我可以使用这个配置文件启动一个 Nginx 容器(注意我把主机上的 8080 端口映射到了 Nginx 容器上的 80 端口):


  1. docker run -d -p 8080:80 \
  2. -v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \
  3. nginx

一个到 http://localhost:8080 的请求将被代理到应用上。注意下面 curl 响应的 Server 响应头:

DOCKER COMPOSE

在这个地方,我为自己的进步而感到高兴,但我认为一定还有更好的方法来配置 Nginx,可以不需要知道应用程序容器的确切 IP 地址。另一个当地的 Scott Logic DevOps 大师 Jason Ebbin 在这个地方进行了改进,并建议使用 Docker Compose

概况描述一下,Docker Compose 使得一组通过声明式语法互相连接的容器很容易启动。我不想再细说 Docker Compose 是如何工作的,因为你可以在之前的文章中找到。

我将通过一个我所使用的 docker-compose.yml 文件来启动:


  1. version: '2'
  2. services:
  3. books-service:
  4. container_name: books-api
  5. build: .
  6. reverse-proxy:
  7. container_name: reverse-proxy
  8. image: nginx
  9. ports:
  10. - "9090:8080"
  11. volumes:
  12. - ./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 文件看起来像下面这样:


  1. server {
  2. listen 8080;
  3. location / {
  4. proxy_pass http://books-service:5000;
  5. }
  6. }

这儿的关键点是我们现在可以通过名字引用 books-service,因此我们不需要知道 books-api 这个容器的 IP 地址!

现在我们可以通过一个运行着的反向代理启动两个容器(-d 意味着这是独立的,因此我们不能看到来自容器的输出):


  1. docker compose up -d

验证我们所创建的容器:


  1. docker ps

最后来验证我们可以通过反向代理来控制该 API :


  1. 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 运行应用程序,不需要对主机操作系统做任何长期的改变:


  1. git clone https://github.com/niksoper/aspnet5-books.git
  2. cd aspnet5-books/src/MvcLibrary
  3. git checkout blog-docker
  4. docker-compose up -d
  5. curl -i http://localhost:9090/api/books

我知道我在这篇文章中所写的内容不是一个真正的生产环境就绪的设备,因为我没有写任何有关下面这些的内容,绝大多数下面的这些主题都需要用单独一篇完整的文章来叙述。

  • 安全考虑比如防火墙和 SSL 配置
  • 如何确保应用程序保持运行状态
  • 如何选择需要包含的 Docker 镜像(我把所有的都放入了 Dockerfile 中)
  • 数据库 - 如何在容器中管理它们

对我来说这是一个非常有趣的学习经历,因为有一段时间我对探索 ASP.NET Core 的跨平台支持非常好奇,使用 “Configuratin as Code” 的 Docker Compose 方法来探索一下 DevOps 的世界也是非常愉快并且很有教育意义的。

如果你对 Docker 很好奇,那么我鼓励你来尝试学习它 或许这会让你离开舒适区,不过,有可能你会喜欢它?

原文发布时间为:2017-11-04

本文来自合作伙伴“Linux中国”

时间: 2024-08-11 09:21:58

新手指南:通过 Docker 在 Linux 上托管 .NET Core的相关文章

通过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 .这将会

docker在windows上为啥需要虚拟化。

docker在linux上运行不需要虚拟化,但是,如果在windows上安装运行就需要虚拟化,找到答案了. Docker 底层的核心技术包括 Linux 上的名字空间( Namespaces) . 控制组( Control groups) . Union 文件系统( Union file systems) 和容器格式( Container format) .我们知道, 传统的虚拟机通过在宿主主机中运行 hypervisor 来模拟一整套完整的硬件环境提供给虚拟机的操作系统. 虚拟机系统看到的环境

Linux通过 Docker 可以托管 .NET Core啦!

我是首次接触 Docker 并且距离成为一名 Linux 高手还有很远的一段路程.因此,这里的很多想法是来自一个新手. 内容 安装按照 https://www.microsoft.com/net/core 上的介绍在你的电脑上安装 .NET Core .这将会同时在 Windows 上安装 dotnet 命令行工具以及最新的 Visual Studio 工具. 源代码 你可以直接到 GitHub 上找最到最新完整的源代码. 转换到 .NET CORE 1.0 自然地,当我考虑如何把 API 从

运行Asp.net应用程序在Linux上的3种托管方式

运行Asp.net应用程序在Linux上的3种托管方式   想要运行Asp.net应用程序在Linux上,我们有3种选择: 1.使用Apache作为Web服务器,使用mod_mono:http://www.mono-project.com/docs/web/mod_mono/2.使用Nginx作为Web服务器,使用FastCGI托管:http://www.mono-project.com/docs/web/fastcgi/3.使用XSP作为Web服务器,这是一个用C#写的轻量级简单的web se

在Docker上运行.NET Core

本文讲的是在Docker上运行.NET Core,[编者的话]本文为Jurgis Pasukonis在medium.com博客中发布的关于在Docker上运行.NET Core的文章,介绍了目前.NET Core在Docker上的开源情况及部分演示.Jurgis目前是TRAFI公司的CTO. 对于Microsoft和.NET来说,这是一个新的时代,然而这并不是言过其实的.如果你之前没有追随过这些消息,那么下面是一些发生在去年与此有关的事情: .NET框架和C#语言已经被开源并且可以在GitHub

如何在 Linux 上 使用 ONLYOFFICE 协同编辑文档

"多年前由一些紧张兮兮的.带有强迫症的助理开发的 Bulletin,只是一个放在共享文件夹中只有 Emily 和我可以访问的 Word 文档而已.同一时间我们只有一个人可以打开它并添加消息.想法,或者给条目列表上增加问题.然后我们再打印出更新后的版本并放到我桌子架上的剪贴板,并在完成时删除旧的内容."(--<穿普拉达的女王>,劳伦·魏丝伯格著 ) 直到今天人们仍然在使用这样的"协同编辑",只有一个人可以打开共享文件,对其进行更改,然后告诉其它人什么时候修

10年资深架构师谈Linux上容器背后的虚拟化解决方案

写在前面   目前主流的虚拟化技术有类似Intel VT这样的纯粹底层硬件虚拟化技术,也有类似Xen这样的半虚拟化技术,它并不是一个真正的虚拟机,而是相当于自己运行了一个内核的实例,可以自由的加载内核模块,虚拟的内存和IO,稳定而且可预测.   也有kvm这样的完全虚拟化技术,所有的kvm类型的虚拟技术都可以装各种linux的发行版和各 种win的发行版,当然,还有最近比较流行的容器类技术,例如lxc,docker都是基于操作系统之上的进程级别的隔离技术,本次分享主要围绕上层的虚拟化技术来剖析L

前端也应该了解点 docker 知识:docker 架构(上)

上一篇文章 前端也应该了解点 docker 知识:docker 的理念与场景 介绍了 docker 的一些理念,以及在前端方面可能的应用场景,本篇我们梳理一下 docker 的架构. 话说,我们团队的小明同学看了上篇文章之后,很是激动,迫不及待的想尝试下 docker ,然后按照网上一些教程跑出来了一个 "Hello world" ,激动的差点把鼠标给扔了.如果小明的故事到这里,那就不是我们团队的小明了,他是位爱动脑筋,对细节刨根问底的同学,他没有沉浸在跑出来 "Hello

如何在linux上分享你shell命令的输出

如何在linux上分享你shell命令的输出 前段时间我发布了一篇关于shelr.tv这个网站的文章,它提供一个服务允许你从网站上直接分享你的终端记录. 现在shelr.tv这个网站似乎关闭了,然后我四处寻找是否有类似的网站,于是我发现了commands.com. 从它的主页上来看,它的服务和其他网站提供的服务是类似的,因此让我们来测试它. 步骤 1 – 在网站上注册 只需要注册一个新的 用户名/密码,或者直接使用你的github账户快速登录. 步骤 2 – 下载安装monitor程序 Moni