据说容器技术是我们这个行业的一个重要趋势,而博主恰好在近期遇到了这样的需求。
参考MSDN博客,我们先来看看开发人员迁移到容器的关键原因:
一致性:容器包含应用程序及其所有依赖项。不管是在计算机、本地环境或是云端,应用程序都执行相同的代码。
轻量级:通过使用基于主机操作系统的最小量级抽象,并在容器之间共享公共资源,容器可以快速启动并实现最少的内存占用。
共享性:容器镜像可以通过 Docker Hub、Docker Store 和私有 Docker 仓库(如 Azure 镜像仓库)轻松实现分享。
简单而强大:DockerFile 格式(容器镜像的奥秘所在)是一种可以实现强大场景的简单格式:优雅地将操作系统和容器的特定命令结合,并且还可以创建 Docker 镜像层。
接下来,我们看看具体如何在 Docker 中部署 .NET Core 应用。
注:本篇博客中操作系统使用 CentOS 7
1. 前期准备
.NET Core 工程
本篇博客中新建一个 ASP.NET Core 工程作为示例
VS 2017 Version 15.7.4
.NET Core Runtime 2.1
Linux 切换至 root 身份(方便后续所有需要相关权限的操作)
$ su -
2. 安装 Docker
详情请参考文档:https://docs.docker.com/install/linux/docker-ce/centos/
$ yum install -y docker
$ systemctl start docker
注:
CentOS 7 安装好 Docker ,执行启动命令systemctl start docker
时,可能出现如下报错:
Error starting daemon: SELinux is not supported with the overlay2 graph driver on this kernel. Either boot into a newer kernel or disable selinux in docker (--selinux-enabled=false)
重新编辑 Docker 配置文件即可(设置 OPTIONS 的--selinux-enabled=false
或直接删除--selinux-enabled
):
$ vi /etc/sysconfig/docker
# /etc/sysconfig/docker
# Modify these options if you want to change the way the docker daemon runs
OPTIONS='--selinux-enabled=false --log-driver=journald --signature-verification=false'
if [ -z "${DOCKER_CERT_PATH}" ]; then
DOCKER_CERT_PATH=/etc/docker
fi
:wq
$ systemctl restart docker
3. 确认文件系统
确保 /etc/sysconfig/docker-storage 中 DOCKERSTORAGEOPTIONS="--storage-driver overlay"
注:Docker默认只能识别overlay文件系统,若使用overlay2文件系统,运行Docker镜像时守护进程会报错:
Error response from daemon: error creating overlay mount to /var/lib/docker/overlay2/2c320fe7df5a0e659466c528063c640402e504bc4d1500b78d6a5142707b1487/merged: invalid argument.
$ systemctl stop docker #停掉docker服务
$ rm -rf /var/lib/docker #特别注意:已拉取的docker镜像会被清空
$ vi /etc/sysconfig/docker-storage #设置DOCKER_STORAGE_OPTIONS="--storage-driver overlay"
$ vi /etc/sysconfig/docker #去掉option后面的--selinux-enabled,或将其值置为false(操作同安装Docker中的说明)
$ systemctl start docker #再次启动docker服务
4. 修改 /etc/docker/daemon.json 文件来配置镜像加速(考虑到网络防火长城)
$ vi /etc/docker/daemon.json
{
"registry-mirrors": ["https://registry.docker-cn.com"]
}
:wq
注:请保证 /etc/docker/daemon.json 访问权限
# 授权单个文件
$ chmod 777 /etc/docker/daemon.json
# 递归授权整个文件夹
$ chmod -R 777 /var/lib/docker
5. 拉取或导入microsoft/dotnet镜像
镜像类别:
- microsoft/dotnet or microsoft/dotnet:latest (alias for the SDK image)
- microsoft/dotnet:sdk
- microsoft/dotnet:runtime
- microsoft/dotnet:runtime-deps
详情请参见:https://docs.microsoft.com/en-us/dotnet/core/docker/building-net-docker-images
拉取镜像(以镜像 microsoft/dotnet:2.1-aspnetcore-runtime 为例):
$ docker pull microsoft/dotnet:2.1-aspnetcore-runtime
*注:*
*A. dotnet sdk的镜像包括了runtime与 dotnet 命令(CLI,即Command Line Interface),体积较大(1.72GB),而我们的程序想要正常运行,使用runtime的镜像(255MB)就足够了*
*B. 本篇博客将以 asp.net core 的工程为例进行后续演示,且写作博文时 dotnet core runtime 版本为2.1,故使用镜像 microsoft/dotnet:2.1-aspnetcore-runtime*
导入导出镜像:
#导出镜像(注:625b44243fbe为需要导出的镜像的ID)
$ docker save 625b44243fbe > /home/lary/Documents/dotnetimage.tar
#导入镜像
$ docker load < /home/lary/dotnetimage.tar
#修改导入镜像的名称与标签(注:所导入的镜像名称、标签均为none,625b44243fbe为导入的镜像的ID)
$ docker tag 625b44243fbe microsoft/dotnet:2.1-aspnetcore-runtime
6. 将程序发布内容传输至 CentOS
- 发布程序
- 文件传输至 CentOS
注:我将发布后的文件统统扔到 /home/lary/Projects/Lary.Test.Apis 目录下,以便后续操作
7. 创建 Docker 镜像 [可选]
1) 编写适当的 Dockerfile
A. Dockerfile 实例
FROM microsoft/dotnet:2.1-aspnetcore-runtime
MAINTAINER Lary Mao <dev@lary.me>
LABEL build-date="2018-06-23"
ENV ASPNETCORE_URLS http://0.0.0.0:8023
WORKDIR /app
COPY * /app/
#也可以通过CMD命令启动脚本文件,并在脚本文件中写入dotnet启动命令
ENTRYPOINT ["dotnet", "Lary.Test.Apis.dll"]
注:
a. 请注意 Dockerfile 文件名不能有误
b. 我选择将 Dockerfile 放置到程序目录下。各位请根据实际情况修改 Dockerfile 中所涉及到的文件路径(如 COPY 命令)
B. 详细说明
参考:https://www.cnblogs.com/sorex/p/6481407.html
DockerFile分为四部分组成:基础镜像信、维护者信息、镜像操作指令和容器启动时执行指令。
- FROM
第一行必须指明基于的基础镜像
例:FROM microsoft/dotnet:2.1-aspnetcore-runtime
- MAINTAINER
指定维护者的信息
例:MAINTAINER Lary Mao dev@lary.me
- RUN
格式为Run
或者Run [“executable” ,”Param1”, “param2”]
前者在shell终端上运行,即/bin/sh -C,后者使用exec运行。例如:RUN [“/bin/bash”, “-c”,”echo hello”]
每条run指令在当前基础镜像执行,并且提交新镜像。当命令比较长时,可以使用“/”换行 - CMD
构建容器后调用,也就是在容器启动时才进行调用。
支持三种格式:
CMD [“executable” ,”Param1”, “param2”]
使用exec执行,推荐
CMD command param1 param2
,在/bin/sh上执行
CMD [“Param1”, “param2”]
提供给ENTRYPOINT做默认参数 - EXPOSE
告诉Docker服务端容器暴露的端口号,供互联系统使用。在启动Docker时,可以通过-P,主机会自动分配一个端口号转发到指定的端口。使用-P,则可以具体指定哪个本地端口映射过来
例:EXPOSE 22 80 8023
- ENV
指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持 - ADD
该命令将指定的文件添加到容器中。其中,源可以是Dockerfile所在目录的一个相对路径;也可以是一个URL;还可以是一个tar文件(自动解压至目录) - COPY
将构建命令所在的主机本地的文件或目录(注意源路径使用相对目录),复制到镜像文件系统(不会自动解压) - ENTRYPOINT
配置容器,使其可执行化
每个Dockerfile中只能有一个 ENTRYPOINT ,当指定多个时,只有最后一个起效
例:ENTRYPOINT [“executable”, “param1”, “param2”]
或:ENTRYPOINT command param1 param2 (shell中执行)
- VOLUME
用于指定持久化目录,在容器启动时用-v传递参数,例如-v ~/opt/data/mysql:/var/lib/mysql将本机的~/opt/data/mysql和容器内的/var/lib/mysql做持久化关联
容器启动时会加载,容器关闭后会回写
例:VOLUME [“/data”]
- USER
指定运行容器时的用户名或UID,后续的 RUN 也会使用指定用户
当服务不需要管理员权限时,可以通过该命令指定运行用户。并且可以在之前创建所需要的用户,例如:RUN groupadd -r postgres && useradd -r -g postgres postgres
。要临时获取管理员权限可以使用 gosu ,而不推荐 sudo
例:USER daemon
- WORKDIR
为后续的 RUN 、 CMD 、 ENTRYPOINT 指令配置工作目录。
可以使用多个 WORKDIR 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。例如:
WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd
则最终路径为/a/b/c
例:WORKDIR /path/to/workdir
- ONBUILD
配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。
例如,Dockerfile使用如下的内容创建了镜像 image-A 。
[…]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build –dir /app/src
[…]
如果基于A创建新的镜像时,新的Dockerfile中使用 FROM image-A 指定基础镜像时,会自动执行 ONBUILD 指令内容,等价于在后面添加了两条指令
例:ONBUILD [INSTRUCTION]
2) 通过命令创建 Docker 镜像
#授权,防止Docker守护进程访问文件权限不够
$ chmod -R 777 /home/lary/Projects/Lary.Test.Apis
#进入到Dockerfile目录
$ cd /home/lary/Projects/Lary.Test.Apis
#创建Docker镜像
$ docker build -t lary/apistest:runtime .
注:lary/apistest 是我为新镜像起的名字,runtime 是新镜像的标签(容器名要求全小写)。此外,请额外注意不要遗漏命令行末尾的那个点。
8. 创建 Docker 容器
通过docker run
命令创建新容器(本篇博客中使用端口 8022)
1) 若未按照步骤 7 创建 Docker 镜像
在docker run
命令中写入所需的相关信息(如程序工作目录、端口映射等,详情请参见与此步骤同级的详细说明↓)
$ docker run -w /app -e ASPNETCORE_URLS="http://0.0.0.0:8022" --entrypoint "dotnet" --name dotnet-test -h dotnet-test -p 8022:8022 -v /home/lary/Projects/Lary.Test.Apis:/app --privileged=true -d docker.io/microsoft/dotnet:2.1-aspnetcore-runtime Lary.Test.Apis.dll
注:命令中 -w 用于指定程序工作目录(WORKDIR),-e 用于指定 环境变量(ENV), --entrypoint用于指定程序入口,其参数传递必须放置在创建容器所基于的镜像名之后,使用空格进行分隔(Lary.Test.Apis.dll 作为参数传递给 donet 命令)
参考:https://medium.com/@oprearocks/how-to-properly-override-the-entrypoint-using-docker-run-2e081e5feb9d
2) 若已按照步骤 7 创建 Docker 镜像
直接根据新创建的镜像创建容器即可
docker run --name dotnet-test -h dotnet-test -p 8023:8022 --privileged=true -d lary/apistest:runtime
3) 详细说明
- -a stdin
指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项 - -d
后台运行容器,并返回容器ID - -e key="value"
设置环境变量 - -h "host"
指定容器的hostname - -i
以交互模式运行容器,通常与 -t 同时使用 - -m
设置容器使用内存最大值 - -p
端口映射,使用方法为 主机上的端口:容器内部的端口 - -t
为容器重新分配一个伪输入终端,通常与 -i 同时使用 - -v
挂载数据卷,使用方法为 数据卷 -v 容器目录 或 -v 本地目录:容器目录 - --add-host
为容器添加host与ip的映射关系 - --cpuset="0-2" or --cpuset="0,1,2"
绑定容器到指定CPU运行 - --dns 8.8.8.8
指定容器使用的DNS服务器,默认和宿主一致 - --dns-search example.com
指定容器DNS搜索域名,默认和宿主一致 - --env-file=[]
从指定文件读入环境变量 - --expose=[]
开放一个端口或一组端口 - --link=[]
添加链接到另一个容器 - --name="nginx-lb"
为容器指定一个名称 - --net="bridge"
指定容器的网络连接类型,支持 bridge/host/none/container四种类型 - --privileged=true
使用该参数,container 内的 root 拥有真正的 root 权限。否则, container 内的 root 只是外部的一个普通用户权限。privileged 启动的容器,可以看到很多 host 上的设备,并且可以执行 mount 。甚至允许你在 Docker 容器中启动 Docker 容器。
9. 确保防火墙端口开启
#打开端口/端口段(--permanent永久生效,没有此参数重启后失效)
$ firewall-cmd --zone=public --add-port=8022/tcp --permanent
$ firewall-cmd --zone=public --add-port=8022-8022/tcp --permanent
#重新载入
$ firewall-cmd --reload
#查看端口
$ firewall-cmd --zone=public --query-port=8022/tcp
#删除端口
$ firewall-cmd --zone=public --remove-port=8022/tcp --permanent
10. 验证