簡介

在2023年4月24日晚接到gitlab升級通知后,例行升級惹出的大問題。本文記錄了修復gitlab的全過程及中間放下的錯。由于中間部分操作未能及時記錄截圖,所以在這使用中間備份的一些場景進行復現。對自己做一個反省。

基本情況:

  • 當前版本 gitlab-ce:15.9.5
  • 升級版本 gitlab-ce:15.11.0
  • 操作系統 Linux debian10 4.19.0-10-amd64 #1 SMP Debian 4.19.132-1 (2020-07-24) x86_64 GNU/Linux
  • 硬件 2c8g50g硬盤
  • 500+倉庫 和 執行任務的13個gitlab-runner

問題出現

照例使用這條命令進行升級重啟,這不升級不要緊,一升級就出了大問題。此時為第一個錯誤,沒有對虛擬機進行快照就對內部應用進行升級。由于通過docker部署gitlab已經超過5年了,每次更新都是這么進行的,這次也沒有太多要注意的意識。而且作為一個只有2c8g50g硬盤的虛擬機,在gitlab占據了30g的情況下,也沒有太多硬盤資源做好實時備份。

docker rm -f docker && docker pull gitlab/gitlab-ce:latest && docker run -it --log-opt max-size=10m --log-opt max-file=3 -p 443:443 --name gitlab --restart always -v /root/gitlab/config:/etc/gitlab -v /root/gitlab/logs:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce:latest

第一次報錯

比較關鍵問題日志如下:

```bash
SELECT COUNT(*) FROM container_repositories WHERE migration_state = 'import_skipped'
ERROR:  column "migration_state" does not exist at character 51
If you would like to restart the instance without attempting to
upgrade, add the following to your docker command:
-e GITLAB_SKIP_UNMIGRATED_DATA_CHECK=true

第一次報錯

一看,都給你寫好了,這不得了嗎,直接加上,重啟!

第二次報錯

出我意料,又gg了

Running handlers:
[2023-04-27T15:20:30+00:00] INFO: Running report handlers
Running handlers complete
[2023-04-27T15:20:30+00:00] INFO: Report handlers complete
Infra Phase complete, 35/802 resources updated in 07 seconds
gitlab Reconfigured!
Checking for an omnibus managed postgresql: OK
Checking if postgresql['version'] is set: OK
Checking if we already upgraded: NOT OK
Checking for a newer version of PostgreSQL to install
Upgrading PostgreSQL to 13.8
Checking if PostgreSQL bin files are symlinked to the expected location: OK
Starting the database
Error starting the database. Please fix the error before continuing
Expected process to exit with [0], but received '1'
---- Begin output of gitlab-ctl start postgresql ----
STDOUT: fail: postgresql: runsv not running
STDERR:
---- End output of gitlab-ctl start postgresql ----
Ran gitlab-ctl start postgresql returned 1
Upgrading the existing database failed and was reverted.
Please check the output, and open an issue at:
https://gitlab.com/gitlab-org/omnibus-gitlab/issues
If you would like to restart the instance without attempting to
upgrade, add the following to your docker command:
-e GITLAB_SKIP_PG_UPGRADE=true

加上,然后使用gitlab/gitlab-ce:15.9.5-ce.0容器進行二次重啟!

第三次報錯

2023-04-27_23:47:06.16695 ts=2023-04-27T23:47:06.166Z caller=repair.go:52 level=error component=tsdb msg="failed to read meta.json for a block during repair process; skipping" dir=/var/opt/gitlab/prometheus/data/01GYSNK1JKD2X5125MP0ZRPKFH err="open /var/opt/gitlab/prometheus/data/01GYSNK1JKD2X5125MP0ZRPKFH/meta.json: permission denied"
2023-04-27_23:47:06.16695 ts=2023-04-27T23:47:06.166Z caller=repair.go:52 level=error component=tsdb msg="failed to read meta.json for a block during repair process; skipping" dir=/var/opt/gitlab/prometheus/data/01GYSWERS0EHQ52Y4RT2GF29Q0 err="open /var/opt/gitlab/prometheus/data/01GYSWERS0EHQ52Y4RT2GF29Q0/meta.json: permission denied"
2023-04-27_23:47:06.16698 ts=2023-04-27T23:47:06.166Z caller=main.go:1144 level=error err="opening storage failed: migrate WAL: check first existing segment: open /var/opt/gitlab/prometheus/data/wal/00017925: permission denied"

gitlab仿佛和我說,你敢二次重啟,我就敢三次崩。不過通過log看出來,很簡單的嘛,一個權限,回到了最初的起點,gitlab能自己修復權限,所以直接全給777完事

chmod -R 777 /root/gitlab/config
chmod -R 777 /root/gitlab/logs
chmod -R 777 /root/gitlab/data

第三次重啟!

第四次報錯

==> /var/log/gitlab/gitlab-rails/production.log <==
NoMethodError (undefined method `throttle_unauthenticated_api_enabled' for #    product_analytics_configurator_connection_string: nil, openai_api_key: nil>
Did you mean?  throttle_unauthenticated_enabled
               throttle_unauthenticated_enabled=
               throttle_unauthenticated_enabled?
               throttle_unauthenticated_enabled_was
               throttle_authenticated_api_enabled
               throttle_authenticated_api_enabled=
               throttle_authenticated_api_enabled?
               throttle_authenticated_api_enabled_was
               throttle_authenticated_web_enabled
               throttle_authenticated_web_enabled=
               throttle_authenticated_web_enabled?
               throttle_authenticated_api_enabled_change
               throttle_authenticated_web_enabled_was):

很長一段的錯誤,到了這,我已經放棄了,開始轉到另一個錯誤上。

SELECT COUNT(*) FROM container_repositories WHERE migration_state = 'import_skipped'
ERROR:  column "migration_state" does not exist at character 51

登陸到數據庫進行查看發現表里根本沒有這一列,而且沒有一行數據。

su - gitlab-psql 
psql -h /var/opt/gitlab/postgresql -d gitlabhq_production 
select * from container_repositories;

通過和gitlab官網的建表語句對比(https://gitlab.com/gitlab-org/gitlab/-/blob/master/db/structure.sql)

CREATE TABLE container_repositories (
    id integer NOT NULL,
    project_id integer NOT NULL,
    name character varying NOT NULL,
    created_at timestamp without time zone NOT NULL,
    updated_at timestamp without time zone NOT NULL,
    status smallint,
    expiration_policy_started_at timestamp with time zone,
    expiration_policy_cleanup_status smallint DEFAULT 0 NOT NULL,
    expiration_policy_completed_at timestamp with time zone,
    migration_pre_import_started_at timestamp with time zone,
    migration_pre_import_done_at timestamp with time zone,
    migration_import_started_at timestamp with time zone,
    migration_import_done_at timestamp with time zone,
    migration_aborted_at timestamp with time zone,
    migration_skipped_at timestamp with time zone,
    migration_retries_count integer DEFAULT 0 NOT NULL,
    migration_skipped_reason smallint,
    migration_state text DEFAULT 'default'::text NOT NULL,
    migration_aborted_in_state text,
    migration_plan text,
    last_cleanup_deleted_tags_count integer,
    delete_started_at timestamp with time zone,
    status_updated_at timestamp with time zone,
    CONSTRAINT check_05e9012f36 CHECK ((char_length(migration_plan) <= 255)),
    CONSTRAINT check_13c58fe73a CHECK ((char_length(migration_state) <= 255)),
    CONSTRAINT check_97f0249439 CHECK ((char_length(migration_aborted_in_state) <= 255))
);

發現從migration_state列開始,就都沒了。這時候就犯了第二個錯誤,想當然的相信了自己家服務器的硬盤能力。但是上頭,認為這個問題的出現只是這個表壞了,不行就重建一個嘛,建表語句都有,drop 重新create一個完事,看起來也不是什么重要的表,丟了就丟了唄。登陸上數據庫,刪庫跑路(bushi ~ ~ ~ )

drop table container_repositories;

提示該表有外鍵,刪了會有影響,這時候我哪里還管,刪刪刪!!!

drop table container_repositories cascade;

強行將表刪除,然后用上面的語句重建的表,重啟!

第五次報錯

啪啪啪!!! 打臉來的很突然,第五次報錯的更加離譜。這里已經完全記不清楚原因了。到這,開始想到,google是人類的精華。Pia!(o ‵-′)ノ”(ノ﹏<。)

google摸了摸我的頭說: 我不是!

不過這有怎么難到我,到處搜gitlab恢復文章,終于google不服有心人。找到了一篇類似的文章(https://tech.uupt.com/?p=147) ,對比后,我得出了如下的結論:

  1. gitlab是掛了
  2. gitlab是數據庫出了問題
  3. gitlab是數據庫整個出了問題
  4. gitlab是數據庫整個沒有從data目錄中掛載過去導致gitlab啟動時認為新程序,已經覆蓋了一大部分數據了。
  5. 其他文件暫時應該沒有問題

想到這,我心拔涼拔涼的,五年的代碼都在這里面,五年啊~

冷靜下來,我覺得會有如下幾個方案供我選擇

  1. 上策: 從原來的環境中完全恢復,數據庫丟失通過分析硬盤文件進行恢復,這樣能最大程度的保證不出什么問題。
  2. 中策: 通過遷移git-data下面的所有倉庫數據,新建一個gitlab把原來的倉庫都搞進去恢復。
  3. 下策: 全不要了,大不了重頭再來!!!

此時,距離案發現場已經過去了3個小時,我意識到,以我50g的硬盤,再加上我不斷的重啟操作,我那可憐的數據庫數據應該是活不全了。萬一幾百張表里面壞幾個,我修也得修死,還不知道會不會隱藏什么神奇的bug。至于下策,想都不要想了,這沒了和我的積累沒了也差不多了。

那么壓力就完全給到了中策上面,想到了恢復又以下幾種可能。

  1. 通過git-data進行恢復,解析出所有git-data的倉庫,然后進行復原。
  2. 通過自己平時用的5臺電腦的上倉庫,私人的gitlab對權限管控比較嚴格,能提交代碼的電腦只有這五臺,所以我將這五臺上的倉庫找到最新的提交上去也不是不行。

但倉庫超過500個時,我想了想,我得一個一個的找到他們,還得一個一個的比較5臺電腦上不同倉庫的時間,這工作量好像有點超出了我能力范圍,于是,壓力又來到了第一個選項:git-data恢復

在我的設想中,git-data恢復有三種

  1. git-data直接可用
  2. gitlab支持直接導入git-data
  3. 通過工具把git-data解析出來

這時候,我滿懷希望的看了眼git-data下的倉庫文件,他回敬了我一盆冷水

全是hash,gitlab從13開始使用hash模式儲存代碼,到14已經完全廢除了原來的代碼倉庫模式。只覺得bbq就在眼前~~

此路不同,直接轉其他路,google開啟,對二三進行瘋狂搜索,一無所獲,這時候,我意識到,我只剩一條路可以走了。

開啟倉庫尋找之路

git-data目前亂七八糟的,但是他作為一個存儲倉庫的目錄,一定有他的規范

find ./ |grep config

搜索出了一部分config的文件,那就證明,git-data只是一個不帶名字的目錄而已,而且config內還包括倉庫名字,那這事就明朗了。就以下三步:

  1. 解析出所有的git倉庫
  2. 新建一個gitlab,做好基本配置
  3. 在gitlab新建倉庫
  4. 把每個git倉庫都push上去
  5. 收工

在stackoverflow上找到了提取所有倉庫的代碼,為了安全起見,我將gitlab文件都備份到了test下面,隨便折騰。

for GITDIR in $(find /root/test/gitlab/data/git-data/repositories/@hashed/ -maxdepth 3 -type d -name '*[0-9a-f].git'); do
   echo "$(cat ${GITDIR}/config | grep fullpath | awk -F " = " '{print $2}')   $GITDIR"
done 
xxxxxxx/xxxxxxxxx   /root/test/gitlab/data/git-data/repositories/@hashed/xx/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git
xxxxxxx/xxxxxxxxx   /root/test/gitlab/data/git-data/repositories/@hashed/xx/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git
xxxxxxx/xxxxxxxxx   /root/test/gitlab/data/git-data/repositories/@hashed/xx/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git

ok第一步完工,第二步,建立一個新的gitlab,在這里,我只保留conf目錄,其他的都為空目錄進去,chmod好相關權限,啟動,修改用戶密碼,配置文字等。

docker run -it --log-opt max-size=10m --log-opt max-file=3 -p 443:443 --name gitlab --restart always -v /root/gitlab/config:/etc/gitlab -v /root/gitlab/logs:/var/log/gitlab -v /root/gitlab/data:/var/opt/gitlab gitlab/gitlab-ce:latest
gitlab-rails console
user = User.where(id:1).first
user.password = 'newpassword'
user.password_confirmation = 'newpassword'
user.save!

第二步也很簡單就完成了,接下來就是第三步,把倉庫還原進去 首先新建一個同名倉庫,然后在git-data的該倉庫下面試試git push,由于有些倉庫較為久遠,還是master分支,新庫都默認時main分支,所以在此需要測試推送分支。這里又有個不好的習慣,自己寫的代碼都是master分支,只有極個別的幾個倉庫采用了其他分支,以后一定改!

cd /root/test/gitlab/data/git-data/repositories/@hashed/xx/xx/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx.git
git remote add origin ssh://url/user/xx.git
git push --set-upstream origin master || git push --set-upstream origin main

成功,登陸gitlab倉庫看,歷史記錄都在,換個電腦git pull下來,git log,git branch等都和歷史一樣,這時候,我就知道我穩了。接下來就是批量恢復的階段了。

500多個倉庫,手工建那我手沒了,申請一個token,用token批量建立倉庫。然后python生成命令進行執行,完美。

for i in s.split(''):
    t=i.split('/')[1]
    tmp=f'curl --header "PRIVATE-TOKEN: TOKEN" -X POST "https://url/api/v4/projects?name={t}&namespace_id=1"'
    print(tmp)
for tt in mmm:
    t=tt.split()
    t1=t[0].split('/')[1]
    t2=t[1]
    print(f'cd {t2}')
    print(f"git remote add origin ssh://url/user/{t1}.git")
    print("git push --set-upstream origin master || git push --set-upstream origin main")
    print()

最后經過執行,只有一個倉庫不知道啥原因,沒有name,手工運作一下,over!

gitlab cicd修復

我對于gitlab cicd的依賴很大,歷史上大約有300萬次的cicd記錄,所以gitlab-runner必不可少,還好執行的機器不多,也就7臺,所以每個上去看一下,將同名的runner建立起來,也順便清理了幾個不需要的runner。

cat /etc/gitlab-runner/config.toml
sudo gitlab-runner register --url "" -r='' --name="" --tag-list "" --executor "docker" --docker-image "" --docker-pull-policy="if-not-present" --locked=false --run-untagged=true -n
sudo gitlab-runner register --url "" -r='' --name="" --tag-list "" --executor "shell" ----locked=false --run-untagged=true -n

此時沒有意識到tag的做法,所以后面根據cicd文件,重新填了下對應runner的tag。

gitlab mail修復

在新的環境中,突然發現gitlab的郵件功能不可用了,gitlab郵件對我的任務提醒包括登陸提示都有很重要的作用。測試發了下郵件,得到如下錯誤:

gitlab enable_starttls and :tls are mutually exclusive. Set :tls if you're on an SMTPS connection. Set :enable_starttls if you're on an SMTP connection and using STARTTLS for secure TLS upgrade.

google下相關問題,源于配置的沖突,對配置做如下修改

Notify.test_email('xx@xx','email title','email content desc').deliver_now
修改前:
gitlab_rails['smtp_enable_starttls_auto'] = true
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_enable_starttls'] = true
修改后:
gitlab_rails['smtp_enable_starttls_auto'] = false
gitlab_rails['smtp_tls'] = true
gitlab_rails['smtp_enable_starttls'] = true
gitlab-ctl reconfigure && gitlab-ctl restart 

重啟后完美解決問題。

gitlab 備份

既然都吃了這么大一個虧了,那不得多加備份,上備份配置,保留最近7天的備份

gitlab_rails['manage_backup_path'] = true
gitlab_rails['backup_path'] = "/var/opt/gitlab/backups"
gitlab_rails['backup_archive_permissions'] = 0644
gitlab_rails['backup_keep_time'] = 604800

然后執行下gitlab-rake gitlab:backup:create 嘛 納尼!!! 這也能報錯???

{"command":"create","error":"manager: repository empty: repository skipped",-------
省略10w字
2023-04-24 16:30:56 UTC -- Deleting tar staging files ...
2023-04-24 16:30:56 UTC -- Cleaning up /var/opt/gitlab/backups/db
2023-04-24 16:30:56 UTC -- Cleaning up /var/opt/gitlab/backups/repositories
2023-04-24 16:30:56 UTC -- Deleting tar staging files ... done
2023-04-24 16:30:56 UTC -- Deleting backups/tmp ...
2023-04-24 16:30:56 UTC -- Deleting backups/tmp ... done
2023-04-24 16:30:56 +0000 -- Deleting backup and restore lock file
rake aborted!
- 下面這個可能存在
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  relation "design_management_repositories" does not exist
LINE 8:  WHERE a.attrelid = '"design_management_repositories"'::regc...
- 上面這個可能存在
                            ^
/opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `exec'
/opt/gitlab/embedded/lib/ruby/gems/3.0.0/gems/activerecord-6.1.7.2/lib/active_record/connection_adapters/postgresql/database_statements.rb:19:in `block (2 levels) in query'

通過查詢官方文檔(https://docs.gitlab.cn/ee/raketasks/backup_restore.html) 在gitlab12.1之前使用gitlab-rake gitlab:backup:create 12.2以后使用 sudo gitlab-backup create 命令進行備份,換了命令,搞定。通過crontab進行定時備份

0 5 * * * bash /root/gitlab/bak.sh >> /var/gitlab_bak.log

至此,gitlab恢復,五年的代碼都回來了。

總結

至此,經歷了兩個晚上終于完成了對gitlab的恢復,也不由得寫下此文以作紀念。

  1. 做升級前要做好備份,不可隨意升級,哪怕這個服務器跑了2年沒關過,這個系統升級了2年沒出問題
  2. 出現問題的第一件事,就是google相同問題,并且保存現場。
  3. 對于重要的數據備份最好做到多地多備。
  4. 對于沒把握的事,要慎重點,不然損失難料。