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

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

相手のサーバにHTTP(S)で接続できるかどうかを確認するときにリトライしながらやりたいんですけどって時

例えばCIでテストのためのストレージのコンテナを上げる際,そのストレージが上がりきるまで待たなければテストには使えないわけですが「上がりきるまで何秒待てばよいのか」というのは一概に決められるものではありません.
そうした際,実際にストレージに対してHTTP(S)のリクエストを投げてみて,繋がったら「上がりきった」とみなして処理を次に進めるというようなことがよく書かれます.そういった要件を満たす為には接続に失敗したらリトライをしつつ一定回数試行するという処理を書かなくてはなりません.
つまりはそういう話です.

wgetの場合

  • retry-connrefused
  • tries
  • waitretry

を利用すると出来る.
retry-connrefused が無いと connection refusedに対応できずにリトライが不可能となるので,今回のような要件を満足するためには付ける必要があります.

e.g.

$ wget --tries 10 --waitretry 10 --retry-connrefused localhost:5000

この例だと

  • 最大10回リトライ
  • 1回失敗するごとに1秒ずつリトライ間隔が伸びていきながらリトライ (つまりn回失敗したあとのwaitはn秒)

というような動きをします. tries には最大リトライ回数を指定します.
waitretry は失敗するごとに1秒ずつリトライ間隔を伸ばしてゆく際の上限値となります (例えば,値が1だと常に1秒間隔をあけるようになる.0だと一切待たない).
場合によっては timeout を設定する必要があるでしょう.

なおこのコマンドはTravis CIのドキュメントにも書かれている由緒正しいコマンドから拝借しました.

curlの場合

  • retry-connrefused
  • retry
  • retry-delay
  • retry-max-time

を利用するとできる.
retry-connrefused が必要な理由はwgetのそれと同様です.なお,retry-connrefused2016-12-21にリリースされたバージョン7.52.0から入ったわりと新しい機能なので古いcurlだと使えません.新しいものにアップデートしましょう.

e.g.

$ curl --retry 10 --retry-delay 1 --retry-connrefused localhost:5000

この例では最大10回リトライしつつ (retry で指定),リトライ間隔は常に1秒 (retry-delay で指定) というふうになります.

retry 単体で利用すると失敗する度にretry間隔が倍々になり (つまり失敗回数をnとすると2^(n-1)秒待つ),
retryretry-delay を組み合わせると固定長秒数のretry間隔を設定できます.
また retryretry-max-time を組み合わせると,retry間隔は倍々で増えてゆき,そのリトライ間隔が retry-max-time を超えるとギブアップする (retry で指定された回数に満たなくても) という挙動をします.

どちらを使ったら良いのか

お好みで.双方微妙にリトライ間隔の取扱いが異なるので用途にマッチしたほうを使えば良いと思います.
とは言えcurlは環境によって古い (そしてアップグレードできない) ということがあるのでwgetを使っておけば安心なんでしょうか.

Groovyで動的にメソッドを生やす

インスタンスにメソッドを生やす

class EmptyCat {}
def cat = new EmptyCat()
cat.metaClass.nyan = { return 'meow' }
cat.nyan() // => 'meow'

インスタンスmetaClass からMetaClass (HandleMetaClass) を引っ張ってきて,それ経由で生やしたいメソッドの名前を指定し,Closureを代入してやると良い.

インスタンスメソッドとして生やす

上記がインスタンス自体にメソッドを生やしていたのに対し,こちらはクラスに対してメソッド追加の処理を行い,そのクラスがinstantiateされた際にそのメソッドを利用可能にするという方法.

class EmptyHuman {}
EmptyHuman.metaClass.greet = { return '無……' }
def human = new EmptyHuman()
human.greet() // => '無……'

クラスの metaClass からMetaClass (ExpandoMetaClass) を引っ張ってきて,それ経由で生やしたいメソッドの名前を指定し,Closureを代入してやると良い.

クラスメソッドとして生やす

class EmptyDog {}
EmptyDog.metaClass.static.wan = { return 'bow' }
EmptyDog.wan() // => 'bow'

クラスの metaClass.static からMetaClass (ExpandoMetaClass) を引っ張ってきて,それ経由で生やしたいメソッドの名前を指定し,Closureを代入してやると良い.

所感

Groovyらしい機能という感じ.finalなインスタンスであろうとfinalなクラスであろうとこのような機能を実現することができ,脱出ハッチが用意されているという謎の安心感はある.
こうした広義のメタプログラミングが簡単にできるのは便利である一方で,使う場所を誤るあるいは多用しすぎると途端にコードが魔界と化す予感がある (これはメタプログラミング全般に言えることです).節度を持った利用を心がける必要がありそうだが,しかしこういう処理を容易に書けるように言語側がアフォードするのは中々男らしい,という感想です.

CircleCI 2.0でElasticsearchを起動しつつテストする

例えばElasticsearchを使ったプロジェクトがあったとして,それをCircleCIで継続的にテストしたいとする.CircleCI 2.0はコンテナベースのCI環境なので,そのプロジェクトが採用している言語のコンテナの上でElasticsearchを動かす必要がある.どうするか.

circleci.com

CircleCIのドキュメントに記されている通り,docker imageは複数起動することができる.一番目に指定したdocker imageはprimary containerとして取り扱われ,stepsに書く手続きは全てそのコンテナ上で実行される.それ以降にdocker imageを指定した場合はそのimageがprimary containerと共通のネットワーク上で立ち上がり,そのコンテナ上で公開されているポートについてはprimary containerのlocalhost経由でアクセスすることが可能となる.これで手っ取り早く依存しているストレージをdockerで立ててテストすることができる (今回はたまたまElasticsearchの話題だったが,これはRedisでもmemcachedでも,docker imageがあるものであればなんでも応用が効く).

つまり,例えばjavaのプロジェクト上でElasticsearchのテストを行いたい場合は以下のようなconfig.ymlを書くと良い;

# https://circleci.com/docs/2.0/language-java/
version: 2
jobs:
  build:
    docker:
      - image: circleci/openjdk:8-jdk
      - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.1

    working_directory: ~/awesome-project

    steps:
      # do something!

この場合,JDK8のコンテナがprimary containerとなり,Elasticsearchはsecondary container (という言い方が正しいのかどうかはわからないが) として立ち上がる.

さて,Elasticsearchのデーモンの設定を手っ取り早くいじりたいんだけど (要はdocker run -e'xxx=yyy'みたいな感じで制御したいんだけど) みたいな時にどうすれば良いのかというと

circleci.com

にあるように,environmentを設定すると良い.
例えば,

      - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.1
        environment:
          http.host: '0.0.0.0'
          http.port: 9200
          xpack.security.enabled: false

というふうにしてやると,localhost:9200をlistenしつつ,xpackのセキュリティ設定 (要はBASIC認証) をバイパスしてElasticsearchを立ち上げることが可能となる.

ところで.CircleCIの弱いインスタンスを使っているとElasticsearchがOOMしてずっこけるという事態に出くわす.出くわすのです.僕は出くわした.そういう時にどうすればいいかというと,

  • CircleCIを良いプランにする (本質的な改善!!!)
  • Elasticsearchのヒープサイズを低くする

という方法が考えられる.色々な理由から前者が難しい場合,後者に頼るほか無いのでこうしてやるしかない;

      - image: docker.elastic.co/elasticsearch/elasticsearch:5.6.1
        environment:
          ES_JAVA_OPTS: '-Xms256m -Xmx256m'

プロダクションでは考えがたい蛮行だがまあテストだし……というわけで動く.
これでテストがハチャメチャに遅くなって支障をきたすようであれば調整する必要があるだろう……ガンバだよ!!!

SSL/TLS化しているサイトにリクエストを投げたら証明書の検証にしくじっているという時

表題のような状況のトラブルシュートについて記します.

moznion.hatenablog.com

というかこれの続きです.怪奇現象など存在しない.

背景

  • ブラウザとかcurlとかからはリクエストが通るのにJavaからは通らない
  • Let's encryptの証明書を使っている
    • Let's encryptでSSL/TLS化された他のサイトにはリクエストが通る
  • 当該サーバにSSHする権利がない (あとで出来るようになった)

検討したこと

  • 認証局が違うのではないか
  • Cipherがおかしいのではないか (鍵長が短いとか)
    • 証明書側
    • リバースプロキシ側
    • Java*1
  • 中間証明書がなんかおかしいのではないか

調査方法

とりあえずcipherは調べたけど特におかしいところは見当たりませんでした.
認証局についてはブラウザで確認してみたら双方同じRoot CA,中間CAで,それぞれfingerprintも同一だし問題なかろういう結論に.
というわけで中間証明書周りを疑いはじめる (というかid:hdkshjmさんとid:uzullaさんから示唆を受ける).しからば検証.

$ openssl version
OpenSSL 1.0.2l  25 May 2017
$ openssl s_client -connect $TARGET_HOST:443 -showcerts < /dev/null

とすると末尾に

Verify return code: 21 (unable to verify the first certificate)

というのが出てくる.証明書の検証にしくじっているのはまず間違いなさそう.
では認証チェーンのチェックをしてみましょう (一部ドメイン情報に編集を入れています).

$ openssl s_client  -connect moznion.net:443 < /dev/null | grep "Certificate chain" -A 10
Certificate chain
 0 s:/CN=moznion.net
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
 1 s:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
   i:/O=Digital Signature Trust Co./CN=DST Root CA X3
---
(以下略)
$ openssl s_client -connect $TARGET_HOST:443 < /dev/null | grep "Certificate chain" -A 10
Certificate chain
 0 s:/CN=<host>
   i:/C=US/O=Let's Encrypt/CN=Let's Encrypt Authority X3
---
(以下略)

正常に動いている https://moznion.net と比較すると中間証明書がすっぽ抜けている事がわかります.

こうなるともはや何も難しいことはありません.nginxの ssl_certificateサーバ証明書だけが突っ込まれており,中間証明書が結合されていないという典型的なやつでした.
というわけでLet's encryptが提供してくれる fullchain.pemssl_certificate に食わせてやると果たして正しく動作しましたとさ.めでたしめでたし.

教訓

  • おちついてトラブルシューティングをする.ちゃんとやればわかる.
  • opensslコマンドをちゃんと知っておく.
  • サーバをセットアップした人にちゃんとヒアリングすると楽.

その他

curlやブラウザには当該中間証明書 (すっぽ抜けてたやつ) がバンドルされているからリクエストが通った?
Javaにはそれがなかった? あるいはJavaはチェーンの検証が厳密?

Multiple projectなgradleのprojectでcheckstyle pluginを有効にする

Multiple projectなgradle projectだと, apply plugin: "checkstyle" と書いてもそれだけではcheckstyle pluginがうまく動かない.おそらく Unable to create Root Module: config ... などというエラーとともに死ぬであろう.
というのも,gradle checkstyle pluginのデフォルトの状態だと,各sub projectがおのおの checkstyle.xml を持っていることを期待しているからである (つまり[sub-project-path]/config/checkstyle/checkstyle.xml の存在が期待されている).
たいていの場合, checkstyle.xml を各sub projectが持つというのはやってはおれんので,どこかで一括管理をしてそれを各sub projectから利用したいでしょう.というわけでこう書いてやると良い;

subprojects {
    // do something...

    apply plugin: "checkstyle"

    checkstyle {
        // do something...

        configFile = rootProject.file('code_quality/checkstyle/checkstyle.xml') // <= here!
    }
}

subprojects.checkstyle.configFile に所望の checkstyle.xml を指定してやると良い.
この例ではproject rootに配置されている code_quality/checkstyle/checkstyle.xml が各sub projectからも参照されるようになる.
めでたしめでたし.

ミニマルなサンプルを置いておきます.

github.com

checkstyle.xml に誤りがある場合の挙動

それはそうと checkstyle.xml に誤りがあると

Execution failed for task ':your-project:checkstyleMain'.
> Unable to create Root Module: config {/path/to/your/checkstyle.xml}, classpath {...}.

みたいなエラーとともに死んでしまう.このエラーメッセージで checkstyle.xml にエラーがあるとは思うまい.しかしそうなのです.
仕方がないので --stacktrace オプションを有効にして再度 checkstyleMain を実行すると,それらしきstacktraceがゲロッと吐かれるので,それをもとにxmlファイルを修正していくことができるようになります.マジかよ,つらい.
このエラーメッセージは本当に不親切だと思うしなんとかなってほしい…… これに出くわした時,checkstyle.xml に誤りがあるとは全く思っておらず数時間を浪費してしまった.

checkstyle.xml に変数を埋める方法
checkstyle {
    // do something...

    configProperties = [
            'checkstyle.cache.file': "${buildDir}/checkstyle.cache",
    ]
}

みたいな感じで build.gradle で定義してやると, checkstyle.xml 上で

<property name="cacheFile" value="${checkstyle.cache.file}"/>

という感じで変数を利用できるようになる.この build.gradle 側の設定を怠ると不親切なエラーとともに死にます.勘弁してくれ.

Karabiner-Elementsでcolonとsemicolonを入れ替える

[追記]
コメントで指摘がありましたが,Karabiner-ElementsのGUI上で
complex modifications → Add rule → import more rules from the Internet → Exchange semicolon and colon
を選択することで所望の動作の実現が可能なようです.

従って本記事はcolonとsemicolonを入れ替えるというよりも,任意のキーを入れ替える時のためのtipsとなります.
[追記ここまで]

MacのUSキーボードの話です.

Karabinerにはcolonとsemicolonを入れ替えるという項目があり,これが大変便利だったのですが (特にvimを使っているときとかに便利),Karabiner-Elementsではこのオプションがありません.現時点では,macOS Sierra上だとKarabiner-Elementsを使うほかないので,つまりGUI上からcolonとsemicolonをswapする方法がありません.

そもそもcolonとsemicolonを入れ替えて使うというのがアレなので,Sierraに移行したのを機に元に戻すことも考えたのですが,しかし入れ替えた環境に慣れてしまっているので難しい.なんとかしたい.というわけで以下を ~/.config/karabiner/karabiner.json に書き込むと入れ替えができる.

{
    "global": {
      ...
    },
    "profiles": [
        {
            "complex_modifications": {
                ...,
                "rules": [
                    {
                        "description": "Swap colon and semicolon",
                        "manipulators": [
                            {
                                "type": "basic",
                                "from": {
                                    "key_code": "semicolon",
                                    "modifiers": {
                                        "optional": [
                                            "caps_lock"
                                        ]
                                    }
                                },
                                "to": [
                                    {
                                        "key_code": "semicolon",
                                        "modifiers": [
                                            "left_shift"
                                        ]
                                    }
                                ]
                            },
                            {
                                "type": "basic",
                                "from": {
                                    "key_code": "semicolon",
                                    "modifiers": {
                                        "mandatory": [
                                            "shift"
                                        ],
                                        "optional": [
                                            "caps_lock"
                                        ]
                                    }
                                },
                                "to": [
                                    {
                                        "key_code": "semicolon"
                                    }
                                ]
                            }
                        ]
                    }
                ]
            },
            ...
        }
    ]
}

profiles.complex_modifications.rules の中身をこのように書くと所望の挙動となる.よかったよかった.

他のキーの入れ替えについてもこれと同様な感じで書き込むといけそうで良いですね.

社内でDDD勉強会をやった

DDD (Domain Driven Design/ドメイン駆動設計) についての学習気運があり,その勉強会を社内でやったのでその経過を記すものです.
DDDに関する詳細な内容には触れません (良い本や資料が巷には溢れています).読書会自体をどうやったか的な話です.

前提

developer.hatenastaff.com

基本的にこのスタイルを真似しました.

読んだ本

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計

実践ドメイン駆動設計

エリック・エヴァンスドメイン駆動設計 (青本) を読んでから,実践ドメイン駆動設計 (IDDD/赤本) を読んだという流れです.

.NETのエンタープライズアプリケーションアーキテクチャ 第2版 (マイクロソフト公式解説書)

.NETのエンタープライズアプリケーションアーキテクチャ 第2版 (マイクロソフト公式解説書)

やっていませんが,続いて上記の本 (黒本) を読むというのもありかも知れないね,という話を会のメンバーとしました.

も良いかもわかりません.

が,とにかく青本をやってから赤本をやるということにしました.理由としてはやはり原典を読んで知識を付けてから実践に行ったほうが良いであろうという考えからです.確かにDDDを形作るエッセンスが青本には記されており,一読しておいて損はないと思いましたが,しかし後になって思い返すと別に赤本だけでも問題ない気もしました (もしかしたら青本の代わりにInfoQのDomain Driven Design Quicklyを読むのでも十分なのかもしれない).読みたい本を読むべきです.

進め方

  • 1週間に1回開催 (やむを得ない場合は開催されないこともある)
  • 1回あたりに1章やる (1章が長い場合は2回程度に分割する.話を忘れてしまうので2回を超えて分割しないほうが良いと思っている)
  • 全員があらかじめ該当する範囲を読んでおく
  • 週替りで当番を決め,当番になった人はその章の中で疑問に思ったことや議論したいことをmarkdownにまとめてGitHubのrepositoryにpull requestとして投げる
    • あくまで疑問点や議論したいポイントをまとめるのであって,内容をまとめるわけではない
    • 当番でない人も,疑問に思ったことや議論したい点についてはpull requestのコメント等で表明していく
    • 当番になった人はその回の司会もやる
  • そのmarkdownをベースとしながら疑問の解消や議論等を行なう (適宜本の内容をなぞっていく)
  • 勉強会が終わったら飲みにいく

重要な点は,「参加する全員が本をしっかり読み込んでおく」という点と「疑問点や議論したい点をまとめる」という点です.
前者が成り立つからこそ,後者も成り立ちます.しかし後者は慣れるまでなかなか難しい.うっかり内容のまとめになってしまいがちなので「全員しっかり本を読んでいる」という事を意識していく必要があると思いました.

そして個人的に重要だと思ったのは「勉強会が終わったら飲みに行く」というものです.これは勉強会の延長のようなもので,ざっくばらんに議論してみたり,あるいは「最近こういうことで困ってるんだけど,今日のこのアプローチでいけないか」などという感じで軽く相談が出来るので,なかなか良かったと思いました.また飲み会が勉強会のモチベーションのひとつにもなる (個人の意見です)。
飲み会が嫌いな人には合わないかもしれませんが,幸いにも今回の勉強会のメンバーはみんな飲み会が好きな人が集まったので,これはよくドライブしたように思います.

なおGitHub使うかどうかとかは重要な問題ではなく,チームや企業に合ったツールを使えば良いと思います.

規模

今回は4人でした.これくらいの人数でちょうどよかったな,という感想です.
勉強会の規模が大きくなっていくとどうしても自分事感が希薄になっていき「俺が読んでなくても大丈夫だろう」「議論に参加しなくても問題ないだろう」という感じになっていきがちですが,この程度の規模感だとそうなりにくく,全員参加型でほどよい緊張感を持って進めていけるので良いと思いました (あとこれくらいの人数だと飲み会の予約も取りやすいし).

困った点

DDDに本当に詳しいエキスパートが不在だったため,「これで本当に合っているのか」などといった疑問に襲われることが何度かありました.
そういう時は一応議論をしつつインターネットで調べながら手探りで答えを探してゆき「まあこういうことなんじゃないか」と全員で認識を合わせることで乗り切っていきましたが,もしかしたら間違えた知識を身につける可能性も孕んでいます.
勉強する分野のプロがいた方が良いな〜というのには首肯するところであります.

所感

なんやかやこういうスタイルで1年近く継続して勉強ができて良かったです.
独習に向く分野もあれば,複数人で学ぶのが向いている分野もあると思っており,DDDは後者に近いと思いました.その理由というのも,DDDには方法論だけではなく,プロジェクトに関わる全員に関係のある組織論に近い要素が含まれていると思ったためです.複数人で議論をし,認識を合わせ,不明な点を少なくしていくというスタイルは実際のDDDにも通じるものであり,その結果このような勉強会のスタイルは上手くマッチしたのではないかと思いました.

今後も何か興味深い分野があったら,このようにやってみたいものです.