Linux(CentOS)部署asp.net core应用到docker容器


安装Docker

先决条件

系统要求

要安装Docker引擎,您需要CentOS 7或8的版本。其他版本不受支持或未经测试验证。

  • 必须启用centos-extras存储库。默认情况下,此存储库是启用的,但如果已禁用它,则需要重新启用它。
  • 建议使用overlay2存储驱动程序。

卸载旧版本

较旧的 Docker 版本称为 docker 或 docker-engine 。如果已安装这些程序,请卸载它们以及相关的依赖项。

sudo yum remove docker \
                  docker-client \
                  docker-client-latest \
                  docker-common \
                  docker-latest \
                  docker-latest-logrotate \
                  docker-logrotate \
                  docker-engine

使用 Docker 仓库进行安装

在新主机上首次安装 Docker Engine-Community 之前,需要设置 Docker 仓库。之后,您可以从仓库安装和更新 Docker。

设置仓库

安装yum-utils软件包。(yum-utils 提供了 yum-config-manager) 并设置稳定的仓库。

sudo yum install -y yum-utils

sudo yum-config-manager \
    --add-repo \
    https://download.docker.com/linux/centos/docker-ce.repo

上面使用的是官方源地址,可能比较慢,可以使用国内的一些镜像:

阿里云

sudo yum-config-manager \
    --add-repo \
    http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo

清华大学

sudo yum-config-manager \
    --add-repo \
    https://mirrors.tuna.tsinghua.edu.cn/docker-ce/linux/centos/docker-ce.repo

安装 Docker Engine

1、安装最新版本的 Docker Engine 和 containerd,或者转到下一步安装特定版本:

sudo yum install docker-ce docker-ce-cli containerd.io

如果提示您接受 GPG 密钥,请选是。

有多个 Docker 仓库?
如果启用了多个 Docker 仓库,则在 yum install 或 yum update 命令中未指定版本的情况下,进行的安装或更新将始终安装最高版本,这可能不适合您的稳定性需求。

Docker 安装完默认未启动。并且已经创建好 docker 用户组,但该用户组下没有用户。

2、要安装特定版本的 Docker Engine-Community,请在存储库中列出可用版本,然后选择并安装:
a.列出并排序您存储库中可用的版本。此示例按版本号(从高到低)对结果进行排序。

yum list docker-ce --showduplicates | sort -r

docker-ce.x86_64  3:18.09.1-3.el7                     docker-ce-stable
docker-ce.x86_64  3:18.09.0-3.el7                     docker-ce-stable
docker-ce.x86_64  18.06.1.ce-3.el7                    docker-ce-stable
docker-ce.x86_64  18.06.0.ce-3.el7                    docker-ce-stable

b.通过其完整的软件包名称安装特定版本,该软件包名称是软件包名称(docker-ce)加上版本字符串(第二列),从第一个冒号(:)一直到第一个连字符,并用连字符(-)分隔。例如:docker-ce-18.09.1。

sudo yum install docker-ce-<VERSION_STRING> docker-ce-cli-<VERSION_STRING> containerd.io

3、启动 Docker

sudo systemctl start docker

4、通过运行 hello-world 映像来验证是否正确安装了 Docker Engine

sudo docker run hello-world

如果正确,应该会看到类似以下内容

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
b8dfde127a29: Pull complete 
Digest: sha256:308866a43596e83578c7dfa15e27a73011bdd402185a84c5cd7f32a88b501a24
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://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/

OK, 安装完成!更多安装方式参见官方文档:https://docs.docker.com/engine/install/centos/#install-using-the-repository

几个Docker常用命令:

$ docker images #列出所有的镜像
$ docker ps #列出所有的在运行的容器 加上 -a 列出所有的容器
$ docker start id #运行某个容器
$ docker stop id #停止某个容器
$ docker restart id #重启某个容器
$ docker rm id #删除某个容器
$ docker rmi id #删除某个镜像
$ docker exec -it id /bin/bash #进入某个容器内部
$ docker build -t counter-image -f Dockerfile . #创建镜像  注意后面的. 不要漏了
$ docker create --name core-counter counter-image #使用counter-image镜像创建容器并命名为core-counter
$ docker run --name core-counter -p 8080:80 -d counter-image #使用counter-image镜像创建容器并命名为core-counter 同时运行该容器
	//另外还有-v 参数可以挂载外部文件目录、同步宿主机系统时间等,-rm 停止容器是自动删除容器,--restart设置守护机制等

Docker部署应用

部署应用

1. 构建应用镜像

1) 项目添加Docker支持

VS中在项目上点击右键,选择添加-Docker支持
image
会自动给我创建Dockerfile(以及提示安装容器调试必须的Docker Desktop等)。
自动生成的Dockerfile内容如下:

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
WORKDIR /app
EXPOSE 5000

FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
WORKDIR /src
COPY ["AspNetCoreDemo/AspNetCoreDemo.Web.csproj", "AspNetCoreDemo/"]
COPY ["AspNetCoreDemo.Repository/AspNetCoreDemo.Repository.csproj", "AspNetCoreDemo.Repository/"]
COPY ["AspNetCoreDemo.Model/AspNetCoreDemo.Model.csproj", "AspNetCoreDemo.Model/"]
RUN dotnet restore "AspNetCoreDemo/AspNetCoreDemo.Web.csproj"
COPY . .
WORKDIR "/src/AspNetCoreDemo"
RUN dotnet build "AspNetCoreDemo.Web.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "AspNetCoreDemo.Web.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "AspNetCoreDemo.Web.dll"]

它包含了编译和发布项目所有步骤,但是实际中我们一般不会把所有项目源文件都上传到服务器再去编译发布,所以我精简一下Dockerfile文件内容,简单修改如下:

FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS runtime #从官方仓储拉取asp.net runtime镜像,相当于选择我们依赖的环境
WORKDIR /app #设置容器中工作目录
COPY . /app #复制当前目录的文件到容器中工作目录
EXPOSE 5000 #容器对外暴露端口号,需要与程序中设置监听端口号一致
ENTRYPOINT ["dotnet", "AspNetCoreDemo.Web.dll"] #设置启动命令

最后,VS中在Dockerfile文件上点击右键,修改文件属性,修改复制到输出目录为“较新则复制”,这样我们发布程序的时候Dockerfile就会被复制到程序发布目录了。
image

2) 发布程序

这里可以参考上篇博文Linux上部署Asp.net Core应用程序中一样,将文件发布到本地文件系统,只不过这里多了一个Dockerfile了。然后将我们发布的程序上传到服务器指定目录中。
其实如果不考虑开发过程中容器运行和调试等,上一步中不添加Docker支持也是可以的,我们在发布后手动创建Dockerfile就可以了

3) build镜像

在shell窗口中转到程序根目录,执行docker build命令:

docker build -t aspnetdemo:latest .
  • -t Name and optionally a tag in the 'name:tag' format
  • aspnetdemo:latest :后面的latest就是tag,一般用版本号,可选,如果不指定tag默认就是latest
  • name:tag必须全部小写
  • 最后的” .”别忘了,构建路径,.指当前目录

第一次构建可能会慢点,因为要从远程拉取aspnet的镜像,构建完成后,可以执行docker images查看已有的镜像。
image

2. docker运行程序

docker run -d -p 81:5000 --rm -v /etc/localtime:/etc/localtime:ro --name aspnetcoredemo aspnetdemo:latest
  • -d: 后台运行容器,并返回容器ID;
  • -p 指定端口映射,格式为:主机(宿主)端口:容器端口, 比如上面就是宿主机81端口映射到容器的5000端口
  • --rm 容器停止后自动删除,尤其开发测试阶段很实用
  • -v /etc/localtime:/etc/localtime:ro 同步宿主机时间到容器中
  • --name aspnetcoredemo 指定容器名称
  • aspnetdemo:latest 创建容器用的镜像名称及tag

执行命令docker ps查看运行的容器
image

Docker容器进程守护

在docker run时通过--restart 设置守护机制,有四种模式:

  • no:不自动重新启动容器。(默认)
  • on-failure :由于一个错误退出,它表现为退出状态不等于0,自动启动容器
  • unless-stopped :除非被显式停止 stop、kill 否则docker服务停止或自动重启,自动启动容器
  • always:如果容器停止,总是重新启动容器。如果手动kill容器 无法自动重启

生产环境我们一般只用--restart always,以便在服务器或Docker服务重启等情况下,容器能自动重启。注意这时我们就不能在使用--rm参数了,它们是彼此冲突的。

这时我们的docker run命令就变成如下这样了:

docker run -d -p 81:5000 --restart always -v /etc/localtime:/etc/localtime:ro --name aspnetcoredemo aspnetdemo:latest

如果run时没有添加restart 可以通过update命令追加

docker update --restart=always aspnetcoredemo #[Container ID或Container Name]

关于自动启动容器的更多说明参考:https://docs.docker.com/config/containers/start-containers-automatically/

来看看效果
image
到这一步为止,已经完成了一个最基本的Docker容器应用部署,此时已经可以通过访问宿主机81端口查看应用了,并且还可以结合上一篇中配置Ngin反向代理的内容(宿主机已安装Nginx),来配置Nginx反向代理后请求网站了。此时Nginx的反向代理配置内容类似如下:

server {
        listen       80 default_server;
        #...
        location / {
            proxy_pass         http://localhost:81;
            proxy_http_version 1.1;
            proxy_set_header   Upgrade $http_upgrade;
            proxy_set_header   Connection keep-alive;
            proxy_set_header   Host $host;
            proxy_cache_bypass $http_upgrade;
            proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header   X-Forwarded-Proto $scheme;
        }
        #...
    }

Docker中运行Nginx

前面,已经初步完成了应用在Docker上的部署,只是仍然是基于宿主机安装的Nginx做反向代理来访问的。如果机器上都不想安装和维护nginx的话,还可以直接在Docker中来运行Nginx。

拉取镜像

docker pull nginx:latest

这了指定拉取最新版nginx,如果有特定版本需求可以在 : 后面指定版本号,比如nginx:1.18.0
关于nginx镜像更多说明参看官方说明:https://hub.docker.com/_/nginx

创建并运行nginx容器

首先简单运行一个nginx容器

docker run --name nginx-docker -p 80:80 -d nginx:latest
  • --name nginx-docker 指定容器名称为nginx-docker
  • -p 80:80 宿主机80端口映射nginx容器80端口
  • -d nginx容器保持后台运行
  • nginx:latest 指定创建容器使用的镜像名称和版本

使用docker ps可以查看刚刚创建的nginx容器
image

在宿主机上浏览器访问localhost应该可以看到Nginx的欢迎页面
image

Nginx配置文件修改

到此运行在Docker上的nginx服务就基本搭建好了,但是还有一个问题,配置文件如何修改?两个方式:

  • 进入运行的nginx容器修改
  • 将nginx容器内部配置文件挂载到宿主机
进入运行的nginx容器修改

执行docker exec -it 容器ID /bin/bash进入容器内容,打开nginx配置文件所在目录/etc/nginx,然后用vi或其他文本编辑命令打开配置文件进行修改。这与在宿主机修改nginx配置文件其实是差不多了,不再赘述。
此种方式需要每次都进入容器修改,所以稍微有点繁琐,适合一次配置好好就不需要或极少需要修改配置文件的情况。

将nginx容器内部配置文件挂载到宿主机

将nginx容器内部配置文件挂载到主机,之后就可以在宿主机我们指定挂载的对应目录修改即可。
接下来,将nginx配置文件、默认文档目录和访问日志挂载到宿主机。

1, 在主机var目录创建挂载目录,cd var,mkdir -p ./nginx/{conf,html,logs}
2, 转到创建的/var/nginx目录,将容器内的配置文件分别拷贝到主机配置文件的挂载目录,分别执行

docker cp 6785c32f1200:/etc/nginx/nginx.conf ./
docker cp 6785c32f1200:/etc/nginx/conf.d/default.conf ./conf/
  • 6785c32f1200 为nginx容器ID

3, 停止和删除当前nginx容器docker stop 6785c32f1200docker rm 6785c32f1200
4, 重新创建和运行nginx容器

docker run -v /etc/localtime:/etc/localtime:ro -v /var/nginx/nginx.conf:/etc/nginx/nginx.conf -v /var/nginx/logs:/var/log/nginx -v /var/nginx/html:/usr/share/nginx/html -v /var/nginx/conf:/etc/nginx/conf.d --name nginx-docker -p 80:80 -d --privileged=true nginx:latest
  • -v 挂载目录,格式 -v: 表示将主机目录与容器目录之间进行共享,
  • --privileged=true 容器内部对挂载的目录拥有读写等特权

我们在挂载目录的操作,都实际会映射到容器内部,写配置文件的时候一定要注意路径问题!

因为默认文档目录挂载出来了,所以在/var/nginx/html目录下创建一个index.html,写下我们的默认我文档内容“Hello World”,然后再访问localhost,刚刚创建的页面内容应该被返回了。
image
好了,Docker里面的Nginx就安装完成并跑起来了。

Docker中Nginx代理多个应用(共享80端口)

接下来配置Docker里运行的nginx反向代理到我们运行在docker容器里的应用。
首先把两个测试应用跑起来

docker run -d -p 81:5000 --rm -v /etc/localtime:/etc/localtime:ro --name aspnetcoredemo aspnetdemo:latest
docker run -d -p 82:5000 --rm -v /etc/localtime:/etc/localtime:ro --name aspnetcore aspnetcore:latest

image
现在我们Docker里一共运行了nginx和两个测试应用。

修改nginx配置文件

在之前宿主机上nginx反向代理的配置中我们是这样写的:proxy_pass http://localhost:81;
但是现在nginx已经运行在docker容器里了,如果继续以这样的配置去代理会发现根本请求不了。因为现在这个localhost已经不是宿主机本机了,而是执行容器内部了,而每个运行的容器其实又相当于一个微型的虚拟机。

现在当我们对服务器发起一个80端口的访问流程大致是这样的,客户端发起请求到达服务器后,会被映射到运行的nginx容器内去,然后这时容器内运行的ngin会根据反向代理配置再去跳转到最终指向的应用容器。
image

所以,上面这里显然在nginx容器里再反向代理到localhost:81并不是我们期待的aspnetcoredemo这个应用。运行aspnetcoredemo这个容器时,我是将服务器的81端口映射到此容器内部的5000端口了,所以此时我们的反向代理配置其实有两个地址选择:一是服务器IP:81,二是aspnetcoredemo容器内部IP:5000

容器内容IP可以通过执行命令docker inspect [容器]去查看
image

但是使用容器内部IP地址有个不好的地方,每次容器重启运行IP地址可能发生变化。

因此,我这里使用服务器ip地址配置反向代理做演示。Docker中nginx的配置文件最终内容大致如下:

upstream aspnetcoredemo{
    server [宿主机IP]:81;
}

upstream aspnetcore{
    server [宿主机IP]:82;
}

server {
    listen       80;
    server_name aspnetcoredemo.button4creative.com;

    location / {
      proxy_pass         http://aspnetcoredemo;
        proxy_http_version 1.1;
        proxy_set_header   Upgrade $http_upgrade;
        proxy_set_header   Connection keep-alive;
        proxy_set_header   Host $host;
        proxy_cache_bypass $http_upgrade;
        proxy_set_header   X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header   X-Forwarded-Proto $scheme;
    }

    error_page 404 /404.html;
        location = /40x.html {
    }

    error_page 500 502 503 504 /50x.html;
        location = /50x.html {
    }
}

server {
    listen       80;
    server_name  aspnetcore.button4creative.com;

    location / {
        proxy_pass         http://aspnetcore;
        #root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

#server default
server {
    listen       80;
    listen  [::]:80;
    server_name  localhost 127.0.0.1 default_server;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}

看下最终运行结果,配置的两个反向代理都正确转到指定容器运行的应用了。
image
image

使用 Compose 命令构建和运行应用

未完待续...


附:
关于错误日志中有“an upstream response is buffered to a temporary file /var/cache/nginx/proxy_temp/xxxxxx while reading upstream”的问题
是由于nginx某一块的buffer设置的太小,而response(包含response header和response body)导致response结果不得不临时写到文件中,修复ngin配置文件(http\server\location),添加以下内容增加buffer size:

  client_header_buffer_size 128k;
  client_body_buffer_size 1m;
  proxy_buffer_size 32k;
  proxy_buffers 64 32k;
  proxy_busy_buffers_size 1m;
  proxy_temp_file_write_size 512k;

关于这几个配置的官方文档链接如下:
http://nginx.org/en/docs/http/ngx_http_core_module.html#directives
http://nginx.org/en/docs/http/ngx_http_proxy_module.html#directives


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM