Elastic BeanstalkでImmutable deployを試す
やり方についてはドキュメントを読むと良い.
option_settings: aws:elasticbeanstalk:command: DeploymentPolicy: Immutable HealthCheckSuccessThreshold: Ok IgnoreHealthCheck: false Timeout: "600"
設定ファイルでやるならばこういった設定を書き,.ebextensions/
以下に deploy.config
みたいな名前で放り込んでデプロイするとImmutable deployがされる.
最初は素朴なhealth checkベースのrolling deployを試していたのだけど,JavaのApp serverと相性が悪いのか (私はJavaを書いています) 上手くhealth checkが通らない時があり,また場合によってはJavaのプロセスが2個立ち上がったりしてしまってまともにデプロイできなかったので,ものは試しとImmutable deployを試してみたところ上手く行った.
基礎的な動作・挙動についてはドキュメントを参照してもらうとして,immutable deployの利点は
というものが挙げられる.これはディスポーザブルな構成が持つ良い特徴そのもののように思う.
一方で懸念すべき点としては (公式ドキュメントにもあるが)
- デプロイ完了までにかかる時間がかなり増える
- デプロイ中一時的にインスタンス数が2倍になる
- オンデマンド制限に引っかかる可能性がある
- もちろんお金がかかる
と言うものがある.
前者については「一台だけ新しいインスタンスを立ててデプロイする」->「health checkが通るかどうか確認する」->「health checkが通るのであれば残りのインスタンスも立ててデプロイする」->「health checkが通るかどうか確認する」->「health checkが通るのであれば新しいインスタンス群をauto scaling groupに突っ込む」->「古いインスタンス群をauto scaling groupからterminateする」という手順を踏むのでそれは時間が長くなるだろうなという感じ.急ぎのデプロイの時は中々ヒリヒリしそうなので,場合によってはhealth checkをスキップしたり,別のデプロイ手法を試したりする必要がありそうだと思った.
後者についてはもうそういうもんだと割り切るしかあるまい……2倍の数のインスタンスが必要となるのはデプロイ時だけなので寛容な心が必要であろう (オンデマンド制限については考えなければならないだろうが).
とはいえEC2も秒単位の時間課金になったので,かつてよりはImmutable deployもやりやすくなったんじゃないでしょうか.良い時代になりましたね.
それにしても日本語化されたドキュメント上で「Immutable」を「変更不能」と訳すの,気持ちはわかるんだがそれはどうなんだ……
相手のサーバに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-connrefused
は2016-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)秒待つ),
retry
と retry-delay
を組み合わせると固定長秒数のretry間隔を設定できます.
また retry
と retry-max-time
を組み合わせると,retry間隔は倍々で増えてゆき,そのリトライ間隔が retry-max-time
を超えるとギブアップする (retry
で指定された回数に満たなくても) という挙動をします.
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を代入してやると良い.
CircleCI 2.0でElasticsearchを起動しつつテストする
例えばElasticsearchを使ったプロジェクトがあったとして,それをCircleCIで継続的にテストしたいとする.CircleCI 2.0はコンテナベースのCI環境なので,そのプロジェクトが採用している言語のコンテナの上でElasticsearchを動かす必要がある.どうするか.
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'
みたいな感じで制御したいんだけど) みたいな時にどうすれば良いのかというと
にあるように,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化しているサイトにリクエストを投げたら証明書の検証にしくじっているという時
表題のような状況のトラブルシュートについて記します.
というかこれの続きです.怪奇現象など存在しない.
背景
調査方法
とりあえず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.pem
を ssl_certificate
に食わせてやると果たして正しく動作しましたとさ.めでたしめでたし.
教訓
- おちついてトラブルシューティングをする.ちゃんとやればわかる.
- opensslコマンドをちゃんと知っておく.
- サーバをセットアップした人にちゃんとヒアリングすると楽.
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からも参照されるようになる.
めでたしめでたし.
ミニマルなサンプルを置いておきます.
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
の中身をこのように書くと所望の挙動となる.よかったよかった.
他のキーの入れ替えについてもこれと同様な感じで書き込むといけそうで良いですね.