2025/08/22(金)OCNバーチャルコネクト環境でIPoE・PPPoE併用のIPv4、IPv6デュアルスタックでサーバーを公開する

この記事はフレッツ光ネクストを対象としたものです。フレッツ光クロスには当てはまらないので注意してください。

OCNバーチャルコネクト環境でIPv6サーバーをポート443で公開した時にやったことを派生させて更にIPv4にも対応させるプラン。

OCNバーチャルコネクトのIPoEでIPv4 over IPv6のMAP-E環境だと、IPv4ではWell-known portsが開けないが、PPPoEを使って取得したIPv4であれば開けるので、これとIPoE側のIPv6の併用でデュアルスタックでWell-known portsの開放を行い、サーバーを運用できるようにするのが目的だ。

確認環境

環境 内容
ISP OCN光
ISP契約 OCN 光 with フレッツ マンション・スーパーハイスピード 隼・プラン1・西日本
ISP接続方式 OCNバーチャルコネクト(IPoE, MAP-E)
RouterOS OpenWrt 24.10.0
ServerOS Ubuntu 24.04.3 LTS
HTTPD nginx 1.26.1
ドメインレジストラ Value Domain

今回の要件

やり方

サーバー側の準備

ufwではv4にも穴が開いている状態とする

  1. nginxの設定を開きIPv4向けにlisten 443 ssl;を追記して保存する
  2. nginxを再起動する

PPPoEインターフェースの作成

  1. SSHでOpenWrtに入る
  2. /etc/iproute2/rt_tablesを開き次のようにpppoeの行を追加、保存
    #
    # reserved values
    #
    128     prelocal
    255     local
    254     main
    253     default
    300     pppoe
    0       unspec
    #
    # local
    #
    #1      inr.ruhep
    
  3. 反映する
    • service network restart
  4. LuCIに入る
  5. Network→Interfacesを開く
  6. Add new interfaceから、Nameをwanppp、ProtocolをPPPoEとしたインターフェースを作成する
  7. General SettingsタブのDeviceにONUと疎通しているポートを指定し、PAP/CHAP usernameとPAP/CHAP passwordにISPの接続情報を入れる
  8. Advanced Settingsタブを開き、Use DNS servers advertised by peerのチェックを外し、Use gateway metricを10、Override IPv4 routing tableをpppoe (300)にする
  9. Saveボタンを押して保存する
  10. wanインターフェースのEditを押す
  11. Advanced Settingsタブを開き、Use gateway metricを1にする
  12. Saveボタンを押して保存する

ファイアーウォールの作成

  1. 引き続きLuCI上で作業する
  2. Network→Firewallを開く
  3. ZonesのAddボタンを押す
  4. Nameをwan_pppoeにし、MasqueradingとMSS clampingにチェックを入れ、Covered networksでwanpppを選択し保存する
  5. lan⇒wanのゾーン設定のEditボタンを押す
  6. Allow forward to destination zones:にwan_pppoeを追加し、保存する
  7. Network→Interfacesを開く
  8. wanpppインターフェースのEditボタンを押す
  9. Firewall Settingsタブを開き、Create / Assign firewall-zoneにwan_pppoeが設定されていることを確認する。設定されていなかったら設定する

サーバーだけをPPPoEのIPv4通信の対象にする

  1. 引き続きLuCI上で作業する
  2. Network→Routingを開き、IPv4 Rulesタブを開く
  3. General SettingsタブのIncoming interfaceをlanに、SourceをサーバーのIPにする(例:192.168.1.5/32
  4. Advanced Settingsタブを開く
  5. Tableにpppoe (300)を設定し、保存する

NATを設定する

  1. 引き続きLuCI上で作業する
  2. Network→Firewallを開き、Port Forwardsタブを開く
  3. Addボタンを押す
  4. Nameに適当な名前を付け、Restrict to address familyをIPv4 only、Source zoneをwan_pppoe、External portを443、Internal IP addressを転送先のサーバーIP、Internal portを443にして、保存する
  5. 最後にこれまでの内容をSave & Applyする

疎通確認

  1. Network→Interfacesを開き、wanpppのIPv4を拾う
  2. ドメインレジストラの設定でAレコードにこれを登録
  3. 適当な外部サーバーからcurlを投げて中身が返ってきたらOK
    curl -4 -v https://example.com
    

IP変動対策にDDNSを設定する

/etc/hotplug.d/iface40-pppoeというファイルを755で作り、以下の内容を記述する。

#!/bin/sh

[ -n "$DEVICE" ] || exit 0
[ "$ACTION" = ifup ] || exit 0
[ "$INTERFACE" = wanppp ] || exit 0

vdpw=ここにDDNS更新用のパスワード
pppoeaddr=$(ip -4 addr show pppoe-wanppp | head -2 | tail -1 | awk '{print $2}')
logger -t "DDNS - PPPoE IP" $pppoeaddr
# hoge.example.comに対して更新リクエストを飛ばす例
logger -t "DDNS - DOMAIN: hoge" $(wget -O - "https://dyn.value-domain.com/cgi-bin/dyn.fcg?d=example.com&p=$vdpw$&h=hoge&i=$pppoeaddr")

# When making requests for multiple domains, wait 60 seconds between each request

このスクリプトはhotplugイベントが発生したときにファイルパス順に呼ばれるスクリプトらしい。

ひとまずデバイスでなく、インターフェイスが起動したときで、かつインターフェース名がwanpppの時に実行されるように組んである。

注意点として、このエンドポイントはレートリミットがシビアで、60秒以内に送ると503を返してくるようなので、複数回投げる場合はsleep 61とかして間隔をあけると良さそうだ。数が増えてきたらバリュードメインAPIのDNS設定APIを利用した方が早いだろう。

ただしこのAPIはTTLの制御に難があり、処理方法によっては3600秒に固定される罠が潜んでいるので注意が必要だ。

トラブルシュート

curlで「Connection refused」や「接続が拒絶されました」と出る

  • /var/log/ufw.logにログがあればufwで穴をあける(まずこれはない)
  • ファイアーウォールかルーティングかNATの設定のどこかがおかしいので見直す。

同一LANにぶら下がっている端末でDNS解決に失敗する

PPPoEインターフェース(wanppp)の設定を開き、Advanced SettingsからUse DNS servers advertised by peerのチェックを外すと直る。

雑感:やや苦しい

IPv4, v6のデュアルスタックに出来たのはいいが、幾つかの端末で繋いだところIPv4側に流れるケースがあった。メインのv6でなく、オプションのv4に行くという状況はやや残念だ。

構成的にもマルチセッションという微妙な形態で、v4とv6でインターフェースが二系統あり、セキュリティもNATとFWがあり、なかなか複雑な状態になってしまった。

Mastodonにおいてはv4IPのインスタンス(兵庫丼)との疎通はTLレベルでは出来たものの、画像が出なくなるという頭が痛い問題に出会った。

Misskey.ioからは疎通できず、こちらからはユーザープロフィールまではアクセスできるがフォローを押しても音沙汰がない。

結論としてはリッチなアプリケーション通信を伴う用途において、この方式は向かない気がした。恐れくこれはルーターより外側がIPoEとPPPoEで別系統になっている関係で、通信が混乱して上手くいかないパターンがあるのではないかと感じている。

DDNSも制約が厳しいし、Value DomainのAPIはあるだけマシではあるものの、超絶使いづらい問題もあり、この環境においてのデュアルスタックはあきらめようと思った。

まぁいい勉強にはなったので、そのうち何かに活かせるだろう、きっと。

2026-05-23追記:この状態で安定運用しているがv4側が疎通しない相手については諦めた

OpenWrtからValue-Domainに複数サブドメインのDDNSを行うツールを作ったのもあり、IPv4が変わったときも自動で切り替えることでダウンタイムを減らす運用ができたので、今のところ不満はない。

ただFediverseで繋がらないところは繋がらないので諦めることにした。

2025/08/21(木)Ubuntu実機IPv6シングルスタック環境にMastodonを入れてみた

7月31日に立てたMastodon作成ロードマップの最後である、自宅サーバーでMastodonをセルフホスティングするのを達成したので、そのログをここに記す。

Ubuntu実機AMD64環境でDockerを使わず、IPv6シングルスタックで構成している。

自宅サーバーを使ってIPv6シングルスタックで通信を待ち受ける方法はOCNバーチャルコネクト環境でIPv6サーバーをポート443で公開した時にやったことの方に書いている。

確認環境

Ubuntu実機AMD64環境

Env Ver
Ubuntu 24.04.3 LTS
nginx 1.26.1
Mastodon v4.4.3
Ruby 3.2.3
ruby-build 20250811
rbenv 1.3.2-10-gd1a19a3

前提条件

事前準備

  1. 公開用のドメインを検討し、生み出す
  2. Value Domainのコンパネから生み出したドメインをAAAAレコードで登録
  3. 拙作の@lycolia/value-domain-dns-cert-registerを使ってTLS証明書を発行

依存関係のインストールとセットアップ

  1. 以下のコマンドを流す

    sudo apt update
    
    sudo apt install -y \
        imagemagick ffmpeg libvips-tools libpq-dev libxml2-dev libxslt1-dev file git-core \
        g++ libprotobuf-dev protobuf-compiler pkg-config gcc autoconf \
        bison build-essential libssl-dev libyaml-dev libreadline6-dev \
        zlib1g-dev libncurses5-dev libffi-dev libgdbm-dev \
        nginx redis-server redis-tools postgresql \
        certbot python3-certbot-nginx libidn11-dev libicu-dev libjemalloc-dev \
    
    # 最新版を入れないとうまくいかない
    wget https://github.com/rbenv/ruby-build/archive/refs/tags/v20250811.tar.gz
    tar -xzf v20250811.tar.gz
    sudo PREFIX=/usr/local ./ruby-build-20250811/install.sh
    rm v20250811.tar.gz
    rm -Rf ruby-build-20250811
    
    # aptではサポート切れの化石が入るのとnvmで入れたのは自分しか使えないので新しいのをグローバルに入れる
    curl -fsSL https://deb.nodesource.com/setup_24.x -o nodesource_setup.sh
    sudo bash nodesource_setup.sh
    apt install -y nodejs
    rm nodesource_setup.sh
    corepack enable
    
    # メンテナンスでめっちゃ使うのでサービスユーザーにはせず、ログイン可能なユーザーとして作る
    adduser --disabled-password mastodon
    
  2. /etc/redis/redis.confを開きrequirepassのコメントを外し、パスワードを設定

  3. sudo service redis restart

  4. sudo -u postgres psqlでポスグレに入り、ユーザーを作る。ポスグレは不慣れで同時にPW設定する方法が判らなかったので別建てで設定している
    CREATE USER mastodon CREATEDB;
    ALTER ROLE mastodon WITH PASSWORD 'パスワード';
    \q
    

Mastodon本体のセットアップ

実際にはかなり行ったり来たりしてるので一部の手順には誤りが含まれている可能性がある。

  1. mastodonユーザーにスイッチしてインストール作業を続行

    # Mastodon本体の導入
    sudo su - mastodon
    git clone https://github.com/mastodon/mastodon.git live
    # 超重要、cd忘れててもなんかそれっぽく進むが、以後すべて破綻する
    cd live
    git checkout $(git tag -l | grep '^v[0-9.]*$' | sort -V | tail -n 1)
    
    git clone https://github.com/rbenv/rbenv.git ~/.rbenv
    
    echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bashrc
    echo 'eval "$(rbenv init -)"' >> ~/.bashrc
    exec bash
    
    # 動かないやつなのでコメントアウト
    # 依存関係のインストールとセットアップでやった内容をこっちでやってもいいかもしれない
    # git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
    
    # ここでエラーが出たら何かが間違っているのでやり直し
    RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
    
    # bundleがないとか、rubyのバージョンが存在しないとかエラーが出たら何かが間違っているのでやり直し
    bundle config deployment 'true'
    bundle config without 'development test'
    bundle install -j$(getconf _NPROCESSORS_ONLN)
    yarn install
    
    # 色々尋ねられるので適当に進める
    RAILS_ENV=production bin/rails mastodon:setup
    
  2. デーモン周りの設定をする

    # 静的ファイルをnginxに見させるために権限を設定
    sudo chmod o+x /home/mastodon
    
    # デーモンの登録と起動
    sudo cp /home/mastodon/live/dist/mastodon-*.service /etc/systemd/system/
    sudo systemctl daemon-reload
    sudo systemctl enable --now mastodon-web mastodon-sidekiq mastodon-streaming
    sudo service mastodon-web start
    sudo service mastodon-sidekiq start
    sudo service mastodon-streaming start
    
  3. nginxの設定を書く

    map $http_upgrade $connection_upgrade {
    default upgrade;
    ''      close;
    }
    
    upstream backend {
        server [::1]:3000 fail_timeout=0;
    }
    
    upstream streaming {
        # Instruct nginx to send connections to the server with the least number of connections
        # to ensure load is distributed evenly.
        least_conn;
    
        server 127.0.0.1:4000 fail_timeout=0;
        # Uncomment these lines for load-balancing multiple instances of streaming for scaling,
        # this assumes your running the streaming server on ports 4000, 4001, and 4002:
        # server 127.0.0.1:4001 fail_timeout=0;
        # server 127.0.0.1:4002 fail_timeout=0;
    }
    
    proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=CACHE:10m inactive=7d max_size=1g;
    
    server {
        listen [::]:443 ssl http2;
        server_name  <ドメイン>;
    
        ssl_certificate     /etc/letsencrypt/live/<証明書のパス>/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/<証明書のパス>/privkey.pem;
    
        ssl_protocols TLSv1.2 TLSv1.3;
    
        # You can use https://ssl-config.mozilla.org/ to generate your cipher set.
        # We recommend their "Intermediate" level.
        ssl_ciphers CDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA84:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA38:DHE-RSA-CHACHA20-POLY1305;
    
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_tickets off;
    
        keepalive_timeout    70;
        sendfile             on;
        client_max_body_size 99m;
    
        root /home/mastodon/live/public;
    
        gzip on;
        gzip_disable "msie6";
        gzip_vary on;
        gzip_proxied any;
        gzip_comp_level 6;
        gzip_buffers 16 8k;
        gzip_http_version 1.1;
        gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xmlrss text/javascript image/svg+xml image/x-icon;
        gzip_static on;
    
        location / {
            try_files $uri @proxy;
        }
    
        # If Docker is used for deployment and Rails serves static files,
        # then needed must replace line `try_files $uri =404;` with `try_files $uri @proxy;`.
        location = /sw.js {
            add_header Cache-Control "public, max-age=604800, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/assets/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/avatars/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/emoji/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/headers/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/packs/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/shortcuts/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/sounds/ {
            add_header Cache-Control "public, max-age=2419200, must-revalidate";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            try_files $uri =404;
        }
    
        location ~ ^/system/ {
            add_header Cache-Control "public, max-age=2419200, immutable";
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
            add_header X-Content-Type-Options nosniff;
            add_header Content-Security-Policy "default-src 'none'; form-action 'none'";
            try_files $uri =404;
        }
    
        location ^~ /api/v1/streaming {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Proxy "";
    
            proxy_pass http://streaming;
            proxy_buffering off;
            proxy_redirect off;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
    
            add_header Strict-Transport-Security "max-age=63072000; includeSubDomains";
    
            tcp_nodelay on;
        }
    
        location @proxy {
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;
            proxy_set_header Proxy "";
            proxy_pass_header Server;
    
            proxy_pass http://backend;
            proxy_buffering on;
            proxy_redirect off;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
    
            proxy_cache CACHE;
            proxy_cache_valid 200 7d;
            proxy_cache_valid 410 24h;
            proxy_cache_use_stale error timeout updating http_500 http_502 http_503 http_504;
            add_header X-Cached $upstream_cache_status;
    
            tcp_nodelay on;
        }
    
        error_page 404 500 501 502 503 504 /500.html;
    }
    

なんやかんやの末に立ち上がったMastodonインスタンス

なんやかんやあり色々苦戦したが、無事立ち上がってくれて何よりだった。

立ち上がった直後。

そして現在。こうやって動いているのを見ると感慨深い。

実は中学生のころから自宅サーバーを立てるという夢を持っており、こうやって実際に公開できたことは感慨深い。実際のところ、実験用の小規模サーバーや、SHOUTcastサーバーだとか、シンクライアント用のSSHサーバーみたいなのは作った経験があり、いずれも外に出していたのでサーバーを作った経験が全くない、というわけではないのだが、監視環境を整えたりIPv4が実用困難な環境で、IPv6に挑戦してみたりと、ここまで本格的にやったことはなかったのもあり、個人的には結構達成感がある。

トラブルシューティング

Mastdonのlistenしているポートを変更する

3000番はGrafanaをはじめ、様々なNode.js系のポートと干渉するため変えておく。

  1. /etc/systemd/system/mastodon-web.serviceEnvironment="PORT=3000"を変えることでWeb側のポートが変えられる。Environment="BIND=[::1]"とすることでIPv6で待ち受けられる
  2. /etc/systemd/system/mastodon-streaming.serviceWants=mastodon-streaming@4000.service4000の部分を変えることで、Streaming側のポートも変えられる
  3. サービスを再起動する
    sudo systemctl daemon-reload
    sudo service mastodon-web restart
    sudo service mastodon-streaming restart
    
  4. nginxの設定も対応する箇所を変える
  5. nginxを再起動する
    sudo service nginx restart
    

Mastodonのエラーページが表示される・Rubyがないとか出るなどセットアップ時のエラー全般

基本的にどこかの手順が間違ってるのでしつこくやり直すことで改善する。

投稿可能な文字数を増やす

以下の内容で修正すると増やせる。

app/validators/status_length_validator.rb
 class StatusLengthValidator < ActiveModel::Validator
-  MAX_CHARS = 500
+  MAX_CHARS = 50000
app/javascript/mastodon/features/compose/containers/compose_form_container.js

正直このファイルは直さなくても動くし、直しても、ほぼ意味がなさそうなので、直す必要はないかもしれない。

-  maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 500),
+  maxChars: state.getIn(['server', 'server', 'configuration', 'statuses', 'max_characters'], 50000),
備考

変更直後は投稿画面の初期表示に500と出ていて、数文字打つと50000文字に変化する状態だったが、一日くらい経過すると初期値が50000に変わっていたのでキャッシュか何かが悪さをしていた可能性がある。

フォローできるが、フォロー一覧にもTLにも出てこないユーザーが出る

IPv6と疎通してないところは仕方がないね。AAAAレコード持ちでcurl -6 -v https://example.comしても疎通する場所でも起きることがあるが原因は不明。

兵庫丼でも偶に遭遇してたので、そういうものだと割り切るしかない。IPv4滅ぶべし、慈悲はない。

「制限に達しました1:35:00 以降に再度実行してください。」とエラーが出る

APIのレートリミットにかかっているせいっぽい。5分間に300リクエストまでと、かなり渋い。信用するIPを指定することで回避可能らしい。

.env.productionを開き、前述の資料に書かれている信頼できるIPを列挙した後に、自分のIPv6プレフィクス(aaaa:bbbb:cccc:dddd::/64を自分のプレフィクスに置き換え)を追加し、再起動しておいた。これで緩和されると良いのだが、出先では使えないと思うのでどうするか…。

TRUSTED_PROXY_IP=127.0.0.1/8,::1/128,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16,fc00::/7,aaaa:bbbb:cccc:dddd::/64

ドキュメントを読む限り、アカウントIDで縛る処理がどこかにあるようなので、自分のIDを除外できれば出先でも起きなくなり、更に良さそうだが、やり方はよくわからなかった。

調べた限りconfig/initializers/rack_attack.rbの中でやっているようなので、暇なときに見てみたいとは思う。

Prometheusでメトリクスを取得する

Mastodonは標準でPrometheusに対応しているので、Exporterの有効化設定を見ながら設定していく。

まず.env.productionを開き、以下の二行を追記し、再起動する。これによってMastodonWebのExporterが有効化され、ローカルホストで動くようになるようだ。Streamingの方は設定せずとも、元から有効になっている模様。

MASTODON_PROMETHEUS_EXPORTER_ENABLED=true
MASTODON_PROMETHEUS_EXPORTER_LOCAL=true

再起動したらcurl http://localhost:{Streamingのポート番号}/metricscurl http://localhost:9394/metricsで動いていることを確認。

次にPrometheusのscrape_configsに以下を追記し、Prometheusを再起動する。

  - job_name: 'mastodon-web'
    static_configs:
      - targets: ['localhost:9394']

  - job_name: 'mastodon-streaming'
    static_configs:
      - targets: ['localhost:5001']

Prometheusの管理画面を開き、Target healthに出て動いていればOK。

【未解決】プロフィールを更新すると更新前の状態にぶり返したり、更新後の状態になったりして安定しない

大まかには、このような感じ。再現したりしなかったりでいい感じの動画が撮れなかった…。

キャッシュ周りで何かの問題が起きている可能性があるが原因はよくわかっていない。3分も待てば改善するので一旦気にしないことにした。

Dockerを使わなかった理由

Dockerがあれば一瞬で立ち上がって便利といえば便利なのだが、Ubuntuのネイティブ環境にPrometheusとGrafanaをIPv6スタックで導入した時に、DockerとIPv6の相性が余りにも悪すぎたのが最大の要因。

後はまぁ変なレイヤー隔ててない方が管理楽だし、リソースも抑えられるかなとか。

実際に導入して驚いた部分

簡単な監視ツールがある

Mastodon自体に監視の仕組みがあること。Sidekiqという名前自体はよく聞くので知っていたが、今まではなんとなくジョブ管理システムだと思っていた。これはTLで見かけていた管理人の人たちがトラブルが起きた時にジョブを殺したりしているのを見かけたためだ。

とはいえ、公式サイトの説明にはRuby's fastest job systemとあるので、あながち認識としては間違ってなかったようだ。ジョブ管理システムのついでにメトリクスとかも見れる機能があるのかもしれない。

Webベースの管理画面がある

環境変数周りの設定項目が多く、コンソールから設定を操作するイメージが強く、コンソールベースなのかと思っていたが実際にはWebベースのGUI管理コンソールもあり、中々便利だ。管理面の操作や、サーバー本体への影響がない簡単な設定はここで出来るだろう。

死ぬほど早い

アップロードが爆速だったり、画面遷移もかなり早い。家で繋ぐ分にはLAN環境だから早いのかと思っていたが、外から5Gで繋いでも早い。

今のところ大したアクセスや負荷もなく、リソースがしこたま余ってるお陰かもしれない。あとは単純に手前にCDNがないとか、VPSといった共有資源とかと比べた時のスペックの差だろうか?

スペックは結構ある方だと思う。具体的にはCPUはAMD Ryzen 5 5600G、メモリはDDR4 32GB、SSDもCrucial P1 CT500P1SSD8JPでNVMeといった感じで、そんなハチャメチャに高スペックというわけではないが、よくある?安い中古ノートPCを流用したサーバーと比べたら充実している。

今後やっていきたい部分

主に監視周りの強化をしていきたい。現在のロードマップは以下のような感じだ。

  • Grafanaでnginxのダッシュボードを整備
  • Lokiの導入
  • GrafanaからMastodonのメトリクスを見れるように
  • OpenWrt本体でメトリクスをとれるようにしたい
    • 本体が死ぬとテレメトリ取れないので鯖側のPrometheusだと役不足
    • 問題はストレージどうするか…
    • 寿命の関係でeMMCには書けないからNVMe拡張するのか
  • OpenWrtのメトリクスを鯖側でも取れるようにする(いるかこれ?
  • 外部ステータスページの用意
  • 簡単な外形監視の導入?(やるならさくらに乗せる)

まず、Grafanaをセットアップしたもののnginxのダッシュボードが空っぽなので作っていく必要がある。

次にLokiを入れてログ監視もできた方がいい。個別のログファイルを見るのは割とつらい。

Grafanaで統合管理できると便利なのでMastodonのメトリクスも取れると良いので、これもやっていきたい。

他にも今回のセットアップ中にOpenWrtがOutOfMemoryで落ちるケースがあり、原因不明のネットワーク切断が多発したため、こちらのメトリクス収集も必要だ。ネットワーク自体が死ぬと中身が見れないため、基本的に本体側で、収集して見れるようにしたい。今でも簡単なものは入れているが情報量が少なく、役不足だ。

ストレージ寿命の問題もありeMMCにログを吐くとストレージの寿命がマッハになる恐れがあるため、メモリ管理や追加でストレージを用意するなど、手法についても検討する必要がある。

OpenWrtのメトリクスを鯖側でも取れるようにするのはオプションプランだが、これは別にどうでもいい気はする。平時は統合的に見れて便利かもしれない。

あとは外部環境にステータスページを配置したいと考えている。実用性があるかどうかは謎だが、あるとそれっぽいから。ツールとしてはCachetを採用し、さくらのレンタルサーバー側で運用する予定だ。

簡単な外形監視の導入についても、Cachetで担える可能性がある。

監視面以外ではセルフホスティング方式のWAFであるAnubisに興味があり、これも入れていきたいと考えている。

更に中長期では、現在運用しているlycolia.info配下の全サイトを自宅サーバーへ移行し、メールサーバーやネームサーバーなど、現在あるWebサイトの一切を移行する野望も抱いている。

とにかくやることが沢山あるので、当面は暇になることはなさそうで、充実した生活を送れそうだ。

あとがき:OSグローバルのRubyを消してみた

本来手順になく、私の勘違いで追加して作業してしまっていたOSグローバルのRubyを消しても動くのか試してみた。

# 通常ユーザー作業
ls -la /usr/local/bin | grep ruby
rm /usr/local/bin/ruby-build
sudo rm -Rf /usr/local/bin/ruby-build
ls -la /usr/local/share
sudo rm -Rf /usr/local/share/ruby-build
sudo rm -Rf /usr/local/share/man/man1/ruby-build*
ruby -v
sudo apt remove ruby
sudo su - mastodon

# mastodonユーザー作業
mkdir -p "$(rbenv root)"/plugins/ruby-build
cp ruby-build-20250811/. "$(rbenv root)"/plugins/ruby-build
ls -la "$(rbenv root)"/plugins/ruby-build
cd live
RUBY_CONFIGURE_OPTS=--with-jemalloc rbenv install
ruby -v
exit

# 通常ユーザー作業
sudo service mastodon-web restart
sudo service mastodon-sidekiq restart
sudo service mastodon-streaming restart

一旦ここまでは上手くいったので、上の方にある手順よりこっちでやった方がいいかもしれない。Mastodonのビルドが通るかまでは試してないので、実際に動くかどうかまでは不明だが、まぁ次回考えればよいだろう。

2025/08/19(火)Ubuntuのネイティブ環境にPrometheusとGrafanaをIPv6スタックで導入した

更新日:
投稿日:

この記事では自宅サーバーの監視環境導入にあたり、ほぼ前提知識なしの状態からPrometheusとGrafanaを導入した経緯を書いている。IPv6シングルスタック対応。

確認環境

Ubuntu実機AMD64環境

Env Ver
Ubuntu 24.04.3 LTS
nginx 1.26.1
Prometheus 3.5.0
Node exporter 1.9.1
NGINX Prometheus Exporter 1.4.2
Grafana v12.1.1

OpenWrtで設置サーバー向けのPrometheusとGrafanaのDNS設定をしてアクセス可能にする

今回の作業はLAN内にある作業機からサーバーマシンに対して行うため、利便性のため各サービスに対してドメインを振っておく。

  1. OpenWrtの/etc/hostsを開きPrometheusとGrafanaの設定を書く
    1. ここでは一旦、prometheus.testgrafana.testとする
  2. /etc/init.d/dnsmasq restart
  3. これでLAN配下の端末からnginxで設定したホスト名でアクセスできるようになる

Prometheusの導入

Prometheusとは

PrometheusとはSoundCloudで開発された時系列のスタンドアロンバイナリのツールで、時系列でメトリクスを収集するツールのようだ。オープンソースソフトウェアとして公開されている。

メトリクスとは数値測定のことで、例えばCPU使用率や、メモリ使用量、HTTPサーバーがこれまでに返したレスポンスコード別のカウントなど、そういった数値のことだ。Prometheusはこれを定期的に収集することで、収集時刻+メトリクスで時系列の数値データを蓄積しているものと思われる。

実際のデータとしてはメトリック名に対して連想配列を格納したデータを保持しているっぽい。イメージとしては以下のような感じだろうか。

<metricName>: { <labelName>: <labelValue>, ... }

メトリクスはExporterと呼ばれるサービスに対してHTTP要求を投げ、その応答を記録しているようだ。つまりExporterがメトリクスを取得、Prometheus向けに情報加工し、PrometheusはExporterからこれを取得、時系列のデータベースにしているのだろう。

またエンドポイントが揮発性であるなど、短命である場合はPushgatewayというプロキシを使い、PushgatewayがPrometheus向けに情報公開する仕組みもあるようだ。

EoLはリリースから一年ほどと、短い。

本体のインストール

  1. Prometheusセットアップコマンドを流す

    # 作業場所の作成
    mkdir temp
    cd temp
    
    # バイナリ取得
    wget https://github.com/prometheus/prometheus/releases/download/v3.5.0/prometheus-3.5.0.linux-amd64.tar.gz
    tar xvfz prometheus-3.5.0.linux-amd64.tar.gz
    
    # Prometheus実行ユーザーの作成
    sudo groupadd prometheus
    sudo useradd prometheus -g prometheus -d /var/lib/prometheus -s /usr/sbin/nologin -m
    ls -la /var/lib/prometheus/
    
    # binを配置
    cd prometheus-3.5.0.linux-amd64
    sudo cp prometheus promtool /usr/local/bin/
    ls -la /usr/local/bin | grep prom
    
    # 設定ファイルを配置
    sudo mkdir /etc/prometheus
    cat<<'EOF' | sudo tee /etc/prometheus/prometheus.yaml
    global:
    scrape_interval:     15s
    evaluation_interval: 15s
    
    rule_files:
    # - "first.rules"
    # - "second.rules"
    
    scrape_configs:
    - job_name: prometheus
        static_configs:
        - targets: ['[::]:9090']
    EOF
    
    sudo chown -R prometheus:prometheus /etc/prometheus
    ls -la /etc/prometheus
    
    # デーモン作成
    cat <<'EOF' | sudo tee /etc/systemd/system/prometheus.service
    [Unit]
    Description=Prometheus - Monitoring and alerting toolkit
    After=network-online.target
    Wants=network-online.target
    
    [Service]
    Type=simple
    User=prometheus
    Group=prometheus
    WorkingDirectory=/var/lib/prometheus
    ExecStart=/usr/local/bin/prometheus \
        --config.file=/etc/prometheus/prometheus.yaml \
        --storage.tsdb.path=/var/lib/prometheus \
        --web.listen-address=[::]:9090
    ExecReload=/bin/kill -HUP $MAINPID
    Restart=on-failure
    RestartSec=5s
    
    [Install]
    WantedBy=multi-user.target
    EOF
    
    # デーモン有効化
    sudo systemctl daemon-reload
    sudo systemctl enable --now prometheus
    
  2. nginxからPrometheusの管理画面が開けるようにする。/etc/nginx/conf.d/prometheus.confを作成し、以下を記述。

    upstream prometheus {
      server [::1]:9090;
    }
    
    server {
      listen        [::]:80;
      server_name   prometheus.test;
    
      access_log   /var/log/nginx/prometheus.access.log;
      error_log    /var/log/nginx/prometheus.error.log;
    
      location / {
        proxy_set_header Host $host;
        proxy_pass  http://prometheus;
      }
    }
    
  3. sudo service nginx restart

  4. 設定したホストにアクセスできればOK

本体設定の説明

先ほどの「Prometheusセットアップコマンドを流す」ステップで作成した設定の説明。

First steps with Prometheusに書いてある通り、設定はprometheus.yamlのようなYAMLファイルに定義し、./prometheus --config.file=prometheus.ymlの様にして起動時に読み込んで利用する。

global:
  scrape_interval:     15s
  evaluation_interval: 15s

rule_files:
  # - "first.rules"
  # - "second.rules"

scrape_configs:
  - job_name: prometheus
    static_configs:
      - targets: ['[::1]:9090']

globalセクションではポーリング頻度と、ルールの評価頻度が指定されている。

rule_filesではルールファイルが指定される。

scrape_configsではポーリング先のリソースを指定する。この場合はhttp://localhost:9090/metricsに対して行われる。

ログの保持期限について

ログの保持期限はprometheusを蹴るときのコマンドラインオプションで決まる。

デフォルトでは15日だが、--storage.tsdb.retention.timeを指定することで増やせるようだ。

単位はy, w, d, h, m, s, msがあり、--storage.tsdb.retention.time=90dの様にして指定する模様。

How long to retain samples in storage. If neither this flag nor "storage.tsdb.retention.size" is set, the retention time defaults to 15d. Units Supported: y, w, d, h, m, s, ms. Use with server mode only.

個人的にはデフォルトの15日で様子見し、必要に応じて伸ばしていこうと考えている。商業サービスのように可用性が重視され、障害発生などで過去を遡りたい場合には長期記録があると手がかりが増えるので、有用だと思う。

Exporterの導入

Exporterは実際に数値を収集し、Prometheusに対して情報公開するためのHTTPエンドポイントを提供するサービスで、この章では今回利用するものについて書いていく。

OSやマシンの監視:Node exporter

POSIX系システムのOSやマシンの監視にはPrometheus公式のNode exporterを利用する。

インストール
# 取得
wget https://github.com/prometheus/node_exporter/releases/download/v1.9.1/node_exporter-1.9.1.linux-amd64.tar.gz
tar xvfz node_exporter-1.9.1.linux-amd64.tar.gz
cd node_exporter-1.9.1.linux-amd64/

# binを配置
sudo cp node_exporter /usr/local/bin/
ls -la /usr/local/bin/ | grep node_exporter

# デーモン作成
cat <<'EOF' | sudo tee /etc/systemd/system/node_exporter.service
Description=Node exporter
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=prometheus
Group=prometheus
WorkingDirectory=/var/lib/prometheus
ExecStart=/usr/local/bin/node_exporter \
    --web.listen-address=[::]:9100
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

# デーモン有効化
sudo systemctl daemon-reload
sudo systemctl enable --now node_exporter
prometheus.yamlの設定

/etc/prometheus/prometheus.yamlを開きscrape_configs:に以下を追記

  - job_name: 'node'
    static_configs:
      - targets: ['[::1]:9100']
nginxの監視:NGINX Prometheus Exporter

nginxの監視にはnginx公式のNGINX Prometheus Exporterを利用する。

これはログを監視するわけではなく、接続を許可した数や、現在の接続数、これまでのHTTP要求合計などの数値を公開しているようだ。

nginxにヘルスチェック追加

/etc/nginx/conf.d/status.confを作ってstub_status_moduleを埋める。

server {
    listen [::]:80;

    location /stub_status {
        stub_status on;
        access_log off;
        allow ::1;
        deny all;
    }
}

疎通確認

curl -v http://localhost/stub_status

こんなのが来ればOK

< HTTP/1.1 200 OK
< Server: nginx/1.26.1
< Date: Mon, 18 Aug 2025 10:31:59 GMT
< Content-Type: text/plain
< Content-Length: 97
< Connection: keep-alive
<
Active connections: 1
server accepts handled requests
 1 1 1
Reading: 0 Writing: 1 Waiting: 0
* Connection #0 to host localhost left intact
Exporterのインストール
# 取得
wget https://github.com/nginx/nginx-prometheus-exporter/releases/download/v1.4.2/nginx-prometheus-exporter_1.4.2_linux_amd64.tar.gz
tar xvfz nginx-prometheus-exporter_1.4.2_linux_amd64.tar.gz

# binを配置
sudo cp nginx-prometheus-exporter /usr/local/bin/
ls -la /usr/local/bin/ | grep nginx-prometheus-exporter

# デーモン作成
cat <<'EOF' | sudo tee /etc/systemd/system/nginx_exporter.service
[Unit]
Description=NGINX Exporter
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
User=prometheus
Group=prometheus
ExecStart=/usr/local/bin/nginx-prometheus-exporter \
    --nginx.scrape-uri=http://[::]/stub_status \
    --no-web.systemd-socket \
    --web.listen-address=:9113
ExecReload=/bin/kill -HUP $MAINPID
Restart=on-failure
RestartSec=5s

[Install]
WantedBy=multi-user.target
EOF

# デーモン有効化
sudo systemctl daemon-reload
sudo systemctl enable --now nginx_exporter
prometheus.yamlの設定

/etc/prometheus/prometheus.yamlを開きscrape_configs:に以下を追記

  - job_name: 'nginx'
    static_configs:
      - targets: ['[::1]:9113']

Prometheusの管理画面を確認

http://prometheus.testにアクセスできることを確認し、ついでにStatus→Target healthでExporterの死活も見ておくと良い。

Grafanaの導入

Grafanaとは

Grafanaとはオープンソースのビジュアライザツールである。PrometheusやMySQLなどのデータソースを基にグラフにしたりすることができる。

Lokiというツールを使うことでログも扱うことができ、ログビューワとしても使えるようだ。

インストール

  1. 次のコマンドでインストールする

    # debの取得とインストール
    sudo apt-get install -y adduser libfontconfig1
    wget https://dl.grafana.com/grafana-enterprise/release/12.1.1/grafana-enterprise_12.1.1_16903967602_linux_amd64.deb
    sudo dpkg -i grafana-enterprise_12.1.1_16903967602_linux_amd64.deb
    
    # デーモンの有効化
    sudo /bin/systemctl daemon-reload
    sudo /bin/systemctl enable grafana-server
    sudo service grafana-server start
    

Grafanaの画面を見れるようにする

  1. 適当にnginxの設定ファイルを切って以下のように書き、アクセスする

    # This is required to proxy Grafana Live WebSocket connections.
    map $http_upgrade $connection_upgrade {
      default upgrade;
      '' close;
    }
    
    upstream grafana {
      server [::1]:3000;
    }
    
    server {
      listen        [::]:80;
      server_name   grafana.test;
    
      access_log   /var/log/nginx/grafana.access.log;
      error_log    /var/log/nginx/grafana.error.log;
    
      location / {
        proxy_set_header Host $host;
        proxy_pass  http://grafana;
      }
    
      # Proxy Grafana Live WebSocket connections.
      location /api/live/ {
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection $connection_upgrade;
        proxy_set_header Host $host;
        proxy_pass http://grafana;
      }
    }
    
  2. ID: admin, PW: adminでログインしパスワードを変更する

  3. ログインしたら画面右上からProfileを開き、Usernameを変更する

GrafanaからPrometheusのメトリクスを見れるようにする

Prometheusとの接続
  1. Home→Connections→Add new connectionでPrometheusを検索
  2. 右上のAdd new data sourceを押す
  3. Connectionにhttp://localhost:9090を入れる
  4. Save & testを押す
Prometheusダッシュボードの作成
  1. Connections→Data sourcesからprometheus→Dashboard
  2. Prometheus 2.0 StatsをImport
  3. DashboardsにPrometheus 2.0 Statsが追加されていて、中身が動いていることを確認
Node exporterダッシュボードの作成
  1. Dashboardsを開き、右上のNewボタン→Import
  2. Node Exporter FullのIDをコピーしてID欄に貼り付けLoadする
  3. 「Select a Prometheus data source」でprometheusを選んで保存
  4. 右上のEditボタンを押す
  5. 同じ場所にSettingsボタンが出てくるので更に押す
  6. Settings画面に遷移するのでVariablesタブを開き、jobの文字を押す
  7. 下にスクロールし、Query optionsのLabel filtersをjob = nodeになるようにする(nodeの部分はprometheus.yamlに定義したジョブ名を指定する)
  8. 右上のSave dashboardボタンを押す
  9. Saveボタンを押す
  10. ダッシュボードにメトリクスが反映されていることを確認
  11. 何も出てない場所はウィジェットを編集してメトリクスを手で指定していくしかないと思う
Nginxダッシュボードの作成

Grafana公式のダッシュボードテンプレートに使えるものが見当たらなかったので自作するしかない。

  1. Dashboardsを開き、右上のNewボタン→New Dashboard
  2. +Add visualizationボタンを押す
  3. Select data sourceダイアログが出てくるのでprometheusをクリック
  4. 出てきた画面下部にあるMetricからnginx関係のメトリクスを探して適当に設定していく

Dockerを使わなかった理由

IPv6縛りでやっているとPrometheusのNode exporterがPrometheus本体と疎通できなかったので諦めた。恐らくコンテナの中からIPv6でホストと通信するのが難しいのだと思う。

Node exporterホストのメトリクスを取る場合、Node exporterのネットワークはHost側に行くのため、prometheus.yamlのstatic_configs.targetslocalhostにするとコンテナ内を見てしまうから疎通できないし、host.docker.internalを指定してもv4アドレスの127.0.0.11が返ってきてしまうので疎通できない。IPv6をデフォルトにしてもhost.docker.internal127.0.0.11のままで、AAAAレコードがない。

ググっても当てはまるものは出ず、GPT-5やClaude Opus 4.1に聞いても答えは得られず、消耗戦になるので諦めた。

それに物理サーバー一台の環境だとコンテナ化の恩恵もさほどないので、Docker固有の問題に苦しむくらいならバイナリを直に叩いた方が楽というのもある。

今回の作業で得られたもの

init.dの書き方の記事でイケていなかった部分に気付けた

init.dの書き方で冗長だった記述や、誤解を招く記述の整理ができた。例えばNAME変数の影響範囲が広すぎて、本来波及してほしくない場所まで波及してしまう問題や、giteaにべったりで応用の利かない部分を直せたほか、一度しか出現しないのに変数化されていたり、存在しないファイルを参照するコードがあったのに気づけるなど、収穫が多くあった。

修正点としてはNAME変数はサービス名のみに影響するようにして、実行ファイル名に波及しないようにしたり、"$DAEMON -- $DAEMON_ARGS"となっていた場所も$DAEMON $DAEMON_ARGSとするなど、影響範囲を狭めたり、変数側で調整が効くものをべた書きで依存させないようにした。

今回は微妙な記事を参考にinit.dを書いてしまい、結構ハマってしまったが、今回の修正によって今後init.dを書く場合には、より詰まりづらくなることが期待できる。

Prometheusの役割を知れた

今までPrometheusのことを色んなアプリケーションのログをフェッチしてDBに放り込んでくれるツールだと思っていたが、全然違った。Prometheusはメトリクスを時系列に集め、それをGrafanaなどのビジュアライザに提供できるツールだ。

この「メトリクス」や「時系列」という言葉が鍵で、「メトリクス」は任意の数値、時系列は取得した時間だ。

例えばCPU使用率が20%、メモリ使用量が24GBという情報をExporterが公開しているとして、10:00にPrometheusが取得すれば10:00のCPU使用率は20%、メモリ使用量は24GBとなる。次回10:11に取得すれば別の数値が取れるだろう。これを積み重ねていくことで時系列に数値を取ることが可能になり、時系列で集計することが可能になる。そうするとグラフにしたときに使用率の急増などが掴めるようになるわけだ。

具体的にはExporterと呼ばれるプログラムが、この値を収集し、http://<ホスト>/metricsのエンドポイントで公開している。なのでprometheus.yamlにはホスト部のみを定義すれば読みに行ってくれる。逆に言えばExporterを自作する場合は、ここに値を置けばよいのだろう。

他にもExporterが置けない、揮発性のものに対してもPushgatewayと呼ばれる外部プログラムにデータをプールさせることで、Exporterの代わりになると言う事も知れた。結局のところ相手が何であろうと、Prometheusはメトリクスエンドポイントをポーリングして情報を収集するだけなのだ。

そしてGrafanaとかは恐らくPrometheusにPromQLで問い合わせてデータを引き出すのだろう。

全体像を理解するにあたりPrometheus公式にある、アーキテクチャ図が非常にわかりやすく、だいぶ助けられた。

名前は聞いたことがあるものの今まであまりよくわかっていなかったPrometheusの理解が深まったことは、今回の作業で最大の収穫だった。

トラブルシューティング

Prometheusの管理画面でNode exporterがUpにならない

次のエラーが出る場合、もしDocker Composeを使っているのなら、バイナリをホストにインストールすると回避できる。

  • Error scraping target: Get "http://node_exporter:9100/metrics": dial tcp: lookup node_exporter on 127.0.0.11:53: server misbehaving
  • Error scraping target: Get "http://localhost:9100/metrics?collect%5B%5D=cpu&collect%5B%5D=meminfo&exclude%5B%5D=netdev": dial tcp [::1]:9100: connect: connection refused
  • Error scraping target: Get "http://host.docker.internal:9100/metrics?collect%5B%5D=cpu&collect%5B%5D=meminfo&exclude%5B%5D=netdev": context deadline exceeded

参考までに一番上と一番下はIPv4なので疎通できず、真ん中はlocalhostがコンテナの中を見ているのでホストに疎通できていない状態と思われる。

Prometheus関連のデーモンを起動したが、Prometheusの管理画面のStatus→Target healthに出てこない

数秒~数十秒のラグがあるので少し待つと出てくる。ステータスがUpになるのにも時間がかかるのでゆっくり待つしかない。

参考までに筆者のマシンはCPUがAMD Ryzen 5 5600Gでメモリは32GB積んでいるが、これほど掛かる。

NGINX Prometheus Exporterのメトリクスが取れない

--nginx.scrape-uri=stub_statusのパスが指定できていないか、パスが間違っていないか確認すると解決するかもしれない。

参考までにPrometheusの管理画面でnginx-upのメトリクスが0の場合は取得に失敗しているので、どこかの設定がおかしい。

任意のメトリクスが取れない

以下の手順を試し、原因の切り分けを図り対策する。

  1. Exporterが動いているか
    • ps ax | grep promで上がっているかどうか確認する
    • ポートが塞がっている場合sudo lsof -i :<ポート番号>でプロセスを特定して殺してから起動する(ゾンビプロセスに塞がれていることがある)
  2. Exporterのエンドポイントにcurlが疎通するか
    • curl -v http://localhost:{exporterのport}/metricsで確認可能
      • ポート番号はExporterのGitHubや、起動時のログに出ているので、そこを確認する
  3. Exporterにcurlを投げた時にメトリクスらしきものが出ているか確認する
  4. Prometheusの管理画面を開き、取得できていないメトリクスを取得するクエリを打ち、取れるかどうかを確認

GrafanaのNginx系ダッシュボードテンプレートをImportしたが何も出ない

諦めて自分で作るしかない。幸い素のnginxはメトリクスが少ない。

Node Exporter Fullダッシュボードでメトリクスが表示されない

Node exporterダッシュボードの作成を参照のこと。

Grafanaの待ち受けポートを変える

/etc/nginx/conf.d/grafana.confhttp_portのコメントを外し、任意のポートに変更し、sudo service grafana-server restartで再起動する。

前段にnginxなどがいる場合は、そちらの設定を変えるのも忘れないこと。

3000番はNode.js系がよく使うため変えた方が色々便利だ。

2025/08/13(水)OCNバーチャルコネクト環境でIPv6サーバーをポート443で公開した時にやったこと

この記事はフレッツ光ネクストを対象としたものです。フレッツ光クロスには当てはまらないので注意してください。

本記事はここ昨今のOpenWrtセットアップシリーズの続きである。

OCNバーチャルコネクトのIPoEでIPv4 over IPv6のMAP-E環境だと、IPv4ではWell-known portsが開けない。しかし、IPv6であれば、理論上全部のポートが使えるはずで、それであればサーバーを建てられるのではないかと考えたので、やってみた記録。

セキュリティを考慮し、サーバー以外にはアクセスできないよう、NATのような仕組みで構築する。

確認環境

環境 内容
ISP OCN光
ISP契約 OCN 光 with フレッツ マンション・スーパーハイスピード 隼・プラン1・西日本
ISP接続方式 OCNバーチャルコネクト(IPoE, MAP-E)
RouterOS OpenWrt 24.10.0
ServerOS Ubuntu 24.04.3 LTS
HTTPD nginx 1.26.1
ドメインレジストラ Value Domain

前提条件

IPv4環境下でのHTTPSアクセスが可能で、かつ以下のセットアップが終わっているものとする。

今回の要件

ルーターのファイアウォールでサーバーマシンのみ穴をあけ、それ以外を塞ぐ。つまりIPv4のNATにあったセキュリティの再現を行うことで、関係ない端末が攻撃されないようにする。

やり方

サーバーとDNSの設定

  1. nginxの設定を開きlisten [::]:443を追加して再起動
    1. sudo service nginx restart
  2. サーバーマシンのIPv6(Global Unique Address, GUA)を控える
    ip -6 addr | grep 'global dynamic' | perl -ale '$F[1] =~ /^([^\/]+)/; print $1;'
    
  3. Value DomainのDNS設定を開きAAAAレコードに、先ほど控えたIPv6アドレスを登録する

ルーターのファイアウォールに穴をあける

設定ファイルを編集する方法
  1. vi /etc/config/firewallで以下の行を追加
    config rule
            option name 'Allow-Server-IPv6'
            option src 'wan'
            option dest 'lan'
            option proto 'tcp udp'
            option dest_ip 'さっき控えたサーバーのIPv6アドレス'
            option dest_port '443'
            option family 'ipv6'
            option target 'ACCEPT'
    
  2. service firewall restartでfirewallを再起動する
LuCIでやる方法
  1. LuCIに入りNetwork→Firewall→Traffic Rulesを開く
  2. Addボタンを押し、次の要領で入力して保存
    General Settings

    項目
    Name Allow-Server-IPv6
    Protocol TCP│UDP
    Source zone wan
    Destination zone lan
    Destination address さっき控えたサーバーのIPv6アドレス
    Destination port 443

    Advanced Settings

    項目
    Restrict to address family IPv6 only
  3. 穴をあけたIP以外が外部から疎通しないことを確認

  4. 穴をあけたIPが外部から疎通することを確認

備考

OCN光のIPoE(MAP-E)方式のIPはv4, v6ともに基本的に変動しない

結論から言うと引っ越しでもしない限り、v4が固定なのは知っていたが、v6も固定らしい。

v6のアドレスが変わる気配がないので、OCNのテクニカルサポートに聞いた結果、PPPoEは変動IPv4でルーター再起動時にIPが変わるが、IPoEであればIPv4, IPv6ともに半固定で通常は変わらないとのことだった。

つまりVLANやip6tablesがなくてもサーバーを公開できると言う事でOCN様々と言う事である。

IPv4アクセスをどうするか?

現状は2案検討している。

  1. どっかにペラのページを置いておき、「IPv6でアクセスしてください」みたいなお知らせページにしておく
  2. SNSなどのOGP対策で、簡単なプロキシを組んでおき、OGPだけ出るようにしておく

2のケースだとレンサバにv6側サイトのOGP取得用のCGIを置いておくとか、v6側サイト更新時にOGP付きのペラのHTMLを置いておくなどが検討できると思う。

Cloudflare Tunnelを使わない理由

Cloudflare Tunnelを使えればIPv4を利用でき、デュアルスタック対応もできるだろう、しかしなぜ使わないのかという話。

基本的にオンプレミス至上主義だからというのが答えにはなるが、実務的な理由もある。

まずCloudflare Tunnelを利用する場合、ネームサーバーを委任する必要があるが、CloudflareのDNSはさくらインターネットのSPFレコードを扱おうとするとバリデーションエラーを吐いて使えない。他にも、管理画面のUIがお世辞にもよくなく、言葉を選ばず言えばクソである点もある。勿論、余計なレイヤーを増やすことによる運用コストの増大もあるため、使わなくて済むのであれば、それに越したことはないという考えだ。

またCloudflareのWAF機能やhCAPTCHAがユーザーとして嫌いなのもあり、自分が嫌いなものをユーザーに提供したくないのもあるし、Cloudflareのエラー画面を見て嬉しく思う人もいないと思うので、あのインフラには乗りたくない思いもある。

IPv4のデュアルスタック対応としては、VPNにリバプロをかけるのも検討したが、レガシーに縋っていても仕方がなく、今のところはIPv6に注力する方向にしようとしている。世界のIPv6移行に末端からでも貢献出来たらいいなくらいの気持ちでやっていくのだ。

疎通検証に使える簡易サーバーの作り方

PHP

php -S "[::]:80"でIPv6向けの簡易サーバーをサクッと立てられるので疎通検証をするときに便利。

Node.js

http-serverだとhttp-server -p 80 -a "[::]"でいける。serveは未対応っぽい。

疎通確認に使えるcurl例

IPv6はURL形式が特殊なのでアドレス部分を[]で囲んだ書式で投げる。これはブラウザで確認する場合でも変わらない。

curl -v "http://[aaaa:bbbb:cccc:dddd:eeee:ffff:gggg:hhhh]:80/"

ドメインを利用する場合に、リクエスト先をAレコードとAAAAレコードで明示的に分ける場合は、以下の書き分けができる。

# IPv4向け
curl -4 -v "http://example.com/"
# IPv6向け
curl -6 -v "http://example.com/"

複数ポートの開き方

option dest_portに値を半角スペース区切りで追加すればよい。LuCIでも同じ書き方で通用する。

例:

option dest_port '80 443 8080'

連番で開ける場合は公式ドキュメントによると、'1024:65535'のような書式にすれば、連番で開けられるようだ。

例:

option dest_port '8000:8999'

関連記事

2025/08/04(月)R86SとOpenWrt 24.10でOCNバーチャルコネクトに接続したときのメモ

前回、OSインストールの記事を書いてから約四ヶ月、今回は実際にインターネットへの接続をやっていく。接続にはOCN バーチャルコネクトを利用する。

色々あって四ヶ月放置していたR86Sにはいい感じに埃が積もっていた。

確認環境

  • R86S U1
  • OpenWrt 24.10.0
  • OCNバーチャルコネクト
  • フレッツ光ネクスト

手順

OSインストールが終わっているものとする。

また、この手順ではLANケーブル(RJ45)を使って進めている。

途中でインターネットを利用する場面が何度かあるため、キャリア回線でWWWに出られるスマホがあると便利だ。

R86Sに接続する

eth1にWAN, eth0にLANを接続する。

パッケージのインストール

SSHで192.168.1.1に接続し次のコマンドを流しパッケージをインストールする。

opkg update
opkg install luci
opkg install map
reboot

Web管理画面へのアクセス

http://192.168.1.1/にアクセスするとWeb管理画面が開くので、ここから設定をしていく。

設定情報の取得

管理画面上部のNetworkからInterfacesを開き、wan6のIPv6にある先頭4セクションを控え、http://ipv4.web.fc2.com/map-e.htmlを開き入力、計算する。出力されたoption始まりの行の内容をすべて控えておく。

wan6のIPv6にある先頭4セクション(aaaa:bbbb:cccc:dddd)とoptionの値は後で使うので全部控えておく。

WAN側IPv6のDHCP(DHCPv6 client)の設定

wan6のEditボタンを押す。

DHCP Serverタブを開き、「Set up DHCP Server」ボタンを押す。

IPv6 Settingsタブを開きRA-Service、DHCPv6-Service、NDP-Proxyをrelay modeに変更し、保存する。

LAN側IPv6のDHCPの設定

先ほどと同様にDHCP Serverタブ→IPv6 Settingsタブと開き、RA-Service、DHCPv6-Service、NDP-Proxyをrelay modeに変更し、保存する。

ルータ広告用のインターフェースの作成

画面左下にある「Add new interface...」ボタンを押し、以下の要領で選択し作成する。ルーター広告はIPv6のRouter Advertisementのことなのでインターフェース名をwan6raとしている。

項目
Name wan6ra
Protocol Static address
Device Alias Interface: "@wan6"

Deviceは項目選択後は画像のように@wan6表記になる。

ルータ広告用のインターフェースの設定

ここでは控えておいたwan6のIPv6にある先頭4セクション(aaaa:bbbb:cccc:dddd)を利用する。

wan6raインターフェースのEditボタンを押し、General Settingsタブにある項目を次の要領で設定する。

項目
IPv6 address aaaa:bbbb:cccc:dddd::1001
IPv6 gateway aaaa:bbbb:cccc:dddd::1
IPv6 routed prefix aaaa:bbbb:cccc:dddd::/56

各設定項目に応じて、先頭4セクションの後ろに::1001、::1、::/56を追記するイメージ。

疎通確認

Save&Applyを押し、以下のサイトが開けることを確認する。

この段階ではIPv6にしか繋がらないため、続けてIPv4向けの設定をしてゆく。

MAP-E用インターフェースの作成

次の要領でインターフェースを作成する。

項目
Name wanmap
Protocol MAP / LW4over6

MAP-E用インターフェースの設定

ここでは控えておいたoption始まりの行の内容を利用する。

wanmapインターフェースのEditボタンを押し、General Settingsタブにある項目を次の要領で設定する。

項目
Type MAP-E
BR / DMR / AFTR option peeraddrの値
IPv4 prefix option ipaddrの値
IPv4 prefix length option ip4prefixlenの値
IPv6 prefix option ip6prefixの値
IPv6 prefix length option ip6prefixlenの値
EA-bits length option ealenの値
PSID-bits length option psidlenの値
PSID offset option offsetの値

Advanced Settingsタブを開き「Use legacy MAP」にチェックを入れ、保存する。

Firewallの設定

SSHで192.168.1.1に接続し/etc/config/firewallを開く。

config zone
	option name 'wan'
	...
	list network 'wan'
	list network 'wan6'

となっている箇所の末尾に以下を追記し保存。rebootコマンドで再起動する。

	list network 'wan6ra'
	list network 'wanmap'

ルーター再起動後、http://wa.kiriwake.jpne.co.jp/に接続し、判定ボタンを押し、試験1~4と、契約中のフレッツ東西の光がOKになっていれば疎通は問題ないと思われる。

後は適当に幾つかサイトを開いたりfast.comで回線速度を見て疎通確認を実施した。

ストレージの拡張(2025/08/26追記)

イメージを焼いた関係で、初期パーティションが狭い。R86S U1のeMMCは128GBもあるので、これを拡張する。

  1. GPartedのサイトからイメージを落とし、最新のRufusで適当なメディアにイメージを焼く。
  2. GPartedを焼いたメディアをR86Sに接続
  3. R86Sを起動しDeleteボタン連打でBIOSに入る
  4. Boot OptionでGPartedの起動順位をトップにする
  5. 再起動し一番上のGParted Live(Default settings)で起動
  6. 起動するとGPartedの画面が出ているのでパーティションを最大まで広げる

やり終えてから思ったが、OpenWrtインストール時に使ったUbuntuServerにpartedくらい入ってるだろうし、それでやってもよかったかもと思った。

発生したトラブル

192.168.1.1に繋がらない

eth0にLANを接続する事で繋がる。初期設定ではeth0がブリッジ、eth1がWAN、eth2は未設定。

ポート不足?(ニチバン問題?)(未解決)

ニチバン問題と同じものかはわからないがコネクションが225を超えた付近で通信が詰まる傾向があった。このため、Configuring OpenWrt to work with Japan NTT IPv6 (MAP-E) serviceのADVANCED CUSTOM CONFIGURATIONを利用し解決を試みたが、たぶん特に意味はなかった。

以下のコマンドを流すことで導入できた。

cd /lib/netifd/proto
mv map.sh map.sh.orig
wget -O map.sh https://raw.githubusercontent.com/fakemanhk/openwrt-jp-ipoe/refs/heads/main/map.sh.new
reboot

ひとまず変更して、意図的に大量のサイトを開いた状態がこちら。一見解消しているように見えるが、人為的にやった結果なので同等の結果かどうかは怪しい。

また一気に大量のサイトを開きまくると、流石に若干詰まりが見られたが、詰まっていた時はルーターさえ疎通しなかったので、単にOSのポートを食いつぶしていただけという可能性もある。

参考までに元のmap.shに戻して同じ操作をしてみたところ、ちゃんとコネクションが張れたので関係なさそう。

以前と比べてネットワーク構成が変わっており、Buffaloのルーターをスイッチングハブとして使っている影響などが関係しているかもしれないので、しばし静観の方向で…。

ルーター起動後、Windowsでネットワークアダプタが認識中のままになる、インターネットに繋がらない

電源投入からOCNバーチャルコネクトのリンクアップまで20~50秒ほどかかるので気長に待つ。

LAN側IPv6のDHCPの設定でDHCPv6-Serviceの値はrelay modeかserver modeか?

OpenWrtではDHCPv6の運用が難しいため、基本的にrelay modeでよいと思う。

おまけ

eth0をWAN、eth1をLANにする方法

  1. NetworkからInterfacesを開く
  2. wanとwan6のDeviceをeth0に変更
  3. Devicesタブに切り替る
  4. br-lanの「Configure…」ボタンを押す
  5. General device optionsのBridge portsをeth1にして保存
  6. Save & Applyを押す

CPU温度を見る方法

luci-app-temp-statusをインストールする。

2025年8月4日時点では以下のコマンドでインストールできる。

wget --no-check-certificate -O /tmp/luci-app-temp-status_0.7.1-r2_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/current/luci-app-temp-status_0.7.1-r2_all.ipk
opkg install /tmp/luci-app-temp-status_0.7.1-r2_all.ipk
rm /tmp/luci-app-temp-status_0.7.1-r2_all.ipk
service rpcd restart

インストールが終わるとトップ画面にTemperatureが出てくるほか、Status→Realtime GraphsにもTemperatureが出てくるようになる。

セットアップが完了したR86Sの姿

まぁまぁいい塩梅だと思う。

撤去されたYAMAHA RTX830

R86S + OpenWrtを導入するまで使っていたRTX830。価格の割にIPv6向けの機能が貧弱だったり、設定が手間だったり、マニュアルが雑目だったり、あまりいい思い入れはなかった。

OCNバーチャルコネクトの接続がBuffaloルーターより早く安定している点と、LAN向けのDNSがあるのは便利だったが、あの読みづらいマニュアルはもう少し何とかした方がよいと思う。

今回セットアップした感想

今のところ予想以上にトラブルや問題がなく、平然と動いているため、かなり満足している。

RTX830との比較

管理画面の操作性や充実度はRTX830を遥かに超えており、非常に体験がよかった。

設定変更についてもSave & Applyを押すまでは保留状態になるため、間違った設定が即座に反映されないのもよい。

しかもPending changesをクリックすると設定ファイルに対する差分も確認でき、大変便利だ。

電源投入からOCNバーチャルコネクトへのリンクアップまでの時間も体感RTX830と大差なく、Buffaloルーターのように接続ネゴシエーションの成功を祈っていたら1~2時間経過していたみたいなこともないのは素直にうれしい。fast.comで計測した限りでは速度やレイテンシの劣化もなく大変満足している。

DHCP設定の静的リースのUIが便利すぎる

Network→DHCP and DNS→Static Leasesから現在繋いでいる機器が見れるのがありがたいし、リース情報もわかりやすい。

しかも静的リースを行うときにOpenWrtが把握しているMACアドレスとドメイン名の一覧が出てくるので、手打ちしたり、確認する手間が省けるのも便利だ。リース時間も設定できるし、至れり尽くせりである。

ちょっとうるさい

唯一残念なところを挙げるとするとファンが微妙にうるさく、2mの距離があってもモスキート音レベルのささやかな動作音が聞こえる部分だ。これは慣れれば気にならなくなると思いたい。今の時期だとエアコンの音にかき消されてほぼ聞こえないが、それでも寝る前だと個人的には若干気にならなくもない程度だ。薄型のシロッコファンゆえにNoctuaのファンに変えるのも難しく、正直解決策としては物理的に遠くするか、間に何か防音材を放熱の邪魔にならないように挟むくらいしかないだろう。一応メーカー的には静穏ファンとのこともあり、大半の人は気にならないとは思う。

Banana Pi BPI-R4にもSPF+が2つあり、RJ45ソケットもあり、2.5万程度と安価なため、冷却装置をDIYして静穏化する場合はこちらの方が向いている可能性がある。実際にやってる人もいる

ただまぁスペックはR86Sの方が上等なので、安く静かで多くを求めず、多少大きくてもいいならBanana Pi BPI-R4、多少の騒音を許容でき、スペックが欲しい場合はR86Sというのもありだろう。一般家庭のルーターに、そこまでスペック要るのかどうかというのはさておき…。

微妙なスイッチングハブの使用による性能劣化

今回ルーター側のポート数の制約により、ルーター直結でなくスイッチングハブを通してWWWに接続している。使えるものがBuffaloのWiFiルーターのAPモード(スイッチングハブとして機能する)しかなかったので、これを使っているが、直結と比べた場合にあからさまな性能劣化が見られた。以下は数回計測した上での平均値。小数点以下は切り捨て。

直結時

下り速度 上り速度 アンロードレイテンシ ロード済みレイテンシ
435Mbps 430Mbps 4ms 5ms

Buffalo経由時

下り速度 上り速度 アンロードレイテンシ ロード済みレイテンシ
357Mbps 422Mbps 5ms 13ms

上りは誤差だが、下りが78Mbps低く、ロード済みレイテンシも倍以上の差が出ている。MMOを辞めた今となっては問題ないとはいえ、なんとも微妙だ…。

ただまぁ、将来的には10GbEスイッチを購入する予定であるため、恐らくソフトウェア的な制御によって起きているであろうこの問題は、いずれ解消するとは思う。

次の作業に向けて

設定のカスタマイズなどを通してよりよい環境にしていき、IP変動環境でIPv6を利用した自宅サーバーの公開や、10GbEの導入などに向けて挑戦していきたいところだ。

参考記事