2012-06-28

CORS に対応する nginx 設定

CORS (Cross-Origin Resource Sharing) について以前調べました。

ブラウザは、実際に GET や POST する前に、プリフライトリクエストというリクエストを発行します。通常これは OPTIONS メソッドです。で、このときのレスポンスヘッダをみて、ブラウザは呼び出す/呼び出さないを決定します。
http://torufurukawa.blogspot.jp/2012/05/http-access-control.html

これを実現するのに nginx (1.2.1) の設定で困ったのでメモしておきます。ちなみに、このウェブサイトは Basic 認証をかけます。

http {
     #...
     server {
          listen 8080;
          server_name localhost;
          location / {
               root html;
               index index.html index.htm;

               # GET, POST 用のヘッダ設定
               add_header Access-Control-Allow-Origin *;
               add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
               add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
               add_header Access-Control-Allow-Credentials true;
               # Basic 認証設定
               auth_basic "xaxtuxo";
               auth_basic_user_file  "/usr/local/etc/nginx/.htpasswd";

               # OPTIONS 用の設定。ヘッダの指定をここにも書かないといけない。
               if ($request_method = OPTIONS ) {
                    add_header Access-Control-Allow-Origin *;
                    add_header Access-Control-Allow-Methods "POST, GET, OPTIONS";
                    add_header Access-Control-Allow-Headers "Origin, Authorization, Accept";
                    add_header Access-Control-Allow-Credentials true;
                    add_header Content-Length 0;
                    add_header Content-Type text/plain;
                    return 200;
               }
          }
     }
}

OPTIONS メソッドの設定を書いていなくて、最初、動きませんでした。また、 if ブロックの中にも、add_header をいちいち書く必要があります。

参考

2012-06-27

システム発注側にとって要件は自明ではない


ズボンの股のあたりに穴があいて久しいので、服を買おうと街に繰り出しました。が、もうどうしていいか分からない。どんな色がいいか、どんなスタイルがいいかとか、全くわからない。今年の新色とか、デザインとか言われても、判断のしようがないんですよね。うっかり高そうな店に入ると、すぐに店員が話しかけてきて、要らないものを紹介してきますし。

顧客は自分が何が欲しいか分かっていない。顧客が自分で何が欲しいか分かっていると期待するのはやめることだ。(ジョエル・スポルスキー「ジョエル・オン・ソフトウェア」)

「何が欲しいのかは分からないが、これではないことは分かる」みたいな言い回しをどこかで聞いたのですが、思い出せません。とにかく発注側が要件が分かっている、と思ってはいけません。おそらく分かっていません。開発者自信が自分で使うライブラリやフレームワークを作る、みたいな状況でないかぎり、おそらく分かっていない、という前提くらいがちょうどいいと思います。

もちろん受注側はプロなので、まともな会社なら、ちゃんと考えてやってくれるでしょう。きっと素晴らしい提案が出てくるはずです。それでも、なんというか、自分たちが最終的に何を依頼しているのか、っていうのは、把握しておきたいなあ、と思う次第です。

ぜんぜんまとまってないし、以前にも似たことを書いたのですが、買い物をしようとして痛感したもので、もういちど書いておくことにします。

2012-06-14

運用の継続的改善

ウェブサービスをリリースしたら、当然その運用が必要です。しかし、これがよく分かっていません。だいたい当初の想定にはモレがあって、後で困ります。経験と知識と思考の不足がなせる技です。

だからと言ってほったらかすわけにいかないので、一時期、毎日朝の1時間は運用の改善に当てていました。Skype やメールを見る前に、なにか改善する。改善は、1時間でなんらかの形になることにしていました。ちょっとしたスクリプトを書くこともありましたし、ダッシュボードを作ったりもしました。運用に便利な機能を、組み込んだりもしました。

また、同じようなことを最近始めました。いまでも運用は苦手です。ですが、こうやって毎日ちょっとずつやっていくうちに、じわじわ改善されていくのは嬉しいものです。毎日1%ずつ効率化していくと、1ヶ月たてば20%の効率化になります。

自社サービスの場合には、そういう時間の使い方ができるので、最初は苦しいですが、地味に続けていくと、ある時「おー、楽になったなぁ」となります。

最初から全部できればいいんですが、残念ながらできていないので、銀の弾丸を探さずに、ちまちまやってます。

2012-06-13

Things beta を使い始めました

TODO管理アプリの Things を使っています。Mac、iPhone、iPad にそれぞれアプリがあって、Omni Focus ほど重量級ではないけれど、GTD でいうところのプロジェクトまで管理できます。

最大の難点は、デバイス間での同期には互いにローカルのネットワークで見えていないといけない、ということです。クラウドで同期するベータ版が公開されていたのですが、この度、βのままながらも、これまでのデータをβアプリに移行する機能が公式にサポートされました。

というわけで beta の使用を開始。勝手に同期してくれるのは、ほんといいですね。

2012-06-12

boto で S3 からダウンロード

Amazon S3 に保存されたデータをダウンロードする、という作業を自動化することにしました。認証周りを自分でやるの面倒だったので boto を使うことにしました。

$ python
>>> from boto.s3.connection import S3Connection
>>> conn = S3Connection('<aws access key>', '<aws secret key>')
>>> conn.get_bucket('wozozo')
<Bucket: wozozo>
>>> from boto.s3.key import Key
>>> k = Key(b)
>>> k.key = 'path/to/resource'
>>> fout = open('filename', 'w')
>>> k.get_file(fout)

簡単すぎる。

2012-06-09

私が受け入れテストを自動化しない4つの理由

なんの自慢にもならないどころか、完全に自戒エントリです。まず、そもそも受け入れテストを自動化する試みは有効だという仮説を持っています。

言葉の定義が曖昧で申し訳ないですが、V字モデルを考えた時に右上のほうにあるテストのことを想定しています。ですが、あまり厳密にシステムテストとの違いとか、パフォーマンスや負荷はとかを区別していません。「右上のほう」くらいを考えます。

要求分析 -------> 受け入れテスト
 基本設計 -----> システムテスト
  機能設計 ---> 結合テスト
   詳細設計 -> 単体テスト
       コーディング

反復的に、スパイラルになど、とにかく少しずつソフトウェアを開発していくのが、バグ発生のリスクが少ない印象があります。仕様変更にも耐えやすい。そうすると、受け入れテストが自動化されていると、容易にテストでき、要求とことなる状態になったことがすぐわかります。あるいは、要求との差異の大きさが分かります。なので、受け入れテストは自動化したほうがよいわけです。

が! ほとんどできていません。システムテストでさえ、網羅率が非常に低いのが現状です。これだけ頭でわかっているのに、やっていないのは、なぜなんだろうと考えました。おそらく、レイヤが高いところでのテストは、投資の規模が大きくないと、便益が得られないからだ、という仮説です。

1. 実装に時間がかかる

単体テストなんかは、ほんの数行のコードを書けば、とりあえず何がしかのテストを始められます。自分だけではじめることもできます。一方で、受け入れテストはそもそも手順が多いし、前提条件を作り出すために作業やコーディングが発生します。なのでめんどくさいなぁ、と思ってしまいます。

2. 実装に費用がかかる

単体テストなら開発マシンで実行するところから始められます。受け入れテストは、特にサーバで動かすときには、テスト用の環境を用意する必要です。テスト自体が副作用を生むので、他の作業から隔離する必要があるからです。

3. コミュニケーションに時間がかかる

要求なるものが、要求する側とされる側で、そのまま共有できることは稀です。すくなくとも、私の職場では。何らかの形で文書の変換が必要になります。PowerPoint で「友達がつぶやいたら盛り上がっている絵」みたいなのが書かれていたとして、それを、「N秒以内に友達関係にあるユーザが、システムに対して操作Aをしたことが、画面に表示される」と翻訳しないといけません。逆に、後者だけあっても、受け入れOKを確認する人には通じません。これはけっこう難しくて、いまだに効率良く要件を記述する方法が分かりません。

4. 変更の追跡に時間がかかる

関連して、変更があったときの追跡に時間がかかります。いつのまにか PowerPoint のファイルに「なんとかボタン」がひとつ追加されていたりして、そのボタンを機能させるためには、かなりの影響範囲があったりするわけです。それを追跡して仕様に反映するのだけでも大変なのに、テストとかもうああ、という。

書きだしてみて、ネガティブスパイラルっぷりにびびります。解決策や結論的なものは、残念ながらまだありません。ただ、仕事の大きさに圧倒されて、手を付けられないのであるなぁ、というのが分かったというところです。

2012-06-06

git-daily 的なブランチ方針がしっくりきました


ソースコード管理でのブランチの切り方を模索してきました。いったん落ち着いたので書き残しておきます。

前提条件は以下のとおりです。

  • iPhone アプリがアクセスするウェブAPIを開発する。
  • サーバサイドの開発者はふたり。大阪と東京。
  • 他の案件とかけもち。


行き着いたところ

  • Mercurial
  • git-flow/git-daily に似たブランチ方針
    • 機能追加、開発中機能のバグ修正: default ブランチから、XXX ブランチを切って機能追加/変更し、default へマージする。
    • リリース: default ブランチから、release/XXX ブランチを切って、ステージングでテスト/修正。終わったら、default と master へマージし、master の先頭を本番サーバにデプロイする。
    • リリース済不具合の修正: master ブランチから、hotfix/XXX ブランチを切って、ステージングでテスト/修正。おわったら、default と master へマージ。master からデプロイ。
    • XXX はチケット番号
    • タグづけしない

この方針がしっくりくるまで、紆余曲折がありました。

Phase 1: BP Mercurial Workflow

週末に何をしているか謎な ぁっぉ氏で有名な、ビープラウドで採用されている BP Mercurial Workflow で始まりました。

すべての作業は default ブランチから、チケット番号に対応するブランチを作り、終わったら default ブランチに戻ります。

  • 新機能追加や不具合修正のブランチは、テストが終わったり、リリースする直前に default ブランチへマージする。
  • リリースするときには必要に応じて、default ブランチからリリース用にブランチを作る。確認が終わったら default ブランチにマージする。
  • マスター・リポジトリとリリース・リポジトリを用意する。
  • マスター・リポジトリの default ブランチの先頭がリリース可能な状態になったら、リリース・リポジトリに push する。
  • リリース・リポジトリの default ブランチ先頭は、常にリリース可能である。


リポジトリをふたつ用意する、という運用を、実施したことはありません。最初、このリポジトリの意味を勘違いして、てっきりプロダクション環境にコピーしたリポジトリだと思っていました。

このブランチ戦略のいいところは、とにかくシンプルなことです。ブランチ名はチケットに対応づいているのでユニークですし、個々の作業が完了したら default に戻せばよいのです。

このやり方に馴染めなかったのは、自分がどの環境にデプロイされている機能を変更しているのか分からなくなるからでした。

頻繁なリリース、頻繁なバグフィックスが特徴の開発でした。バグがある状態でリリースすんなっていうのは、ごもっともです。iPhone アプリ側に不具合があったとき、すぐに修正が効かないし、大多数がアップデートするまで時間もかっかります。ですので、サーバサイドでなんとなく頑張るようなこともあります。

また、新機能を鋭意開発中に、別の仕様が飛び込んできたりもします。開発中のコードに手を入れることもあります。default ブランチと、そこから派生したチケットブランチだけでは、ツリーを見た時に「いま本番サーバでデプロイされているコードがどれか? ロールバックするとしたら、どのポイントなのか?」が、私には直感的に分かりませんでした。タグづけとかもしたんですけどね。

そんなわけで、開発中とデプロイ済のコードを、ブランチで分けてあるといいなぁと思いました。

Phase 2: git flow

次に試してみたのが、A successful Git branching model です。これを支援する git-flow という git 拡張があります。

開発用とリリース済用の、ふたつのブランチを使う方針です。

  • 機能追加、開発中の不具合修正: develop ブランチから feature/name-of-feature ブランチを切って作業し、develope ブランチへマージ。
  • リリース: develop ブランチか release/x.y.z ブランチを切って作業し、準備ができたら default と master へマージ。x.y.z とタグをつけて、master からデプロイ。
  • リリース済の不具合修正: master ブランチから、hotfix/x.y.z ブランチを切って作業し、develop と master へマージ。x.y.z とタグをつけて master からデプロイ。


この方針では、master ブランチの先頭が、いまデプロイされているバージョンです。master ブランチのひとつ前のコミットが、その前にデプロイされたバージョンとなります。

develop ブランチは基本的に不安定で、release ブランチでの作業をへて安定し、master へマージする、というものです。これで遠慮無く devepopブランチに変更を反映できますし、master ブランチ(リリース済みのコード)の修正にも影響されずに、リリース用にコードを整理できます。

すばらしい! と思ったわけですが、1日に数回リリースすることもあって、バージョン番号とか面倒なのですね。また、master ブランチの各リビジョンが、基本的に本番サーバでのリビジョンなので、タグとか要らないんじゃないのか、と思ったりもしました。

Phase 3: git daily

@sotarok による git-daily の紹介を聞きました。git-daily はGREE での開発フローと、その支援をするツール。1日に複数回リリースすることだってある、というのが前提。これは、私の置かれている状況と似ています。

  • 重要なのは gitflow というツールではない
  • tag とか切らない
  • リリースブランチ: release/yyyymmdd-hhMM
  • 当時は hotfix と feature は未実装だった。今はhotfix が実装済。


これでかなり、状況に合った方針になりました。けど、ひとつだけ、ほんとひとつだけ馴染まないことがありました。

複数の案件に関わっていて、割り込みが多く hotfix の作業を1日で終わらせられるとは限らない。私があるバグの修正をしている間に、もう1人の開発者が別の機能を実装しおわって、リリースすることもあります。作業開始の日付と、実際にリリースされる時間の順番が一致しない。日付的なもの書かれていると、順番になっていない気になります。「5月7日にリリースした、20120501-1200 ブランチだけどさ」みたいな。気持ちの問題なんですけどね。桁数もおおいし。

前述のプレゼン資料を見てみると、こんなことが書かれています。

会社で使うならその会社での開発スタイルにあわせた Wrapper ツールは必要
gitflow をつかうもよし、git-dailyをつかうもよし、自作するもよし

Phase 4

そんなわけで行き着いたのが、日付の代わりにチケット番号を使えばよいではないか、と。

feature/XXX は作りません。元のブランチから分岐させて、変更し、もとのブランチに戻すというシンプルな作業だからです。SourceTree みたいなツールを使った時、 feasture/XXX みたいなブランチ名にしておくと、まとめてくれるんで便利かも、と最近思い始めています。

hotfix と release のBranch操作が手順が多かったので、hg のラッパスクリプトを書きました。超やっつけ。pelo っていうコマンド名は (ry

# coding: utf8
"""pelo
pelo branch management wrapper commands
"""
from datetime import datetime
from mercurial import commands
HELP_DOC = """COMMAND OPERATION [label]
pelo release start uXXXX .... start new release
pelo release finish ......... finish current release
pelo hotfix start uXXXX ..... start new hotfix
pelo hotfix finish .......... finish current hotfix
"""
def pelo_cmd(ui, repo, cmd, op, label=None, **opts):
if cmd == 'release':
return release(ui, repo, op, label, **opts)
elif cmd == 'hotfix':
return hotfix(ui, repo, op, label, **opts)
else:
_write(ui, HELP_DOC)
def release(ui, repo, op, label, **opts):
prefix = 'release'
start_at = 'default'
return operate_branch(ui, repo, op, label, prefix, start_at, **opts)
def hotfix(ui, repo, op, label, **opts):
prefix = 'hotfix'
start_at = 'master'
return operate_branch(ui, repo, op, label, prefix, start_at, **opts)
def operate_branch(ui, repo, op, label, prefix, start_at, **opts):
if op == 'start':
return start_branch(ui, repo, label, prefix, start_at, **opts)
elif op == 'finish':
return finish_branch(ui, repo, label, prefix)
else:
_write(ui, HELP_DOC)
def start_branch(ui, repo, label, prefix, start_at, **opts):
"""start prefix branch"""
# exit if label is not provided
if not label:
_write(ui,
'abort: You must specify ticket to start %s branch.' % prefix)
return
# exit if not in default branch
if _get_current_branch(repo) != start_at:
_write(ui,
'abort: Run "hg update default" before starting %s branch.' % start_at)
return
# branch <prefix>/<label>
commands.branch(ui, repo, '%s/%s' % (prefix, label))
# commit to mark start
commands.commit(ui, repo, message='started %s/%s' % (prefix, label))
def finish_branch(ui, repo, label, prefix='hotfix', **opts):
"""finish hotfix branch"""
# 変更が残っていたらエラー
status = repo.status()
for l in status:
if len(l) > 0:
_write(ui, 'abort: Your workspace has uncommit content.')
return
# label が指定されていたらエラー
if label:
ui.write('abort: You cannot specify branch name\n')
return
# <prefix> ブランチじゃなかったらエラー
if not _get_current_branch(repo).startswith('%s/' % prefix):
_write(ui,
'abort: you must be in %s branch' % prefix)
return
# determine current branch
current_branch = _get_current_branch(repo)
# merge into default branch
error = _merge(ui, repo, current_branch, 'default')
if error:
return
# merge into master branch
error = _merge(ui, repo, current_branch, 'master')
if error:
return
# notify
_write(ui, '%s was successfully finished.' % current_branch)
def _get_current_branch(repo):
ctx = repo[None]
current_branch = str(ctx.branch())
return current_branch
def _write(ui, message):
ui.status(message + '\n')
def _merge(ui, repo, from_, to):
commands.update(ui, repo, to)
error = commands.merge(ui, repo, from_)
if error:
return True
commands.commit(ui, repo, message='merged %s -> %s' % (from_, to))
cmdtable = {
'pelo': (pelo_cmd, [], HELP_DOC),
}
view raw hgpelo.py hosted with ❤ by GitHub

さいごに

念のために書いておくと、最後の方針が、ユニバーサルにベストな方法だとは思っていません。だいたい職場の開発方針や開発プロセスが変われば、ブランチ方針も変わるかも知れません。 開発スタイルに合わせた、ブランチ方針があると思っています。

「パターンによるソフトウェア構成管理」という本を参考にしました。想定しているツールが Subversion あたりなので、Git や Mercurial などにはバッチリこないこともあります。デスクトップアプリのようなのを想定しているので、メジャーバージョンごとに、リリースブランチがあったりとか。あくまでパターンの紹介なので、抱えている課題と合うものを採用するものだと思います。



2012-06-01

Google App Engine の backends のデプロイ

Google App Engine の backends のデプロイで困りました。
$ appcfg.py update .
が成功するのに、変更した挙動が backends サーバに反映されていないように見えました。そして、実際に反映されていません。
$ appcfg.py backbends . update
のように、backends コマンドに update アクションをつけて実行します。backends.yaml の設定を反映させるだけに使うと思っていたんだけど、コードのデプロイをする役目もあります。

GAEアプリで継続的インテグレーションを試みた雑感


2011年11月の Python 温泉でテストの実践方法を教えてもらう機会がありました。

初めて参加した Python 温泉で、 @voluntas と @aohta に開発プロセスの教えを乞いました。いろいろ教えてもらった中で、実際に手を動かし始めたことを書きます。

その後日談です。

単純だけど動く buildout.cfg を書けるようになったことが、今回のツール知識の中で最大の成果です。

温泉では buildout を使ってテストする側の環境をいっぱつで作れるようになりました。温泉から帰ってから、テストされる側、すなわち、製品側の環境用の buildout.cfg を書きました。

対象となるプロジェクトでは、Google App Enigne を使っていて、本番用と開発用にそれぞれ App Engine アプリを用意していたので、環境絡みの問題がすぐに分かりました。

大したコードではないので自社ライブラリにしなくていいんじゃ、と思ったのです。が、「call_api のテストだけ書いておけば、[simplejson などの] 依存先の使わない機能の不具合を無視できるのだから、自社ライブラリの動作確認だけすればよいでしょ。だから自社ライブラリにしちゃえ」という教えでした。これが一番私にとって重要な考え方でした。

いたく感動していた割に、まったく実践できていません。そして相変わらず車輪の再発明です。素振り、勉強としての車輪の再発明には意味があると思いますが、問題にしているのは避けるべき再発明なのですね。

ウェブAPIを作ることが多いので、製品側のコードも、テスト側のコードも共通化できることは確実にあるのです。なんでやっていないんですかね。なんか面倒そう、と思っているのでしょう。そして、その場で面倒をさけて、あとで面倒になるというわけです。

問題は、Twitter に問題があると、テストが FAILすることです。それは Twitter の問題であって、私が開発しているアプリケーションの問題ではありません。
なので、OAuth が成功した or 失敗したふりをしてくれるモックが必要です。それが次の課題だろうな、と考えています。

これも、ほったらかしです。具体的に何が必要かというと、OAuth Service Provider のインタフェースとモックだけ実装すればいいはずなんですけどね。今後の課題です(放置フラグ)。

何をしようとしているかというと、継続的インテグレーションをしたいのです。サーバサイドのエンジニアが少ないので、できるだけ自動化したいわけです。人間は創造的なことに時間を使うべきだ、と個人的に信じています(それでまあ、例の対談とかの流れになるわけです)。そのためには自動的に環境構築して、テストできるようにする必要があるのですが、ずーっと止まっていました。今回の温泉で、一歩目を踏み出せたのは大きな収穫でした。

デュバルらによる「継続的インテグレーション入門」では、「Integrate ボタンいっぱつで、ビルドできるようにせよ」というのが一貫した主張で、各要素の作りかた/考えかたが書かれています。

まず Jenkins を導入しました。リポジトリに変更があったら、ユニットテストの実行、GAE 開発アプリ空間へのデプロイ、機能テストの実行までをやりました。インスペクションとデータベースのインテグレーションも、ビルドに含むべきであると書かれていますが、そこまでは到達できていません。pep8 を手動では知らせてチェックインしていました。

データベースのインテグレーションが問題です。理屈の上では、古いバージョン用にデータセットが格納されていて、スキーマ(的なもの)が変更されるのであれば、その変更スクリプトを走らせて動かす、ってことになるのだと思います。あと、GAE の場合はインデックスの更新をリクエストする必要があります。

これはテストデータのセットを用意できていることが前提ですね。確かに、用意しないといけません。なんか作業が膨大そうに見えて、怖気づき、躊躇しています。

このプロジェクトは、継続的インテグレーションの実践の場になり、いくつかのことを学びました。まず何よりも、自動的にテストが走るのは非常に楽である、ということ。API呼び出しによる副作用が複雑で(これはこれで問題ではある)ちょっとした仕様変更でも、意外な影響範囲があったりしました。そういうときでも、テストがあるので安心して変更できました。ただし、テストの数がある程度たまるまでは、なかなかきつかったです。

一方で、継続的インテグレーションをぜんぶ実践するのは、大変そうに感じました。じつは大変ではないかかも知れません、一度走り出したら大変ではなくなるのかも知れませんし、実際に大変なのかも知れません。