最も"攻撃力"が高い負荷試験ツールは...?

この記事は決して DoS 攻撃を推奨・肯定するものではありません。DoS 攻撃は攻撃対象へ金銭的・物損的被害が発生する可能性がある非常に危険な行為です。本記事に起因するいかなるトラブル・損失・損害に対しても、著者は責任を負いません。

こんにちは k-jun です。先日 GitHub の海を泳いでいると以下のようなリポジトリを発見しました。

https://github.com/ajax-lives/NoRussian

Volunteer DoS tool via HTML + JS. Made to be user friendly and simple to use - as to maximize the amount of people able to help. Running the HTML locally on your device will run a DoS attack on Russian and Belarussian websites (gov, mil, banks, propaganda sites), through complete volunteering.

HTML と JavaScript を利用して、特定のサイトへボランティア DoS 攻撃を行うためのツールのようです。

https://ja.wikipedia.org/wiki/2022%E5%B9%B4%E3%83%AD%E3%82%B7%E3%82%A2%E3%81%AE%E3%82%A6%E3%82%AF%E3%83%A9%E3%82%A4%E3%83%8A%E4%BE%B5%E6%94%BB

中身のコードを見てみると、リストアップした URL へ JavaScript より大量のリクエストを発行。アクセスするだけで誰でも Dos 攻撃に参加できる、まさに Volunteer DoS tool というわけです。

さてここで、ある疑問が自分の中に湧いてきました。JMeter などに代表される負荷試験ツールならもっと効率的に負荷を与えられるのではと。Volunteer DoS Tool はブラウザから大量のリクエストを発行しているが、負荷試験ツールならより少ないコンピューターリソースで高い負荷を生み出せるのではと。ということで、休日の時間を利用してこれを調べて実験してみることにしました。

目的

今回の目的は、同じコンピューターリソースを利用して、対象に最も高い負荷を与え続けられる負荷試験ツールの特定 です。考えの発端は Volunteer DoS Tool ですが、要するに単純で大量のリクエストを送信し続ける場合、最も効率の良い負荷試験ツールは何かというお話です 笑。

手法

星の数ほどある負荷試験ツールですが、今回は以下の7種類のものを調査の対象としました。選定基準はそれなりに有名であること、簡単に試せることの2つです。これだけ言語がバラバラで7種類もあればそれなりの結果が得られるでしょう。僕の時間も無限ではないのです...。

name url language stars
wrk https://github.com/wg/wrk C 31.5k
hey https://github.com/rakyll/hey Go 13k
ab https://github.com/CloudFundoo/ApacheBench-ab C 89
siege https://github.com/JoeDog/siege C 4.9k
vegeta https://github.com/chidakiyo/vegeta Go 19.2k
locust https://github.com/locustio/locust Python 18.4k
gatling https://github.com/gatling/gatling Scala 5.5k

次に、負荷をかける環境を AWS 上に構築していきます。構築と言ってもターゲットとランチャーだけのシンプルなものです。

f:id:K-jun1221:20220313002124p:plain

ランチャー上では上記7種の負荷試験ツールのインストールを、ターゲットでは負荷を受ける役割の NGINX を起動させておきます。なお、VPC は ap-northeast-1 、Subnet は ap-northeast-1a に作成しました。その他インスタンスタイプ、OS などは以下のとおりです。

name OS instance type
target Ubuntu v18.04 c5.large
launcher Ubuntu v18.04 c5.large

また socket: too many open files を回避するため、ランチャーとターゲットのファイルディスクリプタは予め最大値まで上げておきます。ターゲット上の NGINX にも同様に反映しておきます。

# in target & launcher
$ sudo tail -n 4 /etc/security/limits.conf
* soft     nproc          780246
* hard     nproc          780246
* soft     nofile         780246
* hard     nofile         780246
# in target
$ sudo cat /etc/nginx/nginx.conf | grep worker
worker_processes 16;
worker_rlimit_nofile  780246;
        worker_connections 780246;

最後に実験手順です。ランチャーとターゲットへ ssh でログインし、以下の手順で実験を行いました。(RAM に関しては特段目立った変化が見られたなかったので省略しました。)

  1. ランチャー & ターゲット: vmstat を実行。
  2. ランチャー: 調査対象の負荷試験ツールを30秒間実行。
  3. ランチャー: 負荷試験ツールから出力された RPS を記録。
  4. ランチャー & ターゲット: vmstat から出力された CPU と RAM の変化量を記録。

負荷試験ツールは 実行時間は30秒、RPS は最大となるように設定を弄っています。例として wrk を例に取った実際のコマンドを以下に示します。

# ubuntu@target
$ vmstat 1 -nt | awk '{printf("%s,%s,%s\n", $19,$15,$4)}'
# ubuntu@launcher
$ vmstat 1 -nt | awk '{printf("%s,%s,%s\n", $19,$15,$4)}'
$ ./wrk/wrk -d 30 -t 4 -c 100 http://10.1.101.10

結果

name rps 1回目 rps 2回目 rps 3回目 command
wrk 77491.89 78012.51 76856 ./wrk/wrk -d 30 -t 4 -c 100 http://10.1.101.10
hey 45224.8694 45779.8114 46258.3372 ./hey -z 30s http://10.1.101.10
ab 39328.21 39147.51 38708.19 ab -n 1000000 -c 100 http://10.1.101.10/
siege 32088.11 31622.09 31692.97 siege -t 30S http://10.1.101.10
vegeta 14999.48 15000.44 14999.59 echo "GET http://10.1.101.10" | vegeta attack -rate 15000 -duration 30s | vegeta report
locust 3287 3306 3313 python3 -m locust --master (※詳細は別途参照)
gatling 833.333 833.333 833.333 bin/gatling.sh (※詳細は別途参照)

※ locustfile.py

from locust import HttpUser, task

class HelloWorldUser(HttpUser):
    @task
    def hello_world(self):
        self.client.get("/")

※ BasicSimulation.java

package computerdatabase;

import static io.gatling.javaapi.core.CoreDsl.*;
import static io.gatling.javaapi.http.HttpDsl.*;

import io.gatling.javaapi.core.*;
import io.gatling.javaapi.http.*;
import java.time.Duration;

public class BasicSimulation extends Simulation {

  HttpProtocolBuilder httpProtocol =
      http
          .baseUrl("http://10.1.101.10")
          .acceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8")
          .doNotTrackHeader("1")
          .acceptLanguageHeader("en-US,en;q=0.5")
          .acceptEncodingHeader("gzip, deflate")
          .userAgentHeader(
              "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:16.0) Gecko/20100101 Firefox/16.0");

  ScenarioBuilder scn =
      scenario("simplest")
          .exec(http("request_1").get("/")
          .check(status().is(200)));
  {
    setUp(scn.injectOpen(stressPeakUsers(10000).during(30)).protocols(httpProtocol));
  }
}

wrk

f:id:K-jun1221:20220313012443p:plain

hey

f:id:K-jun1221:20220313011939p:plain

ab

f:id:K-jun1221:20220313011958p:plain

siege

f:id:K-jun1221:20220313012007p:plain

vegeta

f:id:K-jun1221:20220313012018p:plain

locust

f:id:K-jun1221:20220313012030p:plain

gatling

f:id:K-jun1221:20220313012037p:plain

考察

wrk

最も目を引くのは wrk の 78,012 RPS ですね。他を寄せ付けないほど圧倒的な RPS であり、ターゲット側の CPU をほとんど 0% まで使い切らせています。ランチャー側の CPU が 60% 程度で安定しているところを見るに、まだ余力がありそうです。ターゲット側のスペックをより積めばより高い PRS を出すでしょう。

hey, siege

hey, siege はそれぞれ 46,000 RPS, 32,000 RPS と高い数値を記録しています。ランチャー側の CPU を使い切っていることから wrk よりリソースの使用効率が悪いものの、負荷試験ツールとしては十分な RPS を出せています。特に hey はインストール・設定共に簡単であり、良い負荷試験ツールと感じました。

ab

ab は 39,000 RPS とかなり高い値を出しているにも関わらず、ランチャー・ターゲット共に CPU に余裕が見られます。この余剰リソースを使い切るため試行錯誤してみましたが、今回の実験ではこれ以上の PRS を実現することは無理でした...。実験終盤に CPU 使用量が回復しているのは、秒数ではなく 1,000,000 リクエストを打ち切った後に終了するという設定で ab を実行しているためです。

vegeta

vegeta は 14999.59, 15000.44, 14999.59 と全ての PRS がほとんど同じ値になっています。コマンドを見てもらえばわかりますが、vegeta -rate 15000 で RPS を予め指定しているため、このような均一的な結果になっています。どうやら実行時に address の食い合いが起きるらしく、15,000 以上の RPS を指定すると以下のエラーが頻繁に見られました。

Get "http://10.1.101.10": dial tcp 0.0.0.0:0->10.1.101.10:80: bind: address already in use

名前と GitHub の画像は面白いですが、同じ Go 言語の hey と比べてみると微妙かなぁというのが正直な印象です。

locust, gatling

locust, gatling の2種類は他と比較して RPS がかなり低い値に抑えられています。この2つは他と異なり言語によるシナリオが記述可能であり、locust は GUI。gatling は JVM負荷試験の箇所以外でのリソース消費要因も備えています。

コネクションに関しても懸念が残ります。locust, gatling はそれぞれユーザーという設定項目により並列度を指定します。このため、他ツールとはことなり、リクエストごとにコネクションを都度都度切っているのかも...? という予想をしています。

総じてこれら2つは単純なリクエストを送信する以上の複雑な機構を備えた高級な負荷試験ツールであり、純粋に今回の目的には適していなかったのかもしれません。

結論

今回の実験の結果 同じコンピューターリソースを利用して、対象に最も高い負荷を与え続けられる負荷試験ツール は wrk ということになりました。wrk 側の CPU を余らしつつ NGINX 側の CPU を使い切っており、最も負荷を与えるという点ではまず確実に wrk が結論になるでしょう。要するに手っ取り早く大量の負荷を掛けたいときには wrk を脳死でぶっ放せばよさそうということです 笑。

おまけとしてですが、gatling や hey などのツールに触れられたこと。siege・wrk の設定を一通り眺めて知らなかったオプションを発見・利用出来たこと。実験・比較・調査し自分の中に自分なりの回答を持つということが出来たので良い経験となりました。今後もこのようなことをどんどんやっていければと思います。それでは今回はこの辺で!

Golang 製の CUI ツールを作成する! gocui を試してみた。

こんにちは k-jun です。今回は Golang にて TUI を構築するツール gocui を試してみます。

https://github.com/jroimartin/gocui

go"cui" と銘打っていますが、TUI じゃないんですかね、これ。Console User Interface と Terminal User Interface だったら後者のほうが正しそうな。

Setup

$ go get github.com/jroimartin/gocui

Run

一旦 README に記載もある以下のコードを実行してみます。

package main

import (
    "fmt"
    "log"

    "github.com/jroimartin/gocui"
)

func main() {
    g, err := gocui.NewGui(gocui.OutputNormal)
    if err != nil {
        log.Panicln(err)
    }
    defer g.Close()

    g.SetManagerFunc(layout)

    if err := g.SetKeybinding("", gocui.KeyCtrlC, gocui.ModNone, quit); err != nil {
        log.Panicln(err)
    }

    if err := g.MainLoop(); err != nil && err != gocui.ErrQuit {
        log.Panicln(err)
    }
}

func layout(g *gocui.Gui) error {
    maxX, maxY := g.Size()
    if v, err := g.SetView("hello", maxX/2-7, maxY/2, maxX/2+7, maxY/2+2); err != nil {
        if err != gocui.ErrUnknownView {
            return err
        }
        fmt.Fprintln(v, "Hello world!")
    }
    return nil
}

func quit(g *gocui.Gui, v *gocui.View) error {
    return gocui.ErrQuit
}

何やら window らしきものが生成されていそうですね。

f:id:K-jun1221:20211218184337p:plain

よさそうなので、他に何ができるのかを Document を参考に見てみます。

https://pkg.go.dev/github.com/jroimartin/gocui#section-readme

更新する例も作ってみました。

package main

import (
    "fmt"
    "log"
    "math/rand"
    "strconv"
    "time"

    "github.com/jroimartin/gocui"
)

func main() {
...
    g.Update(func(g *gocui.Gui) error {
        time.Sleep(1000 * 3 * time.Millisecond)
        v, err := g.View("hello")
        if err != nil {
            // handle error
        }
        v.Clear()
        fmt.Fprintln(v, "Writing from different goroutines")
        return nil
    })
...
}

Update 関数により View をリセットすることで新しい View が render され、見えるようになるようです。layout 関数にて、for で rendering を繰り返す形にもしてみましたが、どうやら layout 関数が return して初めて rendering が開始されるようです。

layout 関数はあくまでも position を決定するのみで、更新の際には別途 Update 関数が必要ということですね。他にも mouse イベントが取得できるようなのでこれも試してみます。

package main

import (
    "fmt"
    "log"
    "math/rand"
    "time"

    "github.com/jroimartin/gocui"
)

func main() {
    g, err := gocui.NewGui(gocui.OutputNormal)
    if err != nil {
        log.Panicln(err)
    }
    defer g.Close()

    g.SetManagerFunc(layout)
    g.Mouse = true
    if err = g.SetKeybinding("", gocui.MouseLeft, gocui.ModNone, quit); err != nil {
        log.Panicln(err)
    }
...
}

左クリックで 終了する機能をつけてみました。筆者は iTerm2 から基本的にコーディングをしていますが、iTerm2 からは クリックイベントを受け取れず.... しかし、Mac デフォルトの Terminal アプリからは クリックイベントが取得できることが確認できました!!!

これを使用することでなかなかおもしろいことができそうです。それでは今回はこのへんで。

ライブラリのバージョンを一覧表示する syft で遊んでみた。

こんにちは k-jun です。今回は 自動で Software Bill of Materials (SBOM) を出力してくれるツール syft を試して見ます。

https://github.com/anchore/syft

自動で package を探索し、関連するライブラリなどを一覧で出力してくれるようです。再現環境を用意する際にもバージョンを簡単に合わせることができて便利そうです。

Setup

$ brew tap anchore/syft
$ brew install syft

Run

内容すらも忘れた適当な TODO アプリに対して syft を適用してみます。

$ syft todo-app
 ✔ Loaded image
 ✔ Parsed image
 ✔ Cataloged packages      [91 packages]
NAME                    VERSION                 TYPE
adduser                 3.118                   deb
apt                     1.8.2.1                 deb
base-files              10.3+deb10u4            deb
base-passwd             3.5.46                  deb
bash                    5.0-4                   deb
bsdutils                1:2.33.1-0.1            deb
coreutils               8.30-3                  deb
dash                    0.5.10.2-5              deb
debconf                 1.5.71                  deb
debian-archive-keyring  2019.1                  deb
debianutils             4.8.6.1                 deb
diffutils               1:3.7-3                 deb
dpkg                    1.19.7                  deb
e2fsprogs               1.44.5-1+deb10u3        deb
fdisk                   2.33.1-0.1              deb
findutils               4.6.0+git+20190209-2    deb
gcc-8-base              8.3.0-6                 deb
gpgv                    2.2.12-1+deb10u1        deb
grep                    3.3-1                   deb
gzip                    1.9-3                   deb
hostname                3.21                    deb
init-system-helpers     1.56+nmu1               deb
iproute2                4.20.0-2                deb
iputils-ping            3:20180629-2+deb10u1    deb
libacl1                 2.2.53-4                deb
libapt-pkg5.0           1.8.2.1                 deb
libattr1                1:2.4.48-4              deb
libaudit-common         1:2.8.4-3               deb
libaudit1               1:2.8.4-3               deb
libblkid1               2.33.1-0.1              deb
libbz2-1.0              1.0.6-9.2~deb10u1       deb
libc-bin                2.28-10                 deb
libc6                   2.28-10                 deb
libcap-ng0              0.7.9-2                 deb
libcap2                 1:2.25-2                deb
libcap2-bin             1:2.25-2                deb
libcom-err2             1.44.5-1+deb10u3        deb
libdb5.3                5.3.28+dfsg1-0.5        deb
libdebconfclient0       0.249                   deb
libelf1                 0.176-1.1               deb
libext2fs2              1.44.5-1+deb10u3        deb
libfdisk1               2.33.1-0.1              deb
libffi6                 3.2.1-9                 deb
libgcc1                 1:8.3.0-6               deb
libgcrypt20             1.8.4-5                 deb
libgmp10                2:6.1.2+dfsg-4          deb
libgnutls30             3.6.7-4+deb10u4         deb
libgpg-error0           1.35-1                  deb
libhogweed4             3.4.1-1                 deb
libidn2-0               2.0.5-1+deb10u1         deb
liblz4-1                1.8.3-1                 deb
liblzma5                5.2.4-1                 deb
libmnl0                 1.0.4-2                 deb
libmount1               2.33.1-0.1              deb
libncursesw6            6.1+20181013-2+deb10u2  deb
libnettle6              3.4.1-1                 deb
libp11-kit0             0.23.15-2               deb
libpam-modules          1.3.1-5                 deb
libpam-modules-bin      1.3.1-5                 deb
libpam-runtime          1.3.1-5                 deb
libpam0g                1.3.1-5                 deb
libpcre3                2:8.39-12               deb
libseccomp2             2.3.3-4                 deb
libselinux1             2.8-1+b1                deb
libsemanage-common      2.8-2                   deb
libsemanage1            2.8-2                   deb
libsepol1               2.8-1                   deb
libsmartcols1           2.33.1-0.1              deb
libss2                  1.44.5-1+deb10u3        deb
libstdc++6              8.3.0-6                 deb
libsystemd0             241-7~deb10u4           deb
libtasn1-6              4.13-3                  deb
libtinfo6               6.1+20181013-2+deb10u2  deb
libudev1                241-7~deb10u4           deb
libunistring2           0.9.10-1                deb
libuuid1                2.33.1-0.1              deb
libxtables12            1.8.2-4                 deb
libzstd1                1.3.8+dfsg-3            deb
login                   1:4.5-1.1               deb
mawk                    1.3.3-17+b3             deb
mount                   2.33.1-0.1              deb
ncurses-base            6.1+20181013-2+deb10u2  deb
ncurses-bin             6.1+20181013-2+deb10u2  deb
passwd                  1:4.5-1.1               deb
perl-base               5.28.1-6                deb
sed                     4.7-1                   deb
sysvinit-utils          2.93-8                  deb
tar                     1.30+dfsg-6             deb
tzdata                  2020a-0+deb10u1         deb
util-linux              2.33.1-0.1              deb
zlib1g                  1:1.2.11.dfsg-1         deb

こんな感じで出力されるんですね。結構良さそう。json フォーマットなど他のフォーマットにも対応している様子。

$ syft todo-app -o json | head
 ✔ Loaded image
 ✔ Parsed image
 ✔ Cataloged packages      [91 packages]
{
 "artifacts": [
  {
   "id": "c78f941fc6ded0fe",
   "name": "adduser",
   "version": "3.118",
   "type": "deb",
   "foundBy": "dpkgdb-cataloger",
   "locations": [
    {

行えることはシンプルですが、使い所もありそうで結構面白そうですね。それでは今回はこのへんで。

Ingress によるサービス公開! nginx-ingress-controller を試してみる。

こんにちは k-jun です。今回は nginx-ingress-controller を試してみます。

https://github.com/kubernetes/ingress-nginx

そもそも ingress とはなんぞやという話ですが、k8s における 外部からのアクセスを内部の適切な Pod へ ルーティングする LB のようなものです。

https://kubernetes.io/docs/concepts/services-networking/ingress/

Ingress exposes HTTP and HTTPS routes from outside the cluster to services within the cluster. Traffic routing is controlled by rules defined on the Ingress resource.

ここらへんの リソースは自分が前触った際には クラウドサービスの LB をプラグイン連携して使用するみたいになっていた気がしますが、いつの間にか nginx を使用できるようになっていたみたいですね。

Setup

ここ を参考にセットアップを進めていきます。

アプリケーションをデプロイしていきます。使用するイメージはサンプルアプリケーションのもの。

$ kubectl create deployment web --image=gcr.io/google-samples/hello-app:1.0
deployment.apps/web created
$ kubectl expose deployment web --type=NodePort --port=8080
service/web exposed
$ kubectl get service web
NAME   TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
web    NodePort   10.97.252.61   <none>        8080:31074/TCP   35s

Run

ということで、本題の Ingress を作っていきます。

$ kubectl apply -f https://k8s.io/examples/service/networking/example-ingress.yaml

中身で叩いている yaml はこんな感じ。

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 8080

ingress が起動している様子もコマンドから確認できます。

$ kubectl get ingress
NAME              CLASS    HOSTS              ADDRESS        PORTS   AGE
example-ingress   <none>   hello-world.info   35.190.25.54   80      8m58s

External IP でアクセスしてみるとこんな感じ。

$ curl -I http://35.190.25.54 -H 'Host: hello-world.info'
HTTP/1.1 200 OK
date: Sat, 04 Dec 2021 13:35:51 GMT
content-length: 60
content-type: text/plain; charset=utf-8
x-envoy-upstream-service-time: 0
server: istio-envoy
x-envoy-decorator-operation: web.default.svc.cluster.local:8080/*
Via: 1.1 google
$ curl -I http://35.190.25.54 -H 'Host: hello-world.dummy'
HTTP/1.1 404 Not Found
Date: Sat, 04 Dec 2021 13:37:48 GMT
Content-Length: 74
Content-Type: text/plain; charset=utf-8
Via: 1.1 google

HostName の制限もしっかりと生きている様子。外部 IP が簡単に付与できて感激です。数年前はここまで簡単にできなかった記憶。

$ kubectl create deployment web2 --image=gcr.io/google-samples/hello-app:2.0
deployment.apps/web2 created

path ごとの振り分けを試すべく、2つ目のサンプルアプリケーションを作成。

$ kubectl expose deployment web2 --port=8080 --type=NodePort
$ kubectl expose deployment web2 --port=8080 --type=NodePort
$ curl -s https://kubernetes.io/examples/service/networking/example-ingress.yaml > ./ingress.yaml
$ vim ./ingress.yaml
$ cat ./ingress.yaml
$ cat ./ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  rules:
    - host: hello-world.info
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: web
                port:
                  number: 8080
          - path: /v2
            pathType: Prefix
            backend:
              service:
                name: web2
                port:
                  number: 8080
$ curl http://35.190.25.54/v2 -H 'Host: hello-world.info'
Hello, world!
Version: 2.0.0
Hostname: web2-5d47994f45-nj67t

いい感じに、version 2 の方にアクセスが飛んでいますね。 ということで、nginx-ingress-controller を使用することで、いい感じにサービスを外部に公開できることがわかりました。 それでは今回はこのへんで。

ブラウザからターミナル! XTerm を試してみる。

こんにちは k-jun です。今回はブラウザでターミナルが再現できるライブラリ XTerm を試してみます。

https://github.com/xtermjs/xterm.js

以前カロリーメイトのドリンク版の広告サイトが似たようなことをやっていた気がします。これ や。

久しぶりに遊んでみたら、前からあったのか面白い機能を見つけましたw。宗教戦争はごめんとw。

caloriemateliquid $ vim
DON’T FIGHT ANY MORE!
This is a neutral zone.
caloriemateliquid $ emacs
DON’T FIGHT ANY MORE!
This is a neutral zone.

Setup

$ mkdir xterm
$ cd xterm
$ npm install xterm
# xterm/index.html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
    <script src="node_modules/xterm/lib/xterm.js"></script>
  </head>
  <body>
    <div id="terminal"></div>
    <script>
      var term = new Terminal();
      term.open(document.getElementById('terminal'));
      term.write('Hello from \x1B[1;3;31mxterm.js\x1B[0m $ ')
    </script>
  </body>
</html>

Run

作成した index.html にブラウザからアクセスしてみます。

f:id:K-jun1221:20211127170825p:plain

なんか違う...。何も入力できないし...。本当にただターミナルっぽいものが出現しただけじゃん...。 調べてみると input と output は自分で設定する必要があるんだとか。えぇ...

Xterm.js is not bash. Xterm.js can be connected to processes like bash and let you interact with them (provide input, receive output).

なんのためのライブラリ...。まあ、Terminal に接続するとかではなく完全に Terminal が画面に現れただけでしたし。 それでは今回はこのへんで。

分散メッセージングシステム!? NSQ を試してみる。

こんにちは k-jun です。今回はリアルタイムな分散メッセージングシステム NSQ を試してみます。

https://github.com/nsqio/nsq

star 数から見るにかなり有名なプロダクトのようですね。各種クラウドサービスの キューイングサービスももとにしている技術は同じだったりするんでしょうか...?

Setup

$ brew install nsq

Run

ここ に従っていきます。

以下 3つのコマンドをそれぞれ 別ターミナルで起動します。

$ nsqlookupd
$ nsqd --lookupd-tcp-address=127.0.0.1:4160
$ nsqadmin --lookupd-http-address=127.0.0.1:4161

1つ目は名前解決、2つめは NSQ 本体となる daemon, 3つ目は GUI とかでしょうか。

f:id:K-jun1221:20211127163948p:plain

ということでここに 何かを放り込んでみます。

$ curl -d 'hello world 1' 'http://127.0.0.1:4151/pub?topic=test'
OK

何やら Topic が追加されましたね。SNS とかの Topic もここから来ているんでしょうか。

f:id:K-jun1221:20211127164305p:plain

手順にしたがって、nsq_to_file というコマンドを実行してみます。一緒にさらなるメッセージも追加。

$ curl -d 'hello world 2' 'http://127.0.0.1:4151/pub?topic=test'
OK
~/source-code/remix/my-remix-app master*
$ curl -d 'hello world 3' 'http://127.0.0.1:4151/pub?topic=test'
OK
$ nsq_to_file --topic=test --output-dir=/tmp --lookupd-http-address=127.0.0.1:4161
2021/11/27 16:44:10 INF    1 [test/nsq_to_file] querying nsqlookupd http://127.0.0.1:4161/lookup?topic=test
2021/11/27 16:44:10 INF    1 [test/nsq_to_file] (MacBook-Pro.local:4150) connecting to nsqd
[nsq_to_file] 2021/11/27 16:44:10.864197 INFO: [test/nsq_to_file] opening /tmp/test.MacBook-Pro.2021-11-27_16.log
[nsq_to_file] 2021/11/27 16:44:10.864381 INFO: [test/nsq_to_file] syncing 1 records to disk
[nsq_to_file] 2021/11/27 16:44:40.614075 INFO: [test/nsq_to_file] syncing 2 records to disk

管理画面を見てみて、キューがなくなっていることに気づきました。

f:id:K-jun1221:20211127164705p:plain

つまり、nsq_to_file というのはおそらく Topic を listen してファイルに吐き出しているものでは。と /tmp ディレクトリを見に行くと入ってた。

$ tail -f /tmp/test.MacBook-Pro.2021-11-27_16.log
hello world 1
hello world 2
hello world 3

なるほどこうやってメッセージをやり取りできるのか。

ドキュメントをよく見てみると、使用していた daemon の説明も載っています。

https://nsq.io/components/utilities.html を見てみると、他にもいろいろと便利そうなコマンドがあるんですね。

ということで分散メッセージングライブラリの NSQ を見てみました。今回はこのへんで。

次世代のフロントフレームワーク! Remix を試してみる。

こんにちは k-jun です。今回は React Router の作成者が、作ったフレームワーク Remix を試してみます。

https://github.com/remix-run/remix

README によると work in progress ということですが、面白そうなので触ってみてしまいます。

Setup

create-remix で一撃でした。

$ npx create-remix@latest
Need to install the following packages:
  create-remix@latest
Ok to proceed? (y) y

R E M I X

💿 Welcome to Remix! Let's get you set up with a new project.

? Where would you like to create your app? ./my-remix-app
? Where do you want to deploy? Choose Remix if you're unsure, it's easy to change deployment targets. Remix App Server
? TypeScript or JavaScript? TypeScript
? Do you want me to run `npm install`? Yes

Run

では早速起動してみます。

$ npm run dev

> dev
> remix dev

Watching Remix app in development mode...
💿 Built in 373ms
Remix App Server started at http://localhost:3000
GET / 200 - - 43.888 ms

f:id:K-jun1221:20211127160232p:plain

おおお、ひとまず立ち上がることは立ち上がりましたね。

中身を見てみましたが、一番近いのは React ですね。拡張子は tsx ですし。

こちら に従って続きを作っていきます。

app/root.tsx の内容を以下のように書き換えます。

import { LiveReload, Outlet } from "remix";

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <title>Remix: So great, it's funny!</title>
      </head>
      <body>
        <Outlet />
        {process.env.NODE_ENV === "development" ? (
          <LiveReload />
        ) : null}
      </body>
    </html>
  );
}

そして、app/routes/index.tsx を以下のように追加すると、いい感じに。

export default function IndexRoute() {
  return <div>Hello Index Route</div>;
}

変更して気づきましたが、Remix 反映がめちゃくちゃ早いですね。変わったことにすら気づかない Flutter レベルで早いです...。 の component が少なからず影響しているようです。良さげ。

f:id:K-jun1221:20211127161529p:plain

どうもこの という component がかなり便利そうです。下位の component を自動でここに格納するようです。 つまり、以下のように app/routes/jokes.tsx と app/routes/jokes/new.tsx を追加すると。

import { Outlet } from "remix";

export default function JokesRoute() {
  return (
    <div>
      <h1>J🤪KES</h1>
      <main>
        <Outlet />
      </main>
    </div>
  );
}
export default function NewJokeRoute() {
  return (
    <div>
      <p>Add your own hilarious joke</p>
      <form method="post">
        <div>
          <label>
            Name: <input type="text" name="name" />
          </label>
        </div>
        <div>
          <label>
            Content: <textarea name="content" />
          </label>
        </div>
        <div>
          <button type="submit" className="button">
            Add
          </button>
        </div>
      </form>
    </div>
  );
}

こんな感じになる。

f:id:K-jun1221:20211127161910p:plain

絵文字の箇所は app/routes/jokes.tsx から来ているがそれ以外は app/routes/jokes/new.tsx ってことですね。 ワイルドカードを含むルーティングには app/routes/jokes/$jokeId.tsx のように指定するようです。

ということで軽く触るだけしてみましたが、なかなかおもしろそうです。本格的には展開されていないので真の力はこれからですが。 それでは今回はこのへんで。