基於Docker Compose的.NET Core微服務持續發佈
- 2020 年 6 月 27 日
- 筆記
- .NET Core, 【005】容器技術 & DevOps, 【022】.NET Core, 【023】微服務, docker, Docker Compose, 持續集成
是不是現在每個團隊都需要上K8s才夠潮流,不用K8s是不是就落伍了。今天,我就通過這篇文章來回答一下。
一、先給出我的看法和建議
我想說的是,對於很多的微小團隊來說,可能都不是一定要上K8s,畢竟上K8s也是需要成本和人力的。對像我司一樣的傳統企業做數字化轉型的信息團隊來講,人數不多,沒有專門的Ops人員,領導又想要儘快迭代支持公司業務發展,而且關鍵還要節省成本(內心想法是:WTF)。
在此之下,信息團隊需要綜合引入先進技術帶來的價值以及需要承擔的成本和風險。任何架構的產生,都會解決一定的問題,但是同樣也會引入新的複雜度,正如微服務架構風格,看着香實際吃着才知道需要承受很多的「苦」(比如數據一致性又比如服務的治理等等)。
因此,結合考慮下來,我的建議是開發測試環境使用Docker Compose進行容器編排即可,而UAT或生產環境則建議使用雲廠商的K8s服務(比如阿里雲ACK服務)而不選擇自建K8s集群。
那麼,今天就跟大家介紹一下如何使用Docker Compose這個輕量級的編排工具實現.NET Core微服務的持續發佈。
二、Docker Compose
Docker主要用來運行單容器應用,而Docker Compose則是一個用來定義和應用多容器應用的工具,如下圖所示:
使用Docker Compose,我們可以將多容器的定義和部署方式定義在一個yml文件中,這種方式特別是微服務這種架構風格,可以將多個微服務的定義及部署都規範在一個yml文件中,然後一鍵部署、啟動或銷毀整個微服務應用。所有的一切操作,只需要下面的一句話:
$docker-compose up
Compose 的安裝請參考://docs.docker.com/compose/install/#install-compose,這裡就不再贅述,它不是本文重點。
安裝後驗證:
$docker-compose --version
docker-compose version 1.25.1, build a82fef07
三、一個簡單的發佈流程示例
本文演示示例的流程大概會如下圖所示:
閱讀過我之前的一篇文章《基於Jenkins Pipeline的ASP.NET Core持續集成實踐》的童鞋應該對這個流程比較熟悉了。這裡,我仍然延續這個流程,作為一個平滑過渡。首先,我們在Jenkins上觸發容器的發佈流水線任務,此任務會從Git服務器上拉取指定分支(一般都是測試分支)的最新代碼。
其次,在CI服務器上使用.NET Core SDK執行Build編譯和發佈Release文件,基於發佈後的Release文件進行鏡像的打包(確保你的項目裏面都有Dockerfile且設置為「始終複製」)。然後,基於打包後的鏡像,將其推送到企業的私有Registry服務器上(即本地鏡像倉庫,可以基於Harbor搭建一個,也可以直接用Docker Registry搭建一個,不建議使用docker hub的公有庫,如何搭建私有鏡像倉庫可以參考我的這一篇文章:《Docker常用流行鏡像倉庫的搭建》)。
最後,在測試服務器或要運行容器的服務器上執行docker compose up完成容器的版本更新。當然,也可以直接在docker-compose.yml文件內設置編譯路徑完成編譯和發佈的操作(Dockerfile裏面定義進行Build和Publish)。這裡目的在於讓實例更簡單,且能讓初學者更容易理解,於是我就分開了。
四、.NET Core微服務發佈示例
微服務示例準備
假設我們有一堆使用ASP.NET Core開發的微服務,這些微服務主要是為了實現諸如API網關、Identity鑒權、Notification通知、Job中心等基礎設施服務,因此我們將他們整合在一起進行持續集成和部署。
這裡為了讓示例儘可能簡單,每個微服務的Dockerfile只有以下幾句(這裡以一個通知API服務為例):
FROM reg.xdp.xi-life.cn/xdp-service-runtime:2.2 WORKDIR /app COPY . /app EXPOSE 80 ENTRYPOINT ["dotnet", "XDP.Core.Notification.API.dll"]
其中這裡的容器鏡像來自於私有鏡像倉庫,是一個封裝過的用於ASP.NET Core Runtime的容器鏡像。當然,上面說過,也可以在Dockerfile裏面進行服務的編譯和發佈。
流水線任務腳本
同樣,為了在Jenkins上快速進行微服務的鏡像構建和推送以及部署,我們也需要編寫一個流水線構建任務。
下面是這個示例流水線任務的腳本:
pipeline{ agent any environment { API_CODE_BRANCH="*/master" SSH_SERVER_NAME_REGISTRY="XDP-REGISTRY-Server" SSH_SERVER_NAME_DEV="XDP-DEV-Server" SSH_SERVER_NAME_AT="XDP-AT-Server" SSH_SERVER_NAME_SIT="XDP-SIT-Server" } stages { stage('XDP Core APIs Checkout & Build') { steps{ checkout([$class: 'GitSCM', branches: [[name: env.API_CODE_BRANCH]], doGenerateSubmoduleConfigurations: false, extensions: [], submoduleCfg: [], userRemoteConfigs: [[credentialsId: '35b9890b-2338-45e2-8a1a-78e9bbe1d3e2', url: '//192.168.18.150:3000/XDP.Core/XDP.Core.git']]]) echo 'Core APIs Dev Branch Checkout Done' bat ''' dotnet build XDP.Core-InfraServices.sln dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Components\\XDP.Core.ApiGateway\\XDP.Core.ApiGateway.csproj" -o "%WORKSPACE%\\XDP.Core.ApiGateway.API\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Components\\XDP.Core.ApiGateway.Internal\\XDP.Core.ApiGateway.Internal.csproj" -o "%WORKSPACE%\\XDP.Core.ApiGateway.Internal.API\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Services\\XDP.Core.Authorization.API\\XDP.Core.Authorization.API.csproj" -o "%WORKSPACE%\\XDP.Core.Authorization.API\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Services\\XDP.Core.Authorization.Job\\XDP.Core.Authorization.Job.csproj" -o "%WORKSPACE%\\XDP.Core.Authorization.Job\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Services\\XDP.Core.Identity.API\\XDP.Core.Identity.API.csproj" -o "%WORKSPACE%\\XDP.Core.Identity.API\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Services\\XDP.Core.Notification.API\\XDP.Core.Notification.API.csproj" -o "%WORKSPACE%\\XDP.Core.Notification.API\\publish" --framework netcoreapp2.2 dotnet publish "%WORKSPACE%\\src\\services\\XDP.Core\\Services\\XDP.Core.JobCenter\\XDP.Core.JobCenter.csproj" -o "%WORKSPACE%\\XDP.Core.JobCenter.API\\publish" --framework netcoreapp2.2 ''' echo 'Core APIs Build & Publish Done' } } stage('XDP API Gateway Docker Image') { steps{ bat ''' docker rmi reg.xdp.xi-life.cn/core-apigateway-portal:latest; cd XDP.Core.ApiGateway.API/publish; docker build -t reg.xdp.xi-life.cn/core-apigateway-portal:latest .; docker push reg.xdp.xi-life.cn/core-apigateway-portal:latest; ''' echo 'XDP Portal API Gateway Deploy Done' bat ''' docker rmi reg.xdp.xi-life.cn/core-apigateway-internal:latest; cd XDP.Core.ApiGateway.Internal.API/publish; docker build -t reg.xdp.xi-life.cn/core-apigateway-internal:latest .; docker push reg.xdp.xi-life.cn/core-apigateway-internal:latest; ''' echo 'XDP Internal API Gateway Deploy Done' } } stage('Core Identity API Docker Image') { steps{ ...... } } stage('Core Authorization Job Docker Image') { steps{ ...... } } stage('Core Notification API Docker Image') { steps{ ...... } } stage('Core JobCenter API Docker Image') { steps{ ...... } } stage('Deploy to Local SIT Server') { steps{ sshPublisher(publishers: [sshPublisherDesc(configName: env.SSH_SERVER_NAME_SIT, transfers: [sshTransfer(cleanRemote: false, excludes: '', execCommand: ''' cd compose/xdp; IMAGE_TAG=latest docker-compose down; docker rmi $(docker images -q) IMAGE_TAG=latest docker-compose up -d; ''', execTimeout: 120000, flatten: false, makeEmptyDirs: false, noDefaultExcludes: false, patternSeparator: '[, ]+', remoteDirectory: 'compose/xdp/', remoteDirectorySDF: false, removePrefix: '', sourceFiles: '', excludeFiles: '')], usePromotionTimestamp: false, useWorkspaceInPromotion: false, verbose: false)]) echo 'Deploy to XDP SIT Server Done' } } } }
這個腳本我省去了一些重複的內容,只需要了解它的職責即可。
需要注意的地方有幾點:
(1)在進行dotnet build的時候,要明確SDK使用哪個版本,比如因為這裡的示例代碼是基於.NET Core 2.2開發的因此這裡使用的是2.2。如果你使用的是2.1,則標註2.1,如果是3.1,則標註3.1。
(2)在進行docker build的時候,要明確鏡像使用哪個Tag,這裡因為是本地開發測試環境,所以直接簡單暴力的直接使用了latest這個Tag。
(3)在進行sshPublish的時候,要提前將docker-compose.yml配置拷貝到對應的指定目錄下。當然,這一塊建議也將其納入git倉庫進行統一管理和統一發佈到不同的環境的指定目錄下。
(4)如果你的Jenkins是裝在Windows Server上,要記住只有Windows Server 2016及以上版本才支持Docker,否則無法直接進行docker的命令行操作。如果低於2016,Windows 10專業版也可以,不過不建議。
擴展點:
是否可以一套docker-compose方案標準化部署到多個測試環境?是可以的,我們可以在Jenkins構建任務中配置Parameters,這樣就可以一次性部署到多個環境。例如,下面的示例中我設置了一個每次發佈可以選擇到底要發佈到哪個環境,這裡是單選,你也可以設置為多選。
效果如下:
docker-compose.yml
終於來到了compose的重點內容:docker-compose.yml
這裡我給出上面這個示例的yml示例內容(同樣,也省略了重複性的內容):
version: '2' services: core_apigateway_portal: image: reg.xdp.xi-life.cn/core-apigateway-portal:${IMAGE_TAG} container_name: xdp_core_apigateway_portal restart: always privileged: true mem_limit: 1024m memswap_limit: 1024m env_file: - ../docker-variables.env ports: - 5000:80 volumes: - /etc/localtime:/etc/localtime core_apigateway_internal: image: reg.xdp.xi-life.cn/core-apigateway-internal:${IMAGE_TAG} container_name: xdp_core_apigateway_internal restart: always privileged: true mem_limit: 1024m memswap_limit: 1024m env_file: - ../docker-variables.env ports: - 5100:80 volumes: - /etc/localtime:/etc/localtime core_identity_api: image: reg.xdp.xi-life.cn/core-identity-api:${IMAGE_TAG} container_name: xdp_core_identity_api restart: always privileged: true mem_limit: 512m memswap_limit: 512m env_file: - ../docker-variables.env ports: - 6010:80 volumes: - /etc/localtime:/etc/localtime core_authorization_api: ...... core_authorization_job: ...... core_notification_api: ...... core_jobcenter_api: ...... bff_xams_api: ......
備註:這裡使用的是version:2的語法,因為3開始不支持內存限制mem_limit等屬性設置。當然,你可以使用3的語法,去掉mem_limit和memswap_limit屬性即可。
這裡的env環境變量配置是定義在另外一個單獨的env文件裏面的,建議每個環境建立一個單獨的env文件供docker-compose.yml文件使用,比如下面是一個AT(自動化測試)環境的env文件內容示例:
# define xdp containers env ASPNETCORE_ENVIRONMENT=at ALIYUN_ACCESS_KEY=sxxdfdskjfkdsjkds ALIYUN_ACCESS_SECRET=xdfsfjiwerowuoi JWT_TOKEN=sdfsjkfjsdkfjlerwewe IDENTITY_DB_CONNSTR=Server=192.168.16.150;Port=3306;Database=identity_at;Uid=xdpat;Pwd=xdpdba;Charset=utf8mb4 APIGATEWAY_DB_CONNSTR=Server=192.168.16.150;Port=3306;Database=services_at;Uid=xdpat;Pwd=xdpdba;Charset=utf8mb4 ...... API_VERSION=AT-v1.0.0
這裡,最主要的環境變量就是ASPNETCORE_ENVIRONMENT,你需要指定這些要編排的微服務容器使用哪個環境的appSettings。同樣,這裡也引申出另一個問題,那就是配置的集中管理,可能你會說出類似Apollo,Spring Cloud Config,K8s Configmap之類的解決方案。這裡不是本文的重點,也就跳過。
快速實操體驗
現在我們來通過在Jenkins中觸發構建任務,可以看到如下圖所示的流水線任務狀態示意:
這樣,一個簡單的快速發佈流水線就完成了,在單機多容器編排部署方面,Docker Compose是個不錯的選擇。
五、一些擴展
Consul服務發現容器編排
相比很多童鞋也都在使用Consul作為服務發現組件,我們也可以將Consul納入到Compose中來統一編排。例如,我們可以這樣來將其配置到docker-compose.yml中:
services: consul_agent_server: image: reg.xdp.xi-life.cn/xdp-consul-runtime:${IMAGE_TAG} container_name: xdp_consul_agent_server restart: always privileged: true mem_limit: 1024m memswap_limit: 1024m env_file: - ../docker-variables.env ports: - 8500:8500 command: agent -server -bootstrap-expect=1 -ui -node=xdp_local_server -client='0.0.0.0' -data-dir /consul/data -config-dir /consul/config -datacenter=xdp_local_dc volumes: - /etc/localtime:/etc/localtime - /docker/consul/data:/consul/data - /docker/consul/conf:/consul/config
這裡只使用到了一個Consul Server Agent,你可以配置一個3個Server節點的Consul Server集群,請自行查閱相關資料。此外,基於Compose我們也可以為API網關設置links從而實現服務發現的效果,當然前提是你的服務數量不多的前提下。這種方式是通過網絡層面幫你做了一層解析,從而實現多個容器之間的互連。這裡也推薦一下俺們成都地區的小馬甲老哥的一篇《docker-compose真香》的文章,他講解了docker的網橋模式。
基於Compose的編譯發佈一體化
我們可以看到在很多開源項目中都是將編譯發佈一體化的,因此我們可以看到在這些項目的Dockerfile中是這樣寫的:
FROM mcr.microsoft.com/dotnet/core/sdk:3.1 AS build WORKDIR /app COPY ./*.sln ./NuGet.Config ./ COPY ./build/*.props ./build/ # Copy the main source project files COPY src/*/*.csproj ./ RUN for file in $(ls *.csproj); do mkdir -p src/${file%.*}/ && mv $file src/${file%.*}/; done RUN dotnet restore # Copy everything else and build app COPY . . RUN dotnet build -c Release # api-publish FROM build AS api-publish WORKDIR /app/src/Exceptionless.Web RUN dotnet publish -c Release -o out # api FROM mcr.microsoft.com/dotnet/core/aspnet:3.1 AS api WORKDIR /app COPY --from=api-publish /app/src/Exceptionless.Web/out ./ ENTRYPOINT [ "dotnet", "Exceptionless.Web.dll" ] ......
在Dockerfile中我們看到的是拉取.NET Core SDK來進行Restore、Build和Publish,進一步地提高了標準化的遷移性,也儘可能發揮Docker的集裝箱作用。
這時你可以在docker-compose.yml中定義Dockerfile告訴compose先幫我進行Build鏡像(這裡的build配置下就需要指定Dockerfile的位置):
services: api: build: context: . image: exceptionless/api:latest restart: always ......
六、小結
Docker是容器技術的核心、基礎,Docker Compose是一個基於Docker的單主機容器編排工具,功能並不像Docker Swarm和Kubernetes是基於Docker的跨主機的容器管理平台那麼豐富。
我想你看到這裡也應該有了自己的答案,結合我在最開頭給的建議,如果你處在一個小團隊中,綜合人員水平、技能儲備、運維成本 及 真實業務量要求,可以在開發測試環境(一般都是單主機環境的話)中使用Docker Compose進行初步編排。而在生產環境,即使是小團隊也建議上雲主機,利用雲的彈性為未來的業務發展做基礎,然後可以考慮使用雲上的K8s服務來進行生產級的容器編排。