読者です 読者をやめる 読者になる 読者になる

hakobera's blog

技術メモ。たまに雑談

スタディサプリ on Quipper プラットフォームを支える技術

前置き

hakobera.hatenablog.com

この記事を公開した当時はまだオープンにできなかったのですが、実はこの記事は2月25日にリリースされたスタディサプリ(受験サプリ)を Quipper のプラットフォームに載せ替えるという移行プロジェクトを前提として内容も含んでいました。

【公式】スタディサプリ|苦手克服・定期テスト対策~大学受験まで

無事にリリースできたので、このプロジェクトで変わったことや導入したソフトウェア、ツールについていくつかピックアップして書いていきたいと思います。

目次

  • メンバーが増えた
  • Infra as Code
  • Deis
  • AWS ECS + Docker + Locust による負荷テスト
  • nginx (HTTP/2, ngx_mruby)
  • 分析基盤
  • 技術的課題

メンバーが増えた

前回の記事を書いた時はインフラ関連のエンジニア(Quipper では Engineering チームといいます)は私1人だったのですが、kamatama41 さん、lamanotrama さんが加わり、今は3人でちゃんとしたチームになりました。

メンバーが増えたことで、今まで手がつけられなかったことが大分できるようになりました。

Infra as Code

今回は AWS の環境から全て新規に構築するということで、Infra as Code 全開でやってみました。

  • Terraform
  • Roadworker
  • Miam
  • Ansible
  • Ansible Tower
  • ServerSpec
  • Infrataster

Terraform と Roadworker/Miaml で AWS のリソースを定義し、Ansible でサーバーを Provisioning するという至ってオーソドックスな構成です。 全てのツールは CircleCI 上でテストから適用までできるようになっています。(Ansible に関しては、CircleCI から Ansible Tower の API を叩いて実行という構成になっています)

Playbook のテストは CircleCI 上で Docker で素の Ubuntu を起動し、そこに Playbook を適用し、ServerSpec, InfraTester でテストしています。 実際にやってみて、色々と気を付けなければいけない点はあり、初期はスピードが出なかったのですが、インフラの設計図が全てコードになっていて、暗黙知がなくなり、開発後半はスピードも安定性も出すことができたと思っています。

今後は、ここで得た知見を、今度はグローバルのプラットフォームにバックポートしていく予定です。

Deis

Deis は Docker/CoreOS/Fleet/Etcd/Golang/Python で構築された Private PaaS を実現するソフトウェアです。Deis の導入は今回のプロジェクトで一番難しかったことの1つでしょう。なにしろ、日本語はもちろん、英語でもプロダクションでどのように運用しているかという情報がなかったからです。公式からも AWS CloudFormation のテンプレートが提供されているのですが、正直あのままでは不安定すぎてプロダクションに投入できません。これについては長くなるので、別途 Qiita に書いてみたので、ご参照ください。日本でプロダクションに投入したのは初のはず。

Deis を AWS でプロダクションに投入するための Tips - Qiita

色々と大変ではあるのですが、半年以上検証した結果それなりに安定運用できています。 Deis の良い点は、とにかく Heroku Buildpack と互換性があるということです。

今回のスタディサプリのアプリケーションについてもコードは、海外で展開している Quipper School と同じコードを利用しています。そして、海外では Heroku でアプリケーションをホストして、並行して開発が進んでいます。 Deis は Heroku Buildpack と互換性があるため、デプロイの仕組みを別々に作らなくてもよく、git の push 先を Deis に切り替えるだけで、Heroku で動いしていたコードがそのまま動きます。

Heroku を使っているユーザは、行くのも戻るのも簡単なので移行の有力な選択肢になるのではないかと思います。ただし、それなりに手はかかるので、インフラにあまり手をかけられないという人は、Heroku Private Spaces も併せて検討することを忘れずに。

AWS ECS + Docker + Locust による負荷テスト

GKE + Docker + Locust による負荷テストを実施していましたが、今回、ネットワーク的に近いほうが負荷テストをし易いだろうということで、AWS ECS を利用して同じものを作ってみました。

https://github.com/hakobera/docker-locust-gke (これは GKE 版)

使い勝手はほぼ変わりませんが、ECS は利用する場合は幾つかメリットがあります。

ということで、負荷テストにお悩みの方は ECS + Dokcer + Locust も試してみると良いのではないでしょうか。 ECS版はソースを整理したら、公開したいと思います。とりあえず、Dockerfile だけ。Alpine はイメージサイズが小さくていいです。

FROM python:2.7-alpine

RUN apk -U add ca-certificates python-dev build-base && \
    pip install locustio pyzmq && \
    apk del python-dev && \
    rm -r /var/cache/apk/* && \
    mkdir /locust

WORKDIR /locust

COPY locustfile.py /locust/locustfile.py
RUN test -f requirements.txt && pip install -r requirements.txt; exit 0

EXPOSE 8089 5557 5558
ENTRYPOINT [ "/usr/local/bin/locust" ]

nginx (HTTP/2, ngx_mruby)

HTTP/2

https://studysapuri.jp/ にアクセスしてもらえればわかると思いますが、リバースプロキシは nginx かつ、プロトコルは HTTP/2 を利用しています。 HTTP/2 の恩恵で、画像がたくさんあるページなのですが、快適に表示することができていると思います。

今回、静的サイトに関してはアクセスされるのがほぼ日本のみということで、CDN をほぼ利用していませんが、HTTP/2 に対応していない CloudFront を利用するよりも配信が速かったです。

ngx_mruby

時代はプログラマブルなリバースプロキシだ、ということで、nginx 単体では難しい処理を ngx_mruby を利用して実装しています。具体的には、

  • DoS 対策
  • メンテナンス画面への切り替え処理
  • TCP プロキシの backend サーバの動的 DNS Lookup (DNS resolve 機能、nginx+ にはあるけど、OSS にはないのです)

などです。nginx で上記のことができるようになったので、アプリケーション側で考慮することが減って実装や変更が楽になりました。

分析基盤

  • fluentd
  • embulk
  • Kinesis + Lambda
  • TreasureData

Quipper は BigQuery を利用していますが、今回のスタディサプリはリクルートマーケティングパートナーズとの合同プロジェクトでもあるので、彼らの分析基盤である TreasureData へデータ集約を行っています。fluentd で nginx のログを、embulk でマスタデータを、Kinesis + Lambda で Deis からのイベントデータを送信しています。

Kinesis + Lambda によるイベントデータ送信はあまり話を聞かないのですが、かなり便利なのでオススメです。fluentd の再送機能も良いのですが、障害対策のためにバッファをどれくらい取るかなどの設計が悩ましいですが、Kinesis + Lambda の組み合わせなら なら最大7日間イベントを保持、再送処理をしてくれるので、その辺をまるっと AWS にお任せできるので大変助かっています。サーバーレス万歳ですね。

https://github.com/hakobera/lambda-kinesis-bigquery

内部では上記の TreasureData 版を作って使っています。

技術的課題

モニタリングと通知をもっと効率的に

Quipper ではモニタリングに各種ツールを利用しており、

  • Sentry
  • Pingdog
  • NewRelic
  • Datadog

それぞれ便利なのですが、いくつか問題があります。リリースへ向けて、新規構築に重点を置いていたため、これからは監視、運用をどう効率化していくが解決するべき課題となっています。

  • 見る場所が分散して、全体の状況を把握するするのが難しくなっている
    • 一応、Datadog に集約して、Slack 通知という方針をとっているが、Integration がなくて集約しきれないものもあり悩み中
  • アラートが来すぎる
    • 領域がかぶっている監視が同時にアラートをあげて、重複して通知がくる
    • 純粋にアラートが多すぎて、本当に重要なエラーを見逃しがち

Workflow エンジンを Luigi から自作の Ruby のやつに置き換える

Workflow エンジンについては引き続き、課題として認識中です。 Luigi 自体は素晴らしく、運用上も大きな問題はないのですが、

  • Model が Ruby で書いてあるので、Ruby で書かれた Workflow エンジンがあると、そのコードが利用できて便利
  • Python だとバッチ系のコードのメンテができる人が限られてしまう(どちらかというとこちらの方が問題)

という理由により、Ruby で書かれた Workflow エンジンを実装中です。 どんな感じになるのか興味がある方は、以下の連載記事をご覧ください。

http://qiita.com/hakobera/items/d7742cc0801a9c62ef72

そろそろ4回目書きます。 4回目書きました

Workflow Engine をつくろう! Part 4(Task の並列実行) - Qiita

負荷試験を CI に組み込む

ECS + Docker + Locust で負荷試験が簡単にできるようになったので、これを CI に組み込むことで定期的にパフォーマンスをチェックできるようになるといいなと思っていますが、まだできていません。

Ubuntu 16.04 LTS への乗り換え

upstart から systemd への変更が・・・ということでいつやるか悩み中。 Ansible が吸収してくれるようになったらかなとは思っている。

絶賛採用中です

Quipper ではここに書いてあるようなクラウドを積極的に活用したお仕事、技術的課題の解決に興味があるエンジニアを絶賛大募集しております。興味のある方は、以下の募集ページからご応募ください。ちなみに採用関係なく、情報交換もしていきたいので、もっと詳しく聞いてみたいという方は Twitter@hakobera, @kamatama_41, @lamanotrama にメンション or DM をお気軽にどうぞ。

https://www.wantedly.com/projects/43935

Quipper の DevOps のお仕事と技術的課題

このツイートがそれなりに反応があったので、有言実行してみる。

最初に書いておくと、これはQuipperの採用のための記事です。Quipper では下記のようなお仕事、技術的課題の解決に興味がある DevOps エンジニアを絶賛大募集しております。興味のある方は、Wantedlyの募集ページ から「話を聞きに行きたい」をクリックしてみてください。応募までは行かないけど、もっと詳しい聞いてみたいという方は私個人にでも良いのでご連絡ください。(Twitter@hakobera にメンション or DM、または hakoberaアットgmail.com までメールください)

2015年6月時点での技術的課題一覧

  • Heroku から AWS への移行
  • Elastic Beanstalk で運用しているアプリの Docker 化
  • Infra As Code のより一層の推進
  • BigQuery を利用したレポート基盤の構築
  • OSS 業務

合わせて使いそうな技術一覧

  • AWS 全般
  • Docker
  • BigQuery
  • Nginx
  • Hashicorp 製品全般(主に Terraform と Atlas)
  • Ruby/Python/Go など(なんらかの言語でのプログラミングスキルは必要)

Heroku から AWS への移行

Quipper は開発環境も合わせると、200以上のアプリを Heroku で稼働させている Heroku のヘビーユーザです。特に Heroku Review Apps と同じような仕組みをかなり昔から運用していて、便利に使っています。詳細は以下のCTO のブログを参照。

非開発者もGitHub Flowに巻き込んでみんなハッピーになった話 - Masatomo Nakano Blog CircleCIでfeature branchをHerokuに継続deploy - Masatomo Nakano Blog

すごく便利に使っている Heroku ですが、以下の理由から一部のアプリケーションについては AWS への移行を検討しています。

  • アクセスが増えてきて、2X Dyno でさばけなくなってきた。現状 PX (Performance) Dyno を利用しているが、$500/Dyno/Month と高く、コストパフォーマンスが結構悪い。
  • サービスの主戦場が現時点ではアジア(フィリピン、インドネシア、タイ)であり、US リージョンしかない Heroku だとネットワーク的に遠く、レスポンスが気になる
  • セキュリティが緩くなりがちである(詳細は書けないが、IPアドレスが固定できないので、制限が色々とかけにくい。固定IPをつける addon もあるが高い)

AWS に移行するにしても、現状の feature branch を簡単にプレビューできる環境は維持したいので、Docker を利用して以下の構成にする予定です。

なお、SPDY/HTTP2 を利用したいので、Elastic Beanstalk の前段に Nginx を置く予定(現状も、Heroku の前段に Nginx を置いている)

Elastic Beanstalk で運用しているアプリの Docker 化

Heroku の AWS 移行とも絡むのですが、現在 Elastic Beanstalk で運用している Worker アプリケーションを Docker 化するというのも課題の1つです。Elastic Beanstalk は大変便利ですが、2点ほど大きな問題を抱えています。

後者は気合で乗りきれなくもないですが、前者は開発が相当やりにくくなっていたし、Quipper の他のサーバは Ubuntu なので、ここだけ OS が異なるのは管理上も面倒である。ということで、ここも Ubuntu で管理できるように Docker 化をしたいと考えている。

Infra As Code のより一層の推進

現在の Quipper の Infra As Code のレベルは、

今後もこの方向性は強く推進していきたい、アイデアとしては色々とあるし、ツールも進化していくの継続的に改善していきたい。

  • ServerSpec によるインフラテストの追加
  • CircleCI 経由で master ブランチの Ansible の適用の自動化
  • Terraform の適用範囲の拡大
    • CloudFormation テンプレートを Terraform で書き換え
  • Docker イメージの自動ビルド

など。この分野はやることが無限にある感じなので、自動化大好きな方をお待ちしております。

BigQuery を利用したレポート基盤の構築

docs.google.com

上記スライドの後半の事例で書いているように、データの投入は fluentd/embulk/mongobq などで解消済みであるが、スケージューリング、エラー時の再実行などの処理基盤がまだまだ弱い。現状、レポートの数がそれ程多くないので何とかなっているが、今後の業務拡大により回らなくなる可能性が高く、ココは早急に改善が必要だと考えている。

以前発表した BigQuery GAS 連携は1つの解にはなっているが、単純なレポートには良いが、中間テーブルを作成しながら集計していくような複雑なジョブの実行には向かない。これらに対する現在考えている解決策は以下だ。

  • Job ネットの構築
    • Luigi が良さそう
    • Luigi の BigQuery Integration である Luigi-BigQuery を書いて、一部のレポートに適用済みで良い感じ
  • スケジューラの改善
    • 現状の Jenkins から、スケジューリングに特化したミドルウェアへの移行
    • Rundeck が乗り換え候補。スケジュールの良し悪しはまだ良くわかっていないので、スケジューラの選定、運用経験がある人に話を聞きたいところ。
  • 可視化 (Visualize)
    • コストさえ許せば選択肢は色々とある。データサイエンティストの人と相談して決めていきたい。
    • BigQuery コネクタのある製品:Mode Analytics, Bime, Chario, [Tableau(http://www.tableau.com/), QLikView など
    • GAS で何とかするか、ゼロスクラッチで作るのも場合によってはあり

レポートの処理速度に関しては BigQuery が全てを解決してくれたので、スケジューラの構築、安定運用と可視化に取り組んでいきたい。

OSS 業務

これは課題ではないのですが、必要に応じて、OSS にコントリビュートする、OSS を書くというのも Quipper の DevOps の重要な仕事の1つなのであげておきます。書いたものを OSS として公開するのは、業務に支障がない限り、特に制限はありません。

現在使っているものも、使っていないものも含まれていますが、以下にこれまで Quipper の業務に関連する OSS をあげておきます。

業務のためにコントリビュートした OSS

業務のために書いた OSS

同僚のエンジニアが書いた最近の Quipper の状況を知ることができるブログ記事

Heroku アプリのログを fluentd で ElasticSearch に突っ込んで Kibana で監視する方法

AWS Heroku Kibana

目的

Kibana で Heroku アプリのログを可視化したい。ただし、レスポンスタイムとかは New Relic でも見れるので、ここではアプリが出力したらログを可視化する方法を紹介する。

また、今回、アプリからの出力を可能な限りに簡単にするために、アプリからは Heroku の STDOUT に出力するだけで、ログを Kibana サーバに送信できるようにしてみた。

具体的にはこう。(Ruby の場合) 出力の形式は、Treasure Data addon の方法 を真似ている。

puts "@[tag.name] #{{'uid'=>123}.to_json}"

これで、tag.name が fluentd の tag 名に、それに続く JSON 文字列が送信するデータとして処理される。

環境

  • Amazon EC2 上に Kibana サーバを構築
  • OS は Ubuntu 13.10
  • td-agent と ElasticSearch は導入済み

タイトルで fluentd と書いたが、正確には td-agent を使っているので、fluentd を使っている人は適宜読み替えること。

ログデータの流れ

処理の概要は以下の通り。

Heroku -> (syslog drains)
 -> rsyslog -> (syslog file)
 -> Fluentd(in_tail, rewrite, parser, elasticsearch)
 -> ElasticSearch
 -> Kibana
  1. Heroku の syslog drains を利用して、Kibana サーバに syslog を送信
  2. Kibana サーバに rsyslog サーバを立てて、Heroku からの syslog を受信して、ファイルに出力
  3. Fluentd の in_tail 機能を利用して、ログを読み込み、rewrite, parser プラグインを利用して、ログの変形とフィルタリング
  4. elasticsearch プラグインを利用して ElasticSearch にログを投入
  5. Kibana で見る

Heroku の設定

Heroku の syslog drains 機能を利用して、syslog を指定したサーバに送信する設定をする。 Kibana サーバで 5140 ポートで rsyslog サーバを起動する場合。

$ heroku drains:add syslog://[kibana-server]:5140 -a [heroku-app-name]
Successfully added syslog://[kibana-server]:5140

追加が完了したら、heroku drains コマンドで、drain ID を取得する。 この d. で始まる drain ID は後で使うので、記録しておく。

$ heroku drains -a [heroku-app-name]
syslog://[kibana-server]:5140 (d.XXXX)
#                             ~~~~~~~~ ここが drain ID

参考:https://devcenter.heroku.com/articles/logging#syslog-drains

注意点としては、Kibana サーバを EC2 に立てている場合は、Elastic IP を設定した上で、Public DNS をサーバ名として指定する必要があること。これは Heroku 側で AWS の Private Address として Look up できないといけない Heroku のセキュリティ上の仕様のため。

参考: https://devcenter.heroku.com/articles/logging#security-access この制限は今はない。

rsyslog の設定

TCP サーバの起動

Heroku は UDP ではなく TCP で syslog を送信してくるので、rsyslog で TCP サーバを起動する。 /etc/rsyslog.conf を開いて、以下を追記(または、今ある要素をコメントアウト解除)

$ModLoad imtcp
$InputTCPServerRun 5140 # ポート番号は heroku drains で設定したポートを指定。

ファイル作成権限の変更

デフォルトだと、syslog は root:adm に 0640 で作成されるので、fluentd から読み出すことができない。なので、ファイルの作成権限の変更を行います。同じく、/etc/rsyslog.conf の最後に以下を追記する。

$FileCreateMode 0644
$DirCreateMode 0755

参考: http://y-ken.hatenablog.com/entry/fluentd-syslog-permission

ログファイルの振り分け

デフォルトだと、/var/log/syslog に heroku のログが出力されていまうのだが、これだと fluentd で in_tail しにくいので、これを別ファイルに振り分ける。ここでは /var/log/heroku に出力するようにしてみる。

/etc/rsyslog.d/40-heroku.conf という名前のファイルを作成し、以下の内容を記述。

if $hostname startswith '[heroku drain ID]' then /var/log/heroku
& ~

1行目が振り分け設定。2行目が1行目で一致したログを後続の syslog に出力しないようにする設定。 もし、複数の Heroku の syslog を受ける場合は、上記の2行をdrain IDを変えながら、繰り返して記述する。

RuleSet を使って、TCP で受け付けるデータのフィルタリングとかすると良いかも。(ここは調整中) http://www.rsyslog.com/doc/multi_ruleset.html

ログローテーションの設定

振り分け設定したログに対して、/etc/logrotate.d/rsyslog にログローテーションの設定をする。以下は daily で 7日間でローテーションする設定する例。

/var/log/heroku
{
        rotate 7
        daily
        missingok
        notifempty
        delaycompress
        compress
        postrotate
                reload rsyslog >/dev/null 2>&1 || true
        endscript
}

rsyslog を再起動

全ての設定を保存したら、rsyslog を再起動。

$ sudo service rsyslog restart

AWS security group の設定

AWS の security group の設定で、EC2 インスタンスに紐付いた security group の TCP 5140 ポートを開放する設定をする。Management Console からでも良いし、aws cli で以下のようにしても良い。

$ aws ec2 authorize-security-group-ingress --group-name MySecurityGroup --protocol tcp --port 5140 --cidr 0.0.0.0/32

AWSじゃない人は、Firewallの設定で同様に 5140 ポートを開放する。

正しく設定できれば、この時点で /var/log/heroku に heroku の syslog が出力されているはず。tailして確認してみる。

$ tail -f /var/log/heroku
Feb  2 03:41:32 [drain ID] app[web.`] Started GET "/XXX" for XXX.XXX.XXX.XXX at 2014-02-02 03:41:32 +0000

こんな感じのログが見えれば、設定は完了。

fluentd の設定

必要な gem のインストール

td-agentの場合は、fluent-gem を使うように注意する。

$ fluent-gem install fluent-plugin-rewrite
$ fluent-gem install fluent-plugin-parser
$ fluent-gem install fluent-plugin-elasticsearch

td-agent.conf の設定

処理の概要は、以下の通り。

  1. in_tail で heroku の syslog を解析
  2. fluent-plugin-rewrite でアプリログと Heroku のログを振り分け
  3. fluent-plugin-parserJSON データの解析
  4. fluent-plugin-elasticesearch で ElasticSearch に出力

実際の /etc/td-agent/td-agent.conf の設定はこう。

<source>
  type tail
  path /var/log/heroku
  pos_file /var/log/td-agent/posfile_heroku.pos
  format /^(?<time>[^ ]*\s+[^ ]*\s+[^ ]*) (?<host>[^ ]*) (?<ident>[a-zA-Z0-9_\/\.\-]*)(?:\[(?<pid>[a-zA-Z0-9\.]+)\])? *(?<message>.*)$/
  time_format %b %d %H:%M:%S
  tag heroku.syslog
</source>

# アプリログとHerokuが出力するログを振り分け
<match heroku.syslog>
  type rewrite
  <rule>
    key message
    pattern ^@\[([a-zA-Z0-9_\.]+)\]\s+.*$
    append_to_tag true
    fallback others
  </rule>
  <rule>
    key message
    pattern ^@\[[a-zA-Z0-9_\.]+\]\s+(.*)$
    replace \1
  </rule>
</match>

# アプリログ以外は読み捨て
<match heroku.syslog.others>
  type null
</match>

# アプリログの処理
# ここでは @[app.XXX] JSON と出力していることを想定
# JSON データは message プロパティに文字列として格納されている
<match heroku.syslog.app.**>
  type parser
  remove_prefix heroku.syslog
  key_name message
  format json
  reserve_data true
</match>

# ElasticSearch に出力
<match app.**>
  type elasticsearch
  type_name app
  include_tag_key true
  tag_key @log_name
  host localhost
  port 9200
  logstash_format true
  flush_interval 3s
</match>

再起動

設定を保存したら、fluentd (td-agent) を再起動。

$ sudo service td-agent restart

しばらくすると、Kibana でデータが見えるようになる。楽しい!

thunkify する時に注意すべきたった1つのこと

結論

thunkifyをオブジェクトのメソッドに適用する時は、元のメソッドに代入するか、thisを元のオブジェクトにbindすること。

fs.readFile = thunkify(fs.readFile)
// または
var readFile = thunkify(fs.readFile.bind(fs));
  • ただし、fs.readFilevar readFile = thunkify(fs.readFile.bind(fs));でも動きます。詳細は以下。
  • 元のオブジェクトに影響を与えない、ということを考えると後者の方が良いかも。

原因

thunkify は、Node で generator を使ったプログラムを書く時に便利ですが、1つ気をつけることがあります。

thunkify の実装は以下のようになっています。

function thunkify(fn){
  return function(){
    // 省略
    fn.apply(this, args);  // (1) 

    return function(fn){
      // 省略
    }
  }
};

上記の (1) の部分のthisが曲者で、

var obj = {
  func1: function (cb) {
    console.log('func1');
    this.func2(cb); // (2) 動かない!
  },

  func2: function (cb) {
     console.log('func2');
     cb(null, 'func2');
  }
};

こういうオブジェクトに対して、

var func1 = thunkify(obj.func1);

とやってしまうと、(2) の部分のthisがglobalスコープになってしまって、TypeError: Object #<Object> has no method 'func2'と言われてしまうのです。

ただし、fs.readFileのように内部でthisを利用しないメソッドは動いてしまうので、なかなか気が付きにくい。自分で書いたメソッドの場合は良いけど、ライブラリを利用する場合は、念の為に元のメソッドに代入するか、thisを元のオブジェクトにbindしておいた方が良さそう。

気がついた経緯から解決まで

Twitter で高速解決。

Thanks, @Jack_ さん、@KOBA789 さん。

参考資料

とてもわかりやすいジェネレータの解説

ジェネレータの解説と非同期への適用 - Block Rockin’ Codes

おまけ: エラー再現コード

var thunkify = require('thunkify');

var obj = {
  func1: function (cb) {
    console.log('func1');
    this.func2(cb); // (2) 動かない!
  },

  func2: function (cb) {
     console.log('func2');
     cb(null, 'func2');
  }
};

var func1 = thunkify(obj.func1);
//var func1 = thunkify(obj.func1.bind(obj)); // こっちならOK

function* gen() {
  yield func1();
};

var g = gen();
g.next();

ワンクリックで Chrome のスクリーンショットを Gyazo で共有できる Chrome Extension を作りました

Chrome JavaScript

インストール方法

Chrome ウェブストアで公開中です。

Gyazo Shot

ソース

Github上で開発しています。問題報告やプルリクエストなど大歓迎です。

https://github.com/hakobera/gyazo-shot

開発動機および機能

開発中の画面とかをサクッと共有するのに、Gyazoはとても便利です。 ただし、Web開発では、圧倒的にブラウザの表示内容を共有することが多く、いちいち領域選択して行くのは面倒です。また、領域選択に失敗して綺麗に撮れない場合もあります。

Gyazo Shot で目指したのは、「簡単にWebブラウザのスクリーンショットを共有できる」です。 Gyazo Shot はワンクリックで表示しているタブの表示領域のスクリーンショットGyazo で共有することができます。本家のクライアントアプリと同様、共有後は共有ページがブラウザで開き、クリップボードにURLがコピーされているので、すぐに確認して、BugTrackerやチャットなどで共有することができます。

キャプチャ

プライベート Gyazo サーバにも対応

仕事などセキュリティ上の理由で、プライベート Gyazo サーバを使っている人も多いと思います。Gyazo Shot では設定画面でサーバのURLを変更できるので、こうした用途にも対応できます。

設定画面

ユーザIDも指定できる

基本的にはユーザID無しで利用できるけど、消したい、とか、一覧を見たいとかある場合は、本家の Gyazo アプリをインストールして、一度使うと、以下のコマンドで取り出せるので、設定画面で設定してください。

cat ~/Library/Gyazo/id

ただし、Mac OS限定。WindowsとLinuxは後で調べます。

Gyazo Gif は?

APIの仕様が公開されていない(と思う)ので、対応していません。

作ってみた感想

年末で少しだけ時間ができたので、カッとなってやった。半日くらいで、思っていたより簡単にできた。特に、JavaScript で簡単にバイナリデータ(TypedArray, Blob, FormDataなど)が扱えるようなっていて、ちょっと感動した。興味がある人は background.js あたりを見ると良いと思う。

https://github.com/hakobera/gyazo-shot/blob/master/crx/scripts/background.js

Android の DatePickerDialog で「年月」のみ選択させる方法

Android

こんな Fragment を作れば良いと思う。

import android.app.DatePickerDialog;
import android.app.Dialog;
import android.content.Context;
import android.content.res.Resources;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.view.View;
import android.view.Window;
import android.widget.DatePicker;

import java.text.SimpleDateFormat;
import java.util.Calendar;

public class YearMonthPickerDialogFragment extends DialogFragment {

    private int year;
    private int month;
    private DatePickerDialog.OnDateSetListener onDateSetListener;

    public YearMonthPickerDialogFragment(int year, int month, DatePickerDialog.OnDateSetListener onDateSetListener) {
        this.year = year;
        this.month = month;
        this.onDateSetListener = onDateSetListener;
    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        YearMonthPickerDialog dialog = new YearMonthPickerDialog(getActivity(), onDateSetListener, year, month, 1);
        dialog.requestWindowFeature(Window.FEATURE_NO_TITLE);

        DatePicker datePicker = dialog.getDatePicker();
        int dayId = Resources.getSystem().getIdentifier("day", "id", "android");
        datePicker.findViewById(dayId).setVisibility(View.GONE);

        dialog.onDateChanged(datePicker, monthDescriptor.getYear(), monthDescriptor.getMonth(), 1);

        return dialog;
    }

    static class YearMonthPickerDialog extends DatePickerDialog {

        public YearMonthPickerDialog(Context context, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
            super(context, callBack, year, monthOfYear, dayOfMonth);
        }

        public YearMonthPickerDialog(Context context, int theme, OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
            super(context, theme, callBack, year, monthOfYear, dayOfMonth);
        }

        @Override
        public void onDateChanged(DatePicker view, int year, int month, int day) {
            Calendar cal = Calendar.getInstance();
            cal.set(year, month, day);
            String title = new SimpleDateFormat("yyyy-M").format(cal.getTime());
            setTitle(title);
        }
    }
}

参考

http://d.hatena.ne.jp/koko_u/20120225/1330187058

http://www.entrycoder.com/2013/05/how-to-show-only-month-and-year-fields.html

Github Issues を利用したリリースマネージャのお仕事

最近、Quipper という会社で「リリースマネージャ」という名前のお仕事をしています。開発以外の仕事は久しぶりだったので大変でしたが、最終的にそれなり上手く行った方法を振り返りとしてブログに書いておくことにします。

経緯

自分のチームとは別のチームが開発しているサービスのリリースが迫っている中、それまで開発者の1人がリリース管理っぽいことをやっていたのですが、さすがに開発と管理の二足のわらじが辛くなってきたとのことで、急遽サポート的に自分が「リリースマネージャ」という役割りで参加することになりました。

コンセプト

コンセプトは「使用するツールを増やさない」です。 管理のために新しいツールを増やすと、その使い方を教えるなど新たなタスクが発生してしまいます。タスクを減らすためにタスクが増えるなんてナンセンスです。

ということでQuipper では普段の開発に Github を利用しているので、自然と Github Issues を利用することにしました。 (正確には既に Github Issues が使われていたので、それを整理する方向で調整しました)

Quipper の開発スタイルとか開発ワークフローについては、既に同僚の方は書いているので、そちらを参考にしてください。

ルーク、 MongoLab を使え! - @kyanny's blog

非開発者もGitHub Flowに巻き込んでみんなハッピーになった話 - Masatomo Nakano Blog

Github Issues の使い方

ポイントはマイルストーンとラベルの使い方です。

マイルストーン

Issue は必ずマイルストーンに紐付けます。 マイルストーンに紐付けるという作業をすることで、Issue が放置されるのを防ぎます。 後でやるかもしれないがリリースには含めない、という Issue は「チェックはしたけど、意図的に先送りした」という印として、「nice to have」という名前のマイルストーンに紐付けておきます。

マイルストーンは絶対なので、原則リリース日はずらさず、消化しきれなかった Issue はマイルストーンを移動させます。

ラベル

ラベルは主に以下を利用します。

from-customer クライアントからの問い合わせをトリガーに作成された Issue。この時点ではバグかどうかはわからないが、取りこぼさないようにとりあえず Issue として作成する
bug 社内で気が付いた Issue、もしくは from-customer の Issue がバグだった場合につける
critical bugの中でも特に緊急性の高い Issue につける
confirm-spec 仕様確認が必要な Issue につける
final-check 修正が完了し、ビジネス側、クラアントに最終チェックして欲しい Issue につける

実際はこれに加えてプロジェクト固有のラベル(例えば新機能開発なら、機能名とか)と併用して運用しました。

リリースマネージャのお仕事

ここまで準備が整えば、リリースマネージャのお仕事は次の手順を繰り返すだけの簡単なお仕事です。

やること

朝来て、帰るまでに以下の順番で作業をします。順序は日によって多少前後してもOKです。

  1. 朝会での進捗確認と問題点やリリーススケジュールの共有する
  2. マイルストーンに紐付けられていない Issue をマイルストーンに紐付ける
  3. 直近のマイルストーンを眺めて、担当者のいない Issue に担当者をアサインする
  4. critical ラベルの付いた Issue の進行状況を開発者に確認。必要に応じてタスクの前後関係をリソース調整する
  5. from-customer ラベルの付いた Issue の整理。とりあえず手元で再現するかどうかを確かめ、再現する場合は開発者に、しない場合はクライアントに確認する
  6. confirm-spec ラベルの付いた Issue の仕様をビジネス側の人と調整し、クライアントと開発者を含めて仕様を決定する
  7. final-check ラベルの付いた Issue の確認状況をビジネス側、クライアント側の人に確認する
  8. ドサ回り(チーム内をグルグルまわって、個別 Issue の相談をしたり、状況に応じてラベルの付け直しをしたりする)
  9. マイルストーンの Issue の数とスケジュールを見て、終わりそうにない Issue はマイルストーンを移動し、クライアント調整が必要なものは調整する
  10. 夕会での進捗確認と問題点やリリーススケジュールの共有する
  11. Issue は順次、増えていく可能性があるので、1日の最後に、もう一度 「マイルストーンに紐付けられていない Issue をマイルストーンに紐付ける」をして帰ります

リリースマネージャがやってはいけないこと

リリースマネージャはリリース対象サービスの開発をやってはいけません。 これは完全な失敗談ですが、すぐ直せそうだから自分でやろうとした結果、以下の問題が発生しました。

  • 開発環境のセットアップに時間を取られた
  • 開発フレームワークの勉強に時間を取られた
  • どうしても目の前の問題に目が行って、リリース全体を考えられなくなった
  • 結果、リリース管理作業がうまく回らなくなった

人間は基本シングルコア、シングルタスクです。開発したい欲求はグッとこらえましょう。 特に開発者がリリースマネージャの場合に陥りやすいので注意。

まとめ

Github があって本当に良かったです。