839の日記

趣味の話を書くブログです。

アクセス元に応じてCloudflare Tunnel or Directな経路を通す

背景

自鯖Webアプリに出先からアクセスするためにCloudflare Tunnel(プロキシ) + mTLSの構成を取っていた。 LAN内にいるときはプライベートIPなどを直接指定してもアクセスできるのだが、URLを都度変えるのが面倒なのでCloudflare Tunnel経由でアクセスしていた。

ただCloudflare Tunnel経由だといくつかネガティブな点があるためLAN内では直接アクセスしたくなった。

主なモチベーションは以下の2点。

  • Cloudflare Tunnel + mTLSは証明書の設定がされていても確率的にエラーになる問題に悩まされていたので、せめてLAN内ではストレスから解放されたい*1*2
  • インターネット(Cloudflare Tunnel)を経由すると写真の同期が遅くなったり、最大100MBまでしか通信できないなどの制約があるのを解消したい*3

最終的に目指しているのは同じURLにアクセスしても、アクセス元に応じて適切な経路が透過的に選択される仕組み。

  • LAN外からのアクセスの場合:Cloudflare TunnelとmTLSを経由して自鯖Webアプリへ接続
  • LAN内からのアクセスの場合:直接自鯖Webアプリへ接続

構成

元々のCloudflare Tunnelの設定は以下のような感じ。

ingress:
  - hostname: app1.example.com
    service: http://example1
  - hostname: app2.example.com
    service: http://example2

外からアクセスする際は https://app1.example.com でアクセスするため、LAN内でも同様にアクセスするためにSSL証明書を取る必要がある。 オレオレ証明書を作って各端末に配布するのは面倒だし、LE証明書の更新を自前構成で管理するのも嫌だったのでcaddyを使ってみることにした。

PVEでcaddy用のdebian LXCを作り、その中でdocker composeで起動させ、コンテナの管理はsystemdに任せる。 cloudflareでDNS-01 Challengeを行うので対応したイメージを利用。

services:
  app:
    image: ghcr.io/caddybuilds/caddy-cloudflare:2.9.1
    restart: unless-stopped
    cap_add:
      - NET_ADMIN
    ports:
      - 80:80
      - 443:443
      - 443:443/udp
    volumes:
      - /etc/caddy/conf:/etc/caddy
      - /etc/caddy/site:/srv
      - caddy_data:/data
      - caddy_config:/config
    environment:
      - CLOUDFLARE_API_TOKEN=XXX

volumes:
  caddy_data:
  caddy_config:

Caddyfile

{
  acme_dns cloudflare {env.CLOUDFLARE_API_TOKEN}
}

app1.example.com {
  reverse_proxy http://example1
}

app2.example.com {
  reverse_proxy http://example2
}

LAN内のDNSについてはOpenWrtで設定した。LAN内にDNSサーバーがあれば何でもよい。 普段はNextDNSを使っているため、家のWi-Fiの接続設定->DNS->手動設定で今回の用意したサーバーを使うようにした。

config domain
        option name 'app1.example.com'
        option ip 'caddyを動かしているプライベートIP'

config domain
        option name 'app2.example.com'
        option ip 'caddyを動かしているプライベートIP'

Cloudflareのトークン指定とcaddyの設定がうまく動作していればcaddy起動時に証明書を取得してくれる。

ERR_SSL_PROTOCOL_ERROR の解消

Chromeでアクセスすると確率的に ERR_SSL_PROTOCOL_ERROR が表示され、アクセスできない問題に遭遇した。 この問題は2025/3時点でChrome系のブラウザだけで確認しており、FirefoxやSafariなどでは起きないという特徴もあった。

調べたところ、Cloudflare(プロキシ)ではデフォルトでTLSv1.3が使われており、かつECHの利用もデフォルトで有効化されているためだった。 あんまり調べていないがECHに対応しているのが現時点ではChromeだけなのでFirefoxやSafariで問題が起きなかったのだと思う。 ECHの影響であるという切り分けにはChromeのNetLogを利用した。

ECHを無効化するにはTLSv1.2を使うか、(フリープランなので)APIを叩いてECHを無効化するかの2択だったが、TLSv1.2を使い続けても時間の問題になりそうなのでcliでECHを無効化することにした。

# 実行前
$ > curl https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/ech \
  -H "X-Auth-Email: $EMAIL" \
  -H "X-Auth-Key: $CLOUD_FLARE_GLOBAL_API_KEY" \
{"result":{"id":"ech","value":"on","modified_on":null,"editable":true},"success":true,"errors":[],"messages":[]}

# ECH無効化
$ > curl -XPATCH https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/ech \
  -H "X-Auth-Email: $EMAIL" \
  -H "X-Auth-Key: $CLOUD_FLARE_GLOBAL_API_KEY" \
  -H "Content-Type: application/json" --data '{"id":"ech","value":"off"}'

# 実行後
$ > curl https://api.cloudflare.com/client/v4/zones/$ZONE_ID/settings/ech \
  -H "X-Auth-Email: $EMAIL" \
  -H "X-Auth-Key: $CLOUD_FLARE_GLOBAL_API_KEY" \
{"result":{"id":"ech","value":"off","modified_on":null,"editable":true},"success":true,"errors":[],"messages":[]}

しばらくするとECHが返ってこなくなっていることを確認。

$ > curl "https://dns.google/resolve?name=your.domain.com&type=HTTPS"
{"Status":0,...,ech=XXX ipv6hint=...}

$ > curl "https://dns.google/resolve?name=your.domain.com&type=HTTPS"
{"Status":0,..., ipv6hint=...}

また、caddyでもちょうど先週ECH対応が入り2.10系からサポートする*4と見かけたが恐らく本件の問題に直接関係はないので2.10系に乗り換えても問題は解決しないと思う。

参考

*1:iPhoneの場合、プライベートウィンドウで開くことで高確率でこの問題を回避できる

*2:AndroidのChromeやPCでは比較的起きにくい気がする

*3:obsidianのlivesyncサーバーをsync mode: livesyncで運用していて結構な頻度でアクセスしているのもあり、LAN内で解決できるならそうしたい

*4:https://github.com/caddyserver/caddy/issues/4221#issuecomment-2702361739

ミニPC(N150)+OpenWrtで10Gbpsルーターを作る

背景

1Gbps回線+RTX830から10Gbps回線にするにあたってRTX1300が欲しかったのですが、流石に高いのでもっといい選択肢はないものか…と考えていたときに以下のツイートを見かけたのがキッカケです。*1 稼働し始めて1週間ぐらい経ち、概ね問題もなさそうなのでまとめていますが長期的なリスクは未検証である点を留意してください。

下準備

R2 Max - Next-Gen 10G Firewall Gateway Server, Updated to Intel Twin Lake-N N150, N355 – iKOOLCORE CPUはN150、NICはAQC113C x2, i226 x2のミニPCです。

RTX1300所有者が退役させて切り替えるぐらいなので良いのでは?と思いつつ商品ページを見に行き、日本円だと結局だといくらなのか確認するためにチェックアウト操作をしていたら誤購入しました(ガチ)。 即キャンセルすればキャンセルはできるかなと思ったものの、まぁ押してしまったし作るか~と気持ちを切り替えて到着を待ちました。 自分が購入したタイミングはちょうどN150に切り替わったタイミングでメモリ、ストレージ無しのPaypal払いで約5万でした。

メモリはCrucialのCT8G48C40S5 4000円、ストレージはmicroSDのSanDisk 32GB MAX Endurance 1915円で購入しました。 最初はPVEで運用する想定もありストレージに余っているSSD(1TB)を使っていたのですが、PVE化は断念(後述)して容量の使い道がなくなったのでmicroSDに移行しました。 SSDにする場合はSPから出ている128GBのPCIe3.0x4が2300円ぐらいなのでそのあたりで十分*2だと思います。 microSDにすることで多少省電力になることを期待したのですがほぼアイドルだったのか消費電力は変わりませんでした。

商品ページでN150はファンレスと書かれていますが、自分が購入したとき(2025/2末)は確かファンレスと書かれておらずファン付きのモデルが到着しました。 海外Youtuberの解説を見てるとどうもファンレスケースとファン付きケースは構造が微妙に違うらしい?ファンレスケースのほうがファンなしで冷える構造になっているかもしれないです。 ファンはBIOSからPWMで調整可能で初期設定はすぐ回り始めますが、正直結構不快な音がするので割と熱くなるまで回さないように設定しました。 夏にガンガン回るようなら外付けファンで冷やすのも検討しようと思っています。

ちなみにAmazonでも非常に似た構成の製品が売られてます。 刺さる人には刺さるキワモノ。 AIOPCWA「AI407」はデュアル10GbE+デュアル2.5GbEなIntel N150/i3-N305搭載ミニPC | がじぇっとりっぷ 海外サイトやPaypalを使いたくない人はこちらもありかもしれません。

スペック的に異なるところはインタフェースの口が色々増えていたりアンテナホールがあったりする代わりにTF(microSD)カードスロットがないことでしょうか。 見た目はほぼ同じでAIOPCWAのマークがついてるかついてないかぐらいの違いしかなさそうです。 R2 Maxは公式サイトや iKOOLCORE 官方Wiki | 硬酷科技Wiki | 硬酷R2 Max wiki | 硬酷R2 wiki | 硬酷R1 Pro wiki などのwikiでBIOS、ファームウェア更新などにも解説がありますが、AIOPCWAは公式ページを見つけられませんでした。

AIOPCWAはALL M.2 NASの自作を検討していた際にも目に止まったメーカーで結構ニッチな商品を出してるなという印象です。 AI407を買う人がいたらぜひレビューを聞かせてください。

OpenWrt環境の作成

ブツが届いて触ると結構ずっしりしていて(1kg)、金属~という感じでした。 最初はPVEのVMとしてOpenWrtを入れるつもりで、鶏卵問題が発生しそうなのでどうやるのだろう?と思っていたのですが、 ミニPC「HUNSN RJ03」へProxmoxとOpenWrtをインストール、最強ルーターを自作 | Wi-Fiマニュアル で丁寧な解説があったのでこちらの通りに導入したらいい感じに動きました。 ただPVEのVMとして構築すると期待通りのスループットが出なかったので、色々試した後に諦めました。

iKoolcoreのページではQWRTのDLリンクが案内されていますが、自分は普通のOpenWrtで運用したかったので使わず。 QWRTはファームウェア全部入りイメージなので何もしなくてもそのまま使えますが、自前でOpenWrtを入れる場合は kmod-atlantic を入れないと10G NICが反映されないので最低限入れる必要があります。

実際にspeedtest.netで計測を行ってみると5.5Gbpsあたりで頭打ちになってしまい色々と設定を変えて頭を悩ませていたのですが、最終的にパケットステアリングの設定で 有効 (全てのCPU) を選択するだけで概ね満足するスループットが出るようになりました。DLは恐らくISP側(eo光)の頭打ちなのですが、ULはこちら側の問題のような気はしています。このタイミングでSFO/HFOも試していたのですが、これらは有効化しても変わらず or 遅くなるという状況だったためオフにしています。

消費電力は大体平均14W前後で、スピードテストなどで高負荷をかけて26, 7W付近まで上昇します。 RTX1300の消費電力が最大26Wとなっているので10Gルーターとしては妥当なのかな?と思っています。

温度は大体この時期で40~50度ぐらい。一応55度超え始めぐらいからファンを回す設定にしています。

上4つは10G NICの温度、下5つはCPU温度

機材不足で検証しきれていない課題としては、iperf3のreceive6.3Gbpsで頭打ちになる現象で、sendは9.5Gbps出ているので問題ありませんでした。 大きく分けて外から繋いでいるNIC(AQC107)の問題か、外れ個体かの2択ぐらいだとは思っているのですが、まだそこまで困っていないのでもう1つ10GbEのNICが必要になったタイミングで確認してみようと思っています。Ubuntuを入れてspeedtest cliを試したときにもアップロード側が6G程度で律速していたのでOSは関係なさそうでした。他のユーザーでは同様の現象は発生していなさそうなのでおま環っぽいのですが、皆目検討がつきません…。

PVE環境の検証

PVE上でOpenWrtを動かすとスループット出ない問題についてはPVEの問題かVM(OpenWrt)の問題かの切り分けを行って、VMの問題の可能性が高い、ということがわかりました。 PVE単体で動作させる分には直接OpenWrtを入れたときと同程度のスループットが出ます。 PVE+OpenWrtのスループットの劣化具合は

このぐらいで、ここまで劣化するならPVE化は諦めるか…となりました。 正直リソースは余ってる感じがするのでPVEで細かいものをN150マシンで動かせると消費電力的にコスパ良さそうだなと思っていたので残念です。 一方ルーターは常に安定稼働しておいてほしいので、ルーターとしてだけ動いてもらうほうが良いかなと思うところもあります。

AQC113のNICが載ったマシン*3が別途着弾予定なのでPVEでDebianなどの適当なVMを動かしたときのスループットも計測していたのですが、こちらは概ね4~8%程度の性能低下という形でした。 この程度の性能低下であれば許容してPVEで運用する予定です。

pingのレイテンシ

自分の環境でもRTX830と比較してみたのですが、0.3~0.4msぐらいは遅い傾向がありました。 C-StatesをDisableすると0.3ms程度改善して概ねRTX830と同等のレイテンシになったのですが、消費電力が爆上がりして14W程度だったのが23W程度に…。 紹介されてた記事では16%ぐらい消費電力が上がると書かれていたのですが、60%上昇です。

対戦型ゲームなどをする上ではこの0.3msを削りたくなりそうなのですが、自分はあまりやらないので電力と相談してとりあえずC-StatesはEnableのまま運用することにしました。 自分が計測した結果は以下です。

機器 試行回数 C-States min avg max stddev watt
RTX830 300 - 0.284 0.639 1.502 0.240 5.9
R2 Max 300 Enable 0.463 1.039 2.186 0.267 14.2
R2 Max 300 Disable 0.305 0.702 1.420 0.262 22.8

OpenWrtにして良かったポイント

今回がOpenWrtデビューだったのですが、OpenWrtにしてよかった~と思う点を挙げていきます。

メトリクスがGrafanaで見れる

各種スマートメーターや自宅鯖で運用しているメトリクスはprometheus+grafanaに集約しているので、一元化できるのは満足度が高いです。 自分が使っているダッシュボードは

Multiple OpenWRT infrastructure | Grafana Labs

OpenWRT | Grafana Labs

の2つです。 無線APも複数OpenWrt対応ルーター(GL-MT3000)で動かしているのでMultipleの方で全体を俯瞰することが多いです。 MT3000のファームウェアはStableだとWiFiのメトリクスが取れなかったので24.10系のファームウェアに更新しています。

以下は公式サンプルの画像ですが、複数鯖をいい感じに一覧表示できてテンションが上がります。

ただ各情報のソート情報が無秩序で上のメーターと下のメーターの順序が一致していない、みたいなことが発生するのが難点です。

ルーターの消費電力もgrafana上で表示して負荷に合わせて消費電力が変わってる~といった感じで眺めてます。 消費電力の計測には Wi-Fi ワットチェッカー RS-WFWATTCH2|ラトックシステム公式サイト をメインで運用して、ちょろっと測りたいときだけSwitchbotのプラグミニを利用しています。 プラグミニの方はスマホアプリで開いたときしか細かく計測してくれないのであまり実用性がないなと感じています。 ラトックはWiFiに繋がり、そのIPに対してリクエストするとメトリクスを教えてくれるのでgoでexporterを書いて集計しています。

ネットワークに少し詳しくなった

知らないことだらけで非常に楽しかったです。 最初は単純に速度が出ないという点を改善していたのですが、次はWiFiの電波が気になりだし、ローミングの仕様を調べたりとか。 メーカーが言ってるメッシュWiFiって実際何?みたいな話も今回のタイミングで意識するようになりました。

OpenWrtで複数AP間でWifiローミング&バンドステアリングする:802.11k/v/r - 溜池の南側
嘘と誤解だらけのメッシュWiFiの実態と、逆襲の中継機 : Misc Mods

Wi-Fi環境チェックツールの決定版! 無料の「WiFiman」アプリを試す【イニシャルB】 - INTERNET Watch

このアプリを使うと電波状況とローミングの発生を同時に見れるので設定確認に便利でした。

2F->1Fのローミング

バックアップで大体戻る

RTXで言うところのconfigエクスポートと同じような機能があるため、バックアップのリストアで大体すぐに環境を戻せます。 OpenWrtはsquashfsとext4のイメージが提供されていますが、squashfsのほうがリストア時の再現度が高いと見たので自分はsquashfsで運用しています。

スケジュールタスク(cron)でバックアップコマンドを定期的に実行できるので1時間に1回実行し、NASのNFSをマウントして7日分保持するようにしています。

0 * * * * sysupgrade -b /mnt/backup/openwrt/backup-${HOSTNAME}-$(date +'%Y%m%d_%H%M%S').tar.gz
0 * * * * rm $(find /mnt/backup/openwrt -name '*.tar.gz' -mtime 7)

チューニング中に変なところを弄っていたら一度起動しなくなってしまいクリーンインストールしたのですがバックアップのおかげですぐに復旧して作業を継続することができました。 この手の作業をしているとネット遮断による家族への迷惑が発生するため、RTX830にも大体同じ設定をしておき、何かあったらRTX830で一旦急を凌ぐということもしていました。

外部アクセス周りの環境整理

元々自宅鯖でまばらにcloudflare tunnelを動かしていたのですが、ルーターは常に起動しておくものなので全部ここに集約しようという思いで整理しました。 ついでにtailscaleもこのタイミングでデビューしました。 どちらもOpenWrtならではという感じではないのですがいいきっかけにはなりました。

OpenWrtと直接関係ないですがcloudflare tunnelとtailscaleはmtlsを許容できるサービスかどうかで使い分けを行っています。 外からアクセスできる口は可能な限り攻撃される可能性を減らしたいので、cloudflare tunnel経由で公開するサービスはmtlsを必須としています。 cloudflareでmtls用の証明書を発行し、その証明書を使ったアクセスじゃないと遮断してくれるのでセキュアに自分用のサービスを外の世界に置くことができます。*4 Immichもmtlsに対応してくれていたりして、非常に助かります。

地味困りポイントとしてはcloudflareのmtlsは証明書を持っているのにも関わらず何故か遮断されることが多いという点です。 cloudflare側でQUICがデフォルトで有効化されていて、それがChromeと相性悪いらしいのですが、無効化してもその問題はちょくちょく起きます。 プライベートウィンドウで開いたりブラウザを再起動したりすると証明書を使うか聞き直してくれることもあるのでそのワークアラウンドで耐えています。

一方外から自宅鯖にsshしたいとか、Sambaにアクセスしたいなどはmtls経由だと結構手間がかかります。 PCを使ったsshぐらいならcloudflare tunnel自体がサポートしてくれているので何とかなるのですが、スマホからsshしたいとかNASのSambaにアクセスしたいとかは結構大変です。 NextCloudなどはmtlsに対応してくれているのですが、対応している風なだけでバグだらけで個人的には使い物にならないのでNASへのアクセスが一番助かっています。 iPhoneの場合は特定のアプリを開いたときにtailscaleのVPNを起動するといったショートカットを仕込むこともできるので概ね満足なのですが、オンオフにちょっと時間がかかるので可能であればcloudflare tunnel経由でのアクセスに寄せています。cloudflare tunnelは最悪サービスが閉じられても過去に自前で同じような環境を作ってたのでポータビリティの面でも安心できます。

見送ったもの

  • NextDNS
    • NextDNSを有効化しているとたまに見れないサイトがある
    • そういうときにルーターの根本から有効/無効を切り替えるのが面倒
  • AdguardHome
    • NextDNSと同じような機能っぽい
    • 速度全体の低下の報告があったため
  • QoS
    • 単純に動作が確認できなかった…
    • 通常のQoSは入れるとネットワーク全体が低速になり、nftablesの方は全く効かなかった
    • SFO/HFOは使ってない

まとめ

満足です!

  • eoå…‰10G回線のipv4でDL: 8Gbps, UL: 6Gbpsまで確認 ( kmod-atlantic とパケットステアリングの設定をする)
  • iperf3を使ったLAN内計測ではDL: 9.5Gbps, UL: 6.3Gbps程度まで確認
    • ULの律速原因はわからず、誰か助けて
  • 消費電力は平時14W、高負荷時26W程度
  • OpenWrtデビューだったが無事運用できている

*1:10G回線を契約したときに送られてきたHGWにルーター機能もあって最低限はそれで何とかなった。ただRTX830でできることは最低できてほしいという要求もあったので結果的には良かった

*2:帯域は2つのスロット合計で985MB/s

*3:ugreenのnasync、DXP4800 Plusです。OSをPVEにしてNAS+雑多アプリを稼働させる予定。ハードウェア構成が超魅力的だったのと40%オフで購入できたので採用

*4:ただし1度のリクエストの転送量が100MBまでになる縛りはある

Rails 6.1, Ruby3.0.0(rc1)に上げるまでに踏んだ問題

趣味で動かしているRails6.0.x、Ruby 2.7.1のアプリ3つをRails 6.1、Ruby 3.0.0(rc1)にまで上げようと試みた。 そのときに遭遇した問題をまとめたもの。

ひとまずRails 6.1、Ruby 2.7.2まで上げることにした。 以下、その過程で踏んだ問題。

rails aborted! LoadError: cannot load such file -- listen

rails app:update を叩いたときに出てきた。

gem 'listen'

を追加。

Rails 6.1はActiveStorage周りのスキーマも変わっていて、 rails app:update を実行するとマイグレーションファイルが追加されるので実行をお忘れなく。

NoMethodError: undefined method `parent' for MyApplication::Application:Class

rails db:migrate を実行したときにrails-erdで出る。 Rails 6.1 support · Issue #363 · voormedia/rails-erd · GitHub の通り、issueは立っているが作者が1年ぐらい更新してないので更新されるか望み薄。 とりあえずフォーク版で修正してくれていたのでそれを使うようにした。

  gem 'rails-erd', github: 'guapolo/rails-erd'

uninitialized constant NestedForm::BuilderMixin (NameError)

同じく rails db:migrate の時に発生。rails_admin の問題の様子。 config/initializers/rails_admin.rb に2行のrequireを追加した。

refs: uninitialized constant NestedForm::BuilderMixin · Issue #887 · sferik/rails_admin · GitHub

require "nested_form/engine"
require "nested_form/builder_mixin"

NoMethodError: undefined method `assert_nothing_raised'

RSpecのSystemSpecで出るようになっていた。 rspec - Rails 6.1 upgrade: undefined method `assert_nothing_raised' - Stack Overflow これと同じ問題だったのでspec/rails_helper.rb に以下を追加

  config.include ActiveSupport::Testing::Assertions

SystemStackError - stack level too deep

developmentで試しに動作確認しようとしたら出た。

stacktrace的に meta_request というGemが怪しかったので試しにオフにしたら解決。 特に使っていなかったのでそのまま削除。

activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:174:in `block in as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `each'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:57:in `as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:174:in `block in as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `each'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:174:in `block in as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `each'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:173:in `as_json'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:57:in `as_json'
activesupport (6.1.0) lib/active_support/json/encoding.rb:35:in `encode'
activesupport (6.1.0) lib/active_support/json/encoding.rb:22:in `encode'
activesupport (6.1.0) lib/active_support/core_ext/object/json.rb:42:in `to_json'
meta_request (0.7.2) lib/meta_request/event.rb:46:in `block in json_encodable'
meta_request (0.7.2) lib/meta_request/event.rb:66:in `block in transform_hash'
meta_request (0.7.2) lib/meta_request/event.rb:60:in `each'
meta_request (0.7.2) lib/meta_request/event.rb:60:in `inject'
meta_request (0.7.2) lib/meta_request/event.rb:60:in `transform_hash'
meta_request (0.7.2) lib/meta_request/event.rb:38:in `json_encodable'
meta_request (0.7.2) lib/meta_request/event.rb:15:in `initialize'
meta_request (0.7.2) lib/meta_request/app_notifications.rb:69:in `new'
meta_request (0.7.2) lib/meta_request/app_notifications.rb:69:in `block in subscribe'
meta_request (0.7.2) lib/meta_request/app_notifications.rb:81:in `block in subscribe'

NameError: uninitialized constant ActiveJob::Logging::LogSubscriber

ActiveJobの引数に巨大な値を指定するとログが一瞬で埋まるのでsnipするために挟んでいた。 6.0.xから6.1でこの辺が変わっていたみたい。

diff --git a/config/initializers/active_job_custom_logger.rb b/config/initializers/active_job_custom_logger.rb
index 1b352a6..856338a 100644
--- a/config/initializers/active_job_custom_logger.rb
+++ b/config/initializers/active_job_custom_logger.rb
@@ -1,4 +1,4 @@
-require 'active_job/logging'
+require 'active_job/log_subscriber'
 
 module ActiveJobCustomLogger
   def args_info(job)
@@ -9,4 +9,4 @@ module ActiveJobCustomLogger
   end
 end
 
-ActiveJob::Logging::LogSubscriber.prepend(ActiveJobCustomLogger)
+ActiveJob::LogSubscriber.prepend(ActiveJobCustomLogger)

ArgumentError: You tried to define an enum named "bar_status" on the model "Hoge", but this will generate a instance method "_?", which is already defined by another enum.

enum値に日本語を使ってると(正確には英数字以外を使っていると)正規表現で消されてしまって重複メソッド扱いになりエラーとなる。 https://github.com/rails/rails/blob/v6.1.0/activerecord/lib/active_record/enum.rb#L205

一応issueにコメントだけしたけど、コーナーケースすぎるのでそもそもコメントしようか悩んだ…。 背景的にはローカライズが必要ない(する予定がない)のでそのまま日本語を入れていた。

https://github.com/rails/rails/issues/40804#issuecomment-751440989

2021/01/09追記

kamipoさんが6.1.1で直してくれた。感謝。

manifest unknown

Ruby, Railsと直接関係ないポカミス。 masterにマージしてimageを作る際に出た。

ruby:2.7.2-alpine3.10 なんてイメージはないので ruby:2.7.2-alpine3.12 に修正。

Step 1/22 : FROM ruby:2.7.2-alpine3.10
manifest for ruby:2.7.2-alpine3.10 not found: manifest unknown: manifest unknown

rails aborted! LoadError: Error loading shared library liblzma.so.5: No such file or directory (needed by /usr/local/bundle/gems/nokogiri-1.10.10/lib/nokogiri/nokogiri.so) - /usr/local/bundle/gems/nokogiri-1.10.10/lib/nokogiri/nokogiri.so

Rails 6.1、Ruby 2.7.2の段階で一回デプロイしておいた。 趣味なので多少エラーが出ても良いし、Ruby 3に関係ないところで問題があるなら切り分けて確認しておきたいため。

どこかで見たことがあるエラー、 prod環境の rails db:migrate 時に発生。 alpineだとありがちなイメージがあったのでこの機会にslim-busterに移行した。

イメージサイズが135MB->285MBになってしまった…。 とはいえ特に問題なく普通に動くのがコンテナのいいところ。

Ruby 3化

バージョン管理にasdfを使っているが、3.0.0が選べなかったので調べてたらここに行き着いた。 Increment ruby-build for Ruby 3.0.0 support by f440 · Pull Request #192 · asdf-vm/asdf-ruby · GitHub ASDF_RUBY_BUILD_VERSION=v20201225 asdf install ruby 3.0.0 でインストール。

まず起動しようとするとcannot load such file -- webrick/httputils (LoadError) が出た。 全く心当たりが無いのでstacktraceを見ると、

/vendor/bundle/ruby/3.0.0/gems/mechanize-2.7.6/lib/mechanize.rb:12:in `<main>

太古に使ってたGemなので削除。

次に cannot load such file -- binding_of_caller.bundle (LoadError) が出た。 stacktraceを見ると、

/vendor/bundle/ruby/3.0.0/gems/binding_of_caller-0.8.0/lib/binding_of_caller.rb:9:in `<main>'

BetterErrorsを使うために入れていたけど、試しに消してみる。 Gemfile.lockを見ると、

    pry-stack_explorer (0.5.1)
      binding_of_caller (~> 0.7)

の依存でまだ残っていたのでpry-stack_explorerも消す。 これでようやくrails sで起動するようになった。

最近はirbも進化してるし、productionではpryを使ってないのでirbに移行しよう。 BetterErrorsも便利だったけどweb-consoleで十分かな。という断捨離の気持ちが得られた。 Rails触りはじめの頃に使ったアプリなので不要なgemがたくさん入ってしまっている…。

web-consoleはVimiumと干渉してまともにタイプできないことが一番きつい。 jsで入力キーを取得してpreタグの中で入れ込むような挙動になっているようなので、素直にinputタグにしてくれたら大丈夫そうだけど、色々フックされてるっぽいのでややこしそう…。 あるいは一時的にVimiumをオフにするショートカットキーなどがあればそれで対応する形になるか…。

Ruby公式もテスト用の circleci/ruby もRC版しかなかったので一旦RC1のイメージを使うようにした。

ruby:3.0.0-rc1-slim-busterにはgitが入っていなかったので、gitをapt-get installに追加。 この状態でデプロイして特に問題なく動作したので、あとは正式版のイメージが出たら切り替えようかな、というぐらい。 ちょくちょく動かないものがあって断捨離をしたけど、結果的に良かった気がする。

全体の所感としては依存が多くなるほどアップデートのときに大変で運用コストがかかるので極力依存を増やさずにものを作ろうという気持ちに改めてなった。 あと複数アプリで更新作業をしてると rails app:update の実行で config/environments の差分をちゃんと反映する作業が一番精神的に大変だった。 カスタム設定は末尾に記載してなるべく差分反映の影響を受けないところに置いておくのがいいのかもしれない。