くりにっき

フルスタックキュアエンジニアです

「自前でGitLabを管理するために知っておかなければならないこと」の付録 #gihyosd

これは GitLab Advent Calendar 2019 - Qiita の18日目です。

大事なことなので最初に

僕がGitLab特集で書いた「自前でGitLabを管理するために知っておかなければならないこと」が掲載されているSoftware Design 2020年1月号の発売日なのでみんな買ってくれよな!!!

紙面の都合で当初のアウトラインや原稿から削った箇所があるのでアドベントカレンダーとして供養しようと思います。*1

付録A:GitLab.comを使いつつ自前のGitLab Runnerを使う

これはコラムで載ってはいるのですが、紙面の都合でだいぶ削ったので完全版を書きます。

以下、原稿での完全版


GitLabの面白い特徴として、GitLab Runnerを自由に登録できるというのがあります。

GitLab全体に登録するShared Runner以外にもグループ単位とプロジェクト単位で登録できるSpecific Runnerがあるため、 後者のSpecific RunnerであればSaaSであるGitLab.comであっても自前のGitLab Runnerを利用してCIを行うことができます。

SaaSであるGitLab.comだと全体で共有のShared Runnerが用意されているため通常は自分でRunnerを用意する必要はないのですが、下記のような理由でGitLab.comでもRunnerを自分で用意するメリットがあります。

1つ目の理由はShared Runnerの順番待ち回避のためです。前述の通りShared RunnerはGitLab.com全体で共有のため時間帯によっては待たされることもあります。 そのため自分でRunnerを用意することでこの順番待ちを回避できます。この自前のRunnerはShared Runnerとの併用もできます。

2つ目の理由はデプロイ用途です。CIは社外の環境で実行してもいいけど、デプロイだけはイントラネット内からの方が都合がいいことも多いでしょう。 外部からイントラネットにアクセスするための踏み台サーバを用意してもいいのですが、イントラネット内にデプロイ専用のRunnerを用意した方がセキュアで踏み台サーバ自体のメンテナンスコストも不要になるというメリットがあります。

下図のようにRunnerで指定したタグをジョブにも付与することで、指定したRunnerでのみそのジョブを実行することができます。

Runnerにdeployタグを設定する例

f:id:sue445:20191208011216p:plain

.gitlab-ci.yml でdeployタグを設定する例

deploy:
  script:
    - ./deploy.sh
  tags:
    - deploy

3つ目の理由はCIでの非Docker環境の利用のためです。GitLab.comのShared Runnerは全てDockerコンテナ内での実行が前提です。 通常のCIであればこれで問題ないのですが、WindowsMacなどのマシンのハードウェアの機能をCIで利用したいということもあるでしょう。 iOSアプリのビルドをXCodeがインストールされたMac上で行いたいというのが最たる例でしょう。 このような場合はMacのマシンにGitLab RunnerをインストールすることでGitLab CIで実現可能になります。

以上はGitLab.comで自前のGitLab Runnerを用意する理由になりますが、これらは自前のGitLabにおいても用途によってRunnerを使い分ける指針になると思います。

付録B:公式Dockerイメージを使う

こちらはアウトラインには書いたものの、原稿を書く前段階で分量が多くて紙面に入らない可能性が高かったため削った内容です。


公式ではOmnibus GitLabを推奨していますが、Dockerに慣れていて自分でKubernetesやDocker Swarmでクラスタが組める場合には個人的には公式のDockerイメージを使うのもありだと思います。

hub.docker.com

ただしGitLabを動かしているホストマシンのgitユーザのみのsshをDockerコンテナに通す方法があまり資料がないのですが、下記を参考に設定してみてください。

オンプレミスでいく場合KubernetesかDocker Swarmが候補として挙げられます。

GitLab公式でHelm Chartが公開されているのでKubernetes派の人はこれを使うといいでしょう。

https://docs.gitlab.com/charts/

ただしLimitationsにもあるように制限事項がいくつかあって、一番大きいと思われるのはGitLab Pagesが使えないことです。

クラウドであればEKSやGKSなどのマネージドサービスが候補として挙げられます。

しかし注意点が1つあります。

EKSを使う場合同じAWSのマネージドということでデータベースでRDSを使いたくなるかもしれませんが、RDSだとデッドロックが発生する事象があるためそこだけ注意してください。*2

3rd partyのDockerイメージですが https://github.com/sameersbn/docker-gitlab というのもあります。

こちらは実際にピクシブ社内で使っていてその運用事例を下記にまとめているので興味がある人はどうぞ。

inside.pixiv.blog

*1:付録公開にあたって編集者の方からはOKを貰っています

*2: https://gitlab.com/gitlab-org/gitlab/issues/30528#note_215845940

ERDをPlantUML形式で自動生成するツールを作った

PlantUML + ERDでPlantERDです

github.com

モチベーション

既存プロダクトへの不満が一番大きいです。

  • https://github.com/voormedia/rails-erd は出力が画像なので取り回ししづらい
    • そもそもRails前提なので他言語とかでは使えない
  • https://github.com/schemaspy/schemaspy も悪くなさそうなんだけどここまでリッチじゃなくていい
  • テーブル数個の小規模アプリならいいんだけど、中規模以上のアプリで使うと人間が読むに耐えないERDが生成されて精神が崩壊する
    • 僕は初めて触るアプリだと実装を把握するために特定のテーブルに隣接するテーブルのみ抽出して関係性を抽出してるんだけど、そういうことを自動でやりたかった

PlantERDの特徴

  • golangで作ったシングルバイナリなのでバイナリポン置きでどこでも使える
  • 出力形式がPlantUML(プレーンテキスト)なので取り回ししやすい
    • plant_erdで生成したERDをコミットすることでスキーマ変更前後の差分もgit上でいい感じに管理できると思います
    • UMLの出力だけ行うことで、テーブル配置の座標計算や画像生成などをツール内で考えなくてよかったのが嬉しい
  • 対応してるDBは下記
  • 「usersテーブルからForeign Key 2本以内の距離のテーブルまでを出力」のように、出力するテーブルを指定できる。(後述)

使い方

SQLite3だとこんな感じ。MySQLPostgreSQLもオプションが増えるだけで使い方はだいたい一緒です

$ ./plant_erd sqlite3 --database /path/to/test_db.sqlite3

entity articles {
  * id : integer
  --
  * user_id : integer
  --
  index_user_id_on_articles (user_id)
}

entity users {
  * id : integer
  --
  name : text
}

articles }-- users

f:id:sue445:20191212225017p:plain

出力するテーブル数の制限について

例えばこういう7つのテーブルがあるとしてます。

$ ./plant_erd sqlite3

f:id:sue445:20191212230004p:plain

これを

$ ./plant_erd sqlite3 --table articles --distance 1

のようにオプションを渡すことで、articlesテーブルからForeign Keyで隣接する距離1以内のテーブル(つまりarticlesテーブルと直接隣接するテーブル)のみ出力することができます。

f:id:sue445:20191212230239p:plain

技術的に頑張ったこと

テストのこと

ツールの性質上MySQLPostgreSQLといった外部のミドルウェアがないとテストができないという問題があります。

ローカル環境を汚したくなかったので このようなdocker-compose.yml を書いて docker-compose up するだけでローカルでMySQLPostgreSQLを使ったテストができるようにしています。(MySQLPostgreSQL環境変数がセットされていなければスキップしてSQLite3のテストしかしない)

あとマトリクステストを頑張ったのでDockerHubにあったMySQLPostgreSQLのイメージの主要バージョンは全部テストしています。

Foreign keyで隣接している別のテーブルを探す方法

大学の卒論でグラフ理論をやっていたのでその時の経験を生かしてgolangで無向グラフを実装しています。*1

詳しくはこの辺のソースを見てください。(途中まで行列を書いて数学的に説明を書こうとしてたけどブログウケしなさそうなのでやめた)

複数DB対応のつらみ

SQLDDLDMLなどはSQL99で仕様化されているので多少方言はあるものの各DB間でそんなに差異はありません。

しかしplant_umlで必要なテーブル情報やインデックス情報の取得方法は各DBの実装に大きく依存していたので実装するのはかなり苦労しました。

この辺に僕の苦労が詰まっています。

この辺の実装はactiverecordのconnenction_adaptersの実装をむっちゃ参考にさせてもらいました。

https://github.com/rails/rails/tree/v6.0.1/activerecord/lib/active_record/connection_adapters

追記:2019/12/13 9:45

id:tinsep19

schemespyでテーブルのリンク辿ると、距離1,2の関連図があるけどね

重箱の隅をつつくような細かい指摘ありがとうございます。

上にも書いているようにここまでリッチじゃなくていい(cliで完結したい)ってのと、gitでコミットした時に履歴が読める形式のdiffがほしいという理由でUMLで出力したいという気持ちがありました。(schemespyはサンプル見た感じ画像かsvgでしか出力してなかった。もし認識違ってたらまた重箱の隅をつつきにきてください)

あとPlantUMLは距離1,2に関わらず任意の距離の関連図を取れるのでそこだけは優位性がありますね。

*1:余談ですが大学の卒論ではC++で無向グラフを実装しました

Software Design 2020年1月号のGitLab特集に寄稿させて頂きました #gihyosd

商業誌デビューです!

gihyo.jp

f:id:sue445:20191207005442p:plain

きっかけ

GitLab.JP@hiroponz79 さんにお声がけいただいて参加することになりました。

僕の担当について

GitLab特集は全3章構成なのですが、僕は第3章の「自前でGitLabを管理するために知っておかなければならないこと」というタイトルで寄稿させて頂いています。

「自前でGitLabを管理するために知っておかなければならないこと」というタイトルの通り、僕の知ってるGitLab運用の知見を余すところなく書いています。

当初の予定だと GitLab令和最初のリプレイス。フルコンテナ化ポスグレ移行 - pixiv inside を深掘りした内容を書くつもりだったのですが、気づいたら全く別ベクトルのGitLab運用の話になって8〜9割くらい新規に書いていました。

GitLabが巨大なRailsアプリということもありどのRailsアプリでも汎用的に使えるunicorn-worker-killerの監視の話についても書いてるので、そっち方面での知見も得られると思ってます。

紙面の都合でいくらか内容を削ったので削った分は後日付録として別エントリで書こうと思ってます。*1

余談1

余談ですが、hiroponz79さんから声がかかったのと全く同時期に別方面でCI系の執筆依頼があって、てっきりSoftware DesignのCI特集で双方から僕にオファーがきたのかと思ったのですが、全く関係なかったというエピソードがあります。

ちなみに同時期に2本同時に執筆は体力的に厳しいのでもう片方のはお断りさせていただきました。

余談2

この記事の執筆中のBGA*2アイカツ! だったので、実質アイカツでできているといっても過言ではないです。*3

Amazonに自分の名前載ってるのすげーなw

f:id:sue445:20191207010829p:plain

*1:編集の人に問題ないことは確認済

*2:Back Ground Animation

*3:僕の好みはユリカ様です

HP Pavilion 23-q191jp ハイエンドモデルのメモリを増設した

3年前くらいに買った HP Pavilion 23-q191jp ハイエンドモデル のメモリを増設したのですが、分解に手間取った&ググっても詳しい分解手順が書いていなかったのでメモ

注意点

  • 自分で分解するとメーカー保証を受けられなくなるので自己責任で!

メモリ増設の経緯

今回8GBから16GBに増設したんですが、8GBだと下記のようにかなりストレスフルでした

  • PCの電源を入れてまともに使える(ChromeとSlackが操作できる)ようになるまで20分くらいかかる
  • Slackのワークスペースをたくさん開くとメモリを食いつぶす
  • とにかく重い
  • 全てが重い
  • メモリ8GBには人権がない

ここまでくると本当にPC買い直すかなって気持ちになってたのですが、8GBのメモリが数千円で買えたのでダメ元で増設しました

分解手順

1. モニタの下のキャップを2つ外してネジを緩める

ネジを外すのではなく緩めるのがポイント。ネジを緩めることでカバーを浮かせられるようになります

詳しい事 teru-maru.com

f:id:sue445:20191206225255j:plain

f:id:sue445:20191206225123j:plain

2. 背面のスタンドを外す

これが最難関ポイント

スタンドの根本部分

f:id:sue445:20191206221503j:plain

実はカバーになってて外せます。キッチリハマっているのでマイナスドライバーみたいに固くて平たいものを隙間に入れないと外せないです

f:id:sue445:20191206221448j:plain

カバーを外すとネジが4つあるので、こいつを外さないとカバーを外せません。

f:id:sue445:20191206214843j:plain

f:id:sue445:20191206214850j:plain

ちなみにノーヒントでここのネジの存在に気づくまで2日間くらいかかりました。。。

3. カバーを外す

ネジを外すことで前後に開けることができるのですが、これもキッチリハマっているのでマイナスドライバーなどでゆっくり外しましょう

4. 金属のカバーのネジを外してメモリを挿す

写真の右下部分のカバーがネジ4本で固定されているのでドライバーで外すとメモリが挿せるようになります

f:id:sue445:20191206215603j:plain

f:id:sue445:20191206220114j:plain

5. 組み立てる

逆順で組み立てる

6. タスクマネージャーで本当に16GB使えているか確認する

タスクマネージャーで確認したところ、メモリは16GB認識されているものの、「ハードウェア予約済み」が9GBあって16GB分フルで使えない感じでした

ググったら下記のように「ブート詳細オプション」の最大メモリにチェックが入っていてOSで使えるメモリの上限が8GBになっているようでした

blueskyzz.com

チェック外して再起動することで無事16GB使えるようになりました

f:id:sue445:20191206232716p:plain

メモリ増設の効果

PCの電源を入れて10分ちょいで使えるようになったので効果はあった模様

tanuki_reminderを作った

tanuki_reminderとは

マージされていないMRの一覧を指定した時間にチャットに通知するリマインダーで、 Pull Reminders のGitLabクローンです。

f:id:sue445:20191117012728p:plain

Pull Remindersが便利なのでGitLabでも使いたくて作りました。

gitlab.com

名前の由来

Pull Panda がパンダなので動物つながりでGitLabなのでタヌキにしました

余談ですがtanuki呼びは公式設定です

f:id:sue445:20191117084501p:plain

https://gitlab.com/gitlab-org/omnibus-gitlab/blob/12.3.4+ee.0/files/gitlab-ctl-commands/upgrade.rb#L178-197

技術的なこと

使い方

.gitlab-ci.yml に下記のようなジョブを書いてschedulerに登録するだけで使えます。

include:
  - remote: https://gitlab.com/sue445/tanuki_reminder/raw/master/ci-templates/slack_reminder.yml

slack_reminder:
  variables:
    # GITLAB_ACCESS_TOKEN: "" # [required]
    # INCLUDE_WIP: "false"    # [optional] put `true` or `false`
    # MERGE_REQUEST_COUNT: 10 # [optional]
    # SLACK_WEBHOOK_URL: ""   # [required]
    # SLACK_CHANNEL: ""       # [optional]
include:
  - remote: https://gitlab.com/sue445/tanuki_reminder/raw/master/ci-templates/chatwork_reminder.yml

chatwork_reminder:
  variables:
    # GITLAB_ACCESS_TOKEN: "" # [required]
    # INCLUDE_WIP: "false"    # [optional] put `true` or `false`
    # MERGE_REQUEST_COUNT: 10 # [optional]
    # CHATWORK_API_KEY: ""    # [required]
    # CHATWORK_ROOM_ID: ""    # [required]

通知対象のリポジトリにリマインダージョブを書いてもいいですが、通知専用のリポジトリを作ってschedulerに通知対象のリポジトリを複数登録するのが運用的に楽だと思います。

基本的にはGitLab CI上で動かすことを想定していますが、ビルド済のバイナリやDockerイメージでも配布しているのでGitLab CI以外でも使えると思います。

GitLab CIとCircleCIのキャッシュ戦略の違い

仕事でCI全般のお悩み相談されることが多くて .circleci/config.yml.gitlab-ci.ymlリファクタリングすることがよくあるのですが、その時に一番意識してるキャッシュ戦略について長年自分の中の暗黙知になっていて明文化できてなかったので書きます。

前置き

用語の定義

GitLab CIとCircleCIで用語が違っているので説明しやすくするために最初に定義します

  • キャッシュ:CIのビルドの実行を高速化するために保存しておくもの
  • ジョブ : CIで実行するスクリプト群の最小単位
  • ワークフロー:複数のジョブの集合で1コミットで実行される最小単位
    • GitLab CIだとpipeline、CirclrCIだとworkflowにあたる

その他

  • あくまで僕のポリシーなので異論は認めます

GitLab CIとCircleCIの両方に共通すること

キャッシュを過度に使いすぎない

複数のジョブ間でファイルを受け渡すためにキャッシュを利用することがよくあると思うのですが、個人的にこれはアンチパターンだとおもています。

理由はジョブ間が密に依存しているとCIの設定ファイル全体が複雑になり、リファクタリングの障壁になるためです。僕がCIの設定ファイルのリファクタリングをする時もまずはじめにこの手のキャッシュ依存をなくし、各ジョブをステートレスにしてワークフロー全体をシンプルにしています。

許容できるケース

CircleCIの persist_to_workspaceattach_workspace です。

https://circleci.com/docs/ja/2.0/configuration-reference/#persist_to_workspace

これは同一ワークフロー内でジョブ間でファイルを受け渡すための機能なので無問題。

特に persist_to_workspace を複数回読んだ後に attach_workspace を呼び出すとそれまでの persist_to_workspace で保存した結果全てが取り出せて便利です。

GitLab CIだとキャッシュのKeyに $CI_PIPELINE_ID *1を入れることで persist_to_workspaceattach_workspace に近いことはできますが、GitLab CIだと1つのジョブで1種類のキャッシュしか使えない関係で前述のように複数のキャッシュをマージして受け渡せないという問題があり、キャッシュに依存しすぎてると詰むことになります。

GitLab CIだとArtifactsでファイルの受け渡しをするのであれば可。

11/6追記

GitLab CIでもArtifactsでジョブ間でファイルのやり取りできたのを失念してたので修正。

ないと困るものはキャッシュにしない

キャッシュはあくまでもビルドを速くするために存在するので、キャッシュがあればラッキー、キャッシュがなくてもビルドが多少遅くなるだけでビルド自体は成功するって形の方がハマりが少ないです。

10/30 12:00追記

ここで言っているキャッシュというのは、例えばジョブの前段でライブラリをインストールして後続のジョブでそのライブラリがインストールされていることを前提にしているようなケースで引き回しているキャッシュのことを言っていて、ビルドの最終成果物であるArtifactsとは異なります。

ライブラリのインストールに時間にもよるんだけど基本的にはジョブわけない方がシンプルでメンテしやすいです。

persist_to_workspace 使うかどうかは好みによります。(個人的にはこの程度なら使わなくてもいいのでは派)

キャッシュをバージョニングする

キャッシュを互換性のないレベルで仕様変更するために古いキャッシュが使われないようにしたいことがあると思います。

GitLab CIだとリポジトリ単位でCIのキャッシュの全クリアができるのですが、CircleCIだとそういう機能がありません。

僕はよく v1-xxxx のような感じに先頭にバージョン情報を付与していて、必要に応じてインクリメントしています。

複数のブランチでキャッシュを共有できるようにする

キャッシュのkeyにブランチ名が入っているとその異なるブランチ間でキャッシュの共有ができません。

そうすると初めてブランチをpushした時に必ずキャッシュが存在しない状態でフルで bundle installnpm install されるような状態になるので、CI全体の時間で見ると遅くなります。

複数ブランチでキャッシュが共有されるとライブラリのインストールやバージョンアップが複数ブランチで同時進行しているとインストール合戦になってキャッシュがうまく使われなくなるデメリットがありますが、そういうのはレアケースなので複数ブランチでキャッシュが共有されて普段のビルドが速くなる方がメリットの方が多いと思ってます。

GitLab CI固有の話

GitLab CIのキャッシュの仕様

  • 同一のkeyに対して上書きができる
  • キャッシュのkeyは完全一致
    • keyに一致したキャッシュが存在しなければ取得されない
    • GitLab CIのキャッシュの実態はzipファイルなので、キャッシュのkey名でzipファイルが作られていると考えればイメージしやすいでしょう

CircleCI固有の話

CircleCIのキャッシュの仕様

CircleCIのキャッシュはGitLab CIに比べてトリッキーなので注意が必要です。

同一のkeyで一度キャッシュが作られたら上書きができない

save_cache で同名keyでキャッシュが保存できなくても「そのkey名で既にキャッシュを作成してるから作らなかったよ」ってログが出るだけでエラーにはならないため気づきづらいです。

https://circleci.com/docs/ja/2.0/configuration-reference/#save_cache

そのためCircleCIでは myapp-{{ checksum "package-lock.json" }} のようにキャッシュ全体のdigestをキャッシュのkeyに付与する必要があります。

キャッシュのkeyは前方一致

割と勘違いされがちなのですが、CircleCIのキャッシュのkeyは完全一致ではなく前方一致です。(完全一致するkeyがなければ前方一致するkeyのうち最新のものが返る)

またCircleCIの restore_cache では下記のようにキャッシュのkeyを複数設定でき、上から順番に評価されます。

- restore_cache:
    keys:
      - v1-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Gemfile.lock" }}-{{ .Branch }}
      - v1-{{ .Environment.CIRCLE_JOB }}-{{ checksum "Gemfile.lock" }}
      - v1-{{ .Environment.CIRCLE_JOB }}
      - v1

この場合のキャッシュがヒットするルールはこんな感じです

  1. 同一ジョブ・同一Gemfile.lock・同一ブランチでキャッシュが存在すれば復元される
  2. 1でキャッシュが復元できなかった場合、同一ジョブ・同一Gemfile.lock・別ブランチの一番新しいキャッシュが復元される
    • 大抵の場合masterブランチで保存したキャッシュが復元される
  3. 2でキャッシュが復元できなかった場合、同一ジョブ・異なるGemfile.lock・別ブランチの最新のキャッシュが復元される
  4. 3でキャッシュが復元できなかった場合、同一バージョン(v1)のキャッシュが復元される
  5. 4でキャッシュが復元できなかった場合、キャッシュは復元されない

https://circleci.com/docs/ja/2.0/configuration-reference/#restore_cache

どうしても最新の状態でキャッシュを保存し続けたい時

同一key名だとキャッシュを上書きすることができないのですが、下記のように save_cacheepoch (エポックタイム)を含んだkeyのキャッシュを作ることで、 restore_cache で最新のキャッシュ復元できるようになります。

- restore_cache:
    keys:
      - v1-

- run: # キャッシュを作成する処理

- save_cache:
    key: v1-{{ epoch }}
    paths:
      - /caches/image.tar

せやかて

CircleCIのキャッシュの仕様を完全理解した状態で設定を書くのは無理ゲーだし設定ファイルも複雑になりがちなので他の人が作ったorbを使うのがいいでしょう。

Rubyリポジトリであれば下記orbに僕のCircleCIキャッシュの知見が全て詰まっているので何も考えなくてもいい感じにキャッシュを活用することができます。

https://circleci.com/orbs/registry/orb/sue445/ruby-orbs

詳しくは下記を参照

sue445.hatenablog.com

vagantでgem名とrequireする名前が異なるgemを使う時には注意が必要

タイトルが全て

経緯

https://hub.docker.com/r/sue445/vagrant-aws/ っていうDockerイメージをメンテしてて*1Vagrant 2.2.6が出たタイミングでDockerイメージのビルドがコケるようになりました

https://app.circleci.com/jobs/github/sue445/dockerfile-vagrant-aws/494

Installing the 'vagrant-serverspec' plugin. This can take a few minutes...
Vagrant failed to properly resolve required dependencies. These
errors can commonly be caused by misconfigured plugin installations
or transient network issues. The reported error is:

activesupport requires Ruby version >= 2.5.0.
The command '/bin/sh -c vagrant plugin install vagrant-aws  && vagrant plugin install vagrant-serverspec' returned a non-zero code: 1
Exited with code 1

ビルドコケてる原因はいたってシンプル*2でそれ自体は一瞬で直せました。

github.com

+# FIXME: Remove following when vagrant embed ruby is updated to 2.5+
+#        (vagrant embed ruby is 2.4.9, but activesupport 6.0+ requires ruby 2.5+)
+RUN vagrant plugin install activesupport --plugin-version 5.2.3
+
 RUN vagrant plugin install vagrant-aws \
  && vagrant plugin install vagrant-serverspec

誰だってそーする、俺もそーする。

docker buildも成功してvagrantのコマンドもCI上で一通り叩けることも確認してたんですが*3、実際に使おうとすると下記のようなエラーになってちょっとハマってました

root@b9657e1e1d4a:/# vagrant up
Vagrant failed to initialize at a very early stage:

The plugins failed to load properly. The error message given is
shown below.

cannot load such file -- activesupport

原因

vagrantのソース内の実装までは見つけられてないですが、vagrant plugin install でインストールしたgemは vagrant up などのコマンド実行時に自動的にgem名でrequireされているのが原因でした。(activesupportrequire する時は active_support になるのでエラーになってた)

requireする名前を明示的に指定する方法がないかなと思って探したら vagrant plugin install--entry-point があって、それを使うことで解決できました。

github.com

 # FIXME: Remove following when vagrant embed ruby is updated to 2.5+
 #        (vagrant embed ruby is 2.4.9, but activesupport 6.0+ requires ruby 2.5+)
-RUN vagrant plugin install activesupport --plugin-version 5.2.3
+RUN vagrant plugin install activesupport --plugin-version 5.2.3 --entry-point active_support

*1:vagrant-awsのインストールに数分かかるのでCI用にDockerイメージを作ってる

*2:Dockerイメージにvagrant-awsと一緒についでにvagrant-serverspecをインストールしていて、vagrant-serverspecの依存にactivesupportがいて、activesupport v6がリリースされたタイミングで最新版がRuby 2.5以降必須(vagrantに添付されてるRubyのバージョンが2.4.9)になったのが原因

*3:https://github.com/sue445/dockerfile-vagrant-aws/blob/2.2.6/.circleci/config.yml#L58-L61