容器技术—.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 EXPOSE:暴露端口

告诉 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值