Laravel-Swooleでunix domain socketを使う方法

最近PHPで新しくサーバーを立てる機会があったのですが、出来るだけパフォーマンスのオーバーヘッドを意識したくない && シンプルな処理しかしない小規模なサーバーということもありポピュラーなnginx + fpmの構成ではなく、Laravel-Swooleを採用しました。

Laravel-Swooleは、swooleというPHPでイベント駆動な非同期サーバーを実現するextensionをLaravel/Lumenで簡単に利用可能にするライブラリです。

ドキュメントをひと通りチェックしたところ、The support of swoole_http_server for Http is not complete. So, you should configure your swoole server with nginx proxy in your production environment.と記載があり、単体での利用は推奨されていないようでした。

9. Nginx Configuration · swooletw/laravel-swoole Wiki · GitHub

ただ、サンプルで用意されているnginxの設定はリバースプロキシをTCP/IPで行っており、Laravel-Swooleのドキュメントにはunix domain socketに関する具体的な設定例は見当たりませんでした。 一応、swoole自体もunix socketに対応していますし、Laravel-Swooleの設定項目にもunix socketが存在します。


swoole_http.php

<?php

return [
    'server' => [
        'host' => env('SWOOLE_HTTP_HOST', '/var/run/swoole.sock'),  // socketを置くパスを指定する
        'port' => env('SWOOLE_HTTP_PORT', '0'), // unix socketを使う場合0にする
        'socket_type' => SWOOLE_UNIX_STREAM, // unix socktを使う場合 SWOOLE_UNIX_STREAM / SWOOLE_UNIX_DGRAM を指定する
        // ....
    ],
    // ...
];

以上でswoole自体の設定は出来たのです、Laravel-Swooleとnginxを別ユーザーで実行する場合socketのパーミッションをいじってやる必要があります。

取り急ぎpermissionを変更したいのですが、php-fpmでいうところのlisten.modeに相当する設定項目がLaravel-Swooleにはないようです。

dockerで利用するので、swoole起動時にフックしたいのですが、どうしようかと思ったところswooleのexampleに参考になりそうな記述がありました。
どうやら、swooleサーバーの起動時に発行されるイベントにフックしてchmodを叩いてやれば良いようです。

swoole-src/stream_server.php at 15404958c76edfe745e18a9219439f4bd9993b71 · swoole/swoole-src · GitHub

Laravel-Swooleでは、swoole.startというイベントが発行されるので、それにフックしてsocketのパーミッションを変更します。

laravel-swoole/Manager.php at master · swooletw/laravel-swoole · GitHub


EventServiceProvider.php

<?php

namespace App\Providers;

use Illuminate\Auth\Events\Registered;
use Illuminate\Auth\Listeners\SendEmailVerificationNotification;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Event;
use SwooleTW\Http\Server\Facades\Server;

class EventServiceProvider extends ServiceProvider
{
    /**
     * The event listener mappings for the application.
     *
     * @var array
     */
    protected $listen = [
        Registered::class => [
            SendEmailVerificationNotification::class,
        ],
    ];

    /**
     * Register any events for your application.
     *
     * @return void
     */
    public function boot()
    {
        parent::boot();

        // swoole 起動時にsocketのpermissionを変更する
        Event::listen('swoole.start', function () {
            $server = $this->app->make(Server::class);
            chmod($server->host, 0666);
        });
    }
}

ただ、unix domain socketを使う場合のpermissionは666でベストなのか、dockerで複数コンテナ使う場合のユーザーの取り回しなどは未解決、、

FPM opcache + preload / swoole / PHPP-PMあたりの比較してみたい。

選ばれたのはAmplifi HDでした。

新しいルーター

そういえば、4月に新しいWiFiルーターを購入しました。


Amplifi HD

キューブ型のシンプルなデザインにタッチスクリーンが搭載されており、WiFiとしてだけでなく時計にもなるのがとてもエレガントです。

このルーターですがUbiquitiという近年米国でシェアを伸ばしているネットワーク機器メーカーの製品です。

タッチスクリーンで、ネットワークの接続情報を確認できたり、アプリから設定・モニタリング、さらにVPN、メッシュにも対応しているところが気に入っています。

NECWiFiルーターが調子悪くなる

実はNEC Atermを10年近く使っており、これも安定していたので気に入っていたのですが、流石に寿命が来たのか接続が不安定になってしまいました。 感染症の影響で、自宅での作業がほとんどになったこともあり、ネットワークの安定は死活問題。

早急に、かつ信頼できるWiFiルーターはないか、、と検討したところ、最終的にこのAmplifiくんが選ばれました。

ひとたびamazonWiFiルーターと検索すれば、大量の有象無象が出現し頭が痛くなります。 Baffaloは宗教上の理由で使えないし、またNECを買うのも芸がない。 かといって、それ以外のルーターは下品にもツノを生やせばはやすだけ良いというようなデザイン

信頼できて、デザインも優れたルーターはないかと思ったところ、rebuildで聞いたことのないルーターの名前が出てきました。

rebuild.fm

調べたところ、最近一部で注目を集めているWiFiルーターで、信頼性、機能性、デザイン性どれも良さそう。

Wi-Fiを Amplifi HD に移行した - tech.guitarrapc.cóm

yabe.jp

これは間違いないと、購入を決定しました。

IPv6が繋がらない

さて、意気揚々と古いWiFiを撤去してAmplifiをセッティングしたわけですが、全然スピードが出ない。 おいおい、どうなってんだ、こっちは大枚叩いてるのに10年前のWiFiよりスピードが出ないなんでおかしいぞ、、、、 と思って調べたところ、どうやらAmplifiはIPoEに対応しておらず、PPPoE IPv4でのみの通信になっていることが原因のようだった。

しばらくはIPv6パススルー機能を使うために前段に古いAtermを噛ませって使っていました。せっかく新しいルーターを買ったのに古いルーターに頼る羽目になるとは、、 (いまだにこのIPv6パススルーって機能がわかってない、なんでPPP接続するとIPv6降ってくるんだ、、?)

その後、中古のNEC IX2105を購入して、IPv4 over IPv6(MAP-E)を利用しています。 今まで、時間帯や接続先によってスピードが出ないことがあったのですが、IPv4 over IPv6の導入でかなり快適になりました。

UNIVERGE IX2105 : UNIVERGE IXシリーズ | NEC

IX2105を選んだ経緯ですが、(自分はネットワークエンジニアではないのですが)お仕事でYAMAHA RTX830を触る機会があり、設定だったりネットワーク周りの情報を集めていたところ、価格ドットコムの口コミに親切かつ強そうルーター玄人が生息していることを発見。

その玄人様の書き込みによると、YAMAHAよりNECの方が性能が期待でき、CUICISCOに近いということで?メルカリで中古をゲットしました。 (MAP-Eが使えれば良いので、適当な日本向けWiFiルーターを買えばよかったが、Amplifiを導入した手前WiFiがついてるルーターを買うと負けな気がした)

見た目と浪漫でWiFiを選定したため、余計な予算と時間を使ってしまいましたが、WiFiとしてのAmplifiは非常に安定していて満足していますし、IPv4 over IPv6あたりの知識を勉強できたのは結果的によかったし、ネットワークも安定したのでよかったなと。 (実家は、ひかり電話だったため、HWでMAP-E対応していたので、安定していたんだなぁ)

RubyでJSONをPOSTする方法

Net::HTTP.postを使う

なぜか、日本語のリファレンスの方には記載がないので、Net::HTTP#postを使う方法がよく紹介されている。 しかし、Net::HTTPインスタンス作ったり、SSL有効にしたりURIインスタンスを作って、host, port, pathをそれぞれパラメータに設定するのは面倒だ。

Net::HTTP.postならURIスキーマhttpsだったらSSLを有効にしてくれるし、URIインスタンスをそのまま渡してあげれば良いのでとてもシンプルだ。

require 'net/http'
require 'uri'
require 'json'

    response = Net::HTTP.post(
      URI('https://example.jp/path/to/endpoint'),
      { key: 'value' }.to_json,
      'Content-Type' => 'application/json'
    )

class Net::HTTP - Documentation for Ruby 2.7.0

terraformでElastiCache Redis Cluster modeをたてる

terrafromでredisを立てる場合、aws_elasticache_clusterを使う方法とaws_elasticache_replication_groupを使う方法がある。

aws_elasticache_clusterの場合ひとつのノード(cluster mode disabled)しか立てられない 。

レプリカを使う場合やCluster modeを使う場合は、aws_elasticache_replication_groupを使うことになる。

cluster_modeブロックの定義とパラメータグループのcluster-mode-enabledを設定することで、Cluster modeのRedisが立ち上がる。

最初パラメータグループの設定に気づかなかったので、かなり時間を溶かしてしまったよね、、

resource "aws_elasticache_replication_group" "main" {
  replication_group_id          = "${var.sysname}-${var.env}"
  replication_group_description = "redis cluster for ${var.sysname} ${var.env}"
  engine_version                = "5.0.6"
  node_type                     = var.node_type
  port                          = 6379
  parameter_group_name          = aws_elasticache_parameter_group.main.id
  subnet_group_name             = aws_elasticache_subnet_group.main.id
  security_group_ids            = [aws_security_group.elasticache.id]
  maintenance_window            = "mon:21:00-mon:22:00"
  snapshot_window               = "20:00-21:00"         
  snapshot_retention_limit      = var.snapshot_retention_limit

  automatic_failover_enabled = true
  auto_minor_version_upgrade = true

  at_rest_encryption_enabled = true
  kms_key_id                 = aws_kms_key.main.arn

  transit_encryption_enabled = false

  apply_immediately = true

  // ここ
  cluster_mode {
    replicas_per_node_group = var.num_cache_replicas
    num_node_groups         = var.num_cache_nodes
  }

  tags = {
    Name    = "${var.sysname}-${var.env}-redis-cluster"
    SysName = var.sysname
    Env     = var.env
  }
}

resource "aws_elasticache_parameter_group" "main" {
  name   = "${var.sysname}-${var.env}-redis-cluster-on"
  family = "redis5.0"

  // ここ
  parameter {
    name  = "cluster-enabled"
    value = "yes"
  }
}

参考

Resource: aws_elasticache_replication_group - terraform Resource: aws_elasticache_cluster - terraform

dockerでscratchイメージを使う

scratch imageとは

dockerが用意した最小イメージです。 shやlsすら入っていないので、goやrustのようなシングルバイナリを生成できる言語で超軽量なコンテナを作りたい際にうってつけです。 しかし、普段各言語公式のイメージなどに慣れていると、意識せずに使っていたファイルが存在せずにうまく動かないこともあります。

証明書を入れる

httpsを使うならTSLのためにCA証明書を入れておく必要があります。 自前で用意せずに、メンテされてそうなイメージから引っ張ってくるのがおすすめです。 Goの場合、公式alpineベースのイメージに含まれているので、これから引っ張ってきました。

FROM scratch as api

COPY --from=builder /go/bin/api /go/bin/api
COPY --from=golang:1.14-alpine3.11 /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ENTRYPOINT ["/go/bin/api"]

ローカルでawsと連携させたい場合

普段alpineを使っている際は、ローカルのcredentialをdocker内にマウントしている。

version: "3.7"
services:
  api:
    build:
      context: .
      target: api
    ports:
      - 80:80
    volumes:
      - $HOME/.aws/credentials:/.aws/credentials

イメージ内に、root以外のユーザーが存在せず/rootディレクトリがない場合は、ルートディレクト/へマウントすることになるので注意 

参考

Dockerのscratchイメージを探検する

ScratchイメージでGolangアプリの超軽量イメージをビルド

The Linux Information Project - The /root Directory