容器技术—.net core on docker
1. 开发环境使用docker调试
.net core应用想要启用docker支持非常简单,vs能够已经做了很好的支持。我们可以在创建.net core应用时,选择启用docker
也可以对已经创建完成的应用启用docker
上面的两个操作都会在应用根目录生产一个Dockerfile文件,关于Dockerfile下面再细讲。
启用了docker的应用在vs上除了IIS和控制台模式启动之外,还会多出一种docker启动的模式。
但是在使用docker模式进行启动时,可能会遇到问题,如果你遇到以下的情况,长期卡在容器预热这一步
查看容器工具有以下的信息
可以这样解决:
手工下载 https://vsdebugger.azureedge.net/vsdbg-17-2-10518-1/vsdbg-linux-x64.zip 文件,Url中的版本号需要与上面的提示一致。下载后解压到提示的文件夹中,然后创建文件success_rid.txt,内容为linux-x64,创建文件success_version.txt,内容为提示中的版本号。
再下载 https://vsdebugger.azureedge.net/vsdbg-17-2-10518-1/vsdbg-linux-musl-x64.zip 将这个文件解压到vsdbg\vs2017u5\linux-musl-x64中,同样新建success_rid.txt,内容为linux-musl-x64,success_version.txt,内容为提示是中的版本号。重新启动Visual Stuido就可以了。
之后就可以在vs通过docker容器运行我们的应用了。运行起来之后,可以在容器窗口看到以下内容:
通过vs以容器的方式启动我们的应用,需要依赖于windows版本的的docker应用 docker desktop。之后通过cmd命令也可以看到正在运行的容器和生成的镜像。
2. Dockerfile
从上面的例子可以看到,添加了启用了docker支持之后,我们工程的文件结构较之前多了一个Dockerfile,Dockerfile是用来构建镜像的关键文件,包含了一条条构建镜像所需的指令和说明。
Dockerfile常用的指令如下:
2. 1. FROM: 指定基础镜像
定制镜像,需要先有一个基础镜像,FROM 就是指定基础镜像,此指令必需放在有效指令的第一行。
格式:
FROM [--platform=<platform>] <image> [AS <name>]
FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]
FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]
说明:
--platform 指定镜像的平台,比如:linux/amd64, linux/arm64, or windows/amd64
tag 和 digest是可选项,如果不指定,默认为latest
2.2 RUN: 执行shell命令
RUN 指令是用来执行命令的,有以下俩种格式:
#shell 格式:
RUN <命令> # <命令行命令> 等同于,在终端操作的 shell 命令。
#exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
注意:run可以写多个,每一个run指令都会建立一层,所以尽可能合并成一条指令
范例:
RUN echo '<h1>Hello, Docker!</h1>' > /usr/share/nginx/html/index.html
RUN ["/bin/bash", "-c", "echo hello world"]
RUN yum -y install epel-release \
&& yum -y install nginx \
&& rm -rf /usr/share/nginx/html/*
&& echo "<h1> docker test nginx </h1>" > /usr/share/nginx/html/index.html
2.3 COPY:复制文本
复制本地主机的 (为 Dockerfile 所在目录的相对路径)到容器中的 。
COPY <src>... <dest>
COPY ["<src1>",... "<目标路径>"]
说明:
- 可以是多个、以及使用通配符,通配符规则满足Go的filepath.Match 规则
- 使用 COPY 指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等
- 如果是目录,只复制目录内容,而非目录本身
范例:
COPY hom* /mydir/ COPY hom?.txt /mydir/
2.4 ADD:复制和解包文件
ADD 指令和 COPY 的使用格类似(同样需求下,官方推荐使用 COPY),功能也类似。
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]
- ADD 的优点:在执行 <源文件> 为 tar 压缩文件的话,压缩格式为 gzip, bzip2 以及 xz 的情况下,会自动复制并解压到 <目标路径>。
- ADD 的缺点:在不解压的前提下,无法复制 tar 压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
2.5 CMD:容器启动命令
类似于 RUN 指令,用于指定启动容器时默认执行的命令,但二者运行的时间点不同:
- CMD 在docker run 时运行。
- RUN 是在 docker build。
如果docker run没有指定任何的执行命令或者dockerfile里面也没有ENTRYPOINT,那么就会使用执行CMD指定的默认的命令。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。如:docker run xxx /bin/bash,则/bin/bash 会覆盖 CMD 指定的命令。
注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。
# 使用 exec 执行,推荐方式,第一个参数必须是命令的全路径
CMD ["executable","param1","param2"]
# 在 /bin/sh 中执行,提供给需要交互的应用;
CMD command param1 param2
# 提供给 ENTRYPOINT 的默认参数;
CMD ["param1","param2"]
范例:
CMD ["nginx", "-g", "daemon off;"]
使用CMD作为容器启动的默认执行命令时,要注意一点:
如果业务进程可以在CMD中启动,则使用executable模式;如果必须放在shell脚本中启动,则在业务进程的启动命令前加上exec关键字使其pid保持为1
这是因为在容器中,如果pid=1的进程退出了,那么容器就会退出。所以要保证容器持久运行,就要保证pid=1的进程能够持久运行,我们应该一个容器尽量只跑一个业务进程,并且让业务进程的pid保持为1,这样才能利用好容器的重启策略,在容器挂掉之后自动重启,而不会出现容器内部的应用进程挂了,但是容器还在正常运行,导致难以排除问题
2.6 ENTRYPOINT:入口点
功能类似于CMD,配置容器启动后执行的命令及参数,并且不可被 docker run 提供的参数覆盖,而是追加
如果docker run命令有参数,那么参数全部都会作为ENTRYPOINT的参数。如果docker run后面没有额外参数,但是dockerfile中的CMD里有(即上面CMD的第三种用法),那么CMD的全部内容会作为ENTRYPOINT的参数
但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。
使用CMD要在运行时重新写命令才能追加运行参数,ENTRYPOINT则可以执行docker run时接受新参数
每个 Dockerfile 中只能有一个 ENTRYPOINT,当指定多个时,只有最后一个生效
# 使用 exec 执行
ENTRYPOINT ["executable", "param1", "param2"]
# shell中执行
ENTRYPOINT command param1 param2
可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。
示例:
假设已通过 Dockerfile 构建了 nginx:test 镜像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
1、不传参运行
$ docker run nginx:test
容器内会默认运行以下命令,启动主进程。
nginx -c /etc/nginx/nginx.conf
2、传参运行
$ docker run nginx:test -c /etc/nginx/new.conf
容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)
nginx -c /etc/nginx/new.conf
2.7 ENV:设置环境变量
指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
范例:
ENV VERSION=1.0 DEBUG=on NAME="Happy Feet"
ENV PG_MAJOR 9.3
ENV PG_VERSION 9.3.4
RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …
ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH
2.8 ARG:构建参数
指定变量
ARG <name>[=<default value>]
如果和ENV同名,ENV覆盖ARG变量
和ENV不同的是,ARG 只有 docker build 的过程中有效,容器运行时不会存在这些环境变量
可以用 docker build –build-arg <参数名>=<值> 来覆盖
范例:
FROM busybox
ARG user1=someuser
ARG buildno=1
FROM busybox
ARG SETTINGS
RUN ./run/setup $SETTINGS
2.9 VOLUME:挂载点
在容器中创建一个可以从本地主机或其他容器挂载的挂载点,一般用来存放数据库和需要保持的数据等,一般会将宿主机上的目录挂载至VOLUME 指令指定的容器目录。即使容器后期删除,此宿主机的目录仍会保留,从而实现容器数据的持久保存。在启动容器时没有指定挂载数据卷,会自动挂载到匿名卷。
作用:
- 避免重要的数据,因容器重启而丢失,这是非常致命的。
- 避免容器不断变大。
VOLUME ["<容器内路径1>", "<容器内路径2>"...]
VOLUME <路径>
范例:在容器创建一个/data/ 的挂载点
VOLUME [ "/data","/data2" ]
在启动容器 docker run 的时候,我们可以通过 -v 参数修改挂载点。
2.10 XPOSE:暴露端口
告诉 Docker 服务端容器暴露的端口号,以方便配置映射。
EXPOSE 仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射
因此,在启动容器时需要通过 -P 或-p进行端口映射 才可以使用,如果使用-P命令,Docker 主机会随机分配一个端口转发到指定暴露的端口
EXPOSE <端口1> [<端口2>...]
2.11 WORKDIR:指定工作目录
为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录,也是当容器运行后,进入容器内的默认目录。
用 WORKDIR 指定的工作目录,会在构建镜像的每一层中都存在(WORKDIR 指定的工作目录,必须是提前创建好的)。
docker build 构建镜像过程中的,每一个 RUN 命令都是新建的一层。只有通过 WORKDIR 创建的目录才会一直存在。
WORKDIR <工作目录>
范例:
#两次run不在一个环境内,可以使用WORKDIR
RUN cd /app
RUN echo "hello" > world.txt
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为 /a/b/c
以上为Dockerfile常用命令,其他的一些命令可查看网上的文章。看完这些Dokerfile指令,再回过头来看vs为我们生成的Dockerfile文件就能知道是什么意思了。
# 拉取.net 6 aspnet镜像,并重命名为base
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
# 设置工作目录为/app,app目录已存在
WORKDIR /app
# 暴露80端口
EXPOSE 80
# 拉取.net 6 sdk镜像,重命名为build
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
# 设置工作目录为/src
WORKDIR /src
# 复制类库文件到/src/dockerSample/文件夹
# 这里比较奇特,vs在构建镜像的时候宿主机的当前目录并不在Dockerfile的目录
# 而是在解决方案所在的目录
# 如果我们在当前Dockerfile的目录用docker build命令构建镜像是不成功的
COPY ["dockerSample/dockerSample.csproj", "dockerSample/"]
# 还原依赖包
RUN dotnet restore "dockerSample/dockerSample.csproj"
# 复制当前目录到/src
COPY . .
# 设置工作目录为/scr/dockerSample
WORKDIR "/src/dockerSample"
# 生成当前类库
RUN dotnet build "dockerSample.csproj" -c Release -o /app/build
# 使用.net 6 sdk镜像
FROM build AS publish
# 发布当前类库
RUN dotnet publish "dockerSample.csproj" -c Release -o /app/publish
# 使用.net 6 aspnet镜像
FROM base AS final
# 设置工作目录
WORKDIR /app
# 从publish层的/app/publish复制文件到当前目录,这里是因为/app/publish没有被设置成工作目录,所以不会在所有层到存在,只能这样指定从那一层复制,容器启动后进行容器内部也看不到/app/publish文件夹的
COPY --from=publish /app/publish .
# 执行以下脚本启动.net 应用
ENTRYPOINT ["dotnet", "dockerSample.dll"]
3. docker发布
对基于docker的.net core应用进行发布很简单,有多种方式可以实现。
3.1 镜像移植
如果你的开发机上已经安装了docker,并且像上面讲到的一样已经在开发环境利用docker进行运行调试了,可以通过vs直接生成docker镜像,根据上一节讲到的docker的基本操作,可以直接将本地的docker镜像导出,这也适用于某些只能离线部署,无法联网的情况。
步骤如下:
(1) 修改一下swagger配置,去掉开发环境判断,方便测试,生产环境发布根据自己的情况来。
(2)右击Dockerfile,使用VS容器工具生成映像
生成完镜像之后,通过vs的容器窗口或者在cmd中通过docker命令可以看到新生成的镜像。
这里可以看到,发布时vs已经通过环境变量设置了我们的应用在docker启动的端口了。这里还可以看到有两个镜像,标签分别是dev和latest,这两个镜像是不同的,latest标签的镜像才是我们可以导出使用的镜像,通过查看容器,可以看到这两个镜像的启动脚本是不同的。
(3) 导出镜像
(4) 将镜像上传到服务器,并重新导入
导入的镜像是没有名称和标签的,这里对它进行了以下重命名
(5) 启动容器
docker run -d --name dockersample -p 8005:80 dockersample:v1.0
通过浏览器也可以看到特意留出来的swagger了
3.2 release发布
如果按照我们以往的发布方式,都是通过vs将应用发布到一个文件夹中,再将文件夹拷贝到服务器上,通过dotnet xxx.dll的方式运行我们的应用,这种方式也可以结合docker来发布运行。这也是网上开源项目提供release版本支持docker的方式。
下面是通过release发布文件结合docker的方式,步骤如下:
(1) 发布应用
(2) 添加Dockerfile
如果本地不安装docker,不想利用vs进行docker调试,也就没法利用vs一键构建镜像的功能了,那样可以直接修改vs生成的Dockerfile文件,并将文件设置为较新则复制,这样在发布时Dockerfile文件就会发布到相应的文件夹,无需手动创建,这里手动添加只是做一个示例。
新的Dockerfile文件内容如下:
(3) 将文件夹打包,传输到服务器
(4) 通过docker build命令 + Dockerfile构建镜像
docker build -t dockersample:v2.0 .
值得一提的是,docker build 命令最后有一个点,.是指当前目录,用于指定Dockerfile所在的目录,所以使用点时需要进入Dockerfile所在的目录,也可以使用相对路径或者绝对路径。
从这里也可以看到通过FROM依赖于一个镜像构建我们自己的docker镜像是需要将依赖的镜像下载下来的,在某些特殊的项目中,如果是完全离线断网的环境下是无法这样部署的。
(5) 启动容器
docker run -d --name dockersample -p 8005:80 dockersample:v2.0
启动容器之后,通过浏览器可以访问到我们的应用
3.3 发布到docker仓库
除了以上两种发布发布之外,我们还可以将应用镜像发布到docker仓库,再在服务器上从仓库拉取进行安装部署,就像我们使用第三方的docker镜像一样。我们可以将镜像发布到Docker Hub等公开的docker仓库中,但是在项目中我们往往需要使用私有仓库。
(1) 私有仓库部署
开源的私有化docker仓库有很多,如Registry、Harbor、nexus3等,我习惯使用nexus3,工作中的nuget包、npm包等也是用nexus3来管理的。这里就不详细讲解nexus3的安装部署了,只是用官方docker镜像快速启动一个nexus3的应用作为演示,版本为 v3.39.0
,nexus3的详细部署文档请参考网上教程或官方文档。
#因为官方容器运行用户有所指定,挂载目录必须授权
mkdir /home/yyl/nexus/nexus-data && chown -R 200 /home/yyl/nexus/nexus-data
docker run -d -p 8081:8081-p 8082:8082 --name nexus -v /home/yyl/nexus/nexus-data:/nexus-data sonatype/nexus3
持久目录/nexus-data用于配置、日志和存储。此目录需要可由 Nexus 进程写入,该进程以 UID 200 运行。
nexus运行只需要一个8081端口就可以了,这里增加一个8082端口是为了给后面docker仓库的登录端口使用。这里很关键,不加这个端口映射,后续新增了docker仓库之后外部是登录不上的。
启动成功之后就可以通过浏览器访问到nexus管理页面
登录默认管理员admin,密码在 /home/yyl/nexus/nexus-data/admin.password,初次登录之后需要修改下密码。
(2) 创建docker 仓库
nexus部署之后,nuget和maven仓库是默认就有的,但是docker仓库需要我们自己配置,点击设置进行管理页面。
选择仓储,点击创建新仓储
选择docker(hosted),可以看到nexus中的管理一种包的仓储有三种类型,如上面的nuget、maven,这里的docker。
- (hosted)为本地仓库,我们上传自己的镜像就上传到这个仓库
- (proxy)为代理仓库,通过url地址代理到线上的仓库,如Docker Hub,当我们使用到第三方的镜像时,这个仓库会从线上参考拉取并存储到本地,下次再使用这个镜像就是直接使用这个仓库的
- (group)为聚合仓库,将host、proxy两个仓库聚合,我们一般最终使用的就是这个仓库
(3) 设置权限
点击菜单Security->Realms,把 Docker Bearer Token Realm 移到右边的框中保存。
这里在工作中根据实际需要可以针对Role和User进行新增设置,通过专门的用户角色权限来控制对docker仓库操作的权限,这里就直接使用admin用户了。
(4) 镜像上传私有仓库
要像上传镜像到私有仓库,需要先登录。由于我们上面设置的登录方式是http协议,而docker默认是https链接,创建仓库为http判定不安全链接会被拒绝。我们需要修改daemon.json文件内容,将仓库链接手动添加为信任链接。linux下daemon.json文件位于/etc/docker下,如果没有此文件,可以自己创建此文件。
文件内容为:
{
"insecure-registries": ["ip:port" ]
}
之后重启docker daemon
systemctl daemon-reload
systemctl restart docker
如果是windows下,直接在doker desktop 的setting下进行配置
之后通过以下命令登录:
docker login ip:port
之后我们就通过命令可以将本地的镜像推送到私有仓库了,本地镜像推送到私有仓库前需要先修改镜像标签,在镜像repository上指定仓库,否则会推送到默认仓库
docker tag dockersample:dev 192.168.137.200:8082/dockersample:v1.0
docker push 192.168.137.200:8082/dockersample:v1.0
(5) .net 应用发布
对于我们的.net 应用来说,通过命令推送本地镜像始终不方便,vs发布的时候就可以选择直接将应用发布到docker仓库。
这里看到的latest版本就是我们发布到私有仓库中的镜像了。
(6) 服务器下载镜像部署
服务器上通过以下命令拉取私有仓库的镜像,也是需要指定镜像仓库:
docker pull 192.168.137.200:8082/dockersample
docker run -d --name dockersample -p 8005:80 192.168.137.200:8082/dockersample
之后就是正常的启动容器了。
一篇博文下来1万来字,有些长了。剩下的一些内容放到下一篇了。
参考文档:
Visual Studio 准备容器时,卡在 vsbdg\vs2017u5 exists deleting
docker-dockerfile指令详细介绍
Docker Dockerfile
微服务系列文章:
上一篇:容器技术—docker基础
下一篇:容器技术—docker compose