Ruby 関西にて inotify の話を発表してきました #rubykansai

永田 @kizashi1122 です。 昨日の第88回 Ruby 関西にて発表してきました。

デモは各 gem の README レベルの内容でした。

質疑応答

覚えている限り挙げておきますと、、、

  • 常駐プログラムの監視はどうやっていますか?
    • monit (https://mmonit.com/monit/ ) を使ってます。常駐プログラムを落とすときは、Ctrl-C(SIGINT)やkill コマンド(SIGTERM)を使うので、SIGINT、SIGTERM の trap が大事
  • inotify は NFS でも監視できますか?
    • inotify は NFSはサポートしてない(会場)
    • (帰って調べた→)http://inotify.aiken.cz/?section=inotify&page=faq には「Can I watch sysfs (procfs, nfs...)?」に対して「Simply spoken: yes, but with some limitations.」とあります。
  • Guard みんな最近つかってないんですかね?
    • なんとなく使わなくなった・・・(会場)
  • デモ中に rm ではなく、 \rm と打ったのはなんで?
    • alias rm='rm -i' としているので、エイリアスを無視して実行したかったから

体調が悪く発表後はすぐに帰らせてもらいました。 次回は懇親会にも参加したいと思ってます。

Emacs 上で Git を使おう!

こんにちは、masm11 です。

皆さん、Git 使ってますか? 使ってますよね? Git を使っている人はもう Emacs 人口を超えているのでは、と思っています。

一方で、Emacs ってご存知ですか? 高機能なエディタです。 ですが、最近はあまり人気がないらしいですね。 出来の良い IDE の登場によるのでしょうか。 でも私はそんなことはお構いなく、Emacs を使っています。

さて、今回はそんな Emacs 上で Git が使える Magitの使い方をご紹介したいと思います。

インストール

Emacs で M-x package-list-packages してしばらく待つと、magit が現れます (しばらく待たないと現れないと思います)。

f:id:masm11:20191023023351p:plain

magit で Enter を押して、Install で Enter を押すと、インストールできます。 f:id:masm11:20191023023439p:plain

そして少し設定します。私は ~/.emacs で以下のようにしています。

;; magit
(defalias 'magit 'magit-status)
(global-set-key "\C-xg" 'magit-status)

(setenv "GIT_EDITOR" "emacsclient")
(add-hook 'shell-mode-hook 'with-editor-export-git-editor)

使ってみる

  • 起動

    C-x g と入力すると、Magit が起動します。 カレントディレクトリが git 管理されているなら、そのディレクトリが対象になります。 そうでない場合は、ディレクトリを尋ねられますので入力してください。 もし、カレントディレクトリが git 管理されているけど違うディレクトリを対象にしたい 場合は、代わりに C-u C-x g と入力すれば、必ずディレクトリを尋ねてくれます。

    f:id:masm11:20191023023513p:plain

    これで Magit が起動しました。magit: <ディレクトリ名> というバッファが開いています。ここには、現在の状態が表示されています。Magit はこのバッファで操作します。

    まずはファイルを編集してください。普通に Magit とは関係なく編集します。 その後、Magit のバッファに戻ってください。戻り方は C-x b でもいいですし、 C-x g でもう一度起動しても構いません。C-x b で戻った場合は、バッファの内容を 更新するために一度 g を押してください。

    編集したファイルが Unstaged changes という項目に表示されています。 新規ファイルの場合は Untracked files に表示されています。

    f:id:masm11:20191023023549p:plain

  • stage

    Unstaged changes または Untracked files の該当ファイル名で s を押すと、 そのファイルを git add (stage) することができます。

    f:id:masm11:20191023023638p:plain

  • stage をキャンセル

    Staged changes の該当ファイル名で u を押すと、unstage できます。

  • diff

    Staged changes で d d と入力してみてください。今 stage した差分が表示 されます。

    f:id:masm11:20191023023705p:plain

    Unstaged changes がまだあるなら、そちらで d d すれば、まだ stage して いない差分が表示されます。

    Untracked files はファイル名でそのまま Enter を押すとファイルの内容が表示 されます。

  • commit

    Staged changes で c c と入力すると、stage しておいた差分を commit することができます。

    f:id:masm11:20191023023727p:plain

    commit message を入力して、C-c C-c で commit できます。キャンセルしたい場合は C-c C-k でできます。

  • log

    l l と入力すると、ログがグラフ付きで表示されます。

    f:id:masm11:20191023023750p:plain

    ログの各行で Enter すると、その commit の詳細が表示されます。

  • push

    P u と入力すると、 push できます。

    現在のブランチを初めて push する場合は、push 先を尋ねられますので、入力してください。この時、補完を活用すると便利です。2回め以降の場合は最初と同じ場所に push されます。

    f:id:masm11:20191029203540p:plain

    2箇所それぞれに push したいこともあります(あまりないと思いますが…)。 その場合、2箇所めは代わりに P p と入力すればできます。使い方は P u と似ているのですが、push 先はブランチでなく remote のみで指定します。ブランチ名は手元と同じになります。

    u の場合と p の場合でそれぞれに push 先を覚えてくれます。 push 先が心配になった場合は、P まで入力すると以下のバッファが必ず開きますので、 ここで確認できます。

    f:id:masm11:20191023023838p:plain

    この画面のとおり、実は3箇所めもあって、P e です。これは push 先を覚えてくれません。稀にしか使わない remote ブランチに使うと良いでしょう。

  • pull

    pull したい時は F u, F p, F e です。u, p, e は push の場合と同じです。

    この時、マージの commit message を求められますので、そのまま C-c C-c で commit します。

    f:id:masm11:20191023023925p:plain

    通常は vim 等が呼び出されてしまうのですが、冒頭に紹介した設定の↓この部分により、代わりに Emacs に投げることができているわけです。

(setenv "GIT_EDITOR" "emacsclient")
(add-hook 'shell-mode-hook 'with-editor-export-git-editor)
  • コンフリクトの解消

    pull するとコンフリクトすることがありますね。

    この時、Magit バッファには unmerged と表示されています。

    f:id:masm11:20191110005646p:plain

    このファイルを開いて ==== などで検索すると、コンフリクトしている箇所が見つかり、 色分けされているので一目瞭然です。

    f:id:masm11:20191110005717p:plain

    コンフリクトを解決してファイルを保存してください。

    Magit バッファに戻ると、既に stage されています。全てのコンフリクトが解消したら、 c c で commit してください。

以上で基本的な操作は一通りできると思います。

更に便利な操作

更に、もう少し便利な操作を説明します。

  • ファイル中の一部の変更のみ stage

    Unstaged changes で d d すると、stage していない差分が表示されるわけですが、 この中から特定の hunk のみを stage できます。 その hunk に合わせて s を押すだけです。

    …ところで、hunk とは何でしょうか? 差分の表示は @@ -0,0 +1,2 @@ のような @@ の行で区切られていますね? この区切られた一つひとつを hunk と呼んでいます。

    f:id:masm11:20191023024002p:plain

    この hunk ごとに stage できるわけです。しかも手軽に。

  • ファイル中の一部の変更のみ unstage する

    Staged changes の差分表示中に u を押すと、その hunk のみを unstage することができます。

  • ファイル中の一部の変更のみ元に戻す

    Unstaged changes の差分表示中に k を押すと、その hunk をキャンセルして元に戻すことができます。

差分を見ながら、「この hunk を stage」「この hunk はやっぱり変更キャンセル」などが 手軽にできるのは、とても便利です。

まとめ

私がよく使う Magit の操作を紹介してみました。 他にもいろいろな操作ができます。 Magit バッファで ? と入力すると、どのキーでどんな操作ができるのかがわかります。

f:id:masm11:20191023024035p:plain

今回はこれらのうち極一部しか説明しませんでした。いろいろ試してみると良いでしょう。

ではまた。

エンコードされた国際化ドメイン名をデコードする

こんにちは。masm11 です。

国際化ドメイン名をご存知でしょうか? インゲージ.jp みたいなやつのことですね。 しかし、時々、xn--eck2as8usb.jp と表示されていることがあって、「これ、一体どこ??」 と思うことがあります。今回はこの文字列を元に戻す方法をご紹介します。

国際化ドメイン名とは

ドメイン名には日本語は使えません。 しかし、一部には「日本語を使いたいんじゃああ!!」という人もいて、 対応する必要がありました。 ですが、やっぱりドメイン名には日本語は使えないので、 英数字に変換して使うことにしたのです。

例えば、

インゲージ.jpxn--eck2as8usb.jp

といった感じです。xn-- が付いていれば、国際化ドメイン名を変換したものです。

ブラウザなどでは自動で変換してアクセスしてくれて、便利に使えます。 ですが、時々 xn--eck2as8usb.jp だけを見せられて、「これ何?」と判断する必要に迫られる場面もあります。

変換方法

国際化ドメイン名を英数字に変換するには、Punycode と呼ばれる変換方法を使っています。

Linux では、ライブラリとしては libidn、ツールとしては idn というコマンドが 存在します。

実際に使ってみましょう。

[~]$ echo 'インゲージ.jp' | idn
xn--eck2as8usb.jp
[~]$ 

このように変換してくれます。逆に変換したい時には、-u を付けて、

[~]$ echo 'xn--eck2as8usb.jp' | idn -u
インゲージ.jp
[~]$ 

というように使います。また、

[~]$ echo 'xn--6wyu77cyid.xn--eck2as8usb.jp' | idn -u
開発部.インゲージ.jp
[~]$ 

というように、サブドメインにも対応しています。

まとめ

変換された国際化ドメイン名を元に戻す方法をご紹介しました。 これで、国際化ドメイン名で困ることが一つ減りました。

ですが、国際化ドメイン名には他にも問題があります。

[~]$ echo 'インゲ−ジ.jp' | idn
xn--t9gx68chbza60a.jp
[~]$ 

変換すると上に挙げた例と結果が異なるのですが、何故でしょうか? 実は「−」が長音記号でなくハイフンなのです。 英数字なら見間違えることは滅多にないのですが(それでも l1 は区別しづらいですが)、日本語や、もっと広く世界の言語を相手にすると、見間違えることが増えます。以前、Google.com の "G" の文字がよく似た別の文字の詐欺サイトがあったらしいですね。怖い怖い。

以上、国際化ドメイン名が嫌いな masm11 でした。

SFTP で chroot する方法

こんにちは、masm11 です。

実はつい先日まで SFTP で chroot ができることを知りませんでした。 そこで今回は、SFTP での chroot の設定方法についてまとめてみたいと思います。

FTP, FTPS, SFTP...?

その前に、FTP にもいろいろ種類があって、混乱している方もいらっしゃるかもしれませんので、 一度ここで整理してみます。

  • FTP

    古き良き時代から存在する FTP です。当然のように、 ユーザ名やパスワードは生のままインターネットを流れます。

  • FTPS

    HTTP に HTTPS があるように、FTP にも FTPS があります。 HTTPS が TLS トンネルの中で HTTP 通信するのと同じように、 FTPS は TLS トンネルの中で FTP 通信を行います。 従って、ユーザ名やパスワードはしっかり暗号化されます。

    FTPS と SFTP で混乱してしまう方は、HTTPSFTPS も末尾が S で同類、と覚えると良いでしょう。

  • SFTP

    こちらは、SSH 接続の中で独自に FTP っぽいことをします。 FTP, FTPS とはかなり毛色が異なります。

    SSH 接続を使うので、認証にはパスワードでなく公開鍵認証を使う ことができます。もちろん暗号化はバッチリです。

chroot する意義

FTP, FTPS, SFTP は、ログインしてしまえば、そのサーバにあるファイルは、 どのファイルであろうとアクセスし放題です。しかし、これではセキュリティ的に 問題がある場合があります。そういう場合、特定のディレクトリ以下のみにアクセスを 限定したくなります。

そんな時に便利なのが chroot 機能です。この機能を使うと、 ユーザは、サーバが指定したディレクトリより上の階層にはアクセスできなくなります。

SFTP で chroot の設定をする

  1. ~/.ssh/authorized_keys に SSH 公開鍵を追加する

    chroot とは関係ありませんが、パスワードなしでログインできるようにします。

  2. /etc/ssh/sshd_config に以下を追記する

    Match User someone
    ChrootDirectory /var/www/somedir
    ForceCommand internal-sftp -u 002
    

    someone は SFTP を使うユーザ、/var/www/somedir が chroot 先です。

    -u 002 は umask の設定です。umask については、今回は説明を省略します。

    設定を変更したら sshd を再起動します。

    sudo systemctl restart sshd
    

  3. /var/www/somedir のパーミッションを修正する

    sudo chown root:root /var/www/somedir
    sudo chmod 755 /var/www/somedir
    

    chroot 先は root:root で 755 でなければならない仕様だそうです。

確認

以上で、sftp すると chroot しているはずです。

$ sftp サーバ名
Connected to サーバ名.
sftp> pwd
Remote working directory: /
sftp> ls
(省略)

もし、接続した直後に切断されてしまう場合は、何か設定が間違っています。サーバで、

sudo journalctl -f

を実行した状態でもう一度接続してみると、何かメッセージが出力されるかもしれませんので、確認してみてください。

まとめ

つい先日まで、SFTP で chroot できることを知らず、FTP は嫌だし FTPS は面倒いなー、と思っていました。 今回、SFTP で chroot できることを知ったので、これからはこの方法で気軽に設定していきたいと思います!

Mix Leap Study 特別編 - レガシーをぶっつぶせ。現場でDDD! コラボカンファレンスに参加しました -- Ruby での開発に活かせるのか?

yahoo-osaka.connpass.com

どうも永田(@kizashi1122)です。 このイベントに参加してきました。

参加のモチベーション

「DDD」という言葉は当然知っている。ただあんまりよくわかっていない。エリック・エヴァンスの本も読んでいない。

こんな状態で、「DDD とは何なのか?」を少しでも理解できればなというモチベーションで挑みました。

聞いた発表

  • ドメイン駆動設計という設計スタイル by @masuda220
  • DDDのモデリングとは何なのか、そしてどうコードに落とすのか by @little_hand_s
  • 現場でドメイン駆動設計を広げるには何をすれば良いか? by @tsuyok
  • 抽象的な教えを試行錯誤しながら解釈した DDD の実践レポート by @suzuki_hoge
  • 過去の失敗例から再考するモデル駆動設計 by @j5ik2o

全部メイン会場やないかい。

正直、増田さん(@masuda220)の発表は概念的な内容も多く、しっかり理解できたかどうかは不安でした。 ただ後半はコードに落ちる部分の説明もあり、なんとなく「こういうことかな」という理解をしました。

私自身は、今は Ruby で開発しているものの Java の経験者でもあり、特に松岡さん(@little_hand_s)とかとじゅんさん(@j5ik2o)の発表はとても腑に落ちるもので、増田さんの発表の後半の続きのような、よりコード側の説明がありDDD初心者にはとても理解しやすかったです。

ただ、これってDDDと言うかこれって・・・では?と思うところがあり、ちょっとモヤっとしていました(・・・については後述)。

懇親会

登壇者との懇親会の参加させていただきました。 ちょうど隣に、登壇者の松岡さん(@little_hand_s)向かいに Yahoo! の辻さん(@crossroad0201)が座られていたので、思い切って上述の疑問をぶちまけました。

私:

「私はDDD初心者で、今日、みなさんの発表を聞いていて感じたのですが、これって純粋にオブジェクト指向をするってことじゃないんですか?

松岡さん、辻さん:

「そのとおりだと思います」

このときの嬉しさ。我が意を得たり。

さらに補足してくれるスマートな松岡さん。

Web アプリケーションなのでややこしくなるんですよ。スタンドアローンアプリなら自然とDDDになるんです。

なるほど。めちゃくちゃわかりやすい解説。さらに松岡さんはこう続けます。

要は、高凝集で疎結合を目指していればいいんですよ。

インゲージでは今、自社サービスを Ruby(Rails)で開発しているので質問。

「松岡さん、やはり DDD と Ruby は相性よくないんですかね?

松岡さん

「よくないと思いますね」

なるほど。 確かに今日の話を聞く限りは、DDD は型安全でないと実装できない・・・とは言ってないものの、型安全であるほうが望ましいだろうなと思っていました。 この1点だけでもRubyなどのLLは不向きなのかなと思います。

また、Ruby on Rails はそもそも DDD が必要ではないようなサービスに向いているのでは?という意見もありました。 適材適所ということでしょうか?

ただ、私は「Ruby on Rails にだって、DDD の考え方はとても重要ではないか。何か活用できるはず」と思っていました。

Ruby on Rails の構成

DDD と Ruby on Rails が相性が悪いということについて、私の今の理解はこうです。

Webアプリケーションというのは、結局のところ、画面からの入力(ほとんどの場合テキスト)をデータベース(SQLレベルではテキスト)に渡す作業ばかりです。

Rails では、画面からの入力は Controller クラスが行い、Controller クラスはデータベースのテーブルの1対1に対応した Model(Rails の文脈の Model)クラスを通じてデータベースの書き込みや読み込みを行うわけです。

つまり、

画面→ Controller → Model → RDBMS

ということになります。ここでは、View はちょっと割愛します。 つまりこうなると、ビジネスロジックを書くのは、Controller か Model になり、Fat Controller になるか、Fat Model になるかの道しかない。 これはDDD的にうまくない。HTTPリクエストに依存している Controller層とデータベースの永続化を担う Model のどちらからがビジネスロジック(ドメインロジック?)も担うのは確かにおかしい。

ただ、なんとなく、ビジネスロジックをこの間に持たせることがうまくいけば、DDD 的なやり方に近づくのではないかなと思っていました。 (上記の理解は間違っているかもしれないです)

まさにこれを考えていたときに、このブログが公開されました。

engineer.crowdworks.jp

すごい。タイムリー。ちょうどこういうことを考えていました。

最後に

この懇親会のあと、終電をなくし、かとじゅんさん、田中さん、辻さんと3時まで色々なお話ができたのはいい思い出です。

DDD 本ちゃんと読まないと。

Mix Leap 運営の方々、特に、Yahoo! の辻さん(@crossroad0201)、ありがとうございました。

自前 Redis から AWS Elasticache に移行した

http://download.redis.io/logocontest/82.png

どうも永田です。

弊社サービスの「リレーション」では、セッション管理やジョブキュー、一時的なデータ置き場として Redis を使っています。 最近、EC2 上に自前で構築していた Redis から AWS Elasticache に移行しました。

動機

自前で構築していた Redis は Sentinel を使ってクラスタを構築していました。特に問題はなかったのですが、やはり管理対象を減らしてマネージドサービスに移行していきたいという思いがあり、移行することにしました。

データの移行

実は自前 Redis は3系のバージョンを使っていました。AWS Elasticache では現時点で最新の5.0.4を使っています。気になるのはデータ(rdbファイル)を移行する際の互換性。

github.com

こちらには

Redis dump file is 100% backwards compatible. An older dump file format will always work with a newer version of Redis.

とあり、バージョンによる互換性は大丈夫だと言えそうです。

ただし、AWS Elasticache はクラスタを起動するタイミングでしか rdb ファイルをインポートできないため、移行は多少ややこしくなります(後述)

Cluster Mode はどちらにすべきか?

AWS Elasticache はそもそもがクラスタなのですが、作成する際に Cluster Mode のチェックボックスがあります。当然オンでしょ?と思ったらハマりました。弊社サービスでは非同期ジョブに Sidekiq を使っており、ジョブキューには Redis を使っているのですが、その Sidekiq が Redis Cluster には対応していないようなのです。

github.com

ここに、Cluster is NOT appropriate for Sidekiq と太字で書かれています。

実際のエラーメッセージは

ERR CROSSSLOT Keys in request don't hash to the same slot.

というもので、https://github.com/mperham/sidekiq/blob/3d622887c81b28cf0ffc5c8c1f7dd626c463f6a1/lib/sidekiq/api.rb#L205-L206 ここに TODO で誰かクラスターサポートしてよとコメントに書いていますね。

では実際の移行手順はどうなるのか?

上述のとおり、rdb ファイルは、Elasticache のクラスタ作成時でないと指定できません。 そのため、多少時間はかかるのですが、以下の手順を取ることにしました。

  1. AWS で Elasticache のクラスタ作成の入力項目を全部埋めておく(rdb の s3 のロケーションも)
  2. アプリケーションをメンテナンスモードをオンにする
  3. バッチや常駐プログラムの起動を停止し、自前 Redis へのコネクションがないことを確認する(コマンドは後述)
  4. 旧 Redis の rdb ファイルを 1 で指定した s3 のロケーションにアップロードする
  5. アップロードしたファイルのS3上でのパーミッションを変更する(詳細は後述)
  6. クラスタ作成の実施する(大体7分から8分くらいか)
  7. ステータスが creating から modifying に変わり、primary endpoint が確定したら、アプリケーションが参照するRedisのホストをその primary endpoint に変更し、設定内容を反映させる
  8. メンテナンスモードをオフにする
  9. セッションが引き継げているかなど動作確認をする

といった流れとなりました。

Redis のコネクションの確認は

$ redis-cli -h <host> CLIENT LIST 

で確認可能です。

また s3 にアップロードした rdb ファイルのパーミッションは

docs.aws.amazon.com

にあるとおり、 [Add Account] に 540804c33a284a299d2547575ce1010f2312ef3da9b3a053c8bc45bf233e4353 を入力し、読み取り権限を与える必要があります。

移行後

残念ながら、AWS Elasticache にしたことによってパフォーマンスがよくなったとかそういった感触はありません。 メモリ空間は今までより増えたので、さらに活用していこうかと思います。

では!

エンジニアは引き続き募集中だよ!

bash スクリプトの罠

こんにちは、masm11 です。お久しぶりです。 今回は bash スクリプトの罠を一つご紹介したいと思います。

症状

以下のような bash スクリプトを作成します。

#!/bin/bash

sleep 5

5秒間 sleep して終了するだけのプログラムです。

実行すると、

$ ./test.sh
$ 

5秒後にプロンプトに戻ります。

次は、端末をもう一枚開き、test.sh を実行中に、以下のように test.sh を書き換えてみます。

$ echo echo kita >> test.sh
$ 

するとどうでしょう。

$ ./test.sh
kita
$ 

後から追加したコードも実行されてしまっています。

解明

いったい何が起きているのでしょうか?

こういう時にはシステムコールをトレースすると便利です。

以下のように実行します。

$ strace -f -o log ./test.sh
kita
$ 

こうすると、どんなシステムコールが実行され、どんな返り値だったかが、log ファイルに 出力されます。

この log ファイルから重要そうな箇所だけを抜き出してみました。

24863 openat(AT_FDCWD, "./test.sh", O_RDONLY) = 3
24863 read(3, "#!/bin/bash\n\nsleep 5\n", 80) = 21
24863 lseek(3, 0, SEEK_SET)             = 0
24863 dup2(3, 255)                      = 255
24863 fstat(255, {st_mode=S_IFREG|0755, st_size=21, ...}) = 0
24863 lseek(255, 0, SEEK_CUR)           = 0
24863 read(255, "#!/bin/bash\n\nsleep 5\n", 21) = 21
24863 wait4(-1,  <unfinished ...>
24863 <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 24864
24863 read(255, "echo kita\n", 21)      = 10
24863 read(255, "", 21)                 = 0

最初の 24863 はプロセスIDで、その右に実行したシステムコールの内容が出力されています。

wait4 と書いてあるところが、sleep コマンドの終了を待っているところです。その後に、

24863 read(255, "echo kita\n", 21)      = 10

と続いています。なんと、ここでスクリプトの続きを読んでいました。

まとめ

bash スクリプトは実行中にもスクリプトを read していることがわかりました。 実行中に書き換えると、場合によってはとんでもない目に遭うことが想像できます。

bash スクリプトは実行中に書き換えないようにしましょう!