その手の平は尻もつかめるさ

ギジュツ的な事をメーンで書く予定です

macOSでDocker Desktopをアンインストールしてdocker-cli + docker-machineで動かすようにする

www.docker.com

Docker Desktopがここ最近活発に開発されているというか、かなり見た目がオシャレになってきてて「ヤル気あるな〜」と思って眺めていたのですが、なるほど有料化するということなのですね。

Docker Desktop remains free for personal use, education, non-commercial open source projects, and small businesses (fewer than 250 employees AND less than $10M USD in annual revenue).
Commercial use of Docker Desktop in larger enterprises (more than 250 employees OR more than $10 million USD in annual revenue) requires a Docker Pro, Team or Business subscription for as little as $5 per user per month.

実際、こういうところに注力して利益を得ていくというのは良いことだと思います! 頑張れdocker!

それはそうとしてDocker Desktopの機能をあまり使ってこず、実際のところcliでdockerが操作できればよい、つまりdocker engineが動けば良いという感じだったので、docker-machineを使ってローカルでdockerデーモンのためのVMを動かし、ホストからそのVMcliを繋いでdockerを使うという方法をやっていくこととします。どことなく懐しいですね。podman使うとかでも良かったんですけど、ひとまずdocker cliでやります。

まずDocker Desktopをアンインストールする。

f:id:moznion:20210901111820p:plain

"Troubleshoot" メニューを開きます。この虫マークは "Troubleshoot" というメニューだったのですね。バグレポートボタンだと思ってた……

f:id:moznion:20210901111903p:plain

アンインストールしましょう。これで完了です。

そしてdocker cliとdocker-machineを入れる。

$ brew install docker docker-machine

で、docker-machineを使ってローカルにVMを立ててそこでdockerデーモンを動かすこととする。このとき、ローカルマシンにあらかじめVirtualBoxがインストールされている必要があります。
やりかたとしては右のページに従うと良い: Get started with Docker Machine and a local VM | Docker Documentation
おおざっぱに書くと以下のようなかんじ。

$ docker-machine create --driver virtualbox default
$ echo 'eval "$(docker-machine env default)"' >> .bash_profile

そして docker ps などと打ってみてちゃんとレスポンスが返ってくると成功です。


Docker Desktopで動かしていた時と比較するとなにか制限等があるかもしれませんがそれは遭遇した時に考える、あるいは遭遇したらLinux Desktopに乗り換えるなどを検討していきたいと思います。実際Linux Desktopへ移行していきたい……

[追記]

良くみてみるとdocker-machineの開発が停滞しているように見えるので、そこは懸念ポイントと言えそうですね……
github.com
まあなんかあったら手でVM運用していきましょう。俺たちにはそれができるはずだ。

[追記]

Gitlabがforkしたdocker-machineがあるとのこと。

IntelliJ IDEAのsbt pluginがPrivateなGitHubのmaven repositoryに上げたライブラリを解決してくれないとき

表題の件で、sbtの依存解決コンポーネントが突然HTTP Status 400を返却してきてなにをやっても無駄、一生解決してくれない、みたいなことが原因不明ながら周期的に起きていて、そういうときにどうすれば良いかというと、

$ GITHUB_REGISTRY_TOKEN="YOUR-TOKEN" open /Applications/IntelliJ\ IDEA.app/

みたいな感じでトークンを環境変数で与えながらIDEAを起動してやるととりあえず動く……が、まったく本望ではない。
実際IDEAの環境変数を適切に設定すれば動くでしょ、とは思いつつも環境変数どこで設定すりゃ良いんだよ、つーかそもそもsbt pluginでは環境変数を設定することができず本当につらい、mavenやgradleのpluginではできるのだが……という気持ちでいっぱいです。


忙しいときにこういうの踏むと本当に大変ですね。以上です。

Elasticsearchの"index.mapping.total_fields.limit"を監視する話

Elasticsearchには index.mapping.total_fields.limit という設定があり、これは何かというと「1つのindexあたりが保存できるフィールドの上限数」を表現しており、この上限に触れると Limit of total fields [1000] in "your_index" index has been exceededのようなエラーが発生してindexができなくなります。

この上限への対症療法としては先人たちが示している通り様々あるのでそちらに譲り *1、本記事ではこの index.mapping.total_fields.limit を監視する方法について考えていきましょう。上にも書きましたが、この上限にヒットするとそのindexに対するindexingが完全にストップするのでマズいんですよ……
(もちろん、Elasticsearchのパフォーマンスを考えると1つのindexに大量のフィールドを生やすのは良くないし、そもそも理性的な使いかたをしていれば起きないことだとは思うのですが……まあ実際にElasticsearchを運用していると色々なことがありますね)


というわけで、johtaniさんに色々教えてもらったことについて以下に記します。いつもありがとうざいます。

なるほど、コードを読んでみると確かにシンプルなリミットチェックを行っているだけっぽい。

stackoverflow.com

ハハア、なるほど。APIを叩いてそのindexのフィールド数を回収することで現在のステータスを回収しよう、ということですね。


というわけで以下のようなスクリプトを一定周期で流してメトリクスとして回収し、可視化およびアラートを設定することで現状なんとかしております、というお話でした。

for idx in $(curl -Ss "${ES_ENDPOINT}/_cat/indices?format=json" | jq -r .[].index); do
  num_of_fields=$(curl -Ss "${ES_ENDPOINT}/${idx}/_field_caps?fields=*" | jq '.fields | length')
  echo "$idx: $num_of_fields"
  store_metric "$idx" "$num_of_fields"
done

jitterをかけたtickerを提供するgoのライブラリjickerを書いた

github.com

定期的に実行したい何かがあって、そしてそのインターバルにjitterが入っていてほしいということがしばしばあり、必要な時に都度そういうコードを書いていたのですが、毎度書くのもしんどいな〜と思ったのでこの度ライブラリにしたという次第です。

READMEのUsageに書いてあるとおり、

package main

import (
	"context"
	"log"
	"time"

	"github.com/moznion/jicker"
)

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()

	c := jicker.NewJicker().Tick(ctx, 1*time.Second, 0.05)
	for t := range c {
		log.Printf("tick: %v", t)
	}
}

というふうに Tick() を使用すると、1秒に対して±5% jitterがかかったインターバル (すなわち0.95秒から1.05秒の間でランダムなインターバル) おきにtickしてくれるtickerを得ることができます。

package main

import (
	"context"
	"log"
	"time"

	"github.com/moznion/jicker"
)

func main() {
	ctx, cancelFunc := context.WithCancel(context.Background())
	defer cancelFunc()

	c, err := jicker.NewJicker().TickBetween(ctx, 1*time.Second, 2*time.Second)
	if err != nil {
		log.Fatal(err)
	}
	for t := range c {
		log.Printf("tick: %v", t)
	}
}

一方別の関数 TickBetween() を使用すると、任意の区間でjitterがかかったインターバル (この場合1秒から2秒の間でランダムなインターバル) おきにtickしてくれるtickerがやってきます。


「実家」のような名前のライブラリですが今年のゴールデンウィークは実家に帰れません。世界が大変ですね。
小粒なライブラリですが、ぜひご利用くださいませ。

Goのhttptestパッケージを使ってUNIX domain socketを使ったHTTPサーバのテストをする

Goが提供するHTTPのテストのためのユーティリティであるところのhttptestを使って、UNIX domain socketを使用するHTTPサーバのテストをするという内容についてのメモです。

import (
	"log"
	"net"
	"net/http"
	"net/http/httptest"
	"os"
	"testing"
)

func TestFoo(t *testing.T) {
	mux := http.NewServeMux()
	mux.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
		_, err := w.Write([]byte("hello"))
		if err != nil {
			log.Printf("error: %s", err)
			w.WriteHeader(500)
			return
		}
		w.WriteHeader(200)
	})

	tempSock, err := os.CreateTemp("", "sock")
	if err != nil {
		t.Fatal(err)
	}
	err = os.Remove(tempSock.Name())
	if err != nil {
		t.Fatal(err)
	}

	listener, err := net.Listen("unix", tempSock.Name())
	if err != nil {
		t.Fatal(err)
	}

	server := httptest.NewUnstartedServer(mux)
	server.Listener = listener
	server.Start()
	defer server.Close()

	// do something
}
  • os.CreateTemp()でtemporaryなファイルを作り、それを削除してファイル名だけを引っ張りつつ (削除せず実ファイルが残っているとbind(2)がEADDRINUSEを吐く)
  • それを net.Listen()に渡してlistenerを作り
  • httptest.NewUnstartedServer()でテスト用のサーバのインスタンスを作り
  • listenerをそのサーバのインスタンスに食わせ
  • サーバスタート
  • あとはUNIX domain socketを使ったクライアントを使ってテストを書く

という感じで使うことが可能です。簡単ですね。


もちろんtemp fileを使わずに任意のパスをソケットファイルとして与えても良い (server.Close()が実行されるとそのソケットファイルも削除されるので) のですが、うっかりserver.Close()が実行されないとゴミファイルが残ってしまったりしてダルいので、まあ保険のようなものです。

macOSでgit-send-emailがsmtpauthオプションを上手く扱えないという問題

brewでインストールしたgitのgit-send-mailsmtpauth を指定してもそれが上手く適用されず、問答無用で DIGEST-MD5 が利用されるという問題に当たったので、その解決方法をメモとして記します。
さくらのメールボックスMD5-DIGEST をサポートしておらず、PLAIN もしくは CRAM-MD5 を使う必要があるのでこの挙動は困るんですよね……

TL;DR

  • brewで入れたgitのgit-send-mailが依存しているCPANモジュールのバージョンが古い
  • 新しいバージョンのCPANモジュールがインストールされているPerlランタイムの PERL5LIBGITPERLLIB 環境変数に渡すと動く
    • 例:
$ GITPERLLIB="/path/to/.plenv/versions/5.32.1/lib/perl5/5.32.1/:/usr/local/Cellar/git/2.30.0/share/perl5" git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
  • もしくは依存している Net::SMTP のバージョンを上げる (例えば3.11) と動く

環境

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.15.7
BuildVersion:   19H15
$ git version
git version 2.30.0
$ perl --version
This is perl 5, version 32, subversion 1 (v5.32.1) built for darwin-2level

なぜperlが必要かというとgit-send-mailはperlで実装されているためです。
なお、gitはbrewでインストールされており、おそらくこれも原因の一つです。

症状

$ git send-email 0001-something.patch
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

死んでまんな。そしてこのエラーメッセージは見覚えが……perlですね。

debug modeをenableにして再度見てみましょう。

$ git send-email --smtp-debug 1 0001-something.patch
...snip...
Send this email? ([y]es|[n]o|[e]dit|[q]uit|[a]ll): y
Net::SMTP>>> Net::SMTP(2.31)
Net::SMTP>>>   Net::Cmd(2.29)
Net::SMTP>>>     Exporter(5.68)
Net::SMTP>>>   IO::Socket::INET(1.33)
Net::SMTP>>>     IO::Socket(1.36)
Net::SMTP>>>       IO::Handle(1.34)
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-DELIVERBY
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250 HELP
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>> AUTH DIGEST-MD5
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>>
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 235 2.0.0 OK Authenticated
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

依存しているCPANモジュールが軒並み古く、なおかつ >>> AUTH DIGEST-MD5 によって DIGEST-MD5 が指定されており、そして死んでいることがわかりますね (表面上は OK Authenticated が返ってきている一方、これは上手く動かない様子。参照: https://adiary.adiary.jp/0407)。


ウェブで検索をかけてみると、過去に同じような原因で詰まっており解決されている方が。

http://www.katsuster.net/index.php?arg_act=cmd_show_diary&arg_date=20180906&arg_range=1&arg_direction=0

なるほど〜、smtpauthPLAIN とかにしてみると良いんですね。現在はさくらのメールボックスCRAM-MD5 もサポートしているので、これを指定してみましょう。

$ git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250-DELIVERBY
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 250 HELP
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>> AUTH DIGEST-MD5
...snip...
Net::SMTP::SSL=GLOB(0x7ff38a07c878)>>>
Net::SMTP::SSL=GLOB(0x7ff38a07c878)<<< 235 2.0.0 OK Authenticated
...snip...
Died at /usr/local/Cellar/git/2.30.0/libexec/git-core/git-send-email line 1578.

うーん、オプションで明示しても DIGEST-MD5 がまだ使われていますね。困った。

解決

とりあえずコード読んで、ちまちまデバッグしてみたところコード的には問題なさそう。つまり依存しているモジュールあるいはそのロードっぽいな〜、ということで git-send-email のコードを読んでみる。

#!/usr/bin/perl

use lib (split(/:/, $ENV{GITPERLLIB} || '/usr/local/Cellar/git/2.30.0/share/perl5:/Applications/Xcode.app/Contents/Developer/Library/Perl/5.18/darwin-thread-multi-2level:/Library/Developer/CommandLineTools/Library/Perl/5.18/darwin-thread-multi-2level'));
#
# Copyright 2002,2005 Greg Kroah-Hartman <greg@kroah.com>

なるほど〜、 GITPERLLIB 環境変数が渡されていない時はなんか全体的に古いモジュールを見てそうですね。

というわけで GITPERLLIB に使っているperlPERL5LIB 相当を渡して実行してみましょう。

$ GITPERLLIB="/path/to/.plenv/versions/5.32.1/lib/perl5/5.32.1/:/usr/local/Cellar/git/2.30.0/share/perl5" git send-email --smtp-debug 1 --smtp-auth CRAM-MD5 0001-something.patch
...snip...
Net::SMTP>>> Net::SMTP(3.11)
Net::SMTP>>>   Net::Cmd(3.11)
Net::SMTP>>>     Exporter(5.74)
Net::SMTP>>>   IO::Socket::IP(0.39)
Net::SMTP>>>     IO::Socket(1.36)
Net::SMTP>>>       IO::Handle(1.34)
...snip...
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-STARTTLS
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250-DELIVERBY
Net::SMTP=GLOB(0x7fb7d9825d38)<<< 250 HELP
Net::SMTP=GLOB(0x7fb7d9825d38)>>> AUTH CRAM-MD5
...snip...
Result: 250
Net::SMTP=GLOB(0x7fb7d9825d38)>>> QUIT

モジュールのバージョンが新しめで、ちゃんと >>> AUTH CRAM-MD5 が指定され、そして成功していますね。やりました。
なお /usr/local/Cellar/git/2.30.0/share/perl5 も併せて指定している理由は、これが存在しないとgitが用意しているモジュールをロードできない為です。ここは適宜環境に合わせて変更する必要があるでしょう。

ちなみに、今回は新しめのバージョンのperlランタイムのライブラリパスを渡すことで解決しましたが、単純に Net::SMTP のバージョンを上げる (3.11とか)、でも解決できると思います。


というわけでようやく動きました……ところで git-send-email、実ははじめて使いました。以上です。

独自ドメインのメールアドレスを使うようにした

2021年の記事とは思えないタイトルですが、そのようにしたのです。

特定のメールサービスが提供するメールアドレスに依存していると、そのメールサービスからBANされた際に人権を維持できない可能性があります。というのも仮にメールアドレスが凍結すると、そのアドレスをアカウントのidentifierとして登録しているサービスを巻き込んでしまい大惨事が起きてしまいます。
プレッパーじみた危機意識ではありますが、そのような気持ちになったのでこのたび独自ドメインでメールアドレスを払い出し、それを使うようにしてみました。

しかし自前でpostfixを運用する……みたいなことは断固やりたくなかったので、今回はさくらのメールボックスを利用して、元々保有していたドメインサブドメインを使ったメールアドレスを払い出し、そこに送られてくる全てのメールをGmailへと転送するという構成を取りました。
メールボックスに保存せず、即座に転送するような設定とすることで、さくらのメールボックスの容量制限を考えずに済むので便利です。GmailのUI/Appをそのまま使えるというのもグッドポイント。これでGoogleから仮にBANされてもメール機能については「UIが使えなくなるだけ」というところで被害を食いとめることができますね。

自分のドメインにメール用のサブドメインを切り、そのサブドメインに対して以下のようにNSエントリを設定すると、ドメインの移管等をすることなくメール用にドメインが使えるのは簡単で良いですね。

subdomain    NS    ns1.dns.ne.jp.
subdomain    NS    ns2.dns.ne.jp.

なぜサブドメインをわざわざ使っているかというと、主ドメインを使おうとするとさくらが管理するネームサーバーを利用する必要があるのですが、しかし今使っているネームサーバーはCloudflareのものなので引越し等がだるい。しかし、サブドメインだとサブドメインだけNSレコードの設定 (僕の場合はCloudflareで設定する) によりネームサーバーを差し向けることができるので楽ちん、という感じの動機でした。


さくらのメールボックスの特筆事項としては以下のような感じ。

  • ユーザーを制限無く作れる

※1 同じメールアカウント名で異なるドメイン毎のメールアドレスの運用は利用できません。
(例:「info@◯◯◯.com」と「info@△△△.net」の場合、同じ「info」のメールボックスに配信されます)

さくらのレンタルサーバとメールボックスには同一のドメインを登録することはできません。
https://www.sakura.ne.jp/mail/

  • DKIMは使えない


というわけで最小労力で自ドメインのメールアドレスをゲットしましたから、今後はこれを使っていきたいと思います。
メールサービスの選定のようなものは以下にメモを残しておきました:
scrapbox.io

moznion@mail.moznion.net
メル友募集中です。ちなみに僕はメールを見るという習慣がありません。


[追記]

「メールをメールボックスに保存せずにGmailへ転送だけ行うと、GmailからBANされたときに送られてきたメールがロストするのでは?」という指摘があり、それはその通りだと思いますが、僕はまあそれで良いかなと思っています。GoogleからBANされたらメールだけでなくありとあらゆるものが大倒壊し、暮らしがめちゃくちゃなことになると思うので、メールのロスト程度はかすり傷でしょう……という心意気です。メール自体の機能よりも「安定したアカウントのIdentifier」として使いたいというところが大きいというのもありますね。
とはいえ心配であれば、メールボックスに保存した上で転送、という方法を採用するのも手だと思います。さくらのメールボックスは10GBもあるし、メール用途であれば実質無限と言っても差し支えなさそうですし。