簡介
在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) ,對比后,我得出了如下的結論:
- gitlab是掛了
- gitlab是數據庫出了問題
- gitlab是數據庫整個出了問題
- gitlab是數據庫整個沒有從data目錄中掛載過去導致gitlab啟動時認為新程序,已經覆蓋了一大部分數據了。
- 其他文件暫時應該沒有問題
想到這,我心拔涼拔涼的,五年的代碼都在這里面,五年啊~
冷靜下來,我覺得會有如下幾個方案供我選擇
- 上策: 從原來的環境中完全恢復,數據庫丟失通過分析硬盤文件進行恢復,這樣能最大程度的保證不出什么問題。
- 中策: 通過遷移git-data下面的所有倉庫數據,新建一個gitlab把原來的倉庫都搞進去恢復。
- 下策: 全不要了,大不了重頭再來!!!
此時,距離案發現場已經過去了3個小時,我意識到,以我50g的硬盤,再加上我不斷的重啟操作,我那可憐的數據庫數據應該是活不全了。萬一幾百張表里面壞幾個,我修也得修死,還不知道會不會隱藏什么神奇的bug。至于下策,想都不要想了,這沒了和我的積累沒了也差不多了。
那么壓力就完全給到了中策上面,想到了恢復又以下幾種可能。
- 通過git-data進行恢復,解析出所有git-data的倉庫,然后進行復原。
- 通過自己平時用的5臺電腦的上倉庫,私人的gitlab對權限管控比較嚴格,能提交代碼的電腦只有這五臺,所以我將這五臺上的倉庫找到最新的提交上去也不是不行。
但倉庫超過500個時,我想了想,我得一個一個的找到他們,還得一個一個的比較5臺電腦上不同倉庫的時間,這工作量好像有點超出了我能力范圍,于是,壓力又來到了第一個選項:git-data恢復
在我的設想中,git-data恢復有三種
- git-data直接可用
- gitlab支持直接導入git-data
- 通過工具把git-data解析出來
這時候,我滿懷希望的看了眼git-data下的倉庫文件,他回敬了我一盆冷水

全是hash,gitlab從13開始使用hash模式儲存代碼,到14已經完全廢除了原來的代碼倉庫模式。只覺得bbq就在眼前~~
此路不同,直接轉其他路,google開啟,對二三進行瘋狂搜索,一無所獲,這時候,我意識到,我只剩一條路可以走了。
開啟倉庫尋找之路
git-data目前亂七八糟的,但是他作為一個存儲倉庫的目錄,一定有他的規范
find ./ |grep config
搜索出了一部分config的文件,那就證明,git-data只是一個不帶名字的目錄而已,而且config內還包括倉庫名字,那這事就明朗了。就以下三步:
- 解析出所有的git倉庫
- 新建一個gitlab,做好基本配置
- 在gitlab新建倉庫
- 把每個git倉庫都push上去
- 收工
在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的恢復,也不由得寫下此文以作紀念。
- 做升級前要做好備份,不可隨意升級,哪怕這個服務器跑了2年沒關過,這個系統升級了2年沒出問題
- 出現問題的第一件事,就是google相同問題,并且保存現場。
- 對于重要的數據備份最好做到多地多備。
- 對于沒把握的事,要慎重點,不然損失難料。
看雪學苑
合天網安實驗室
E安全
安全圈
FreeBuf
天融信
betasec
聚銘網絡
雁行安全團隊
HACK學習呀
betasec