1. 程式人生 > >.NetCore&Linux&Docker&Portainer踩坑歷險記

.NetCore&Linux&Docker&Portainer踩坑歷險記

功能 values idg 完全 entos service spec fff 循環

最近有一個雲服務器和數據庫的遷移任務,踩坑爬坑無數次,覺得必須要記錄一下。大家瓜子花生準備好,聽我慢慢講故事#手動笑哭#。

故事背景

公司是做電商業務的,在天貓有幾家旗艦店數據量也很大。阿裏有一個稱為聚石塔的平臺,專門給這些ISV提供各種雲資源,強制綁定了一些業務,原本我們在聚石塔中有一臺ECS和一臺RDS部署在華東杭州節點,本月初突然收到阿裏的郵件說是要整體遷移到張北節點,華東節點將會在9月底全部停止服務,並附帶發了一份遷移文檔,要我們盡快遷移。好在我們用到的資源不多,最初覺得遷移過程並不會太復雜,實際還是太天真了。像我這樣只有一臺服務器和一臺數據庫的用戶遷移過程都謂之艱辛,對於那些有幾十甚至上百實例的ISV,那真是欲哭無淚了。每天看著遷移群裏大家的各種吐槽、抱怨、焦急、無可奈何,還有那幾位一整天都在被艾特的阿裏技術支持,說起來都是淚。

於是接下來制定好遷移計劃,發郵件購買要用到的資源,等過了兩天東西到位,就擼起袖子開幹了。

忘了說了,這些東西原先是由另外一位同事負責,然而年後他就開溜了,上級指示我扛過大旗(guo)。


開胃菜

我們的RDS是SQL Server 08 R2版本,阿裏在遷移通知中專門提到了這個產品,而且用到了重要提示字樣,大意是說微軟已經對這個版本的數據庫停止了安全更新,所以張北節點已經不再售賣這個版本的實例,要先在當前節點完成版本升級後再遷移。看了下他的遷移手冊,覺得異常復雜和危險重重,於是果斷放棄官方方案,決定在張北節點買好2016版本數據庫,直接切換數據推送,後來找阿裏的技術支持咨詢了這個方案,也表示可以執行。當然了,我能這樣做是有一個前提的,我們的這個庫是只讀庫,用來接收阿裏的數據推送然後給業務系統查詢,可以理解為只是一個過渡不存儲實際的業務數據,對安全性要求不高,就算丟失也能通過淘寶開放平臺的API去查詢。如果是業務庫,那就只能老老實實的按官方文檔摸著石頭過河了,看群裏的反饋,這道開胃菜不好吃,我也算是幸運跳過了第一個坑。

初進坑

RDS處理完畢,那就著手開始折騰服務器,這是一臺Linux的機器,系統是Centos7,主要跑了3個服務:上文提到的RDS數據查詢API(一個dotnetcore2.1的程序)、Rabbitmq實例和它的管理工具、Portainer,由Docker統一管理,而Docker又由Portainer來管理。按照官方文檔,先在原服務器上創建鏡像,經過漫長的等待(大概40分鐘吧,有的人反映等了大半天最後生成失敗的,心態崩…),然後把鏡像復制到張北節點,然後通過鏡像生成實例,按理說新機器和原機器是完全一樣的,各項服務都應該運行正常,並且專門找技術支持確認了,可實際真的不是這樣。

聚石塔的服務器只開放30001-30005這幾個端口,於是嘗試訪問一下Portainer所在的30003端口。瀏覽器輸入地址再回車,等了幾十秒後顯示超時無法訪問,一臉懵逼。Ping了一下服務器IP,沒毛病,又登錄服務器查看docker和container的運行狀態以及端口映射,都沒問題,又查看端口監聽和防火墻,還是正常,二臉懵逼。

技術分享圖片

查一下container的日誌,提示運行正常,三臉懵逼。

技術分享圖片

我的招已經用完了,沒辦法轉向群裏咨詢技術支持,回復說這幾個端口要走工單申請開通,WTF……老實寫工單提交再到群裏艾特幫忙快點處理,又陷入漫長的等待中,當時大概2點鐘的樣子。下午5點多工單狀態更新了說正在轉給技術處理請耐心等待,然後,就沒有然後了接著等,到7點還是沒消息決定先下班。

第二天上班發現還是沒有消息,又去群裏艾特技術支持,幾分鐘後回復叫我去給ECS綁定一個安全組,照做後再次訪問30003端口依然不行,長嘆一口氣。又嘗試訪問了一下webapi所在的30001端口,神奇般的成功了#手動黑人問號臉#。咨詢了公司運維,教我幾個命令簡單排查了下,後來因為太忙沒回復我了,後來又一頓百度谷歌無果,陷入僵局。心理暗自把這個鍋丟給了阿裏,覺得是他們哪裏配置有問題。

技術分享圖片

事情不能就這樣僵著啊,Portainer起不來程序不能更新,於是打算直接在宿主機上跑一下修改後的dotnetcore程序看數據庫訪問是否正常。按照微軟文檔安裝對應版本的SDK:

技術分享圖片

安裝好後把發布文件上傳到服務器,然後用dotnet命令啟動了程序,一切正常。訪問我的測試入口:

Curl http://locahost:5000/api/values/testdb/123

看到返回了數據庫的測試數據,信心重拾。回過頭重新折騰docker,發現docker死活起不來了,囧:

技術分享圖片

技術分享圖片

  技術分享圖片

拿著錯誤信息又是一頓百度谷歌,不斷的照網上改配置重啟系統,幾個小時過去依然不行,決定卸載docker重裝。於是依次執行:

yum remove docker*

reboot

yum install docker

docker version

systemctl start docker

然而啟動的時候問題依舊,又是長嘆一口氣。仔細回想了一下,只有yum update對系統做了大的改動,難道是這個問題麽?不知不覺又到了晚上7點多,腦子懵的很決定先下班第二天接著搞。

真所謂一波未平一波又起。


再進坑

早上到公司和微信群的小夥伴吐糟著遭遇,大家勸我重裝系統,我一邊發著捂臉笑哭的表情,一邊默默地上聚石塔後臺點了磁盤初始化,docker啟動不了的問題就算翻篇了,一切從頭再來。

依然還是端口的問題,實在沒轍了只有給阿裏提工單問為什麽端口不通,阿裏工程師先後叫我排查了iptables、端口監聽情況、清除iptables等等還是不行,最後要了我的服務器賬號上去排查,在工單中看到阿裏的工程師晚上11點多還在幫我排查問題,也真不容易。

技術分享圖片

終於,在阿裏後面的回復中事情迎來了轉機,給了我非常大的提示:

技術分享圖片

從中我捕捉到了2個重要信息,一個是容器的IP,一個是路由解析問題。我馬上百度如何查容器的IP地址,然後試著去ping容器的IP,發現30001端口綁定的容器(172.22.0網段)正常,30003端口綁定的容器(192.168.0網段)無法訪問,那麽這就說明是宿主機和容器網絡不通導致的問題。又查看了系統的路由表:

技術分享圖片

這個路由表有個奇怪的現象,就是192.168.0這個網段指向了2個不同的網卡,分別是eth0和docker0。我知道,eth0是宿主機默認的網關,docker0是docker啟動時自動創建的虛擬網關,但是還不清楚這樣的配置會有什麽影響,於是百度了一下Linux路由的詳細介紹,得知相同的配置會有優先級的問題,又嘗試著刪除eth0的配置:

route del -net 192.168.0.0 netmask 255.255.255.0

再次用公網訪問30003端口,成功了!!!終於看到了熟悉的頁面:

技術分享圖片

沒那麽簡單

以為事情就此告一段落後面都是平坦大道,想不到問題又來了。通過docker run我新鏡像後發現容器總是自動退出,於是尋找各種讓容器持續運行的辦法,一陣折騰沒有效果,去微信群問小夥伴,問我是不是程序拋異常了,我頓時一種柳暗花明的感覺,立馬查看容器日誌:

docker logs topapi

果然是報錯了:

技術分享圖片

很顯然,是說我的framework版本不對,但是我的dockerfile中確實引入的2.1版本運行時:

FROM microsoft/dotnet:2.1-runtime

COPY . /app

WORKDIR /app

EXPOSE 5000 80

ENTRYPOINT ["dotnet", "DRP.API.dll"]

退一萬步說,宿主機我也已經安裝過SDK,而且直接在宿主機上運行都是可以的,為什麽通過docker來運行就掛了,百思不得解。只能按照提示中的信息排查是不是少裝了什麽組件,一陣yum install下來還是失敗:

技術分享圖片

去廣州微軟.net俱樂部的微信群請教別人,兩位大佬給我分析解答了一下,一位說是我的dockerfile在copy文件時漏了一些引用文件,要我重新修改dockerfile,不過經過多次調整測試依然無效,不得不采用第二位的辦法,就是把運行時改為2.2版本:

技術分享圖片

修改dockerfile為如下內容:

# 添加基礎鏡像
FROM microsoft/dotnet:2.2-aspnetcore-runtime

#容器中系統的工作空間
WORKDIR /app

#拷貝當前文件夾下的文件到容器中系統的工作空間
COPY . /app

#設置Docker容器對外暴露的端口
EXPOSE 5000 80

#運行應用程序
ENTRYPOINT ["dotnet", "DRP.API.dll"]

重新打包鏡像,然後run起來,這次一切都是那麽的自然,docker ps查看容器已經狀態是up了。欣喜若狂,以為即將看到勝利的曙光,接著用瀏覽器打開我的測試入口:

http://xxx.xxx.xxx.xxx:30001/api/values/testdb/123

尷尬的報了500,心中萬馬奔騰….

這次學機靈了,第一時間docker logs,發現是數據庫報錯了:

fail: Microsoft.AspNetCore.Server.Kestrel[13]
      => ConnectionId:0HLM4DDINAGJC => RequestId:0HLM4DDINAGJC:00000001 RequestPath:/api/values/testdb/123
      Connection id "0HLM4DDINAGJC", Request id "0HLM4DDINAGJC:00000001": An unhandled exception was thrown by the application.
SqlSugar.UtilExceptions: English Message : Connection open error . A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server)
Chinese Message :  連接數據庫過程中發生錯誤,檢查服務器是否正常連接字符串是否正確,實在找不到原因請先Google錯誤信息:A network-related or instance-specific error occurred while establishing a connection to SQL Server. The server was not found or was not accessible. Verify that the instance name is correct and that SQL Server is configured to allow remote connections. (provider: TCP Provider, error: 40 - Could not open a connection to SQL Server).
   at SqlSugar.AdoProvider.GetDataReader(String sql, SugarParameter[] parameters)
   at SqlSugar.QueryableProvider`1.GetData[TResult](KeyValuePair`2 sqlObj)
   at SqlSugar.QueryableProvider`1._ToList[TResult]()

很明顯是數據庫連接不上,檢查連接字符串,沒毛病,再次進入僵局。

正在苦惱時,突然想起前面刪掉的那條路由,嘗試重啟網絡恢復路由:

service network restart

再次訪問測試地址,確實成功了。可問題又進入了死循環,容器內的應用無法訪問。

終見天日

經過以上的種種分析後,最終把問題定在了路由這兒。既然是因為同一網段有2個網關,那麽我修改一下docker的默認網段不就可以了嗎?再次面向百度編程,得到兩種方案:

第一種方案,創建新的的網關和路由,然後分配給docker:

service docker stop

ip addr add 192.168.1.1/24 dev bridge0

ip link set dev bridge0 up

vim /etc/docker/daemon.json

加上"bridge": "bridge0"節點並保存退出,再重啟docker:

service docker start

第二種方案,直接修改docker0的默認網段:

service docker stop

vim /etc/docker/daemon.json

加上"bip": "192.168.1.1/24"節點並保存退出,再重啟docker即可。

我這裏采用第二種方式,修改後的路由表為:

技術分享圖片

重新訪問各種服務,全部都正常運行,到此總算是撥開雲霧見青天。

有個小細節不知大家是否發現,也是我當時存在的一個疑惑,就是前面有提過兩個容器的網段不一樣,按理說通過docker run來的容器應該都是相同的網段,為什麽會這樣呢?後來在折騰Portainer的時候找到了這個問題。

Portainer是一款docker管理工具,簡而言之的說就是把用命令操作的東西可視化,當然功能遠不止這些。Portainer中有一個Stack功能,我並不清楚這是幹什麽用的,只是看到舊的Portainer中的容器綁定了一個stack所以想依葫蘆畫瓢也搞一個:

技術分享圖片

於是拿stack的配置文件新創建一個,沒想到居然報錯,提示已存在相同名稱的容器。我馬上意識到這個特殊的容器應該是通過stack創建,我刪掉已存在的容器再次創建stack,這次成功了。出於好奇,仔細分析了stack的配置文件:

技術分享圖片

發現裏面主要是定義了鏡像名、容器名、網絡模式、端口映射這些,而其中vhnet這個網絡配置讓我很感興趣,轉而查看docker已經配置好的網關:

技術分享圖片

看到這裏,一種恍然大悟的感覺,你懂的。

除此之外,從前任留下的文檔裏可以知道,stack有一種類似熱更新的功能,修改配置文件中的鏡像名後update stack就能實現對應的容器更新,不用起新的容器,這點確實很不錯。更多強大的功能日後也會慢慢學習。

我的收獲

經過前面幾天的折騰,我更加熟悉了docker的各種基本操作和配置,也學會了使用新的命令,像docker inspect查看容器信息、docker attach進入容器內部,也加深了在Linux上排查問題的思路理解,學到了新的操作命令。也實際使用docker在Linux上部署了一次dotnetcore的生產環境,收獲頗豐。


遺留的問題

1、 yum update後到底經歷了什麽讓docker跪地不起,報錯原因至今沒搞明白。

2、 為什麽2.1的dotnetcore程序在2.1運行時跑不起來,換成2.2版本就可以。

3、stack是怎麽實現修改鏡像後容器就能生效的呢?

有知道的大佬還請多多指導。

總結

表面上全篇都在講才踩坑的事,但追根究底還是因為自己在Linux方面的知識欠缺和經驗不足。還是那句話,多踩坑,會讓你記憶深刻,會讓你學到意想不到的東西,會讓你的身體變得足夠大,下次碰到坑能一腳踏過去。

故事講完了,大家周末愉快~


.NetCore&Linux&Docker&Portainer踩坑歷險記