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