我的Docker之路(三):容器化APS.NET Core应用

Docker容器说到底还是为应用服务的,其最根本的作用还是用来部署我们开发的应用。在开发容器化的应用时,通常都会要创建自定义的镜像。目前在Docker中创建镜像最常用的方法应该就是使用Dockerfile了。Dockerfile是文本文件,一种类似脚本的描述文件,来定义镜像的每层是如何构建,正文部分会详细介绍。
简单概括,开发基于Docker容器的应用程序的常用工作流程:

  1. 开发环境中完成应用的开发
  2. 编写Dockerfile文件
  3. 使用Dockerfile创建自己的镜像
  4. 使用镜像实例化容器并正常运行
  5. 测试与修正
  6. 可以将镜像Push到Hub

本次的学习任务是构建自定义镜像,并将一个ASP.NET Core应用程序使用容器化的方式部署到Linux服务器中。

Dockerfile指令

Docker可以通过定义好的Dockerfile中的指令自动构建映像。虽然现在通过一些高级的IDE(比如visual studio或者visual studio code)可以自动根据当前项目生成Dockerfile,但是了解Dockerfile仍是很有必要的。

为了对Dockerfile有个整体印象,先预览一份官方文档中Dockefile的示例。可以看出来这里就是按步骤定义指令,大小写不敏感但建议全部大写,“#”标识注释文本。

FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build-env
WORKDIR /app

# Copy csproj and restore as distinct layers
COPY *.csproj ./
RUN dotnet restore

# Copy everything else and build
COPY . ./
RUN dotnet publish -c Release -o out

# Build runtime image
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1
WORKDIR /app
COPY --from=build-env /app/out .
ENTRYPOINT ["dotnet", "aspnetapp.dll"]

接下来对Dockerfile中的常用指令做一个总结。

FROM <image>[:<Tag>] [AS <name>]

  1. 指明本镜像是基于哪个基础镜像来创建的
  2. 必须放在Dockerfile第一行
# 以官方 ASP.NET Core镜像(Tag是3.1)作为基础镜像
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS build-env

RUN <command>

RUN ["executable","arg1","arg2"]

  1. 运行shell命令的两种写法,第一种可当作Windows上的 cmd /S /C 或者Linux上的 /bin/sh -c ,第二种是exec form,要注意的是参数会被传递成Json,所以不要忘记双引号。
  2. 注意的是每运行一个RUN命令都会在当前镜像的顶层生成一个新层,并将结果提交。
RUN dotnet restore
RUN ["dotnet", "restore"]

CMD ["executable","param1","param2"]

CMD ["param1","param2"]

CMD command param1 param2

  1.  定义镜像创建的容器默认执行的命令,理论上一个Dockerfile中只有一个CMD指令,如果存在多个只执行最后一条。
  2. CMD指令有3中写法,第二种是最为ENTRYPOINT的参数
  3. 要注意区别RUN和CMD,CMD指令在创建镜像的过程中不会执行,是在容器创建好后执行的。
  4. 当Docker执行run命令并附加了CMD时,我们在Dockerfile中指定的CMD将会被重写。
CMD echo "This is a test container."

LABEL <key>=<value> <key>=<value> <key>=<value> ...

  1. LABEL指令为镜像添加键值对形式的元数据
  2. 如果在键值对中需要写入空格,要使用双引号或者反斜杠
  3. 若想查看镜像中定义的label,可使用 docker image inspect --format='' <image> 命令
LABEL version="1.0"
LABEL maintainer="xuhy"

 EXPOSE <port> [<port>/<protocol>...]

  1. 用来声明容器运行时监听的网络端口
  2. 并且可以指定监听的协议时TCP或是UDP,默认时采用TCP
  3. 注意当使用EXPOSE指定指定端口后其实并不会在容器运行时就自动开启这个端口,主要是针对镜像使用者作为一个声明,在启动容器还是需要使用 -p 标识来映射端口。
EXPOSE 80/tcp
EXPOSE 80/udp
docker run -p 80:80/tcp -p 80:80/udp ...

ENV <key> <value>
ENV <key>=<value> ...

  1. 设置环境变量,参数使用键值对的形式增加
  2. 后续的所有指令包括RUN都可以使用它所创建的环境变量
  3. 使用该镜像创建的容器中也可以使用该环境变量,可以使用 docker inspect 命令来查看环境变量,并可以使用 docker run --env <key>=<value> 来修改这些环境变量。
ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

  1. 将 <src> 中指定的路径/文件(也可以是远端文件的URL)拷贝到镜像中的文件系统的 <dest> 路径。
  2.  <src> 中指定的路径或者文件可以使用通配符
  3.  --chown 特性只在创建Linux容器时起作用
# 添加所有以 "hom" 开头的文件
ADD hom* /mydir/   
# ? 替换任何单个的字符, e.g., "home.txt"
ADD hom?.txt /mydir/  
# 添加 "test" 到 `WORKDIR`/relativeDir/
ADD test relativeDir/   
# 添加 "test" 到 /absoluteDir/
ADD test /absoluteDir/
# 当添加特殊字符如“[”时,需要遵循Go语言的规则对其转移,如拷贝文件“ arr[0].txt”时使用
ADD arr[[]0].txt /mydir/

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

  1. COPY指令与上面的ADD指令基本功能一样
COPY hom* /mydir/
COPY hom?.txt /mydir/
COPY test.txt relativeDir/
COPY test.txt /absoluteDir/
ADD arr[[]0].txt /mydir/

ENTRYPOINT command param1 param2

ENTRYPOINT ["executable", "param1", "param2"]

  1. 使用ENTRYPOINT指令可以将容器配置成一个可执行文件,这样就不能通过docker run中的命令行所覆盖,但是可以通过参数传递的方式影响到容器的执行
  2. 当定义了ENTRYPOINT后,CMD只能够作为参数进行传递
  3. 每个Dockerfile理论上只有一个ENTRYPOINT,若定义了多个ENTRYPOINT只有最后一个起作用
ENTRYPOINT ["dotnet", " MyWebApi.dll "]

VOLUME ["/data"]

  1. 为镜像创建一个挂载点供创建容器时用卷来挂载
VOLUME ["/usr/data/mysql"]
VOLUME ["/var/data1","/var/data2"]

USER <user>[:<group>]
USER <UID>[:<GID>]

  1. 设定运行RUN或者CMD的用户名或用户ID
# Set it for subsequent commands
USER xuhy

WORKDIR /path/to/workdir

  1. 后续的 RUN , CMD , ENTRYPOINT  COPY 指令设定工作目录
  2. 如果指定的路径不存在会自动创建
  3.  WORKDIR 在Dockerfile中可以多次使用,如果提供的是一个相对路径,那么这个相对路径会被指定为前一个 WORKDIR 指令的相对路径下
WORKDIR /a
WORKDIR b
WORKDIR c

# pwd命令的工作目录在“/a/b/c”下
RUN pwd

ARG <name>[=<default value>]

  1. 该指令用来定义参数变量,定义了参数后,可以在创建镜像是使用 docker build 命令时使用 --build-arg <varname>=<value> 标识来为参数赋值。
  2. 在定义参数时也可为其设定默认值 ARG user1=someusr 
  3. 一个 ARG 变量是在Dockerfile中定义的行之后才开始生效
  4. 如果在同个Dockerfile中使用 ARG 和 ENV 定义了同名变量,那么使用 ENV 定义的环境变量会覆盖 ARG 定义的参数
  5.  ARG 定义的变量的作用域只在当前构建层中有效,也就是说在使用 RUN 命令之后便超出了其作用域
ARG user1="abc"
ARG CONT_IMG_VER = "v1.0"
ENV CONT_IMG_VER v1.0.0        #同名变量,ENV会覆盖ARG的变量 
USER $user1
#...

$ docker build --build-arg user=what_user,CONT_IMG_VER=v2.0.1

构建ASP.NET Core项目镜像

接下来我们尝试定制一个ASP.NET Core应用的镜像,当前版本的Visual Studio和Visual Studio Code都已经集成了Docker插件,真不愧时宇宙级IDE。使用VS创建一个测试项目“MyWebApp.MVC”,可以看到在创建项目时便可以同时创建Dockerfile,只需勾选“启动Docker”支持即可。如下图所示。

等待VS生成要默认脚手架代码后,试运行没问题后在项目上右键 → 添加 → Docker支持,随后目标OS选择Linux。随后VS会帮我们生成一份Dockerfile文件,并且会自动执行镜像的构建和容器的创建执行,全自动挡。

既然已经学习了Dockerfile的命令,让我们把VS生成的Dockerfile过目一遍。

 1 # 拉取微软官方提供的asp.net core运行时镜像
 2 FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim AS base
 3 WORKDIR /app    #指定当前工作目录
 4 EXPOSE 5000     #声明暴露5000端口
 5 
 6 # 拉取微软官方提供的.NET Core SDK 镜像,用于编译和发布项目
 7 FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
 8 WORKDIR /src    #指定当前工作目录
 9 COPY ["MyWebApp.MVC/MyWebApp.MVC.csproj", "MyWebApp.MVC/"]   #项目文件拷贝到镜像中的“/src/MyWebApp.MVC/”目录中
10 RUN dotnet restore "MyWebApp.MVC/MyWebApp.MVC.csproj"        #执行命令来恢复项目的依赖项
11 COPY . .        #把当前目录(windows主机)的所有内容拷贝到镜像的当前目录“/src/”
12 WORKDIR "/src/MyWebApp.MVC"        #切换当前工作目录
13 RUN dotnet build "MyWebApp.MVC.csproj" -c Release -o /app/build    #使用.net core编译器执行“生成”
14 
15 # 基于上面生成项目用的镜像“build”继续搭建本层,工作目录为“/src/MyWebApp.MVC/”
16 FROM build AS publish
17 RUN dotnet publish "MyWebApp.MVC.csproj" -c Release -o /app/publish    
18 # 至此项目的编译与发布已完成,就是说现在生成了“MyWebApp.MVC.dll”文件了
19 
20 FROM base AS final
21 WORKDIR /app
22 COPY --from=publish /app/publish .        #将刚刚发布项目的publish文件夹下的所有文件拷贝到本镜像的当前目录下(/app),注意结尾的"."代表当前目录
23 ENTRYPOINT ["dotnet", "MyWebApp.MVC.dll"] #启动网站

通过powershell来执行镜像的命令,先进入项目文件夹的目录,执行命令。

PS D:\workspace\MyWebApp.MVC > docker build -f "${pwd}\MyWebApp.MVC\Dockerfile" --force-rm -t mywebappmvc:test .

其中 -f 后跟dockerfile的路径, --force-rm 表示删除中间层镜像, -t 后跟“镜像名:Tag”,最后跟“.”表示当前工作路径或上下文(context)为当前路径。

等待16个Step(Dockerfile中每一句命令是一个Step)执行完成,可以检查下当前镜像列表中多出刚刚构建的镜像。

Successfully built 9ae226b0a4e9
Successfully tagged mywebappmvc:test

PS D:\workspace\MyWebApp.MVC> docker image ls                                                              
REPOSITORY TAG IMAGE ID CREATED SIZE mywebappmvc test 9ae226b0a4e9
22 seconds ago 212MB

在本地试执行,将容器的5000端口映射到本地的8080端口。

docker run -dt -p 8080:5000 --name aspnetcore.test  mywebappmvc:test

执行成功后在浏览器中访问8080端口,不出意外便可以看到asp.net core mvc的默认页面。

可执行 docker logs aspnetcore.test 查看容器执行的日志。

我们可以将创建好的Docker镜像上传到DockerHub上,首先需要我们在DockerHub上建立自己的账户,在本地Docker上登录 docker login <username> 。登录成功之后便可以使用 docker push <image> 命令上传镜像。注意镜像名称的书写有格式要求,要按照 docker push username/image:tag 的。本地可以使用 docker tag  命令来标记本地镜像。

docker tag [OPTIONS] IMAGE[:TAG] [REGISTRYHOST/][USERNAME/]NAME[:TAG]

在Linux服务器上运行容器

 1. 在Linux上安装Docker

Linux上安装Docker也很简单,直接参照官方文档一步步执行即可,没啥坑。由于是在一台全新的Ubuntu系统上安装,先更新 apt ,下载和更新必要的包

~$ sudo apt-get update

~$ sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

搭建本地Repository

~$ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -

~$ sudo apt-key fingerprint 0EBFCD88

~$ sudo add-apt-repository \
   "deb [arch=amd64] https://download.docker.com/linux/ubuntu \
   $(lsb_release -cs) \
   stable"

安装Docker Engine,执行下面命令安装最新版Docker Engine

$ sudo apt-get update
$ sudo apt-get install docker-ce docker-ce-cli containerd.io

安装完成之后试一下命令

~$ docker --version
Docker version 19.03.8, build afacb8b7f0

~$ sudo docker run --rm hello-world

2.拉取镜像并运行容器

首先拉取刚刚上传到DockerHub上的镜像

~$ sudo docker pull mark826/mywebappmvc:test20515

查看下本地Docker镜像可以看到拉取成功

~$ sudo docker images
REPOSITORY            TAG                 IMAGE ID            CREATED             SIZE
mark826/mywebappmvc   test20515           9ae226b0a4e9        4 hours ago         212MB

接着和在Windows下一样运行容器即可。

~$ sudo docker run -dt -p 8080:5000 --name aspnetcore.test  mark826/mywebappmvc:test20515

现在访问服务器的8080端口便可以访问asp.net core的页面了。

参考文献

[1] 官文:https://docs.docker.com/engine/reference/builder/

[2] 微软文档:https://docs.microsoft.com/zh-cn/dotnet/architecture/microservices/docker-application-development-process/docker-app-development-workflow

[3] 介绍Dockerfile的博文:https://www.cnblogs.com/jsonhc/p/7766841.html

[4] 博文:https://www.cnblogs.com/edisonchou/p/dockerfile_inside_introduction.html

[5] Ubuntu安装Docker:https://docs.docker.com/engine/install/ubuntu/

[6] 官文介绍Docker容器化ASP.NET Core应用:https://docs.docker.com/engine/examples/dotnetcore/

 

posted @ 2020-05-15 21:07  冬瓜山总教练  阅读(440)  评论(0编辑  收藏  举报