转自圣杰。https://www.jianshu.com/p/23465dc86d3e
.NET Core容器化@Docker
温馨提示:本文适合动手演练,效果更佳。
1. 引言
我们知道. NET Core最大的特性之一就是跨平台,而对于跨平台,似乎大家印象中就是可以在非Windows系统上部署运行。而至于如何操作,可能就有所欠缺。那这一节我们就结合简单实例一步一步教你如何借助Docker来容器化 .NET Core应用,以完成跨平台的构建和部署。
2. 环境准备
自从玩.NET就一直和Windows系统打交道,如果还基于Windows来展开本节内容,不就跑题了吗?!那咱们就切换到Linux系统。
如果没有Linux基础和Docker基础,请自觉完成以下两个实验:
腾讯云开发者实验室:Linux 基础入门
腾讯云开发者实验室:搭建 Docker 环境
完成了以上两个实验后,我们就离Linux的世界更近一步。
因为后续是基于Linux-CentOS系统进行实操演练,没有Linux上机环境的,可以考虑从腾讯云实验室列表找一个CentOS相关的实验项目作为本文的演练环境。
3. Docker简介
在开始之前,有必要对Docker做一下简单了解,可以参考我的上一篇文章Hello Docker。
这里就简要的再重复一下。
Docker是用Go语言编写基于Linux操作系统的一些特性开发的,其提供了操作系统级别的抽象,是一种容器管理技术,它隔离了应用程序对基础架构(操作系统等)的依赖。相较于虚拟机而言,Docker共享的是宿主机的硬件资源,使用容器来提供独立的运行环境来运行应用。虚拟机则是基于Supervisor(虚拟机管理程序)使用虚拟化技术来提供隔离的虚拟机,在虚拟机的操作系统上提供运行环境!虽然两者都提供了很好的资源隔离,但很明显Docker的虚拟化开销更低!
Docker涉及了三个核心概念:Register、Image、Container。
- Registry:仓库。用来存储Docker镜像,比如Docker官方的Docker Hub就是一个公开的仓库,在上面我们可以下载我们需要的镜像。
- Image:镜像。开发人员创建一个应用程序或服务,并将它及其依赖关系打包到一个容器镜像中。镜像是应用程序的配置及其依赖关系的静态形式。
- Container:容器。Container是镜像的运行实例,它是一个隔离的、资源受控的可移植的运行时环境,其中包含操作系统、需要运行的程序、运行程序的相关依赖、环境变量等。
它们三者的相互作用关系是:
当我们执行Docker pull或Docker run命令时,若本地无所需的镜像,那么将会从仓库(一般为DockerHub)下载(pull)一个镜像。Docker执行run方法得到一个容器,用户在容器里执行各种操作。Docker执行commit方法将一个容器转化为镜像。Docker利用login、push等命令将本地镜像推送(push)到仓库。其他机器或服务器上就可以使用该镜像去生成容器,进而运行相应的应用程序。
4. 安装Docker
4.1. 使用脚本自动安装Docker
在测试或开发环境中 Docker 官方为了简化安装流程,提供了一套便捷的安装脚本,CentOS系统上可以使用这套脚本安装:
//使用脚本自动化安装Docker
$ curl -fsSL get.docker.com -o get-docker.sh
$ sudo sh get-docker.sh --mirror Aliyun
4.2. 启动Docker
执行这个命令后,脚本就会自动的将一切准备工作做好,并且把 Docker CE 的 Edge 版本安装在系统中。
//启动 Docker CE
$ sudo systemctl enable docker
$ sudo systemctl start docker
//查看docker版本
$ sudo docker -v
Docker version 1.12.6, build ec8512b/1.12.6
4.3 测试Docker是否正确安装
命令行执行docker run hello-world
:
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
ca4f61b1923c: Pull complete
Digest: sha256:be0cd392e45be79ffeffa6b05338b98ebb16c87b255f48e297ec7f98e123905c
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(amd64)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run an Ubuntu container with:
$ docker run -it ubuntu bash
Share images, automate workflows, and more with a free Docker ID:
https://cloud.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/engine/userguide/
当执行docker run hello-world
时,docker首先会从本地找hello-world
的镜像,如果本地没有,它将会从默认的镜像仓库Docker Hub上拉取镜像。镜像拉取到本地后,就实例化镜像得到容器,输出Hello from Docker!
。
4.4. 配置镜像加速
因为默认的镜像仓库远在国外,拉取一个小的镜像时间还可以忍受,若拉取一个上G的镜像就有点太折磨人了,我们使用DaoCloud镜像加速器来进行镜像加速。Linux上配置方法如下:
$ curl -sSL https://get.daocloud.io/daotools/set_mirror.sh | sh -s http://37bb3af1.m.daocloud.io`
$ sudo systemctl restart docker
5. Hello Docker With .NET Core
Docker安装完毕,我们来结合.NET Core玩一玩吧。
5.1. 拉取microsoft/dotnet镜像
命令行执行docker pull microsoft/dotnet
,等几分钟后即可安装完毕,执行docker images
可以看到本地已经包含microsoft/dotnet
、docker.io/hello-world
两个镜像。
5.2. 运行microsoft/dotnet镜像
使用docker run <image>
可以启动镜像,通过指定参数-it
以交互模式(进入容器内部)启动。依次执行以下命令:
//启动一个dotnet镜像
$ docker run -it microsoft/dotnet
//创建项目名为HelloDocker.Web的.NET Core MVC项目
dotnet new mvc -n HelloDocker.Web
//进入HelloDocker.Web文件夹
cd HelloDocker.Web
//启动.NET Core MVC项目
dotnet run
运行结果如下所示:
[root@iZ288a3qazlZ ~]# docker run -it microsoft/dotnet
root@816b4e94de67:/# dotnet new mvc -n HelloDocker.Web
The template "ASP.NET Core Web App (Model-View-Controller)" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.
Processing post-creation actions...
Running 'dotnet restore' on HelloDocker.Web/HelloDocker.Web.csproj...
Restoring packages for /HelloDocker.Web/HelloDocker.Web.csproj...
Generating MSBuild file /HelloDocker.Web/obj/HelloDocker.Web.csproj.nuget.g.props.
Generating MSBuild file /HelloDocker.Web/obj/HelloDocker.Web.csproj.nuget.g.targets.
Restore completed in 1.83 sec for /HelloDocker.Web/HelloDocker.Web.csproj.
Restoring packages for /HelloDocker.Web/HelloDocker.Web.csproj...
Restore completed in 376.14 ms for /HelloDocker.Web/HelloDocker.Web.csproj.
Restore succeeded.
root@816b4e94de67:/# cd HelloDocker.Web
root@816b4e94de67:/HelloDocker.Web# dotnet run
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {727df196-978f-4df8-b3d3-e92a77e410ee} may be persisted to storage in unencrypted form.
Hosting environment: Production
Content root path: /HelloDocker.Web
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
键盘按住Ctrl+C
即可关闭应用,输入exit
即可退出当前容器。
是不是简单的几步就完成了一个.NET Core MVC项目的创建和运行?!这个时候你可能会好奇,Linux宿主机上并没有安装.NET Core SDK啊,MVC项目是如何创建的呢?这就是Docker神奇的地方,我们从镜像仓库中拉取的dotnet镜像,包含了创建、构建、运行.NET Core项目所需的一切依赖和运行时环境。
退出容器之后,执行find -name HelloDocker.Web
(查找HelloDocker.Web文件),我们发现并没有找到。这说明我们刚才创建的.NET Core MVC项目是在容器内部创建的,是与宿主机完全隔离的。这个时候你可能会想,每次都要在容器中安装源代码太不方便了,我们能不能让容器运行我们宿主机的源代码项目?嗯,这是个好问题。下面我们就来解答这个问题。
5.3. 挂载源代码
为了在宿主机上创建.NET Core 项目,这个时候我们就需要在Linux宿主机上安装.NET Core SDK。
5.3.1. 宿主机安装.NET Core SDK
步骤如下:
sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
sudo sh -c 'echo -e "[packages-microsoft-com-prod]
name=packages-microsoft-com-prod
baseurl= https://packages.microsoft.com/yumrepos/microsoft-rhel7.3-prod
enabled=1
gpgcheck=1
gpgkey=https://packages.microsoft.com/keys/microsoft.asc" > /etc/yum.repos.d/dotnetdev.repo'
sudo yum update
sudo yum install libunwind libicu
sudo yum install dotnet-sdk-2.1.3
安装完毕后,我们依次执行以下命令创建一个.NET Core MVC项目:
//回到根目录
$ cd $HOME
//创建demo文件夹
$ mkdir demo
$ cd demo
//创建项目名为HelloDocker.Web的.NET Core MVC项目
dotnet new mvc -n HelloDocker.Web
//进入HelloDocker.Web文件夹
cd HelloDocker.Web
//启动.NET Core MVC项目
dotnet run
如果知道本机的ip地址的话(可以使用ifconfig
命令查询),直接浏览器访问http://<ip address>:5000
即可访问我们刚刚运行的MVC项目。
这一步我们就在$HOME/demo/HelloDocker.Web
目录下成功创建了MVC项目,下一步我们就将该目录下的源码项目通过挂载的方式共享到容器中去。
5.3.2. 挂载宿主机项目到容器中
在启动Docker镜像时,Docker允许我们通过使用-v
参数挂载宿主机的文件到容器的指定目录下。换句话说,就相当于宿主机共享指定文件供容器去访问。废话不多说,实践出真知。
// 命令中的`\`结合`Enter`键构成换行符,允许我们换行输入一个长命令。
$ docker run -it
-v $HOME/demo/HelloDocker.Web:/app
microsoft/dotnet:latest
上面的命令就是把$HOME/demo/HelloDocker.Web
文件夹下的文件挂载到容器的app
目录下。
[root@iZ288a3qazlZ HelloDocker.Web]# docker run -it
> -v $HOME/demo/HelloDocker.Web:/app
> microsoft/dotnet:latest
root@d70b327f4b7e:/# ls
app bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys tmp usr var
root@d70b327f4b7e:/# cd app
root@d70b327f4b7e:/app# ls
Controllers HelloDocker.Web.csproj Models Program.cs Startup.cs Views appsettings.Development.json appsettings.json bundleconfig.json obj wwwroot
root@d70b327f4b7e:/app# dotnet run
warn: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35]
No XML encryptor configured. Key {09a69edf-c1c5-4909-ad24-15a43a572fca} may be persisted to storage in unencrypted form.
Hosting environment: Production
Content root path: /app
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
从上面的执行结果来看,容器内部中的app目录下包含了宿主机上的源码项目。
上面说到是以共享的形式,而不是容器拥有一份宿主机目录的拷贝,意味着,在宿主机上对目录的更改,会即时反应到容器中。但反过来,容器中对共享目录的更改,不会反应到宿主机上,不然就打破了容器具有的隔离特性。
通过这样一个简单场景,聪明的你是否会联想到这一场景在我们日常编码的应用之处呢?是的,我们可以用来持续构建(CI)。基本思路是,通过git clone
源码到宿主机上,然后将源码目录挂载到容器中去进行构建。
5.4. 借助Dockerfile
Dockerfile用来定义你将要在容器中执行的系列操作。我们来创建第一个Dockerfile:
//确保进入我们创建的MVC项目目录中去
$ cd $HOME/demo/HelloDocker.Web
//使用touch命令创建Dockerfile
$ touch Dockerfile
//使用vi命令编辑Dockerfile
vi Dockerfile
进入VI编辑界面后,复制以下代码,使用shift + Ins
命令即可粘贴。然后按ESE
退出编辑模式,按shift + :
,输入wq
即可保存并退出编辑界面。
FROM microsoft/dotnet:latest
WORKDIR /app
COPY . /app
RUN dotnet restore
EXPOSE 5000
ENV ASPNETCORE_URLS http://*:5000
ENTRYPOINT ["dotnet","run"]
上面的命令我依次解释一下:
- 使用
FROM
指定容器使用的镜像 - 使用
WORKDIR
指定工作目录 - 使用
COPY
指令,复制当前目录(其中.
即代表当前目录)到容器中的/app目录下 - 使用
RUN
命令指定容器中执行的命令 - 使用
EXPOSE
指定容器暴露的端口号 - 使用
ENV
指定环境参数,上面用来告诉.NETCore项目在所有网络接口上监听5000端口 - 使用
ENTRYPOINT
制定容器的入口点
Dockerfile就绪,我们就可以将我们当前项目打包成镜像以分发部署。
使用docker build -t <name> <path>
指令打包镜像:
$ docker build -t hellodocker.web .
以上命令就是告诉docker将当前目录打包成镜像,并命名为hellodocker.web。命令执行完毕,输入docker images
即可看到我们新打包的镜像。镜像创建完毕我们就可以直接运行了:
docker run -d -p 80:5000 hellodocker.web
上面的指令就是运行我们新打包的镜像,并通过-p
参数映射容器的5000到宿主机的80端口,其中-d
参数告诉docker以后台任务形式运行镜像。因为80是默认的web端口,所以我们通过浏览器直接访问ip即可访问到我们容器中运行的MVC网站。或者通过curl -i http://localhost
来验证。操作示例如下:
[root@iZ288a3qazlZ HelloDocker.Web]# docker build -t hellodocker.web .
Sending build context to Docker daemon 3.3 MB
Step 1 : FROM microsoft/dotnet:latest
---> 7d4dc5c258eb
Step 2 : WORKDIR /app
---> Using cache
---> 98d48a4e278c
Step 3 : COPY . /app
---> d5df216b274a
Removing intermediate container 0a70f0f2b681
Step 4 : RUN dotnet restore
---> Running in 0c8a9c4d5ba1
Restore completed in 939.01 ms for /app/HelloDocker.Web.csproj.
Restoring packages for /app/HelloDocker.Web.csproj...
Restore completed in 1.38 sec for /app/HelloDocker.Web.csproj.
---> 479f6b5cc7f0
Removing intermediate container 0c8a9c4d5ba1
Step 5 : EXPOSE 5000
---> Running in f97feceb7f1b
---> 562a95328196
Removing intermediate container f97feceb7f1b
Step 6 : ENV ASPNETCORE_URLS http://*:5000
---> Running in 403d8e2e25a6
---> 16b7bd572410
Removing intermediate container 403d8e2e25a6
Step 7 : ENTRYPOINT dotnet run
---> Running in 0294f87ce3fd
---> 532e44a7fd54
Removing intermediate container 0294f87ce3fd
Successfully built 532e44a7fd54
[root@iZ288a3qazlZ HelloDocker.Web]# docker run -d -p 80:5000 hellodocker.web
9d28bb3fa553653e4c26bf727715c82a837a2c224a0942107f3fab08c0a2686d
[root@iZ288a3qazlZ HelloDocker.Web]# curl -i http://localhost
HTTP/1.1 200 OK
Date: Sat, 23 Dec 2017 14:23:15 GMT
Content-Type: text/html; charset=utf-8
Server: Kestrel
Transfer-Encoding: chunked
至此,我们借助Docker就完美的完成了.NET Core项目的容器化部署。
结束了?还没有!
我打包的镜像是保存在本地的,我如何把镜像部署到其他机器上呢?请继续看。
6. 推送镜像到仓库
在第三节中,我们就简要介绍了,有个Registry是专门用来存储镜像的。请自行到Docker Hub注册个账号,然后我们把本地打包的镜像放到自己账号下的仓库下不就得了?!
注册完毕后,执行docker login
:
[root@iZ288a3qazlZ HelloDocker.Web]# docker login
Login with your Docker ID to push and pull images from Docker Hub. If you don't have a Docker ID, head over to https://hub.docker.com to create one.
Username: shengjie
Password:
Login Succeeded
[root@iZ288a3qazlZ HelloDocker.Web]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hellodocker.web latest 532e44a7fd54 13 minutes ago 1.745 GB
再执行docker push
:
$ docker push hellodocker.web
Error response from daemon: You cannot push a "root" repository. Please rename your repository to docker.io/<user>/<repo> (ex: docker.io/shengjie/hellodocker.web)
推送失败,提示我们的镜像命名不符规范。原来在推送之前要把镜像按<user>/<repo>
格式来命名。那如何重命名呢,我们用打标签的方式重命名:
$ docker tag hellodocker.web shengjie/hellodocker.web:v1
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
hellodocker.web latest 532e44a7fd54 35 minutes ago 1.745 GB
shengjie/hellodocker.web v1 532e44a7fd54 35 minutes ago 1.745 GB
$ docker push shengjie/hellodocker.web
The push refers to a repository [docker.io/shengjie/hellodocker.web]
774b128a8c4f: Pushed
7bf42a9b5527: Pushed
bd7f01c2dc6f: Pushed
....
换一台机器,我们直接执行以下命令,就完成了多重部署。
docker run -p 80:5000 <username>/hellodocker.web:v1
7.最后
如果你一步一步跟着练习的话,相信你对Docker以及.NET Core的跨平台特性有了初步的理解,也相信你对Docker的Build, Ship, and Run Any App, Anywhere有了更深的体会。
本文的实战演练就先到这里,下一篇,我们来看如何借助Docker使用Nginx完成.NET Core Web项目的反向代理!!!
参考资料
Hello Docker
HOSTING .NET CORE ON LINUX WITH DOCKER - A NOOB'S GUIDE
Docker命令收集
Linux常用命令