mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-10 22:21:11 +08:00
commit
357a2d577d
@ -1,437 +0,0 @@
|
||||
translating by ucasFL
|
||||
HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE
|
||||
=====
|
||||
|
||||
This post builds on my previous [introduction to .NET Core][1]. First I upgrade that RESTful API from .NET Core RC1 to .NET Core 1.0, then I add support for Docker and describe how to host it on Linux in a production environment.
|
||||
|
||||
|
||||
![](http://blog.scottlogic.com/nsoper/assets/noob.png)
|
||||
|
||||
I’m completely new to Docker and I’m far from a Linux expert, so these are very much the thoughts of a noob.
|
||||
|
||||
### INSTALLATION
|
||||
|
||||
Follow the instructions on https://www.microsoft.com/net/core to install .NET Core on your development machine. This will include the dotnet command line tool and the latest Visual Studio tooling for Windows.
|
||||
|
||||
### SOURCE CODE
|
||||
|
||||
You can jump straight to the finished source code on GitHub.
|
||||
|
||||
### CONVERTING TO .NET CORE 1.0
|
||||
|
||||
Naturally, my first port of call when thinking about how to upgrade the API from RC1 to 1.0 was to Google it with Bing. There are two pretty comprehensive guides that I followed:
|
||||
|
||||
- [Migrating from DNX to .NET Core CLI][2]
|
||||
- [Migrating from ASP.NET 5 RC1 to ASP.NET Core 1.0][3]
|
||||
|
||||
I advise reading through both of these very carefully when migrating your code because I tried to skim read the second one without reading the first one and got very confused and frustrated!
|
||||
|
||||
I won’t describe the changes in detail because you can look at the commit on GitHub. Here is a summary of what I changed:
|
||||
|
||||
- Updated version numbers on global.json and project.json
|
||||
- Removed obsolete sections from project.json
|
||||
- Using the more lightweight ControllerBase rather than Controller because I don’t need methods related to MVC views (this was an optional change)
|
||||
- Removed the Http prefix from helper methods e.g. HttpNotFound -> NotFound
|
||||
- LogVerbose -> LogTrace
|
||||
- Namespace changes: Microsoft.AspNetCore.*
|
||||
- Using SetBasePath in Startup (appsettings.json won’t be found without this)
|
||||
- Running via WebHostBuilder rather than WebApplication.Run
|
||||
- Removed Serilog (at the time of writing it does not support .NET Core 1.0)
|
||||
|
||||
The only real headache here is the need to remove Serilog. I could have implemented my own file logger, but I just deleted file logging because I didn’t want to focus on it for this exercise.
|
||||
|
||||
Unfortunately, there will be plenty of third party developers that will be playing catch up with support for .NET Core 1.0 but I have sympathy for them because they are often working in their spare time without anything close to the resources available to Microsoft. I recommend reading Travis Illig’s [.NET Core 1.0 is Released, but Where is Autofac][4]? for a third party developer’s point of view on this!
|
||||
|
||||
Having made these changes I was able to dotnet restore, dotnet build and dotnet run from the project.json directory and see the API working as before.
|
||||
|
||||
### RUNNING WITH DOCKER
|
||||
|
||||
At the time of writing, Docker only really works on Linux. There is beta support for Docker on Windows and OS X but they both rely on virtualisation so I’ve chosen to run Ubuntu 14.04 as a VirtualBox. Follow these instructions if you haven’t already got Docker installed.
|
||||
|
||||
I’ve been doing a bit of reading about Docker recently but I’ve never tried to actually do anything with it until now. I’ll assume the reader has no Docker knowledge so I’ll explain all parts of the commands that I’m using.
|
||||
|
||||
#### HELLO DOCKER
|
||||
|
||||
Having installed Docker on my Ubuntu machine, my next move was to follow the instructions at https://www.microsoft.com/net/core#docker to see how to get started with .NET Core and Docker.
|
||||
|
||||
First start a container with .NET Core installed:
|
||||
|
||||
```
|
||||
docker run -it microsoft/dotnet:latest
|
||||
```
|
||||
|
||||
The -it option means interactive so having executed this command you will be inside the container and free to run any bash commands you like.
|
||||
|
||||
Then we can run five commands to get Microsoft’s Hello World .NET Core console application running inside Docker!
|
||||
|
||||
1. mkdir hwapp
|
||||
2. cd hwapp
|
||||
3. dotnet new
|
||||
4. dotnet restore
|
||||
5. dotnet run
|
||||
|
||||
You can exit to leave the container, then docker ps -a to show that you have created a container which has exited. You should really now tidy up that container using docker rm <container_name>.
|
||||
|
||||
#### MOUNTING THE SOURCE
|
||||
|
||||
My next move was to use the same microsoft/dotnet image as above but to mount the source for my application as a data volume.
|
||||
|
||||
First check out the repository at the relevant commit:
|
||||
|
||||
1. git clone https://github.com/niksoper/aspnet5-books.git
|
||||
2. cd aspnet5-books/src/MvcLibrary
|
||||
3. git checkout dotnet-core-1.0
|
||||
|
||||
Now start a container running .NET Core 1.0 with the source located at /books. Note that you’ll need to change the /path/to/repo part to match your machine:
|
||||
|
||||
```
|
||||
docker run -it \
|
||||
-v /path/to/repo/aspnet5-books/src/MvcLibrary:/books \
|
||||
microsoft/dotnet:latest
|
||||
```
|
||||
|
||||
Now you can run the application inside the container!
|
||||
|
||||
```
|
||||
cd /books
|
||||
dotnet restore
|
||||
dotnet run
|
||||
```
|
||||
|
||||
That’s great as a proof of concept but we don’t really want to have to worry about mounting the source code into a container like this whenever we want to start the application.
|
||||
|
||||
#### ADDING A DOCKERFILE
|
||||
|
||||
The next step I took was to introduce a Dockerfile, which will allow the application to be started easily inside its own container.
|
||||
|
||||
My Dockerfile lives in the src/MvcLibrary directory alongside project.json and looks like this:
|
||||
|
||||
```
|
||||
FROM microsoft/dotnet:latest
|
||||
|
||||
# Create directory for the app source code
|
||||
RUN mkdir -p /usr/src/books
|
||||
WORKDIR /usr/src/books
|
||||
|
||||
# Copy the source and restore dependencies
|
||||
COPY . /usr/src/books
|
||||
RUN dotnet restore
|
||||
|
||||
# Expose the port and start the app
|
||||
EXPOSE 5000
|
||||
CMD [ "dotnet", "run" ]
|
||||
```
|
||||
|
||||
Strictly, the `RUN mkdir -p /usr/src/books` command is not needed because COPY will create any missing directories automatically.
|
||||
|
||||
Docker images are built in layers. We start from the image containing .NET Core and add another layer which builds the application from source then runs the application.
|
||||
|
||||
Having added the Dockerfile, I then ran the following commands to build the image and start a container using that image (make sure you are in the same directory as the Dockerfile and you should really use your own username):
|
||||
|
||||
1. docker build -t niksoper/netcore-books .
|
||||
2. docker run -it niksoper/netcore-books
|
||||
|
||||
You should see that the application started listening just as before, except this time we don’t need to bother mounting the source code because it’s already contained in the docker image.
|
||||
|
||||
#### EXPOSING AND PUBLISHING A PORT
|
||||
|
||||
This API isn’t going to be very useful unless we can communicate with it from outside the container. Docker has the concept of exposing and publishing ports, which are two very different things.
|
||||
|
||||
From the official Docker documentation:
|
||||
|
||||
>The EXPOSE instruction informs Docker that the container listens on the specified network ports at runtime. EXPOSE does not make the ports of the container accessible to the host. To do that, you must use either the -p flag to publish a range of ports or the -P flag to publish all of the exposed ports.
|
||||
|
||||
EXPOSE only adds metadata to the image so you can think of it as documentation for the consumers of the image. Technically, I could have left out the EXPOSE 5000 line completely because I know the port that the API is listening on but leaving it in is helpful and certainly recommended.
|
||||
|
||||
At this stage I want to access the API directly from the host so I need to use -p to publish the port - this allows a request to port 5000 on the host be forwarded to port 5000 in the container regardless of whether the port has previously been exposed via the Dockerfile:
|
||||
|
||||
```
|
||||
docker run -d -p 5000:5000 niksoper/netcore-books
|
||||
```
|
||||
|
||||
Using -d tells docker to run the container in detached mode so we won’t see its output but it will still be running and listening on port 5000 - prove this to yourself with docker ps.
|
||||
|
||||
So then I prepared to celebrate by making a request from the host to the container:
|
||||
|
||||
```
|
||||
curl http://localhost:5000/api/books
|
||||
```
|
||||
|
||||
It didn’t work.
|
||||
|
||||
Making the same curl request repeatedly, I see one of two errors - either curl: (56) Recv failure: Connection reset by peer or curl: (52) Empty reply from server.
|
||||
|
||||
I went back to the docker run documentation and double checked I was using the -p option correctly as well as EXPOSE in the Dockerfile. I couldn’t see the problem and became a bit sad…
|
||||
|
||||
After pulling myself together, I decided to consult one of my local DevOps heroes - Dave Wybourn (also mentioned in this post on Docker Swarm). His team had run into this exact problem and the issue was the way that I had (not) configured Kestrel - the new lightweight, cross platform web server used for .NET Core.
|
||||
|
||||
By default, Kestrel will listen on http://localhost:5000. The problem here is that localhost is a loopback interface.
|
||||
|
||||
From Wikipedia:
|
||||
|
||||
>In computer networking, localhost is a hostname that means this computer. It is used to access the network services that are running on the host via its loopback network interface. Using the loopback interface bypasses any local network interface hardware.
|
||||
|
||||
This is a problem when running inside a container because localhost can only be reached from within that container. The solution was to update the Main method in Startup.cs to configure the URLs that Kestrel will listen on:
|
||||
|
||||
```
|
||||
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<Startup>()
|
||||
.Build();
|
||||
|
||||
host.Run();
|
||||
}
|
||||
```
|
||||
|
||||
With this extra configuration in place, I could then rebuild image and run the application in a container which will accept requests from the host:
|
||||
|
||||
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
|
||||
|
||||
I now get the following response:
|
||||
|
||||
```
|
||||
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 IN PRODUCTION
|
||||
|
||||
Microsoft’s words:
|
||||
|
||||
>Kestrel is great for serving dynamic content from ASP.NET, however the web serving parts aren’t as feature rich as full-featured servers like IIS, Apache or Nginx. A reverse proxy-server can allow you to offload work like serving static content, caching requests, compressing requests, and SSL termination from the HTTP server.
|
||||
|
||||
So I need to set up Nginx on my Linux machine to act as my reverse proxy. Microsoft spell out how to do this in Publish to a Linux Production Environment. I’ll summarise the instructions here:
|
||||
|
||||
1. Use dotnet publish to produce a self contained package for the application
|
||||
2. Copy the published application to the server
|
||||
3. Install and configure Nginx (as a reverse proxy server)
|
||||
4. Install and configure supervisor (for keeping the Kestrel server running)
|
||||
5. Enable and configure AppArmor (for limiting the resources available to an application)
|
||||
6. Configure the server firewall
|
||||
7. Secure Nginx (involves building from source and configuring SSL)
|
||||
|
||||
It’s beyond the scope of this post to cover all of that, so I’m only going to concentrate on configuring Nginx as a reverse proxy - and naturally, I’m going to use Docker to do it.
|
||||
|
||||
### RUN NGINX IN ANOTHER CONTAINER
|
||||
|
||||
My aim is to run Nginx in a second Docker container and configure it as a reverse proxy to my application container.
|
||||
|
||||
I’ve used the official Nginx image from Docker Hub. First I tried it out like this:
|
||||
|
||||
```
|
||||
docker run -d -p 8080:80 --name web nginx
|
||||
```
|
||||
|
||||
This starts a container running Nginx and maps port 8080 on the host to port 80 in the container. Hitting http://localhost:8080 in the browser now shows the default Nginx landing page.
|
||||
|
||||
Now we’ve proved how easy it is to get Nginx running, we can kill the container.
|
||||
|
||||
```
|
||||
docker rm -f web
|
||||
```
|
||||
|
||||
### CONFIGURING NGINX AS A REVERSE PROXY
|
||||
|
||||
Nginx can be configured as a reverse proxy by editing the config file at /etc/nginx/conf.d/default.conf like this:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:6666;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The config above will cause Nginx to proxy all requests from the root to http://localhost:6666. Remember localhost here refers to the container running Nginx. We can use our own config file inside the Nginx container using a volume:
|
||||
|
||||
```
|
||||
docker run -d -p 8080:80 \
|
||||
-v /path/to/my.conf:/etc/nginx/conf.d/default.conf \
|
||||
nginx
|
||||
```
|
||||
|
||||
Note: this maps a single file from the host to the container, rather than an entire directory.
|
||||
|
||||
### COMMUNICATING BETWEEN CONTAINERS
|
||||
|
||||
Docker allows inter-container communication using shared virtual networks. By default, all containers started by the Docker daemon will have access to a virtual network called bridge. This allows containers to be referenced from other containers on the same network via IP address and port.
|
||||
|
||||
You can discover the IP address of a running container by inspecting it. I’ll start a container from the niksoper/netcore-books image that I created earlier, and inspect it:
|
||||
|
||||
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)
|
||||
|
||||
We can see this container has "IPAddress": "172.17.0.3".
|
||||
|
||||
So now if I create the following Nginx config file, then start an Nginx container using that file, then it will proxy requests to my API:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
|
||||
location / {
|
||||
proxy_pass http://172.17.0.3:5000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Now I can start an Nginx container using that config (note I’m mapping port 8080 on the host to port 80 on the Nginx container):
|
||||
|
||||
```
|
||||
docker run -d -p 8080:80 \
|
||||
-v ~/dev/nginx/my.nginx.conf:/etc/nginx/conf.d/default.conf \
|
||||
nginx
|
||||
```
|
||||
|
||||
A request to http://localhost:8080 will now be proxied to my application. Note the Server header in the following curl response:
|
||||
|
||||
![](http://blog.scottlogic.com/nsoper/assets/nginx-proxy-response.PNG)
|
||||
|
||||
### DOCKER COMPOSE
|
||||
|
||||
At this point I was fairly pleased with my progress but I thought there must be a better way of configuring Nginx without needing to know the exact IP address of the application container. Another of the local Scott Logic DevOps heroes - Jason Ebbin - stepped up at this point and suggested Docker Compose.
|
||||
|
||||
As a high level description - Docker Compose makes it very easy to start up a collection of interconnected containers using a declarative syntax. I won’t go into the details of how Docker Compose works because you can read about it in this previous post.
|
||||
|
||||
I’ll start with the docker-compose.yml file that I’m using:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
This is version 2 syntax, so you’ll need to have at least version 1.6 of Docker Compose in order for this to work.
|
||||
|
||||
This file tells Docker to create two services - one for the application and another for the Nginx reverse proxy.
|
||||
|
||||
### BOOKS-SERVICE
|
||||
|
||||
This builds a container called books-api from the Dockerfile in the same directory as this docker-compose.yml. Note that this container does not need to publish any ports because it only needs to be accessed from the reverse-proxy container rather than the host operating system.
|
||||
|
||||
### REVERSE-PROXY
|
||||
|
||||
This starts a container called reverse-proxy based on the nginx image with a proxy.conf file mounted as the config from the current directory. It maps port 9090 on the host to port 8080 in the container which allows us to access the container from the host at http://localhost:9090.
|
||||
|
||||
The proxy.conf file looks like this:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 8080;
|
||||
|
||||
location / {
|
||||
proxy_pass http://books-service:5000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The key point here is that we can now refer to books-service by name so we don’t need to know the IP address of the books-api container!
|
||||
|
||||
Now we can start the two containers with a working reverse proxy (-d means detached so we don’t see the output from the containers):
|
||||
|
||||
```
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Prove the containers were created:
|
||||
|
||||
```
|
||||
docker ps
|
||||
```
|
||||
|
||||
And finally confirm that we can hit the API via the reverse proxy:
|
||||
|
||||
```
|
||||
curl -i http://localhost:9090/api/books
|
||||
```
|
||||
|
||||
### WHAT’S GOING ON?
|
||||
|
||||
Docker Compose makes this happen by creating a new virtual network called mvclibrary_default which is used by both books-api and reverse-proxy containers (the name is based on the parent directory of the docker-compose.yml file).
|
||||
|
||||
Prove the network exists with docker network ls:
|
||||
|
||||
![](http://blog.scottlogic.com/nsoper/assets/docker-network-ls.PNG)
|
||||
|
||||
You can see the details of the new network using docker network inspect mvclibrary_default:
|
||||
|
||||
![](http://blog.scottlogic.com/nsoper/assets/network-inspect.PNG)
|
||||
|
||||
Note that Docker has assigned "Subnet": "172.18.0.0/16" to the network. The /16 part is CIDR notation and a full explanation is way beyond the scope of this post but CIDR just refers to a range of IP addresses. Running docker network inspect bridge shows "Subnet": "172.17.0.0/16" so the two networks do not overlap.
|
||||
|
||||
Now docker inspect books-api to confirm the application container is using this network:
|
||||
|
||||
![](http://blog.scottlogic.com/nsoper/assets/docker-inspect-books-api.PNG)
|
||||
|
||||
Notice the two "Aliases" for the container are the container identifier (3c42db680459) and the service name given in docker-compose.yml (books-service). We’re using the books-service alias to reference the application container in the custom Nginx configuration file. This could have been done manually with docker network create but I like Docker Compose because it wraps up container creation and interdependencies cleanly and succinctly.
|
||||
|
||||
### CONCLUSION
|
||||
|
||||
So now I can get the application running on Linux with Nginx in a few easy steps, without making any lasting changes to the host operating system:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
I know what I have described in this post is not a truly production ready setup because I’ve not spoken about any of the following, but most of these topics could take an entire post on their own:
|
||||
|
||||
- Security concerns like firewalls or SSL configuration
|
||||
- How to ensure the application keeps running
|
||||
- How to be selective about what to include in a Docker image (I dumped everything in via the Dockerfile)
|
||||
- Databases - how to manage them in containers
|
||||
|
||||
This has been a very interesting learning experience for me because for a while now I have been curious to explore the new cross platform support that comes with ASP.NET Core, and the opportunity to explore a little bit of the DevOps world using Docker Compose for a “Configuration as Code” approach has been both enjoyable and educational.
|
||||
|
||||
If you’re at all curious about Docker then I encourage you to get stuck in by trying it out - especially if this puts you out of your comfort zone. Who knows, you might enjoy it?
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: http://blog.scottlogic.com/2016/09/05/hosting-netcore-on-linux-with-docker.html?utm_source=webopsweekly&utm_medium=email
|
||||
|
||||
作者:[Nick Soper][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者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/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
@ -0,0 +1,446 @@
|
||||
|
||||
新手指南 - 通过 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_name>. 来整理一下 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<Startup>()
|
||||
.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/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user