Gitlab修復記

張京 發表於 2022-05-09
Git

Gitlab突然不能部署了

大約三個月前的一天,徒弟愁眉苦臉地跟我說:師傅,不好了,我最近用我們們這gitlab打包釋出的時候總是失敗。

我說:原先不是好好的嗎?

他說:是啊,原先確實很好用,也不知道怎麼了,最近這段時間就是不好用了,你說徹底不能用吧,也不是,偶爾也能用,但總要重試那麼好幾次,有時候甚至十幾次才能用。

我仔細想了想,也沒改啥啊,只是為了程式碼安全起見,把原先的giblab的IP地址從公網切換到了內網了,但是DNS我同時也改了,大家沒有反映有什麼問題啊。

我說:你這樣的,你先把編譯指令碼改成ping gitlab.mydomain.com試試看能不能ping通。

ping了,不穩定,有時候通有時候不通。

這就奇了怪了,能ping到IP地址,說明DNS沒有問題,只是到這個IP地址不通。(到此我依然執迷不悟,沒有想到是路由的問題,後面再說)

被MTU誤導的三個月

徒弟開始嘗試用做減法的方式定位問題,很快有了重大發現。

只要我們不在任務中增加docker:dind服務,網路就沒有問題,那說明問題就出在這個dind上。

dind的全稱是docker in docker,因為本身我們編譯用的gitlab runner就是執行在docker容器裡面的,現在還要在這個容器裡面再執行docker命令進行打包,就必須依賴這個dind服務,dind服務本身又是一個小的容器,它裡面再啟動一個docker守護程式,這樣外面這個容器才能執行docker命令。

習慣性地開啟Google,開始面向Google解決問題。

很多網頁都把問題的焦點指向了一個名叫MTU的神祕設定,說這個dind容器預設的MTU1500,在某種情況下會導致網路傳輸不穩定,跟我們遇到的這個症狀很相似。

最大傳輸單元 (Maximum Transmission Unit):最大傳輸單元是指資料連結層上面所能通過的最大資料包大小。最大傳輸單元這個引數通常與通訊介面有關。 因特網協議允許IP分片,這樣就可以將資料包包分成足夠小的片段以通過那些最大傳輸單元小於該資料包原始大小的鏈路了。這一分片過程發生在IP層,它使用的是將分組傳送到鏈路上的網路介面的最大傳輸單元的值。

問題變為:如何設定這個MTU?我們需要了解gitlab流水線任務中的服務裡面的子容器如何設定,查了很多資料,有人信誓旦旦地說只要在service裡增加一個command引數mtu=1400就可以了,但是實驗之後發現還是不行,我們把命令列改為ifconfig,直接查閱網路卡引數,發現還是1500,於是查閱docker:dind原始碼,發現dind會讀取一個環境變數DOCKERD_ROOTLESS_ROOTLESSKIT_MTU

於是問題又變成了:如何設定這個環境變數以讓dind讀到?嘗試了各種方法,dind依然讀取不到。

靜下心來,重新讀Gitlab官網的文件,裡面介紹了一個引數variables

Additional environment variables that are passed exclusively to the service.
提供給服務使用的附加的環境變數

似乎這個東西就是我們想要的,但是它有附加條件:此設定只能用於Gitlab 14.5版本及以上。而我們的Gitlab版本還是13.12.3

升級Gitlab驚魂記

我找了一個安靜的週末,開始果斷升級Gitlab,然後就差點陷入萬劫不復之境。

我想,升級嘛,這還不是很簡單的一件事情,而且對於我們這樣嚴格守規矩的人來說,升級只是增加一個版本號而已。

docker-compose.yml裡原本就已有這一句了,這還是大約一年前初始安裝gitlab時設定好的:image: 'gitlab/gitlab-ee:latest',那麼這已經是最高版了,於是直接docker-compose down然後docker-compose up -d應該就可以了吧?

不是的,它還是13.12.3

查資料,才知道應該先docker-compose pull下來才能得到最新版。

好吧,按步驟執行。

壞了,gitlab容器怎麼不停地重啟?趕緊執行docker logs檢查容器,發現裡面赫然寫著一行大字:大版本的升級,必須先升到14.0版本!

鬱悶,我們還是先看看gitlab升級說明書吧,這裡面倒是提到了升級路徑:

8.11.Z -> 8.12.0 -> 8.17.7 -> 9.5.10 -> 10.8.7 -> 11.11.8 -> 12.0.12 -> 12.1.17 -> 12.10.14 -> 13.0.14 -> 13.1.11 -> 13.8.8 -> 13.12.15 -> 14.0.12 -> 14.9.0 -> latest 14.Y.Z

於是,直接將docker-compose.yml改成image: 'gitlab/gitlab-ee:14.0.12',居然報告找不到包,再查閱gitlab標籤,發現版本號後面居然還要有-ee.0,你這前面已經有ee了,為什麼標籤裡還要再寫一遍?(所謂ee就是企業版的簡稱Enterprise Edition,gitlab現在其實不區分企業版和社群版,統一都要求大家使用企業版,只是你不付費的話,裡面屬於企業版的那部分功能就不能用而已)

改成image: 'gitlab/gitlab-ee:14.0.12-ee.0'再試一次,這次終於成功了!

好吧,再接再勵,升到14.9.0,然而gitlab容器又起不來了!

再次開啟docker logs檢視,這次是滿屏的字元飛滾,似乎在做什麼資料庫升級的事情,但是為什麼會不停地重啟呢?

再次Google搜尋,說是Gitlab14.0版本以後引進了資料庫migration機制,每次升級都必須要等到上一個版本的遷移完成之後才能進行下一版本的升級,而且這個遷移過程可能長達幾小時甚至數天!

壞了,我肯定是升級過快了,上一個版本還沒倒騰完,我就開始升下一版本了😭。

現在怎麼辦?我腦子飛快運轉。向老闆報告災難?向同事們承認失誤?說我把你們的程式碼全搞丟了?

冷靜冷靜。我想了五分鐘,這麼的吧,看能不能降級降回14.0

於是我再次把docker-compose.yml裡改成14.0。重新啟動,祈禱資料沒有丟失。

14.0總算是啟起來了,但是當我訪問頁面的時候:

image.png

這下麻煩大了!

強忍悲痛,開啟docker logs看,沒有線索,報告說一切正常。

但頁面就是500啊!

網上有資料說,遇到500不要慌,進到容器裡面看端詳。好吧,docker exec -it進到容器裡面,執行gitlab-ctl tail看輸出,這邊一重新整理頁面,日誌裡面就報告缺少一個叫services的資料庫表!

那不還是毀了嗎?我這資料庫弄不好升到一半,程式碼又是舊的,咋整?上又上不去,下又下不來,這下麻煩大了。

沒辦法,再次Google,終於找到了同命相憐的兄弟。聽聽他的血淚控訴:

我從13.12升級到14.0.7,我以為遷移已經結束了,一切都正常了,於是我就停止了容器,升到14.2,結果啟動不起來了,於是我就又退回到14.0.7,這回產生了500錯誤,日誌詳情如下:
ActionView::Template::Error (PG::UndefinedTable: ERROR: relation "services" does not exist
並且我沒有備份。

簡直跟我遇到的情況一模一樣。底下有人說,Gitlab每次升級前會自動把資料庫備份在backup資料夾下面,這哥們兒說並沒有,我也去看了,也沒有。

好在他提交的bug報告裡有我們們中國兄弟解決了這事,就是這位
image.png

方法竟然如此簡單:慢慢升!

既然快升會出問題,就慢慢升,先升級到14.1.1,等遷移結束之後,再升級到14.2.1這樣的。

抱著最後一絲希望,再次把docker-compose.yml升級到14.1.1,啟動。

等了五分鐘之後,容器終於啟動起來了。

開啟瀏覽器,訪問網頁,祈禱不要再500了。

啊,長舒一口氣,終於看到了熟悉的頁面。

可不敢亂動了。按照既定步驟,到管理員監控下面檢視遷移進度,果然有14個任務正在遷移。等這14個任務遷移完成,我開始思考下一步行動。

終於找到了問題的根源

但是我還是想升級到Gitlab 14.5,我覺得既然已經站到14.1.1上了,而且遷移完成了,應該後續不會太難,保險起見,還是先升到14.2.1,這次也成功了。

我靜靜地等待遷移完成,然後再升到14.9.0。其實還可以再升到14.10.0,但是先不用了,14.9.0已經夠用。

於是我們重新回到Gitlab,將dind的環境變數設定好,再次編譯,不行,網路MTU還是1500

再次搜尋關於dind設定MTU問題,設定方法其實還是和以前一樣,就是設定command就夠了,然後檢視docker network inspect bridge,但是這裡的MTUifconfig查出來的MTU並不一樣,docker network裡雖然已經是1400了,但是ifconfig裡顯示的還是1500,這代表什麼含義呢?

我模糊地感覺到,這個docker橋接網路的MTU可能和外面並不需要一致,但無論如何,容器裡面現在已經是1400了,甚至可以更低,但無論如何總是和外面無法通訊,並且我還嘗試了ping www.baidu.com也是可以ping通的,只有到我們的內網伺服器無法ping通。

太累了,我先去睡個午覺。

醒來之後,躺在床上,我開始思考這個問題:如果我不新增dind服務,就可以ping通,如果我加上了,就ping不通,那說明這個dind服務修改了我的網路配置。

我看到如果我不加dind的話,我這個容器是兩塊網路卡,一塊是eth0,一塊是localhost,這種情況下是正常的,但當我加上dind服務後,容器裡就產生了三塊網路卡,多了一個docker0,會不會是我的這個ping請求只能在eth0上工作,不能在docker0上工作,而當增加了dind服務之後,所有的ping請求都自動跑到了docker0上去了呢?我能不能強制讓ping請求走eth0呢?

再次嘗試:ping -I eth0 -c 10 gitlab.mydomain.com,這次成功了!

那說明問題就出在docker0這塊網路卡上。

因為有了它,所有網路請求都走了這塊網路卡,導致網路不通。

那為什麼網路請求會走這塊網路卡呢?仔細看它的IP設定,我突然意識到了問題。docker0docker的橋接網路。

By default, Docker uses 172.17.0.0/16 subnet range.
預設情況下,Docker使用172.17.0.0/16作為子網範圍。

而我們的內網地址172.17.111.27剛好也是在這個網段內!

那麼這就解釋得通了,原先我們採用公網IP的時候沒有問題,我們的容器訪問www.baidu.com也沒有問題,獨獨只有訪問我們的內網伺服器的時候有問題,因為我們的內網伺服器地址剛好和docker的預設內網地址重合,所以所有的網路請求都被轉發到了docker的橋接網路內,導致無法與內網伺服器通訊!

最後的決戰

我們不可能也沒有必要修改內網伺服器的地址,現在要研究的是如何修改docker的預設子網地址。

網上所有的貼子都是說修改/etc/docker/daemon.json這個檔案,但是我們的容器里根本就沒有這個檔案,因為我們是在容器裡面再起一個dind的服務,必須讓dind取得這個修改之後的設定才行,而gitlabservice設定很有限,不可能輕易修改service容器裡的內容。

又是一番激烈的搜尋,最後在另外一個老哥那裡找到了答案

  variables:
    DAEMON_CONFIG: '{"bip": "192.168.123.1/24"}'
  services:
    - name: docker:dind
      entrypoint: ["/bin/sh", "-c", "mkdir -p /etc/docker && echo \"${DAEMON_CONFIG}\" > /etc/docker/daemon.json && exec dockerd-entrypoint.sh"]

原理也很簡單:強行修改了dind這個服務的入口地址,在開始執行之前將我們想要修改的內容寫入daemon.json,這下docker的橋接網格docker0的網段不和我們的內網網段重合了,應該就生效了。

按照這個方法修改之後,再次執行編譯過程,現在我們再ping gitlab.mydomain.com直接就可以ping通了,不需要再指定網路卡,說明整個網路已經正常。

至此,終於徹底解決了這個困擾我們三個月之久的問題:在kubernetes網路中安裝gitlab runner並執行一個docker打包的任務


回想解決問題的整個過程,還是在一開始的時候忽略了網路環境變化這個最大的變數,走了很多彎路,在這個過程中,我們瞭解了什麼是MTU,瞭解了Gitlab應該如何升級,瞭解了Docker的橋接網路的設定。雖然吃了一點虧,但收穫是巨大的,從此我們又可以暢通無阻地使用Gitlab來進行持續部署了。😄