本ドキュメントでは、トラブルシューティングの事例を取り上げ、それぞれのトラブルの原因調査の流れと判明した原因、解決方法について記載します。
また、トラブルシューティングを円滑に進められるように心がけていることをご紹介します。
事例1:GitLab バックアップ復元スクリプトでオプションが効かない
背景
あるお客様では東京リージョンで GitLab をセルフホストしており
ディザスタリカバリのため、大阪リージョンにも構築することになりました。
東京リージョンの日次のバックアップを復元することでミラーリングし、リポジトリを参照できるようにします。なお、Web からログインしてのリポジトリの参照および git clone
ができればよく、それ以外の機能は無効でよいというものです。
状況
GitLab の Helm Chart にはバックアップ用の CronJob が含まれており、そこで使われている backup-utility というスクリプトは復元にも使えます。
ただ、復元対象から不要なものをオプションで指定しましたが、それが反映されず、対象外のものを復元しようとしてエラーになりました。
$ cp /etc/gitlab/.s3cfg $HOME/.s3cfg && rm -f /srv/gitlab/tmp/backups/0_gitlab_backup.tar && echo yes | backup-utility \\
--restore \\
--skip registry \\
--skip uploads \\
--skip artifacts \\
--skip lfs \\
--skip packages \\
--skip external_diffs \\
--skip terraform_state \\
--skip pages \\
-t $(aws s3api list-objects-v2 --bucket sample-bucket --query 'sort_by(Contents, &Key)[-1].Key' --output text | sed -E 's/^([0-9]{10}_[0-9]{4}_[0-9]{2}_[0-9]{2}_.+)_gitlab_backup.tar$/\\1/g')
...
2022-11-15 02:33:10 +0000 -- done
Restoring pages ...
sync existing of pages failed
[Error] WARNING: Ignoring invalid line in '/home/git/.s3cfg': provider: AWS
WARNING: Ignoring invalid line in '/home/git/.s3cfg': region: ap-northeast-3
WARNING: Ignoring invalid line in '/home/git/.s3cfg': aws_access_key_id: ???
WARNING: Ignoring invalid line in '/home/git/.s3cfg': aws_secret_access_key: ???
ERROR: S3 error: 404 (NoSuchBucket): The specified bucket does not exist
試行錯誤
コマンドヘルプ
コマンドの使い方を確認してみます。
Usage: backup-utility [--restore|--cleanup] [-f URL] [-t TIMESTAMP] [--skip COMPONENT] [--backend BACKEND] [--s3config CONFIG]
Options:
-h, --help Show this help message and exit.
--restore [-t TIMESTAMP | -f URL] When specified, utility restores from an existing backup specified
as url or timestamp in object storage.
-f URL http(s):/ftp:/file: URL with backup location. Use with --restore.
-t TIMESTAMP Timestamp (part before '_gitlab_backup.tar' in archive name),
can be used to specify backup source or target name.
--rsyncable Pass the '--rsyncable' parameter to gzip for artifact compression.
--skip COMPONENT When specified, utility will skip the backup or restore of COMPONENT.
May be defined multiple times. Valid values for COMPONENT are
db, repositories, and any of the object storages (e.g. 'lfs').
...
--skip
オプションは列挙でき、使い方は合っているように見受けられます。
処理順列挙
backup-utility の中身を読んで処理を追ったところ処理される関数・コマンドは次のようでした。
fetch_remote_backup
unpack_backup
gitlab-rake gitlab:backup:db:restore
gitlab-rake gitlab:backup:repo:restore
- (
gitlab-rake gitlab:backup:builds:restore
はスキップ) - (
object-storage-restore pages
はスキップのはず) gitlab-rake cache:clear
cleanup
object-storage-restore pages
を手元で実行したところ同じエラーになったので、ここが実行されているとわかりました。
git@gitlab-toolbox-5f5f845954-lcvzk:/$ object-storage-restore pages /srv/gitlab/tmp/backups/pages.tar.gz
Restoring pages ...
[Error] WARNING: Ignoring invalid line in '/home/git/.s3cfg': provider: AWS
WARNING: Ignoring invalid line in '/home/git/.s3cfg': region: ap-northeast-3
スキップ対象の処理
スキップ対象が ,
区切りで入っている変数 SKIPPED
を echo
したところ、スキップ対象のはずの pages
が入っていませんでした。
builds,registry,uploads,artifacts,lfs,packages,external_diffs,terraform_state,registry
skipped_via_flag
は配列ですが、この書き方だと先頭の要素しか出力されません。
export SKIPPED=$(echo ${skipped_line#:skipped:},${skipped_via_flag})
検証用スクリプト options.sh
#!/usr/bin/env bash
skipped_via_flag=()
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--skip)
skipped_via_flag+=( "$2" )
shift
shift
;;
*)
echo "Unexpected parameter: $key"
exit 1
;;
esac
done
skipped_line=registry
echo ${skipped_line},${skipped_via_flag}
echo ${skipped_line},$(IFS=,; echo "${skipped_via_flag[*]}")
./options.sh --skip terraform_state --skip pages
registry,terraform_state
registry,terraform_state,pages
原因
backup-utility のオプション指定の不具合でした。
対処
--skip
オプションのうち、先頭に指定したものは実際にスキップされるので、--skip pages
を先頭に指定して回避しました。
また GitLab 公式に MR を送り、次のリリースで正しく機能するようにしました。
https://gitlab.com/gitlab-org/build/CNG/-/merge_requests/1210
事例2:CI の config を変えたら go mod download でリポジトリへの接続に失敗するようになった
状況
GitLab のリポジトリ名を hoge-grpc-proto から hogehoge-grpc-proto 変更したところ、CI ジョブで go mod download
する途中にリポジトリへの接続に失敗しました。
$ GOPRIVATE=${GOPRIVATE} go mod download
go: gitlab.example.com/application/hogehoge-grpc-proto.git@v0.9.0: invalid version: git ls-remote -q origin in /builds/application/hogehoge-backend/.go/pkg/mod/cache/vcs/88fd5b99149db552bc9bdee05ebe7995c96689713f73961826c83633a5752ee1: exit status 128:
fatal: unable to connect to gitlab.example.com:
gitlab.example.com[0: 52.69.45.177]: errno=Connection timed out
gitlab.example.com[1: 13.231.92.164]: errno=Connection timed out
gitlab.example.com[2: 54.199.161.138]: errno=Connection timed out
.gitlab-ci.yml
variables:
IMAGE_NAME: hogehoge-backend
AWS_ECR: $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
GOPRIVATE: gitlab.example.com/*
stages:
- lint
- build_and_push
lint:
stage: lint
image: ${CI_DEPENDENCY_PROXY_DIRECT_GROUP_IMAGE_PREFIX}/golangci/golangci-lint:latest
only:
changes:
- internal/**/*
- injector/**/*
- internal/**/*
- pkg/**/*
- .gitlab-ci.yml
- go.mod
- go.sum
- main.go
cache:
key: $CI_COMMIT_REF_SLUG
paths:
- .go/pkg/mod
variables:
GOPATH: $CI_PROJECT_DIR/.go
before_script:
- mkdir -p .go
- git config --global url."https://hogehoge-grpc-proto:$TOKEN1@gitlab.example.com/application/hogehoge-grpc-proto".insteadOf "https://gitlab.example.com/application/hogehoge-grpc-proto"
- git config --global url."https://gopkg:$TOKEN2@gitlab.example.com/application/gopkg".insteadOf "https://gitlab.example.com/application/gopkg"
- GOPRIVATE=${GOPRIVATE} go mod download
script:
- golangci-lint run --fix --fast --timeout 5m ./...
プライベートモジュールの認証情報を git config
で登録しておき、go mod download
でダウンロードします。
go.mod
module gitlab.example.com/application/hogehoge-backend
go 1.16
require (
/* 省略 */
gitlab.example.com/application/gopkg v0.0.4
gitlab.example.com/application/hogehoge-grpc-proto v0.9.0
)
replace (
gitlab.example.com/application/gopkg => gitlab.example.com/application/gopkg.git v0.0.4
gitlab.example.com/application/hogehoge-grpc-proto => gitlab.example.com/application/hogehoge-grpc-proto.git v0.9.0
)
URL のパス中のリポジトリ名を特定するために .git を付与しています。
試行錯誤
GitLab Runner から GitLab リポジトリへの接続確認
GitLab を管理するための Toolbox Pod からリポジトリに HTTPS で接続したところ、成功しました。
git@gitlab-toolbox-5689dd6c97-chfhh:/$ curl https://gitlab.example.com
<html><body>You are being <a href="https://gitlab.example.com/users/sign_in">redirected</a>.</body></html>
ALB のセキュリティグループ
リポジトリ側の ALB のセキュリティグループを見直したところ、インバウンドの 80番・443 番ポートは許可されていました。
類似 CI のジョブ
他の類似プロジェクトでも同じくプライベートモジュールを使って構成しているので、そちらで CI ジョブが正常に実行できるか確かめたところ、問題なく完了しました。
同等環境を立ち上げて再現
golangci-lint の Pod を立ち上げて CI ジョブのコマンドを逐次実行します。
go mod download には -x
オプションがあり、内部で実行しているコマンドを表示します。
$ kubectl run golanci-lint --image=golangci/golangci-lint:latest -it --rm --restart=Never -n gitlab --overrides='{"spec":{"nodeSelector":{"workload/gitlab-runner":"true"}}}' --command -- /bin/bash
$ cd /root
$ cat <<EOF > go.mod
module gitlab.example.com/application/hogehoge-backend
go 1.16
require (
/* 省略 */
gitlab.example.com/application/gopkg v0.0.4
gitlab.example.com/application/hogehoge-grpc-proto v0.9.0
)
replace (
gitlab.example.com/application/gopkg => gitlab.example.com/application/gopkg.git v0.0.4
gitlab.example.com/application/hogehoge-grpc-proto => gitlab.example.com/application/hogehoge-grpc-proto.git v0.9.0
)
EOF
$ git config --global url."https://hogehoge-grpc-proto:$TOKEN1@gitlab.example.com/application/hogehoge-grpc-proto".insteadOf "https://gitlab.example.com/application/hogehoge-grpc-proto"
$ git config --global url."https://gopkg:$TOKEN2@gitlab.example.com/application/gopkg".insteadOf "https://gitlab.example.com/application/gopkg"
$ GOPRIVATE=gitlab.example.com/* go mod download -x
cd /go/pkg/mod
git ls-remote https://gitlab.example.com/application/hogehoge-grpc-proto
cd /go/pkg/mod
git ls-remote git+ssh://gitlab.example.com/application/hogehoge-grpc-proto
^C
$ git ls-remote https://gitlab.example.com/application/hogehoge-grpc-proto
remote: HTTP Basic: Access denied
fatal: Authentication failed for 'https://gitlab.example.com/application/hogehoge-grpc-proto/'
原因
プライベートモジュールが入ったリポジトリのデプロイトークンが間違っていました。
デプロイトークン名も hoge-grpc-proto から hogehoge-grpc-proto に変えたが、新しい方を作成していませんでした。
そのため、go mod download
において HTTPS でリポジトリに接続を試みるも、認証に失敗して SSH で再試行しようとして接続拒否されました。
Config ファイルの文字列を一斉に置換した時点で満足してしまい、認証情報の変更を忘れるのはありがちだなと思いました。
対処
プライベートモジュールのリポジトリ omatome-grpc-proto でデプロイトークンを再生成し,利用プロジェクト omatome-backend の CI 設定で環境変数の値を差し替えました。
トラブルシューティングの際に心がけていること
トラブルシューティングを何度か経験してきて,円滑に進めるために心がけていることを挙げてみました。
- ログを素直に解釈する
- 余計な推測で遠回りにならないようにする
- 原因の仮説を(可能性が低いものも含めて)網羅的に列挙する
- 類似プロダクトでも発生しているか確認する
- 発生していれば、共通の根本原因がある
- この場合、想定より影響範囲が大きいことになる
- 発生していなければ、原因は類似プロダクトとの差分にある
- 同等環境で再現・検証してみる
- コマンドに
verbose
やdebug
オプションがあれば有効にして情報を得る
- コマンドに
- 前も同じものがあったかもバイアスを排除する
- 同じだったら無意識にスキップしてしまうのに気をつける
- そもそもログを適切に出力する
- ソースコード上でエラーを握りつぶしている箇所を疑う
まとめ
いかかでしたでしょうか。
今回は2つの事例を紹介しつつ、調査の流れや個人的に意識していることをまとめてみました。
他にもよいプラクティスがあると思いますが、参考になれば幸いです。