Posts for: #Go

ホットクックのレシピを upnote で書く

2時半に寝て5時過ぎに起きた。それから二度寝して7時に起きた。

今日の運動はレッグレイズ(椅子),腹筋ローラー,腕立て,散歩をした。統計を 運動の記録 にまとめる。

トランクルームの契約

家電を購入すると大きな箱が付いてきて物理的にその置き場所がない。箱を捨てるという戦略もあるが、オリジナルの箱があると引っ越しや売買/処分するときに便利なのでできれば残しておきたい。これまでデスクトップマシンの箱やオフィス備え付けの椅子などをマンションの部屋に保管したりしていたけど、家電の箱が増えてくると邪魔になってきて、トランクルームを借りることにした。面倒なのであまり調べていないが、スペラボ というサービスの屋内型トランクルームをレンタルすることにした。0.7畳で6,450円/月(税込)になる。会社の経費だしこのぐらいの金額ならいいかと思って、朝から内見に行って問題なさそうなので即決した。

ホットクックレシピの公開

ホットクックのレシピをどこかに整理したい。スマホ上でも調理しながら簡単に確認したいとなると web よりもアプリの方がよい。そこで evernote から移行した upnote に書くことにした。実際に調理してみて、デスクトップマシンでレシピを編集・整理して、写真はスマホアプリからアップロードするといった使い方ができる。View shared notes によると、ノート単位で web 公開もできる。試しに次のレシピを公開してみた。ノートブック単位で公開設定して一覧ページがあるともっとよいが、その機能はないみたい。公開リンクを作成する一手間はあるけれど、そんなにレシピノートを書くわけでもないから気にはならない。

ホットクック調理は材料入れてボタン押したら終わりではないか?と思うかもしれない。いや、そうでもないようだ。たしかに材料を内鍋に入れてボタン押したら人間ができることは何もない。だからこそ、ボタンを押す前の過程が大事になってくる。どんな切り方をするのか、素材のサイズはどうか、初期配置はどうか。例えば、豚ロース肉の生姜焼き用を買ってきて、適当に切って3枚4枚重なった状態で投入したらそのままの状態で出来上がった。かき混ぜ棒があるからうまいこと豚肉もバラけるだろうと期待したが、ぴったりくっついているようなお肉をバラかすほどのパワーはないようだ。人間が最初からバラかした状態で内鍋に投入すると、出来上がりのときに豚肉のかたまりがなくなってよいと思う。

あと気付いたこととして、野菜もお肉も素材を少し小さめに切った方がおいしいように感じる。というのは、ホットクックは圧力鍋ほどの火力で調理しない。圧力鍋に慣れていると、少し大きめに切っても原形がなくなって溶けてしまったり、出来上がり時点で原形があっても混ぜたりしているうちに角がとれて、どんどん小さくなっていく。小さくならなくても口の中でとろけるので大きいままでも問題ない。相対的にホットクックはそこまでの火力はないから、小さめで味が染み込むような出来上がりになるため、小さめに切っても原形は保ったままで溶けてなくなってしまうことはない。これは良い悪いではなくて、それぞれの製品の特徴と言えるだろう。ホットクック調理は素材を小さめに切るというのが、いまところ、私が作ったレシピではうまくいっている。ホットクックでは、小さく切った複数の素材をほお張って食べるのがおいしい。

go-ldap への context 導入の考察

go-ldap の issue は subscribe しているので、たまたま initial cut of context support #406 の draft pr のコメントに気付いた。過去に context を導入しようとして途中で断念した残骸みたいな pr になっている。いまの go の api は context を受け取るのが当たり前になっているので確かにほしいというのは理解できる。

代わりに私がやってみようかとソースコードを読んでみた。オリジナルの draft pr は client の interface に WithContext なメソッドを追加しようというもの。go の context の扱いとしてもっとも基本的なもの。それでもよいけれど、net/http の実装はどうなっているのかを調べていたら Request 構造体のメンバーとして context を保持していることがわかった。このやり方は原則のルールに反するものだけど、context を状態として扱わず、限定的な用途にのみ使っている。これは既存の interface を変えずに context 導入をしようという意図があると推測する。この考え方でいくと、go-ldap の Request 構造体に context を保持するように変更する方が既存の API の変更を少なくして net/http の Request も同様にやっているからと説明もしやすいように思えた。

type Request struct {
...
	// ctx is either the client or server context. It should only
	// be modified via copying the whole Request using Clone or WithContext.
	// It is unexported to prevent people from using Context wrong
	// and mutating the contexts held by callers of the same request.
	ctx context.Context
...
}

今日のところは go-ldap と net/http のソースコードを読んで設計をしていた。また明日余裕があったらサンプル実装してみる。

rfc に書いてある仕様の矛盾

昨日は21時前に神戸に戻ってきて、オフィスへ行く元気もなくて、そのまま寝てた。0時頃に起きて2-3時ぐらいまでだらだらしていてまた寝て7時半に起きた。

今日の筋トレはレッグレイズ(椅子):20x2,腹筋ローラー:5x1,スクワット:20x1,縄跳び(両足跳:50x3)をした。縄跳びの疲労で日常生活で痛くなったことのない膝の後ろの、なんと呼ぶのかすらわからない筋が痛い。いまは両足跳以外の負荷のかかる跳び方はできない。

scim パーサー周りの改善

先日 実装した scim パーサー を使って scim プロトコルによる id 連携の内部処理をリファクタリングした。たまたま rfc7643 を読んでいたら go-urn の実装で食い違っているところがあって pr を送ってみた。

作者から教えてもらって、食い違っているのは rfc7643 の方で相反する内容が書いてある。これはこの rfc を作成した scim WG とやらにメールしてこの仕様はどちらが正なの?と聞いて訂正してもらえばいいんやろか?

加圧シャツ

トレーニングが楽しくなってきてその効率を上げようと思って加圧シャツも買ってみた。伸縮する着圧のあるシャツで体を締め付けることで血流が悪くなる。血流が悪くなると、筋繊維 (脳?) は疲労を感じやすくなり、それでより強く太い筋繊維を生成しようとする。要は脳を騙すみたいなアイテムだという。1日着ていたが、とくにお仕事や身体の動作に支障はない。上半身が締め付けられることで背筋が伸びるような気もする。よい意味で緊張感をもつことができているように思う。しばらく使ってみる。

野菜スープ改

2時前に寝て6時半に起きた。たぶん夜中には起きていない。夜は野菜スープしか食べてないから安眠できたのかもしれない。

今日の筋トレはレッグレイズ(椅子):20x1,スクワット:20x1,縄跳び(両足跳:50x2,駆け足跳び:10x3)をした。今日もお昼休みに公園へ行って縄跳びした。昨日縄跳びしたせいだと思うが跳ぶと膝の後ろの筋が痛い。普段やらないことしたら変なところの筋肉が痛くなる。

scim パーサーの実装

半年ほど前に scim 向け urn パーサーの提案 を PR で送っていた。これまでその PR は放置されていた。数日前からライブラリの作者が PR をみてくれたようだ。私が送った PR 自体は作者の意図したものではなかったようでクローズされたが、作者が scim の仕様を把握して代わりに作ってくれた。パーサーの実装が automaton? のようになっていてパッと見ではロジックがよくわからない。

せっかく作ってくれたのでこの機能を使って、お手伝いしている会社のアプリケーションの scim urn のパース処理を書き直した。これまでは正規表現で実装されていた。rfc に準拠した go-urn を使うことでバリデーションもできるし、パフォーマンスもよいし、urn の部分文字列も参照できる。ライブラリの学習コストや依存ライブラリを増やすデメリット以外はとくに問題はない。

テックブログを読む会談義

テックブログ一気読み選手権20240129杯 に参加した。イベントが終わってから軽くにしはらさんに お手伝い先で社内イベント を始めたことを情報共有した。にしはらさんが知る限り、このイベントを他社でもやり始めたというのは初めてのことらしい。とても洗練されたよいイベントなのに社内でもその価値を理解できる人たちは少ないという。前にも書いたけど、まさにこれだと思う。

恐ろしく速い手刀。オレでなきゃ見逃しちゃうね

お手伝い先ではまだ始まったばかり。1回目はそれなりにうまくいったつもり。毎週30分を4週やってみてメンバーに感想を聞いてみる。そして継続しようと決まったらまたブログにでもそのことを書こうと思う。

野菜スープのレシピ改

昨日初めて作った野菜スープ はいくつか気付きがあったので再挑戦。

メニューから「野菜スープ」を選択すると最初から画面に約25分と表示されて急加熱しているようにみえる。残り20分ぐらいのところからスマホのタイマーと一緒に比較しながら画面の時間がどう遷移するのかを観察していた。すると、スマホのタイマーと比べてホットクックの時間経過は遅くなっていった。残り13分の状態がもっとも長かった。残り7分のところでスマホのタイマーは20分経過した。残り7分で画面上に残り時間が大きく表示される。この後の時間の推移はスマホのタイマーとだいたい同じぐらいだったので信頼できる残り時間のようにみえる。したがって、ホットクックの調理をスタートした時点での、残り時間表記は目安であってまったく信頼できない。この野菜スープの調理時間は約25分とあるけど、今日作った分量とレシピでは約40分ほどかかっていた。ホットクックは内鍋を加熱して予熱したり、水があれば沸騰させたりする時間が必要らしい。その予熱時間が10分強はかかるとみておくとよさそう。

圧力鍋と比べるとホットクックの方が調理時間そのものは大きいというのは概ね正しいとは思う。しかし、ホットクックは基本的にスタートボタンを押下したら他にやることないので放ったらかしにする感覚でよいと思う。圧力鍋は圧力がかかったら火加減を調整したり圧力を下げて蓋を開けたらかきまぜしたりしないといけない。その「かきまぜ」の手間暇がホットクックはないのだから多少調理時間がかかっても全体としては気にならないと思う。

昨日のオリジナルを改変したレシピをさらに改変して今日のレシピはこれ。

  • にんじん x 1
  • 新玉ねぎ x 1
  • かぼちゃ 1/4切れ
  • ミディアムトマト x 8
  • セロリ x 1 の葉っぱ部分
  • しめじ 1袋
  • えのき 1袋
  • ローリエ 1枚
  • 塩コショウ 適当
  • コンソメ (小さじ4杯)
  • 水 600cc

にんじんは乱切りをやめて 1cm 程度の正方形に切る。かぼちゃもにんじん同様、1-2cm の正方形に切る。セロリの茎の部分は使うのをやめて、葉っぱが付いている部分を適当なサイズに切って入れる。野菜を小さくしたことで、いろいろな食材を一緒に口に運べるようになって、にんじんやかぼちゃだけ頬張るのではなく、複数の素材の味を一緒に楽しめるようになった。そうすると、えのきは石づきを切り離してそのままの長さで入れていたが、他の野菜と一緒に食べやすくしようと思ったらもう半分ぐらいに切り落として短くした方がよいのではないかと思えた。あとスープも少なかったので昨日よりも水を 200cc 増やした。

昨日作ったものは薄味の、なにかひと味足りないというところに、コンソメはうまくはまった。レシピに入れろと書いてあるものを勝手に抜くのよくない。足りなかった旨味が揃ったことで他の食材とのバランスも断然によくなった。セロリを減らしたことでセロリの癖も弱くなってちょうどよくなったと思う。昨日と比べて、お料理ベーコンを買い忘れてなしで作ったが、あってもなくてもそんな変わらない気がする。コンソメにより肉エキスも入ったことで鶏もも肉をちょっと入れてみてもよいような気がした。お肉的なたんぱく質のアクセントがあってもよいんじゃないかと思えた。次に作るときに試してみる。

昨日作ったものよりもおいしくなって料理としての完成度は増したように思う。野菜スープと一緒におにぎりを2個食べた。100 kcal + (200 kcal * 2) = 500 kcal ぐらいかな。晩ご飯のカロリーとしてちょうどよい。

仮に調理の味付けを失敗したときでもなにかふりかけのようにかけて味変できるものがあるとよいと思う。昨日のコンソメ抜きの野菜スープも鰹節をふりかけたらおいしくなった。フレークタイプのカレーで市販のカレールウに比べて油脂分が少ないらしい。こういうのも容易しておくと失敗したときに誤魔化せるかもしれない。

年明けからコーポレート業務いろいろ

23時に寝て4時半頃に起きてそのまま6時半までだらだらして起きた。早寝早起き。

今日の筋トレは腹筋:20x1,腕立て:15x1,スクワット20x1をした。

隔週の雑談

顧問のはらさんと隔週の打ち合わせ。今日の議題はこれら。内容が多くて1時間超えた。

  • 電子帳簿保存法対応の事務処理規定の共有
    • まだ始まったばかりで税理士さんの温度感も低い
    • 事務処理規定が省令に沿っているかどうかの判断はプロセス監査で行われる
    • 電子帳簿保存法には規定されていないため、事務処理規定の妥当性の検証は行われない
  • 融資を受ける構想作り
    • 日本政策金融公庫のみで検討していたが、融資実績を作りたいなら信用金庫も加えた方がよい
    • 借金のメンタリティ、担当者との折衝や審査など余裕のあるときに経験を積んでおくことはよいように思えた
  • ファイナンシャルプランナーさんとのやり取り
    • 会社の経費で役員のための保険に入ろうと考えている
    • 個人の保険控除は8万円らしい
  • ローカル複業化プロジェクトの考察
    • IT コミュニティに老人や子どもたちは入りにくい。農業なら老若男女誰でも入れる気がする
    • 農業や地元の特産品を切り口にコワーキング (コミュニティ) で街の人たちと田舎の人たちをつなげるのはすごいことだと思う (関係人口の創出)
    • 地元の有力者と仲良くなると、行政の口利きもしてくれて活動しやすくなる

はらさんが よりひろいフロントエンド を始めたそうでその話しも聞いていた。個人のブログサイトにするのか、複数人で記事を共有するサイトにするのかもまだ曖昧だという。Contentful + Next.js + Cloudflare Pages という構成らしい。Contentful というツールを私が知らなかったのでまた時間のあるときに調べてみようと思う。

母が一人暮らしをしていて体調もよくないことから要介護状態になるリスクがそこそこあると考えている。最悪の場合、会社を休眠させてしばらく介護をするかもしれない。はらさんが仰るには休眠はよいアイディアだという。会社員に例えると退職した日の帰り道を想像するとよいと話されていたのだけど、私は過去の記憶があまりないというのもあるが、これまで6回も退職してきたのに退職日を特別に思ったことはあまりない。退職日と他の日に大きな違いはなくて、次のお仕事の準備や調査をしていることが私は多かったと思う。それでも退職にあわせて有休を1-2ヶ月とってゆっくり過ごしていたことには変わりない。私もそういう、メリハリのある働き方が好きだ。

smtp 接続のタイムアウト

たまたまメンバーが誤った設定で smtp サーバーに接続したときにタイムアウトするまで5分ほどかかるという現象を発見した。タイムアウトの設定をせずに接続しようとするからそんなことが起きるのかな?と考えて smtp クライアントのタイムアウト設定を調べてみると net.DialTimeout を使えばよいという。

多めに見積もって30秒のタイムアウトを設定して接続するようにして再度メンバーに再現検証してもらったら直っていないという。接続そのものは出来ていたのだ。ソースを読んでみると、smtp クライアントを生成するときに 220 というレスポンスを読むことがわかる。誤った接続設定でもコネクションは確立するが、レスポンスが返ってこなくて待ち状態になっていた。

func NewClient(conn net.Conn, host string) (*Client, error) {
	text := textproto.NewConn(conn)
	_, _, err := text.ReadResponse(220)
	if err != nil {
		text.Close()
		return nil, err
	}
	c := &Client{Text: text, conn: conn, serverName: host, localName: "localhost"}
	_, c.tls = conn.(*tls.Conn)
	return c, nil
}

調べてみると Conn インターフェースにデッドラインを設定する API が提供されている。注意事項としては接続した後にデッドラインをリセットしないといけない。

デッドラインとは、I/O操作がブロックされずに失敗する絶対時間のことである。デッドラインは、ReadやWriteを呼び出した直後のI/Oだけでなく、将来の保留中の I/O すべてに適用される。デッドラインを超過した後は、未来にデッドラインを設定することで、接続をリフレッシュすることができる。

デッドラインを超えた場合、ReadやWrite、その他のI/Oメソッドの呼び出しは、os.ErrDeadlineExceeded をラップしたエラーを返します。これは、errors.Is(err, os.ErrDeadlineExceeded)を使用してテストすることができます。errorのTimeoutメソッドはtrueを返しますが、期限を超過していなくてもTimeoutメソッドがtrueを返すエラーが他にもあることに注意してください。

アイドルタイムアウトは、ReadまたはWrite呼び出しに成功した後、デッドラインを繰り返し延長することで実装できる。tの値が0であれば、I/O操作はタイムアウトしない。

https://pkg.go.dev/net#Conn

次のように SetReadDeadline() を使ってタイムアウトを5分から30秒に短縮できた。

func (s *Clinet) connectWithReadDeadline(conn net.Conn) (*smtp.Client, error) {
	if err := conn.SetReadDeadline(time.Now().Add(dialTimeout)); err != nil {
		return nil, fmt.Errorf("failed to set read deadline: %w", err)
	}
	c, err := smtp.NewClient(conn, s.config.Host)
	if err != nil {
		return nil, fmt.Errorf("failed to connect to the smtp server: %w", err)
	}
	// clear read deadline
	if err := conn.SetReadDeadline(time.Time{}); err != nil {
		return nil, fmt.Errorf("failed to reset read deadline: %w", err)
	}
	return c, nil
}

RWMutex の試用

0時に寝て3時半に起きてネットみたてたら6時半になってそれから寝て7時半に起きた。やっぱり生活のリズムがおかしい。

今日の筋トレは腹筋:20x1,腕立て:10x1,スクワット10x1をした。あと1kmほど散歩した。

go の RWMutex を使う

テストツールでちょっとしたプールを実装していて mutex を使うコードを書いた。書き込みはロックしないといけないけど、読み込みはロックする必要がないようなものは RWMutex を使うと普通の Mutex よりは読み込みが並行に動くので効率がよくなる。実際に RWMutex を使ってコードを書いたことがなかったので試しに書いて単体テストを実行して振る舞いを確認してみた。

type pool struct {
	l  []string
	m  map[string]struct{}
	mu sync.RWMutex
}

func (p *pool) exists(dn *ldap.DN) bool {
	p.mu.RLock()
	defer p.mu.RUnlock()
	_, ok := p.m[dn.String()]
	return ok
}

func (p *pool) get() *ldap.DN {
	p.mu.RLock()
	defer p.mu.RUnlock()
	length := len(p.l)
	if length == 0 {
		return nil
	}
	s := p.l[rand.Intn(length)]
	return mustParseDN(s)
}

func (p *pool) put(dn *ldap.DN) {
	p.mu.Lock()
	defer p.mu.Unlock()
	s := dn.String()
	p.l = append(p.l, s)
	p.m[s] = struct{}{}
}

4年ぶりの神戸ルミナリエ

たまたま 第29回 神戸ルミナリエ が1月の19日から28日にかけて行われるという記事をみかけた。そう言えば、いつも12月に開催されていたのが今回から1月に変わったみたい。毎年今回が最後みたいな含みをもたせて、なんやらかんやらでもう29回も続いているんやね。今回が第29回だからおそらく来年に第30回もやるのでしょう。「4年ぶりの開催が決定」と書いてあって、あれ?やってなかったっけ?と思ったらコロナ禍のために2020-2023年まで中止という意思決定はされていて、代替イベントとして小さいルミナリエっぽいイルミネーションをやっていたらしい。ルミナリエの展示場となる東遊園地がうちの近所なのでこれまでもやっていたような記憶があるのはそのせいかもしれない。今年は規模を拡張してやるのかな?久しぶりなので時間があるときに観に行ってみようと思う。

RemoteAddr はほとんど役に立たない

0時に寝て何度か起きて7時に起きた。寒くて朝起きれなくなってきた。

ローカルネットワークからのリクエストのみを受け付けるミドルウェア

Request 構造体の中にある RemoteAddr を参照すればすぐできるだろ思って、すぐにできた。すぐにできたんだけど、これは実際の運用で使えるものではない。

type LocalNetworkConfig struct {
	Skipper middleware.Skipper
}

func localNetworkWithConfig(cfg LocalNetworkConfig) echo.MiddlewareFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if cfg.Skipper(c) {
				return next(c)
			}

			req := c.Request()
			addr, _, err := net.SplitHostPort(req.RemoteAddr)
			if err != nil {
				slog.Error("failed to get remote address",
					"err", err, "addr", addr)
				return echo.ErrForbidden
			}
			ip := net.ParseIP(addr)
			if ip.IsPrivate() || ip.IsLoopback() {
				return next(c)
			}

			slog.Error("requested from an external network", "ip", ip.String())
			return echo.ErrForbidden
		}
	}
}

func NewLocalNetwork() echo.MiddlewareFunc {
	return localNetworkWithConfig(LocalNetworkConfig{
		Skipper: func(c echo.Context) bool { return false },
	})
}

過去にも同じようなことをやらかしたような既視感がある。

golangのnet.Request構造体内にはRemoteAddr属性があり、当然のことながら要求元のリモートアドレスが含まれています。これで仕事は終わりですね?しかし、リバースプロキシやロードバランサーをアプリケーションに使っている場合はそうではありません。これはgoサーバには常に、すべてのリクエストがロードバランサから来ているかのように見えます。これは、これを何らかのスロットリングの指標として使いたいのであれば、最悪なことです。ですから、何らかの形のリバースプロキシの後ろでなく、goサーバーが1つだけ動いているのでない限り、私たちの目的には役に立たないとして、すぐにこれを捨てることができます。

https://husobee.github.io/golang/ip-address/2015/12/17/remote-ip-go.html

途中のロードバランサーやアプリケーションゲートウェイ、リバースプロキシなど、中継するサーバーの ip アドレスに置き換わってしまうため、ネットワークのインフラを管理していないとどこからリクエストされたかを追跡することはできない。X-Real-IPX-Forwarded-For という http ヘッダーに中継したサーバーの ip アドレスを記録するという慣習があるようだけど、これはクライアント側で書き換えできるのでこれ単体でアクセス制御のようなものに使うことはできない。

次に同じ失敗をしないようにここに書いておく。

mongodb のサードパーティのコンテナイメージ

23時に寝て3時に起きて寝たかどうか覚えていないうちに6時半になっていて7時半に起きた。

json を介した go の bool 値のバリエーション

go-playground/validator のバリデータには required というバリデーションオプションがある。しかし、このオプションは go のゼロ値でないことをチェックするという仕様になっている。bool のゼロ値は false となるため、リクエストした JSON データに false を設定していたのか、未設定だったのかの違いを検出できない。これはバリデータの問題ではなく、go の json ライブラリの制約のようなもので使い勝手のよい仕様とは言えない。私もこの振る舞いに起因する不具合に遭遇したこともあるし、こういうときにどうしたらよいかも過去に3回ぐらいは調べている気がする。

現時点での私の最適化は次のコードになる。データ構造として *bool 型にすれば、ポインタ型のゼロ値は nil となるため、true, false, nil の3値でバリデーションできる。しかし、私はこのデータ構造を好ましく思わない。というのは、内部的には true/false の2値でしか管理しないメンバーを、json のバリデーションのためだけに nil も許容する3値にすることがよい設計だと私は思えない。そこでバリデータによるバリデーションは諦めて、json の Unmarshal 処理をフックしてバリデーション相当の処理を自分で実装する。このやり方のデメリットはメンバーが追加されたときに自分で UnmarshalJSON() メソッドを保守する必要がある点になる。しかし、メリットとして内部のデータ構造の型は bool 型で扱える。一概にどちらがよいとは言いにくいかもしれないし、設計上の好みかもしれない。

type reqMyData struct {
	Name       string `json:"name"`
	View       *bool  `json:"view"`
}

type MyData struct {
	Name       string `json:"name"`
	View       bool   `json:"view"`
}

func (d *MyData) UnmarshalJSON(data []byte) error {
	var tmp reqMyData
	if err := json.Unmarshal(data, &tmp); err != nil {
		return fmt.Errorf("failed to unmarshal as reqMyData")
	}
	if tmp.View == nil {
		return fmt.Errorf("required view field")
	}
	d.Name = tmp.Name
	d.View = *tmp.View
	return nil
}

サードパーティの mongodb コンテナイメージ

先日の mongodb のレプリカセット調査 の続き。コードレビューをしていて bitnami/mongodb というサードパーティのコンテナイメージを使った方がよいのではないか?というコメントがあったのでその調査をしてみた。VMware 社が提供しているサードパーティのコンテナイメージらしい。

MongoDB(R) is run and maintained by MongoDB, which is a completely separate project from Bitnami.

まず MongoDB プロジェクトとはまったく別管理であることが書いてある。

Bitnami イメージを使用する理由

  • Bitnamiはアップストリームソースの変更を綿密に追跡し、自動化されたシステムを使用してこのイメージの新しいバージョンを迅速に公開します。
  • Bitnami イメージでは、最新のバグ修正と機能をできるだけ早く利用できます。
  • Bitnamiのコンテナ、仮想マシン、クラウドイメージは、同じコンポーネントと構成アプローチを使用しているため、プロジェクトのニーズに応じて形式を簡単に切り替えることができます。
  • Bitnamiのイメージはすべて、minideb(最小限のDebianベースのコンテナイメージ)またはscratch(明示的に空のイメージ)をベースにしています。
  • Docker Hubで利用可能なすべてのBitnamiイメージは、Docker Content Trust(DCT)で署名されています。DOCKER_CONTENT_TRUST=1 を使用して、イメージの完全性を確認できます。
  • Bitnamiコンテナイメージは定期的にリリースされ、最新のディストリビューションパッケージが利用可能です。

MongoDB®を本番環境で使用したいですか?Bitnami Application Catalogのエンタープライズ版であるVMware Tanzu Application Catalogをお試しください。

mongo の公式イメージは ubuntu をベースイメージにしている。ubuntu よりは minideb の方が軽いのかな?そしてちゃんと upstream にも追随しているみたい。このベースイメージの違いによるものかは定かではないが、結合テストのイメージも移行してみたところ、10-20秒ほど結合テストの実行時間が速くなった。割合にすると10%程度かな。

KubernetesにMongoDB®をデプロイするには?

Bitnami アプリケーションを Helm Chart としてデプロイすることは、Kubernetes 上で当社のアプリケーションを使い始める最も簡単な方法です。インストールの詳細については、Bitnami MongoDB® Chart GitHub リポジトリを参照してください。

Bitnami コンテナは、クラスタへの Helm Charts のデプロイと管理に Kubeapps と一緒に使用できます。

helm chart も提供しているようで、いずれクラウド版を作るときに MongoDB も k8s 上にデプロイする上でこのことは都合がよいように思える。

レプリケーションを前提とした初期設定があり、entrypoint スクリプトもいくつか読んでみた感じだと、きれいに管理されていて保守もちゃんとやってくれそうにみえる。

昨日、導入したばかりの公式イメージ + 自作スクリプトによるレプリケーション設定を廃止して、Bitnami のコンテナイメージを使うことに決めた。

mongodb のレプリカセットのデプロイ調査

4時前に寝て6時半に起きた。1時過ぎまで作業して、帰って少しゲームして、うまく眠れなくてだらだらしていた。

mongodb のレプリカセットの調査

以前 mongodb でトランザクションを使うときにレプリカセットが必要 なことがわかった。他機能の開発途中だったので一旦後回しにしていたものを回収している。状況によってはメンバーに委譲してもよかったんだけど、私が遊撃で出張ってみることにした。実際に調べてみてコンテナの運用も考慮するとけっこう難しいことがわかってきた。

mongosh からは Replication Methods を使ってレプリカセットの操作ができる。これはユーティリティのようなもので mongodb としての低レベルのコマンド操作は Replication Commands になる。mongo-go-driver はレプリカセット向けのユーティリティを提供していないため、Replication Commands を RunCommand() の低レベル API を使って自分で実装しないといけない。

例えば、レプリカセットの初期化をするときは次のように replSetInitiate というコマンドを適切なパラメーターで呼び出す。あまりドキュメントで丁寧に説明されていないので試行錯誤でエラーメッセージをみながら実装することになる。とくにはまるのが mongod のサーバーは --replSet myrs のようにレプリカセットを指定して起動させるものの、初期化コマンドを実行するときはまだレプリカセットを設定していないため、レプリカセットを指定せず、且つ direct パラメーターをセットしないと mongod サーバーに接続できない。この微妙な設定を把握するのにはまった。これが正しい手順かどうかもわからないが、ググったりしているとフォーラムでそういったコメントが散見されたりする。おそらく mongosh の Replication Methods を使うと、クライアントからサーバー接続は裏方でよしなにやってくれるのでそっちの方が簡単ではある。

func (r *ReplicaSet) Initiate(ctx context.Context, config bson.M) error {
	client, err := r.connectDirect(ctx)
	if err != nil {
		return fmt.Errorf("failed to connect with direct: %w", err)
	}
	defer client.Disconnect(ctx)

	var result bson.M
	cmd := bson.D{{Key: "replSetInitiate", Value: config}}
	if err := client.Database(r.db).RunCommand(ctx, cmd).Decode(&result); err != nil {
		return fmt.Errorf("failed to run replSetInitiate(): %w", err)
	}
	log.PrettyPrint("completed to initiate", result)
	return nil
}

func (r *ReplicaSet) connectDirect(ctx context.Context) (*mongo.Client, error) {
	opts := options.Client().
		SetAuth(options.Credential{
			Username: r.config.User,
			Password: r.config.Passwd.String(),
		}).
		SetHosts(r.config.Hosts).
		SetDirect(true) // must be true
	return mongo.Connect(ctx, opts)
}

func InitSingleReplicaSet(
	ctx context.Context, cfg *config.MongoDB,
) error {
	rs := NewReplicaSet(cfg)
	initConfig := bson.M{
		"_id": cfg.ReplicaSet,
		"members": []bson.M{
			{"_id": 0, "host": "localhost:27017"},
		},
	}
	return rs.Initiate(ctx, initConfig)
}

さらに mongod サーバーを起動するときに --replSet--keyFile (認証が必要な場合のみ?) という2つのパラメーターを指定する必要がある。--replSet はレプリカセットの識別子を指定する。そして --keyFile は共通鍵を指定する。この共通鍵を生成するには次のようにする。

$ openssl rand -base64 756 > my-mongo-keyfile
$ chown mongodb:mongodb my-mongo-keyfile
$ chmod 400 my-mongo-keyfile

普通のサーバーインスタンスならすぐできることだが、コンテナの運用において面倒なのが owner とパーミッションを設定しないといけないところ。mongo のコンテナは mongodb ユーザーで起動するため、root でマウントされたファイルシステムには書き込みできなかったりして keyFile の配置をどう扱えばよいのかが難しい。docker hub の mongo の issues でもどうやって設定したらいいの?って議論が発散している。mongo 本体が公式のスクリプトや仕組みを提供していれば済む話しだけど、どうもそうではないみたい。だから泥臭い方法で自分でなんとかしないといけないようにみえる。

dockertest でもレプリカセットの設定について次の issue として登録されている。mongo のコンテナを使ったテストの場合、dockertest のレイヤーが挟まるのでさらにわかりにくくなっている。テストを動かすためにどういった設定が必要かは把握できたのでなにかよい方法を考えてコントリビュートしたい。

ミニカンファレンスみながら作業してた

1時に寝て7時に起きて朝からゲームしてた。お酒飲んだせいで眠りが浅かったような気がする。

ストレッチ

先週は前日に車であちこち移動して、長時間運転していたのもあり腰に負担がかかっていた。今週は東京出張だったものの、すねの外側の筋やふくらはぎの張りをやや感じた程度でとくに悪いところのない状態に戻ったようにみえる。寒くなってきたのもあり、上半身の肩周りの動きが堅くなっているとトレーナーさんは話していた。今日の開脚幅は開始前153cmで、ストレッチ後158cmだった。

ミニカンファレンスのオンライン参加

オンラインのミニカンファレンスを視聴しながらいつも通りの作業をしていた。あまりちゃんと聞いていたわけではないけど、要所要所でおもしろい発表もあった。

tinygo の中の人が自作キーボードに関する発表をしていて、その人は明石市に住んでいるので、明石市から三ノ宮にかけてイベントをやりたいと話されていた。go について話す相手がいると私も嬉しい。またなにかの機会でご一緒できればと思う。

SakeBash と LT 発表

2時に寝て4時に起きて5時に起きて7時に起きた。呪術廻戦ゲーム の初心者ミッションだけクリアしたらやめようと思って、なかなかミッションのレベルが高くて届かない。あともうちょっと。

SakeBash

神戸に acall (アコール) さんという会社があって、コロナ禍が始まる前に SakeBash イベントを開催されていた。2019-2020年にかけてのとき。それ以来ずっと開催されていなかったイベントを復活させたみたい。コロナ禍が acall さんの転機になったようで、オフィスのオートメーションに関するプロダクトを開発していて会社が大きくなっているらしい。

acall さんは go でプロダクト開発をしていると知っていたので神戸で go の話しができると思って次の LT 発表をした。発表したら向こうから声をかけてくれて go の話しができるかな?と期待したのだけど、あまりそういう雰囲気でもなかった。私はコミュ障なので知らない人に話すのが苦手。

acall さんの社員でしやさんという方が宮崎県から来られて、始めて LT 発表するということで応援しながら聞いた。

私の周りでも flutter を採用している人たちがいるので気にはなっていた。最近は flutter web というのも出ていて、flutter でフロントエンドとスマホアプリの両方に対応できそうにも思えた。次にスマホアプリ対応が必要になったら flutter を採用してみてもいいなと思えた。acall さんの制度をみていてユニークだなと感じたものに「いまあい旅費」がある。リモートワーク前提の会社だからこそ、こういった制度の価値が出てきたとも言える。一昔前は出張するのが当たり前だったのでこんな制度に意味はなかったが、リモートワークにより出張しないことが当たり前になったのでこういう制度設計になったんだと思えた。

いま、あいにいきます旅費

リモート勤務でオフラインで集うことが難しいメンバーと「会うこと」の価値を再認識し、オフィスで自社プロダクトのプラクティスを行うことで、製品のアップデートに貢献することを目的とした制度です。自宅から100km以上離れた神戸または東京オフィスに行くための旅費を支給します。(1往復分の交通費と宿泊費/ケ月)

イベントに acall の社員さんが10人ぐらい参加していた。勢いのある会社は会社イベントに参加する人も多くてよい雰囲気にみえた。宮崎県から来られていたしやさんの他にも埼玉県から来られていたとしさんという方もいた。他にも遠方から来ていた方もいたのかもしれない。

2次会

いまひとつ飲み足りなかったので三ノ宮.dev の仲のよい人たちと飲みに行った。RailRoad no.57 というバーでよい雰囲気のお店だった。またなにかの機会で使いたいと思う。葉っぱ (ハーブ?) がたくさん入っているはちみつカクテルがおいしかった。

そこで以前コミュニティで起業相談にのった方の経営について話題になった。創業してから1年弱かけて少し前にある web サービスをリリースした。その web サービスはコモディティで、すでに競合も数社ある。あまり新規のベンチャー企業にとって勝ち目のある分野だと私からはみえない。どうやってこの web サービスを黒字化するのだろう?と私は疑問に思っていた。創業者の相談に乗った方も今後の戦略について尋ねても明確な答えは返ってこなかったようでとても不安になったと話されていた。融資を受けたという話しを聞いていたのでしばらく会社を存続できるのかもしれないけど、作った web サービスを収益化しないと次の融資を受けられることはないだろうから先行きは厳しいのではないかと推測する。外部からみえる範囲内では今後の先行きは危ぶまれる。創業して1年もたつのに、若い開発者にすら心配されるような戦略しかもっていない経営者というのはちょっと想像できない。融資を受けることが出来たぐらいだから、起業や経営について相談にのってくれる人は周りにいるんじゃないかと思うんだけど、経営目線で厳しいことを言ってくれる人はいないのかもしれない。

生活リズムが崩れた月曜日

1時に寝て2時半に起きて5時に起きて7時に起きた。朝から昼過ぎまで寝てたのでトータルでは睡眠時間をたくさん取っているのになんか疲れている。生活のリズムを崩すのがよくないのかも。

ガンダムに学ぶ経営学

教えてもらって寝る前にみたらおもしろかった。入山先生がガンダム大好きなことが伝わってくる。おもしろいのはそうだけど、まじレスすると、アニメの世界の組織や経済に学ぶというのは誤りで、当時の組織や経済のリアルを参考にして、ガンダムの世界観は作られていると推測する。だから入山先生はユーモアで盛り上げているのだと思うけれど、ガンダムから学ぶのではなく歴史から学べが正解だと思う。でも、ガンダムの世界観を知るよい番組だと思う。

go-ldap へのプルリクエスト

2週間ほど前に送った pr がまだレビューすらしてもらえていない。github actions のテストがいくつか落ちていて、この pr の修正によるものではないところでエラーになっている。メンテナーの1人が再実行してくれたんだけど、たくさんあるマトリックステストのどこかが落ちてまた再実行しないといけない。

それを待っている間に、テストがエラーになる本当の原因の問題を直そうと2-3日前に issue 登録していた。この issue の対応を本当は昨日やろうと思っていたのに、思いの外、寝てしまって、その後もいろいろ書きものをしていて時間を使ってしまってできていなかった。今朝からそれを片付けた。業務の一環なので日曜日にプルリクエストを送らなくてもよいのだけど、コントリビューションなので空き時間に終わらせて、業務の時間は別のことに使いたいという思いもあったりする。

さらに github actions のログに deprecated ワーニングが出ていたのでついでにそれも直した。

473, 474, 471 の順番にマージされていくのが望ましい。そろそろコミット権をくれたりしないかな?と思ったりもする。というのは、タイミングの問題で action の job が落ちたり、バージョン上げるだけの pr とか、自分でマージしてしまえばいいと思ったりする。

hugo で書いた記事に目次を生成する

お手伝い先のテックブログは hugo で運用されている。記事の目次がないなと気付いて追加してみた。hugo v0.60.0 以降のバージョンなら標準で目次生成の機能をもっている。

<aside>
  {{ .TableOfContents }}
</aside>

デフォルトはヘッダー2レベルより下の目次を生成する。レベル1も生成したい場合は config.toml に次の設定を追加する。

[markup]
  [markup.tableOfContents]
    startLevel = 1
    endLevel = 3