From 486b0ddb5a3cfebbdad3f8d6b6def5256b701c1c Mon Sep 17 00:00:00 2001 From: wxy Date: Fri, 4 Nov 2016 21:38:04 +0800 Subject: [PATCH] PUB:20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @ucasFL 辛苦了,不过有些地方对 docker 不够熟悉。另外,翻译时候没有参照原文格式,有些翻译就理解错误了。 --- ...E ON LINUX WITH DOCKER - A NOOB'S GUIDE.md | 447 ++++++++++++++++++ ...E ON LINUX WITH DOCKER - A NOOB'S GUIDE.md | 446 ----------------- 2 files changed, 447 insertions(+), 446 deletions(-) create mode 100644 published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md delete mode 100644 translated/tech/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md diff --git a/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md b/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md new file mode 100644 index 0000000000..65be6bea8b --- /dev/null +++ b/published/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md @@ -0,0 +1,447 @@ +新手指南 - 通过 Docker 在 Linux 上托管 .NET Core +===== + +这篇文章基于我之前的文章 [.NET Core 入门][1]。首先,我把 RESTful API 从 .NET Core RC1 升级到了 .NET Core 1.0,然后,我增加了对 Docker 的支持并描述了如何在 Linux 生产环境里托管它。 + +![](http://blog.scottlogic.com/nsoper/assets/noob.png) + +我是首次接触 Docker 并且距离成为一名 Linux 高手还有很远的一段路程。因此,这里的很多想法是来自一个新手。 + +### 安装 + +按照 https://www.microsoft.com/net/core 上的介绍在你的电脑上安装 .NET Core 。这将会同时在 Windows 上安装 dotnet 命令行工具以及最新的 Visual Studio 工具。 + +### 源代码 + +你可以直接到 [GitHub](https://github.com/niksoper/aspnet5-books/tree/blog-docker) 上找最到最新完整的源代码。 + +### 转换到 .NET CORE 1.0 + +自然地,当我考虑如何把 API 从 .NET Core RC1 升级到 .NET Core 1.0 时想到的第一个求助的地方就是谷歌搜索。我是按照下面这两条非常全面的指导来进行升级的: + +- [从 DNX 迁移到 .NET Core CLI][2] +- [从 ASP.NET 5 RC1 迁移到 ASP.NET Core 1.0][3] + +当你迁移代码的时候,我建议仔细阅读这两篇指导,因为我在没有阅读第一篇指导的情况下又尝试浏览第二篇,结果感到非常迷惑和沮丧。 + +我不想描述细节上的改变因为你可以看 GitHub 上的[提交](https://github.com/niksoper/aspnet5-books/commit/b41ad38794c69a70a572be3ffad051fd2d7c53c0)。这儿是我所作改变的总结: + +- 更新 `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 在哪儿][4]?这是一篇关于第三方开发者观点的文章。 + +做了这些改变以后,我可以从 `project.json` 目录恢复、构建并运行 dotnet,可以看到 API 又像以前一样工作了。 + +### 通过 Docker 运行 + +在我写这篇文章的时候, Docker 只能够在 Linux 系统上工作。在 [Windows](https://docs.docker.com/engine/installation/windows/#/docker-for-windows) 系统和 [OS X](https://docs.docker.com/engine/installation/mac/) 上有 beta 支持 Docker,但是它们都必须依赖于虚拟化技术,因此,我选择把 Ubuntu 14.04 当作虚拟机来运行。如果你还没有安装过 Docker,请按照[指导](https://docs.docker.com/engine/installation/linux/ubuntulinux/)来安装。 + +我最近阅读了一些关于 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 ` 来清除容器。 + +#### 挂载源代码 + +我的下一步骤是使用和上面相同的 microsoft/dotnet 镜像,但是将为我们的应用程序以[数据卷](https://docs.docker.com/engine/tutorials/dockervolumes/1)的方式挂载上源代码。 + +首先签出有相关提交的仓库: + +``` +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 [官方文档](https://docs.docker.com/engine/reference/builder/#/expose): + +> `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 的[文档](https://docs.docker.com/engine/reference/run/#/expose-incoming-ports),然后再次检查我所使用的 `-p` 选项以及 Dockerfile 中的 `EXPOSE` 指令是否正确。我没有发现任何问题,这让我开始有些沮丧。 + +重新振作起来以后,我决定去咨询当地的一个 Scott Logic DevOps 大师 - Dave Wybourn(也在[这篇 Docker Swarm 的文章](http://blog.scottlogic.com/2016/08/30/docker-1-12-swarm-mode-round-robin.html)里提到过),他的团队也曾遇到这个实际问题。这个问题是我没有配置过 [Kestral](https://docs.asp.net/en/latest/fundamentals/servers.html#kestrel),这是一个全新的轻量级、跨平台 web 服务器,用于 .NET Core 。 + +默认情况下, Kestrel 会监听 http://localhost:5000。但问题是,这儿的 `localhost` 是一个回路接口。 + +据[维基百科](https://en.wikipedia.org/wiki/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() + .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 + +[微软的介绍](https://docs.asp.net/en/latest/publishing/linuxproduction.html#why-use-a-reverse-proxy-server): + +> Kestrel 可以很好的处理来自 ASP.NET 的动态内容,然而,网络服务部分的特性没有如 IIS,Apache 或者 Nginx 那样的全特性服务器那么好。反向代理服务器可以让你不用去做像处理静态内容、缓存请求、压缩请求、SSL 端点这样的来自 HTTP 服务器的工作。 + +因此我需要在我的 Linux 机器上把 Nginx 设置成一个反向代理服务器。微软介绍了如何[发布到 Linux 生产环境下](https://docs.asp.net/en/latest/publishing/linuxproduction.html)的指导教程。我把说明总结在这儿: + +1. 通过 `dotnet publish` 来给应用程序产生一个自包含包。 +2. 把已发布的应用程序复制到服务器上 +3. 安装并配置 Nginx(作为反向代理服务器) +4. 安装并配置 [supervisor](http://supervisord.org/)(用于确保 Nginx 服务器处于运行状态中) +5. 安装并配置 [AppArmor](https://wiki.ubuntu.com/AppArmor)(用于限制应用的资源使用) +6. 配置服务器防火墙 +7. 安全加固 Nginx(从源代码构建和配置 SSL) + +这些内容已经超出了本文的范围,因此我将侧重于如何把 Nginx 配置成一个反向代理服务器。自然地,我通过 Docker 来完成这件事。 + +### 在另一个容器中运行 NGINX + +我的目标是在第二个 Docker 容器中运行 Nginx 并把它配置成我们的应用程序容器的反向代理服务器。 + +我使用的是[来自 Docker Hub 的官方 Nginx 镜像](https://hub.docker.com/_/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 地址和端口来引用。 + +你可以通过监测(inspect)容器来找到它的 IP 地址。我将从之前创建的 `niksoper/netcore-books` 镜像中启动一个容器并监测(inspect)它: + +``` +docker run -d -p 5000:5000 --name books niksoper/netcore-books +docker inspect books +``` + +![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-ip.PNG) + +我们可以看到这个容器的 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` 响应头: + +![](http://blog.scottlogic.com/nsoper/assets/nginx-proxy-response.PNG) + +### DOCKER COMPOSE + +在这个地方,我为自己的进步而感到高兴,但我认为一定还有更好的方法来配置 Nginx,可以不需要知道应用程序容器的确切 IP 地址。另一个当地的 Scott Logic DevOps 大师 Jason Ebbin 在这个地方进行了改进,并建议使用 [Docker Compose](https://docs.docker.com/compose/)。 + +概况描述一下,Docker Compose 使得一组通过声明式语法互相连接的容器很容易启动。我不想再细说 Docker Compose 是如何工作的,因为你可以在[之前的文章](http://blog.scottlogic.com/2016/01/25/playing-with-docker-compose-and-erlang.html)中找到。 + +我将通过一个我所使用的 `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` 来验证网络已经存在: + +![](http://blog.scottlogic.com/nsoper/assets/docker-network-ls.PNG) + +你可以使用 `docker network inspect mvclibrary_default` 来看到新的网络的细节: + +![](http://blog.scottlogic.com/nsoper/assets/network-inspect.PNG) + +注意 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` 来确认应用程序的容器正在使用该网络: + +![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-books-api.PNG) + +注意容器的两个别名(`"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][a] +译者:[ucasFL](https://github.com/ucasFL) +校对:[wxy](https://github.com/wxy) + +本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 + +[a]: http://blog.scottlogic.com/nsoper +[1]: http://blog.scottlogic.com/2016/01/20/restful-api-with-aspnet50.html +[2]: https://docs.microsoft.com/en-us/dotnet/articles/core/migrating-from-dnx +[3]: https://docs.asp.net/en/latest/migration/rc1-to-rtm.html +[4]: http://www.paraesthesia.com/archive/2016/06/29/netcore-rtm-where-is-autofac/ + + + + + + diff --git a/translated/tech/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md b/translated/tech/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md deleted file mode 100644 index c9c7bfc26b..0000000000 --- a/translated/tech/20160910 HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE.md +++ /dev/null @@ -1,446 +0,0 @@ - -新手指南 - 通过 DOCKER 在 Linux 上托管 .NET 核心 -===== - -这篇文章基于我之前的文章【.NET 核心入门】【1】。首先,我把应用程序界面【API】从 .NET Core RC1 升级到了 .NET Core 1.0,然后,我增加了对 Docker 的支持并描述了如何在 Linux 生产环境里托管它。 - -![](http://blog.scottlogic.com/nsoper/assets/noob.png) - -我是首次接触 Docker 并且距离成为一名 Linux 高手还有很远的一段路程。因此,这里的很多想法是来自一个新手。 - -### 安装 - -按照 https://www.microsoft.com/net/core 上的介绍在你的电脑上安装 .NET 核心。这将会同时在 Windows 上安装 dotnet 命令行工具以及最新的 Visual Studio 工具。 - -### 源代码 - -你可以直接到 GitHub 上找最到最新完整的源代码。 - -### 转换到 .NET CORE 1.0 - - -自然地,当我考虑如何把应用程序界面【API】 .NET Core RC1 升级到 .NET Core 1.0 时想到的第一个求助的地方就是谷歌搜索。我是按照下面这两条非常全面的指导来进行升级的: - -- [Migrating from DNX to .NET Core CLI][2] -- [Migrating from ASP.NET 5 RC1 to ASP.NET Core 1.0][3] - -当你迁移代码的时候,我建议仔细阅读这两条指导,因为我在没有阅读第一篇指导的情况下又尝试浏览第二篇,结果感到非常迷惑和沮丧。 - -我不想描述细节上的改变因为你可以看 GitHub 上的提交。这儿是我所作改变的摘要: - -- 更新 global.json and project.json 上的版本数 -- 删除 project.json 上的废弃章节 -- 使用轻型 ControllerBase 而不是 Controller, 因为我不需要与 MVC 视图相关的方法(这是一个可选的改变)。 -- 从辅助方法中去掉前缀,比如:HttpNotFound -> NotFound -- LogVerbose -> LogTrace -- 名字空间改变: Microsoft.AspNetCore.* -- 使用 SetBasePath 启动(没有它 appsettings.json 将不会被发现) -- 通过 WebHostBuilder 来运行而不是通过 WebApplication.Run 来运行 -- 删除 Serilog(在写文章的时候,它不支持 .NET Core 1.0) - -唯一令我真正头疼的事是需要移动 Serilog。我本能够执行自己的文件记录器,但是我删除了文件记录因为我不想为了这次操作集中精力在这件事情上。 - -不幸的是,将有大量的第三方开发者扮演追赶 .NET Core 1.0 的角色,我非常同情他们,因为他们通常在休息时间还坚持工作但却依旧根本无法接近微软的可用资源。我建议阅读 Travis Illig 的文章【.NET Core 1.0 发布了,但 Autofac 在哪儿】【4】?这是一篇关于第三方开发者观点的文章。 - -做了这些改变以后,我可以从 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#docke 上的介绍来开始运行 .NET 核心和 Docker. - -首先启动一个已安装有 .NET Core 的 container。 - - -``` -docker run -it microsoft/dotnet:latest -``` - --it 选项意味着你可以在 container 内交互执行这条命令并按照你所希望的那样自由执行任何 bash 命令。 - -然后我们可以运行下面这五条命令个来获得在 Docker 内部运行的 Microsoft’s Hello World .NET 核心控制台运用程序。 - -1. mkdir hwapp -2. cd hwapp -3. dotnet new -4. dotnet restore -5. dotnet run - - -你可以通过退出来离开 container,然后在 Docker 内运行 ps -a 命令,这会显示你已经创建了一个已退出的 container。你可以通过在 Docker 上运行命令 rm . 来整理一下 Container。 - - -#### 安装源代码 - -我的下一步骤是使用和上面相同的 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 - -现在启动一个 container 来运行位于 /book 目录下伴有源程序的 .NET Core 1.0。 注意更改 /path/to/repo 这部分文件来匹配你的电脑: - -``` -docker run -it \ --v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \ -microsoft/dotnet:latest -``` - -现在你可以运行 container 中的应用程序了! - -``` -cd /books -dotnet restore -dotnet run -``` - -作为一个概念的证明,这的确很棒,但是我们不想无论何时打算运行一个程序都要考虑如何把源代码安装到 container 里。 - - -#### 增加一个 DOCKERFILE - -我的下一步骤是引入一个 Docker 文件,这将允许应用程序很容易在自己的 container 内启动。 - -我的 Docker 文件和 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 的图像开始,添加另一个从源生成应用程序的层,然后运行这个运用程序。 - -添加了 Docker 文件以后,我通过运行下面的命令来生成一个图像并使用生成的图像打开一个 container(确保在和 Docker 文件相同的目录下进行操作并且你应该使用自己的用户名)。 - -1. docker build -t niksoper/netcore-books . -2. docker run -it niksoper/netcore-books - -你应该看到程序能够和之前一样的运行,不过这一次我们不需要像之前那样安装源代码,因为源代码已经包含在 docker 图像里面了。 - - -#### 暴露并发布端口 - -应用程序界面【API】用处不大,除非我们可以从 container 外面和它进行通信。 Docker 已经有了暴露和发布端口的概念,但这是两件完全不同的事。 - - -通过 Docker 官方文件: - -> EXPOSE 指令通知 Docker 上的 container 监听正在运行的特殊网络端口。EXPOSE 指令不能够让 container 访问主机。要使 container 能够访问主机,你必须通过 -p 标志来发布一系列端口或者使用 -P 标志来发布所有暴露的端口 - -EXPOSE 指令只会将元数据添加到图像上,所以你可以认为它是图像消费者的文件。从技术上讲,我本应该忽略 5000 行 EXPOSE 指令因为我知道应用程序界面【API】正在监听的端口,但把它们留下很有用并且是值得推荐的。 - -在这个阶段,我想直接从主机访问应用程序界面【API】,因此我需要通过 -p 指令来发布端口,这将允许请求从主机上的端口 5000 转发到 container 上的端口 5000,无论这个端口是不是之前通过 Docker 文件暴露的。 - -``` -docker run -d -p 5000:5000 niksoper/netcore-books -``` - -通过 -d 指令告诉 docker 在分离模式下运行 container,因此我们不能看到它的输出,但是它依旧会运行和监听端口 5000。你可以通过 docker ps 来证实这件事。 - - -因此,接下来我准备庆祝成功把一个请求从主机转发到了 container 里: - -``` -curl http://localhost:5000/api/books -``` - -它不能够工作。 - -重复进行相同请求,我看到了一两个错误:(56)接收失败:连接相同等或卷曲复位:(52)来自服务器的空回复。 - -我返回 docker运行文件,然后双重检查我所使用的 -p 选项以及 Docker 文件中的 EXPOSE 指令是否正确。我没有发现任何问题,这让我开始有些沮丧。 - -重新站起来以后,我决定去咨询当地的一个大师 - Dave Wybourn(他也在文章里提到 Docker Swarm),他的团队也曾遇到这个实际问题。但问题是,我没有配置过 Kestral - 一个全新的轻量级跨平台 web 服务器,用于 .NET 核心。 - -默认情况下, Kestrel 会监听 http://localhost:5000。但问题是,这儿的本地主机是一个回路接口。 - -通过维基百科: - -> 在计算机网络中,本地主机是一个象征着这台电脑的主机名。本地主机可以通过网络回路接口访问在主机上运行的网络服务。通过使用回路接口可以绕过任何硬件网络接口。 - -在 container 内运行应用是一个新的问题,因为本地主机只能够通过 container 到达。解决方法是更新 Startup.cs 里的主要方法来配置被 Kestral 监听的 URLs: - -``` -public static void Main(string[] args) -{ - var host = new WebHostBuilder() - .UseKestrel() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseUrls("http://*:5000") // listen on port 5000 on all network interfaces - .UseIISIntegration() - .UseStartup() - .Build(); - - host.Run(); -} -``` - -通过这些额外的配置,我可以重建图像,并在 container 中运行应用程序,它将能够接收来自主机的请求: - - -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 - -我现在得到下面这些回应: - -``` -HTTP/1.1 200 OK -日期:Tue, 30 Aug 2016 15:25:43 GMT -编码传输: chunked -内容类型: application/json; charset=utf-8 -服务器: Kestrel - -[{"id":"1","title":"RESTful API with ASP.NET Core MVC 1.0","author":"Nick Soper"}] -``` - -### 创建 KESTREL - -微软的介绍: - ->Kestrel 可以很好的处理来自 ASP.NET 的动态内容,然而,网络服务部分的特性没有达到 IIS,Apache 或者 Niginx 那样的全特性服务器好。反向代理服务器可以让你不用去做像处理静态内容,缓存请求,压缩请求,来自 HTTP 服务器的 SSL 终止这样的工作。 - -因此我需要在 Linux 上把 Nginx 设置成一个反向代理服务器。微软发布了如何在 Linux 生产环境下进行设置的指导教程。我把说明总结在这儿: - -1. 通过 dotnet 发布来给应用程序产生一个自包含包。 -2. 把已发布的应用程序复制到服务器上 -3. 安装并配置 Nginx(如同配置一个反向代理服务器一样) -4.安装并配置管理员(确保 Nginx 服务器处于运行状态中) -5.安装并配置 AppArmor(限制应用的可用资源) -6.配置服务器防火墙 -7.保护好 Nginx(涉及从源代码构建和配置 SSL) - -这些内容已经超出了本文的范围,因此我将侧重于如何把 Nginx 配置成一个反向代理服务器。自然地,我通过 Docker 来完成这件事。 - -### 在另一个 CONTAIER 中运行 NGINX - -我的目标是在一个二级 Docker container 中运行 Nginx 并把它配置成应用程序 container 的反向代理服务器。 - -我使用的是来自 Docker Hub 的官方 Nginx 图像。首先我尝试这样做: - -``` -docker run -d -p 8080:80 --name web nginx -``` - -这启动了一个运行 Nginx 的 container 并把主机上的 8080 端口映射到了 container 的 80 端口上。现在在浏览器中打开网址 http://localhost:8080 会显示出 Nginx 的默认登录页面。 - -现在我们证实了运行 Nginx 是多么的简单,我们可以关闭 container. - -``` -docker rm -f web -``` - -### 把 NGINX配置成一个反向代理服务器 - -可以通过像下面这样编辑位于目录 /etc/nginx/conf.d/default.conf 下的配置文件把 Nginx 配置成一个反向代理服务器: - -``` -server { - listen 80; - - location / { - proxy_pass http://localhost:6666; - } -} -``` - -通过上面的配置可以让 Nginx 代理从 root 到 http://localhost:6666 的所有请求。记住这里的本地主机指的是运行 Nginx 的 container. 我们可以利用卷来使用在 Nginx container 内部的配置文件: - -``` -docker run -d -p 8080:80 \ --v /path/to/my.conf:/etc/nginx/conf.d/default.conf \ -nginx -``` - -注意:这把一个单一文件从主机映射到 container 中,而不是一个完整目录。 - -### 在 CONTAINER 间进行通信 - -Docer 允许内部 container 通过共享虚拟网络进行通信。默认情况下,所有 -通过 Dcocker 后台程序启动的 container 都可以访问一种叫做桥的虚拟网络。这使得一个 container 可以被另一个 container 在相同的网络上通过 IP 地址和端口来引用。 -你可以通过监测 container 来找到它的 IP 地址。我将从之前创建的 niksoper/netcore-books 图像中启动一个 container 并监测它: - -1. docker run -d -p 5000:5000 --name books niksoper/netcore-books -2. docker inspect books - -![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-ip.PNG) - -我们可以看到这个 container 的 IP 地址是 "IPAddress": "172.17.0.3". - -所以现在如果我创建下面的 Nginx 配置文件,并使用这个文件启动一个 Nginx container, 它将代理请求到我的应用程序界面【API】: - -``` -server { - listen 80; - - location / { - proxy_pass http://172.17.0.3:5000; - } -} -``` - -现在我可以使用这个配置文件启动一个 Nginx container(注意我把主机上的 8080 端口映射到了 Nginx container 上的 80 端口): - -``` -docker run -d -p 8080:80 \ --v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \ -nginx -``` - -一个到 http://localhost:8080 的请求代理到应用上。注意下面卷反应的服务器标题: - -![](http://blog.scottlogic.com/nsoper/assets/nginx-proxy-response.PNG) - -### DOCKER Compose - -在这个地方,我为自己的进步而感到高兴,但我认为一定还有更好的方法来配置 Nginx,可以不需要知道应用程序 container 的确切 IP 地址。另一个当地的大师 Jason Ebbin - Scott Logic DevOps 在这个地方进行了改进,并建议使用 Docker Compose。 - -作为一个高层次的描述 - Docker Compose 使得一组互相连接的 container 很容易通过声明式语法来启动。我不想再细说 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 反向代理服务器的。 - -### 服务列表 - -这将从相同目录下的 Docker 文件创建一个叫做 books-api 的 conainer 作为 docker-compose.yml。注意这个 container 不需要发布任何端口,因为只要能够从反向代理服务器访问它就可以,而不需要从主机操作系统访问它。 - -### 反向代理 - -这将启动一个基于 nginx 图像叫做 反向代理(reverse-proxy)的container,并把位于当前目录下的 proxy.conf 文件安装成为配置。它把主机上的 9090 端口映射到 container 中的 8080 端口,这将允许我们在 http://localhost:9090. 上通过主机访问 container. - -proxy.conf 文件看起来像下面这样: - -``` -server { - listen 8080; - - location / { - proxy_pass http://books-service:5000; - } -} -``` - -这儿的关键点是我们现在可以通过名字引用 【服务列表】,因此我们不需要知道 books-api 这个 container 的 IP 地址! - -现在我们可以通过一个运行着的反向代理启动两个 container(-d 意味着这是独立的,因此我们不能看到来自 container 的输出): - -``` -docker compose up -d -``` - -证实我们所创建的 container: - -``` -docker ps -``` - -最后来证实我们可以通过反向代理来控制应用程序界面【API】: - -``` -curl -i http://localhost:9090/api/books -``` - -### 发生了什么? - -Docker Compose 通过创建一个新的叫做 mvclibrary_default 的虚拟网络来实现这件事,这个虚拟网络同时用于 books-api 和反向代理的 container(名字是基于 docker-compose.yml 文件的父目录)。 - -![](http://blog.scottlogic.com/nsoper/assets/docker-network-ls.PNG) - -你可以看到新的网络通过 docker 网络来监测 mvclibrary_default 的所有细节: - -![](http://blog.scottlogic.com/nsoper/assets/network-inspect.PNG) - -注意 Docker 已经分配了子网:“172.18.0.0/16”。/16 部分是无类域内路由选择【CIDR】,一个完整的解释已经超出了本文的范围,但无类域内路由选择【CIDR】仅表示一个 IP 地址范围。运行 docker 网络来监测桥,显示子网:“172.17.0.0/16”,因此这两个网络是不重叠的。 - -现在用 docker 来监测 books-api 从而证实应用程序的 container 是使用该网络: - -![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-books-api.PNG) - -注意 container 的两个别名是 container 标识符(3c42db680459)和由 docker-compose.yml (服务列表)给出的服务名。我们通过服务列表别名来引用在自定义 Nginx 配置文件中的应用程序的 container。这本可以通过 docker 网络手动创建,但是我喜欢用 Docker Compose,因为它可以干净简洁的收捲已创建和相互依存的 container。 - -### 结论 - -所以现在我可以通过几个简单的步骤在 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 中) -- 数据库 - 如何在 container 中管理应用 - -对我来说这是一个非常有趣的学习经历,因为有一段时间我对探索伴有 ASP.NET 核心的跨平台支持非常好奇,使用针对 “Configuratin as Code” 的 Docker Compose 方法来探索一下 DevOps 的世界也是非常愉快并且很有教育意义的。 - -如果你对 Docker 很好奇,那么我鼓励你来尝试学习它 - 特别地,这是否会让你感到痛苦,谁知道呢,有可能你会喜欢它? - --------------------------------------------------------------------------------- - -via: http://blog.scottlogic.com/2016/09/05/hosting-netcore-on-linux-with-docker.html?utm_source=webopsweekly&utm_medium=email - -作者:[Nick Soper][a] -译者:[ucasFL](https://github.com/ucasFL) -校对:[校对者ID](https://github.com/校对者ID) - -本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出 - -[a]: http://blog.scottlogic.com/nsoper -[1]: http://blog.scottlogic.com/2016/01/20/restful-api-with-aspnet50.html -[2]: https://docs.microsoft.com/en-us/dotnet/articles/core/migrating-from-dnx -[3]: https://docs.asp.net/en/latest/migration/rc1-to-rtm.html -[4]: http://www.paraesthesia.com/archive/2016/06/29/netcore-rtm-where-is-autofac/ - - - - - -