ちょこっとサーバーを立てたいときのTerraformの構成を考える

この記事は、「Speee Advent Calendar 2016」の5日目です。
4日目は、id:nisshieeより、「Turbolinks再考」です。

今日は、Terraformの構成について再度考えたいと思います。


先日書いたブログで、hashcorpが公開しているTerraformのbest-practiseについて読み解きました。

takanamito.hateblo.jp

ブコメでもいくつかあった「つらそう」という意見
つらさにも種類があるとは思うのですが
僕が実際にベストプラクティスを読んでいて思ったのは

  • モジュールが2階層構造になっており、抽象化されたモジュールを読み解くのがしんどい
  • モジュール単位で変数定義が必要なため、3回変数宣言をしなければならないのがめんどくさい

という2点。

ただこれも、多くのリソースを抽象化して管理したり、ファイルの肥大化を防ぐ意味では有用であろうという結論を出していました。

実際にはオーバースペックだった

いざ使ってみると、仕事である程度の規模のインフラを管理する場合には役立つこのベストプラクティスも
個人で数台のインスタンスを立てて、とりあえずwebサービスが動く構成にするには
少しオーバーだなと感じることが多かったのです。

そもそも、Terraformを使ってAWSのインフラ管理を楽にしたいと思って導入しているのに

  • まずは必要なリソースをmoduleに切り出して
  • 変数定義をmoduleの数だけ書いて
  • terraform getやplanで動作の確認をして

と、ちょこっとインスタンスを立ち上げるだけの作業が非常に大きくなっていき
結果Terraformの準備に時間がかかる本末転倒な結果になってしまいました。

例えばマイクロな構成でEC2インスタンスだけ立てるならブラウザでポチポチやればいいじゃん。とも思ったのですが

運用するサービスが分かれている場合、最低限ネットワークは分離したかったりするので EC2 1台立てるのに必要な VPC, subnet, route table, gateway...etcを考えるとやはりTerraformを使ってコマンド1発で構築したい。

軽量Terraformテンプレートを製作

そこで

  • hashcorpのベストプラクティスほど大きくなく
  • ありがちな構成を整えた

Terraformのテンプレートを作り始めました。

github.com

構成はこのような感じ

├── micro
│   ├── micro.tf
│   └── terraform.tfvars
└── modules
    ├── compute
    │   └── ec2
    │       └── ec2.tf
    └── network
        ├── route_table
        │   └── route_table.tf
        ├── security_group
        │   └── security_group.tf
        ├── subnet
        │   └── subnet.tf
        └── vpc
            └── vpc.tf

このテンプレートで生成可能なのは「80番ポートだけ空いた、インターネットに疎通可能な1台のEC2インスタンス」です。
各リソースを操作可能なIAMユーザーを用意して、EC2用のキーペアだけ作成しておけばコマンド一発で立ち上がります。

読み始める場合は /micro/micro.tfからスタートし、各moduleをのぞいてもらえるとわかりやすいと思います。
※これからいくつも構成のテンプレートを用意しようと思っているので、最小構成の意味でmicroと命名しています。

この構成の利点は

  • 階層が減ることで各moduleの見通しが良くなる
  • 変数定義の回数を減らせる (micro.tfと各moduleの2回)

の2点です。

ベストプラクティスでは存在したcomputeモジュールなどの、1段階リソースを抽象化したモジュールを廃止し、直接リソースを定義したモジュールに変数を渡すことで 構成を1階層減らしました。
これによって構成がシンプルになり、小規模な環境を準備する際のコストが減らせるかと思います。

またベストプラクティスに比べて、Terraform初心者に読んでもらうときにも非常に優しくなっていますね。

反面、この構成だとPRO/STG/DEVのような開発環境に分けてコードを記述することには対応させていないのですが
その場合はmicroディレクトリの下に環境ごとのディレクトリを作って、tfファイルとtfvarsファイルを置けば対応可能かと思います。

この先の展望

このリポジトリは「個人がAWSでWebサービスを始めるときに使えるよくある構成のテンプレート集」化していこうと思います。

仕事をしていても感じるのですが
Webサービスをつくるときに「絶対するであろう作業」が自動化されていないことで、各個人の時間が吸い取られていくので
これを解決することで、より価値提供のための開発に集中できるはずです。

まずはよくある構成を充実させて、そのうちコンテナを使った構成などにも対応できればと思っています。

入社して3年が経ちそうなので振り返り

もうすぐ社会人になって丸3年。
いい機会なのでここまでやってきたことの棚卸しをしてみる。

1年目

同期より2ヶ月ほど早く入社し、いきなり新規サービスの立ち上げに参加させてもらった。
僕は2年目の先輩エンジニアの下について、ユーザーのコンテンツ投稿画面のフロントエンドを中心に開発をしていた。

このときAngularJSみたいなフレームワークの導入もできたのに「JSの勉強も兼ねてjQueryだけでやってみよっか」と大きなフレームワークなしで設計/実装にチャレンジさせてもらえたことにとても感謝している。
JSのオブジェクトや関数について理解を深めるきっかけになった。
この時期にJavaScriptパターンを読みまくっていた。

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

サーバーサイドもPHPJavaを使った基本的なwebサービス開発をしていて
学生時代には意識したことのなかったMySQLの基本的なクエリチューニング(といってもEXPLAIN程度のものだけど)も教わった。
ミドルウェアやインフラを意識した開発は初めてだったので新鮮なことが多くてすごく楽しかった記憶がある。

半年ほど経った9月頃には、チームを移ることになった。
次のチームでは海外向けメディアを扱っていて、僕は今に至るまでこのチームに在籍することになる。

1番最初のチームでは作業のほとんどがアプリケーション実装だったのに対し、今のチームではインフラ関連の作業をやることが多くなった。 少し責任範囲が広がった感じ。

当時、先輩が1人で6サイト運用しながら、さらに2サイトの新規開発をしていて 運用するだけで手一杯、アプリケーションにバグも埋もれていたりコードのコピペもあったりと直し放題の環境だったので 1チーム目のときに教わったことをそのままこのチームにも展開していった。

あげだすとキリがないけど
運用面ではJenkinsによる自動化やバッチ実行管理、リリースノートを含むドキュメントの整備、backlogで管理していたチケットの整理と、とにかく散らかってるものを全て整理していった。
アプリケーションに関してはまずコピペ脱却のために各種機能を役割ごとにクラスやモジュールに切り出していく作業をひたすらやっていた。またその過程で見つけた誰も知らないバグをひたすら潰してた。

チームメンバーが兼務だらけで、とにかく散らかっていて整理されてない状態だったことや
プロダクトマネージャーが(幸運なことに)裁量を与えて自由に動かせてくれるタイプの人だったことにより
開発/運用/その他もろもろのいろんな種類のタスクを自分で見つけ、優先順位をつけてつぶしていくコツをこの時期にかなり学べたと思う。

またミドルウェアを触る作業が増え、先輩からインフラ系作業を教わることができた。
MySQLのチューニングやLinuxカーネルチューニング、nginxの設定について勉強し始めたのがこの時期だった。
それ以来nginxが好きで、社内のQiita::Teamにnginxの設定に関する記事をよく書いていた。

また、この時期に勉強したことがISUCONでめちゃくちゃ役立っていて、教えてくれた先輩にはめちゃくちゃ感謝している。 takanamito.hateblo.jp

そういえば仕事をする上で相談することが苦手だった僕が下手なりに相談できるようになったのは、この時期にミドルウェアに関する相談をこの先輩が嫌がらず聞いてくれたおかげな気がする。

年が明けるころには大きなインフラ移設も行っていて
さくらVPSで動かしているメディアが成長していき、スケーラビリティと地理的な条件からAWSに移行することになった。

設計からサービス無停止での移行の実作業を1人でやることになった。
この作業はかなりきつくて、そもそもインフラはどうやって設計するものなのか、どう考えて組み立てていくのかなどなど
何がわからないのかもわからない状態だったと思う。
とにかく理解を深めるために、既存のインフラをAWS上で再現するために必要なリソースをリストアップして図に起こしていく作業をやっていた。

この時期にゼロからWebサービスを作る時に必要な

  • 自分の考える理想的な構成を描く
  • 現実的な条件をもとに松竹梅程度の選択肢に落とす
  • 選択肢のグラデーションの中から最適解を探す

みたいな考え方を学べた。

移行が終わってからは少し落ち着いたので、当時流行り始めていたリアクティブプログラミングの流れに乗ってriot.jsを触ってみたり
レコメンドエンジンに触れたりしていた。 takanamito.hateblo.jp

2年目

2年目はチームの開発サイドリーダーとなって、自分がメイン開発者としてサービス開発を進めることが多かった。 また会社がPHP, JavaからRubyScalaへ移行することになり、Railsでのアプリケーション開発の先陣を切ることになる。

全社的にテストの重要性を意識しはじめ、CircleCIの導入を始めたりとやっと少しナウみのある開発ができ始めた。
同時にmocha, chaiを使ったJSのテストも導入したりと、作ることに対しての余裕ができて
普段の開発に効果がありそうな、自分なりのプラスを取り入れることができるようになった気がする。

それまでどのチームもテストゼロ、誰が何を作ってるのかもわからないような状態だったので
この時の開発体制の大改革はとてもありがたかった。

チームの人数が増え始めたことで、イテレーション運用をより力を入れて回すことになり 開発スケジュール管理やディレクションに関して大きな時間を割いて関わることになる。

チームのエンジニアリーダーである自分が進捗管理をしつつ、ディレクションも手伝いつつ、自分でもアプリケーションを書いていたので
特にリリース間際の佳境に差し掛かった時期には仕事をする時間が増え、見事に腰を破壊された。

年末から年明けにかけてはFuelPHP -> Rails移行のプロジェクトもあり 数十万URLの301リダイレクト処理にビビりながらも特に大きな事故なく終えることができた。
このあたりから社内勉強会で話す機会を少し増やしたり、外の勉強会でLTをするようにした。 takanamito.hateblo.jp

3年目

2年目につくったサービスのトラフィックを伸ばすための改善、運用コストを下げるための施策を中心にやっている。

インフラ移設時に手動でコンソール画面をポチポチして立てていたAWSのインフラを TerraformとItamaeの導入を進めて自動化したり (Docker導入もやりたい) takanamito.hateblo.jp

A/Bテストをリリース速度重視で行うために
今まではmunin, nagiosでやっていた監視に加えて、Datadog, Sentryを使った監視や可視化の仕組みを導入したり

A/Bテストのためのログ解析要望も高まり、fluentdとelasticsearchを使ったログ蓄積基盤を作ってサービス改善のためのログを取り始めたり
その過程でけっこうメジャーなfluentdのプラグインやsunspotといったでかいgemにPR送ったりするOSSっぽいことも始めた。

効率化という文脈では、対象を自チームだけに絞らず
趣味で社内Gyazoサーバーを立てたり、サーバーレスHLS動画配信の仕組みを作ったりして
業務に関係ないことで新しい技術には触れるようにしている。(この取り組みが自分のチームに還元できるとうれしい) takanamito.hateblo.jp

所感

3年間webアプリケーション開発に関わってみて
新しい技術を学ぶのももちろんだけど、今ある環境や生活に非効率なものがないか見渡して
それを解決することが合っている気がしてきた。

  • nginx
  • S3, Lambdaなどのいわゆるサーバーレス系サービス
  • HLS

あたりの技術が自分の中ではしばらくホットで、全部に共通しているのが
「自分の実現したいことが楽に実現できる」ということ。

すごく頑張っていいコードが書けたときもうれしいけど、やりたいことに対して「◯◯使えば一瞬で終わるしみんなが楽だ」って気付いて実現できたときの喜びのほうが大きい。

チーム内でも、もっと効率化できるとこあるなぁと思いながらも
ここ1年くらいは開発以外の業務量が増えていたという事実もあり
最近はそういった仕事の量を相談して減らして、開発に集中させてもらっている。

インフラ作業が多く、特にサービスの特徴的にもフロントエンドの重要性が低いので
しばらく触れていないJSを中心にSPAにも触れていけると楽しくなりそう。

HLSでサーバーレス動画ストリーミング配信をする

家にある動画を再生するのに、いちいちデスクトップパソコンを立ち上げたり、NASっぽいものを構築するのもめんどくさくて
URLを生成してiPhoneとかのブラウザでシュッと見られるようにしたかった。

長い動画を再生するためにストリーミング配信したくて
手持ちのMaciOS系デバイスで再生するのだと、どうやらHLSが向いてそうだという結論に落ち着いた。

自宅にサーバーを置きたくないので (でかいし、うるさいしで邪魔)
クラウド環境でいろいろやってみることにした。

結果できたのが以下のようなもの。

f:id:takanamito:20161022175237p:plain:w350

動画視聴までのワークフロー

  • 手元にある動画ファイルをS3にアップロード
  • S3にファイルが置かれたのを検知してLambdaを起動
  • LambdaでElasticTranscoderにエンコードジョブを作成してアップした動画をエンコード
  • エンコードが完了したらファイルをS3に出力
  • S3のファイルのURLをSafariで開いて再生

必要な作業は以下の通り

  • ElasticTranscoder側の設定 (Pipeline, Preset)
  • Lambda Functionの作成

S3のPUTイベントを検知してElasticTranscoderにジョブを投げるLambda Functionを作ります。
基本的には以下の記事を参考に進めました。

qiita.com

ElasticTranscoderの設定

Presetの作成

動画をエンコードする際の設定を決めます。
今回はさほど画質にこだわっていなかったのでデフォルトで用意されている設定を使うことにしました。
1M ~ 1.5MくらいのPresetがHD周辺の解像度でエンコードしてくれたので、そのまま採用しています。
この際、IDというカラムに表示されているPreset IDをメモしておきます。

f:id:takanamito:20161022181254p:plain

Pipelineの作成

ElasticTranscoderの作業フローを設定していきます。
S3上のどのBucketにある動画を、どのBucketにエンコードして出力するのかを決めます。

基本的には2つのBucketを用意し、仮にA, BというBucketがあるとすると

Aに動画ファイルをアップロード -> Lamba Functionがジョブを投げる -> ElasticTranscoderがエンコード -> Bにエンコード結果のファイルを保存

という流れになります。

こちらも生成後のPipeline IDをメモしておきましょう。

Lambda Functionの作成

先程のQiitaの記事を参考に、Lambda Functionを作っていきます。

メモしていた2つのIDを変数に埋めてください。

var aws = require('aws-sdk');
var s3 = new aws.S3({apiVersion: '2006-03-01'});
var ets = new aws.ElasticTranscoder({apiVersion: '2012-09-25', region: 'ap-northeast-1'});

exports.handler = function(event, context) {
   console.log('Received event:');
   console.log(JSON.stringify(event, null, '  '));
   
   var bucket = event.Records[0].s3.bucket.name;
   var key = event.Records[0].s3.object.key;
   
   var pipelineId = 'xxxxxxx';
   var presetId = 'xxxxxx';
  
   var fileName = key.split('.')[0];
   
   ets.createJob({
     PipelineId: pipelineId,
     OutputKeyPrefix: fileName + '/',
     Input: {
       Key: key,
       FrameRate: 'auto',
       Resolution: 'auto',
       AspectRatio: 'auto',
       Interlaced: 'auto',
       Container: 'auto',
     },
     Outputs: [
       {
         Key: 'ts-720p',
         PresetId: presetId,
         Rotate: 'auto',
         SegmentDuration: "10"
       },
     ],
     Playlists: [
       {
         Format: 'HLSv3',
         Name: 'master',
         OutputKeys: [
           'ts-720p',
         ],
       },
     ],
   }, function(error, data) {
        if(error) {
            console.log(error);
            context.done('error',error);
        } else {
            console.log('Job submitted');
            context.done(null,'');
        }
    });
};

またTriggersも設定します。
今回はS3上の.mp4というSuffixのファイルのObjectCreatedByPutObjectCreatedByCompleteMultipartUploadイベントを検知して起動するように設定しています。

これでS3に動画がアップされると自動でエンコードが開始されるようになります。

動画の再生

エンコードが完了すると.m3u8ファイルが2種類と.tsファイルが大量にbucket上に生成されます。

f:id:takanamito:20161022182541p:plain

.m3u8ファイルは動画のプレイリストとしてのテキストファイルです。
HLSはAppleが仕様を作っているプロトコルということもあり、Safariであればこの.m3u8ファイルのURLを開くだけで動画配信が始まります。

S3のPolicyを編集してIP絞るなど
インターネットに広く公開しないような設定をお忘れなく。

所感

ほぼプログラムを書かずにストリーミング配信を実現することができるので非常に楽。

ただElasticTranscoderの料金がバツグンに高いので、気になる方は手元のマシンでffmpeg使うなりしてHLS形式にするところまでやってS3に上げるのがよさそう。

HLSがHTTP/1.1の上で動作するため考えることも少なく、基本的にS3に動画ファイルとプレイリストを置いて静的に配信できれば
あとはプレーヤー側がうまくつなげて再生してくれるため、とても楽にストリーミング再生が実現できます。

またRTMPなどのプロトコルと違い、配信用サーバーを置かずにすんだり
CDNを使うことで容易に配信負荷分散ができるので、そのうち仕事で使う場面もでてきそう。

実際こないだのAbemaTVのカンファレンスでも現場のおもしろい話が聞けたので今後もHLS注目していこうと思います。

AbemaTV Developer Conference 2016

iPhone7をお風呂に落として水没させるも、乾燥剤のシリカゲルで復活した ※iPhone12もいけるはず

f:id:takanamito:20161017200027j:plain:w350

お風呂に浸かりながらiPhone7触ってたら、見事に浴槽に落としました。

浴槽のふちに乾いたタオルを敷いて、iPhoneをその上に置いていたのですが
ふとした拍子に腕が当たってしまい「ボチャッ」という音で気がつく。

慌てて救出して(約1秒) 起動すると、さすが防水端末。余裕で起動したのでひと安心。

15~20分ほどお風呂でiPhoneを操作していて、のぼせてもいけないのでそろそろお風呂からあがることに。

しかし様子がおかしい

着替えてから再度iPhoneを触ると何かおかしい。
具体的には以下のような症状

  • 液晶上部にシミ
  • 電源、ホームボタン接触不良。勝手に押されたり、押しても反応しなかったり
  • レンズに水滴侵入
  • SIMスロット内部に水滴

先日話題になった記事
iPhone 7 Plusを、シャワー中に使ったり水ですすいだりしてたら、水没判定で有償交換となってしまった話 – mono-gadget – Medium

読んだばかりだったので一気に血の気が引く。

水没判定を食らうとアウトとのことだったので、どうやったら水没判定アウトなのか調べる。

f:id:takanamito:20161018182944p:plain:w350

iPhone や iPod が水濡れにより損傷した場合は保証対象外 - Apple サポート

僕の場合はSIMスロット内部に水滴がついていたので、半分あきらめてましたが
もちろんシールも赤で、完全にアウトでした。

修理費用も3万円を超えるとのことでかなり絶望。

シリカゲルを試す

スマートフォンが水没した場合、電源OFF状態でシリカゲルとともに密封作戦は何度か聞いたことがあったので
Amazonシリカゲルを買って試してみることに。

あわせ買い不要で、オーソドックスなやつを探しました。
お急ぎ便ですぐ届いてよかった。ありがとうAmazonプライム

復活ツールと称した謎セットも売られていて驚く

手頃なサイズのタッパーを用意して、シリカゲルを敷き詰め
その上に電源を落としてSIMスロットを抜いたiPhone7を寝かせます。
※SIMスロットを抜いたのは、端末内部の通気性をより増すためです。効くのかわかりません。

f:id:takanamito:20161017200203j:plain:w350

さらに上からシリカゲルをかぶせます。

f:id:takanamito:20161017200225j:plain:w350

この状態で、蓋を閉め密封して一晩放置。

翌朝

タッパーから取り出し起動してみると、ほぼ全ての症状が改善していました。
よかったよかった。

所感

お風呂で使うやつが悪いけど、もうちょい頑張って水を防いでくれると助かります。

あとシリカゲル常備はマジでしといたほうがいいと思った。
僕の場合は時間経過とともに症状が重くなっていったので
水没 → 電源OFF → シリカゲルの対応スピードで被害範囲がけっこう変わってきそう。

Terraformベストプラクティス解釈

最近、自分のチームでTerraformを使ってAWSオーケストレーションをやろうという話になった。

サーバー台数も10~20台程度、今まではAWSコンソールをポチポチしてインスタンスを用意していたが
サービス規模の拡大に伴って、手動オペレーションによるミスを防いだり、インフラ説明コストなどを省くこと、インフラ整理の意味合いで導入している最中。

Terraformに触るのは初めてだったので、ドキュメントを読みつつ基本的な機能について勉強しつつ
どのようなファイル構成で管理をするのがいいのか、経験者の同僚にアドバイスをもらいにいったところ
Hashcorpが公開しているベストプラクティスのリポジトリを教えてくれた。

github.com

ところがこの構成、けっこう複雑で初心者がいきなり読み始めるのは辛かったので
復習も兼ねてベストプラクティスを読み解いていく。

理解するために必要な要素

  • AWSの基本的な知識
  • 基本的なTerraformの操作
  • tfvarsファイルを使った変数管理
  • module機能

おおむねこの4つを理解すれば、その知識を使って読み解くことができるはず。
最初の2つは今回は解説を省きます。

tfvarsファイルを使った変数管理

Terraformではtfファイル内に変数を記述することができますが、変数に値を入力するには

  • tfファイル内に記述
  • 実行時にオプションで指定
  • 環境変数に埋めておく
  • tfvarsファイルに記述

の4種類の方法があります。

Input Variables - Terraform by HashiCorp

ベストプラクティスではtfvarsの手法が採用されており
以下のようなファイルが存在します。

best-practices/terraform.tfvars at master · hashicorp/best-practices

ここで定義した変数はtfファイル内で参照することができます。
ベストプラクティスでは環境ごとに利用する変数を全て1つのtfvarsファイルで管理しています。

module機能

Terraformにおけるmoduleは各resourceを抽象化するためのものです。

例えばベストプラクティスの中では

AWSのコンピューティングリソース(要するにEC2)群を管理する compute module
HAProxyが動くであろうインスタンスのresourceを抽象化した haproxy moduleが存在します。

ベストプラクティスでは、上に示したように

  • 各resource自体の抽象化 (haproxy module)
  • AWSのリソース群を示す抽象化 (compute module)

の2段階の抽象化が行われています。

さらにmoduleには、自分自身の変数をmodule外から出力するための outputという構文も用意されており
これを使うことで、例えばTerraformで作成したVPCのidをmoduleの外からも参照することが可能です。
https://github.com/hashicorp/best-practices/blob/master/terraform/modules/aws/network/vpc/vpc.tf#L17

ベストプラクティスでは、このmodule機能を使って
各resourceを抽象化、そして作成したresourceのidなど動的な情報をoutputすることで
module間の値の受け渡しを実現しています。

具体的にはEC2インスタンスを立ち上げる際に
先にネットワークを構築し、VPC, subnetなどのidをoutputしてEC2の設定に利用するなどの使い方をしています。

ベストプラクティスの構成

ここまでで、必要な知識は揃ったので
改めてベストプラクティスの構成を見ていきます。

module/aws
├── compute
│   ├── compute.tf
│   └── haproxy
│          ├── haproxy.sh.tpl
│          └── haproxy.tf
└── network
    ├── bastion
    │   └── bastion.tf
    └── vpc
     └── vpc.tf

providers/aws
├── README.md
├── global
│   ├── global.tf
│   └── terraform.tfvars
├── us_east_1_prod
│   ├── terraform.tfvars
│   └── us_east_1_prod.tf
└── us_east_1_staging
    ├── terraform.tfvars
    └── us_east_1_staging.tf

まず、1番上の階層には moduleとprovidersの2つのディレクトリが存在します。

前者はそのままmoduleを、後者は環境ごとのtfvarsやリソース群のmoduleを管理するtfファイルを含みます。

どんなリソースを作成しているか追うためにproduction配下のtfファイルを見ていきます。

production.tfでは、利用するリソース群のmoduleを定義しています。 以下はEC2リソース群を管理するcompute moduleです。

# production.tf
module "compute" {
  source = "../../../modules/aws/compute"

  name               = "${var.name}"
  region             = "${var.region}"
  vpc_id             = "${module.network.vpc_id}"
  vpc_cidr           = "${var.vpc_cidr}"
  key_name           = "${aws_key_pair.site_key.key_name}"
  azs                = "${var.azs}"
  private_subnet_ids = "${module.network.private_subnet_ids}"
  public_subnet_ids  = "${module.network.public_subnet_ids}"
  ~以下略~
}

compute moduleで読み込んでいるcompute.tf内には
各resourceを抽象化したmoduleが定義されています。
このmoduleで渡した変数が最終的にresourceとして使用されます。

# compute.tf
module "haproxy" {
  source = "./haproxy"

  name               = "${var.name}-haproxy"
  vpc_id             = "${var.vpc_id}"
  vpc_cidr           = "${var.vpc_cidr}"
  key_name           = "${var.key_name}"
  subnet_ids         = "${var.public_subnet_ids}"
  atlas_username     = "${var.atlas_username}"
  atlas_environment  = "${var.atlas_environment}"
  atlas_token        = "${var.atlas_token}"
  amis               = "${var.haproxy_amis}"
  nodes              = "${var.haproxy_node_count}"
  instance_type      = "${var.haproxy_instance_type}"
  sub_domain         = "${var.sub_domain}"
  route_zone_id      = "${var.route_zone_id}"
}

順番に見ていったように

ファイル 記述内容
production.tf どういったリソース群を利用しているのか
compute.tf どういうresourceを利用しようとしているのか
haproxy.tf どういう設定でリソースを利用しようとしているのか

が、それぞれわかるようになっており
大きく3階層の構造で ファイル構成が成り立っています。

ここにEC2のインスタンスを追加したければ最下層のmoduleを
新しいリソース群を追加したければ、中段以降のmoduleを追加していくような形です。

所感

ひと通り触ってみましたが

  • 階層の深さ
  • 変数定義の非DRYな感じ

は最後まで辛かったです。

階層に関しては、初見時にコードを追いかけていくのが大変だったので
中段のリソース群をまとめているcompute.tfなどのファイルを減らせないかと考えたのですが
そうするとproduction.tfなどの大元のファイルに各moduleを定義することになり、行数が肥大化してしまい
その環境にどういった種類のリソースが存在するのか、見通しが悪くなってしまうので、今の構成を受け入れるしかないという結論に落ち着きました。

2つ目の変数定義なのですが、各tfファイルに何度も variable hoge { } と書かされるのが本当にしんどいです。

今回は tfvars -> environment.tf -> resource_group.tf -> resource.tf といった具合に計3階層に渡って変数の値を受け渡す必要があったため
同じ変数の定義を3回書いています。

めちゃくちゃ面倒です。
この辺はTerraform側の対応を待つしかないようです。

しかし、それら不便な点を差し引いても Terraformは非常に便利な点が多いため、これからも積極的に利用していこうと思います。

ISUCON6 3万点台で予選敗退

会社の同僚と一緒に「尿酸値E」としてISUCON6出ました。
Ruby実装を選んで、最終的に「32660」というスコアで予選敗退。今年こそは本戦出るぞと意気込んでいたのでとても悔しい。

事前準備

前回出場時に、序盤の解析系作業で手こずったため
今回はかなり念入りに解析ツールの準備を行っていました。

事前に立てていた作戦

  • 午前
    • アプリケーションをサーバー上で動かしてベンチマーク流す
    • 同様にローカルで動かす
    • コマンド一発でデプロイできるスクリプトを置く
    • kataribe, pt-query-digest, stack-profなどで各種解析データを揃える
    • 解析データを元に作戦を立てて、各自作業していく
  • 午後
    • まずはベタにまずいクエリをつぶす (index効いてない, n+1)
    • 同様にミドルウェアもチューニングする (普段の業務で使っているnginx, unicorn, MySQL, sysctl.confを用意)
    • ↑やりきってからキャッシュ戦略考えよう

今回は午後の作戦を事前に話してしまったことで、キャッシュ戦略への切り替えが遅くなったのが敗因ぽい。
htmlifyボトルネックだということに午前の段階で気づけていたものの、ロジック改善を頑張ってしまい
結果スコアが伸びずキャッシュに切り替える時間をロスした。

当日タイムライン

9:30

無事に集合。前回に引き続き、会社のオフィスで作業させてもらうことに。
普段なれてる環境で作業するのが1番でした。

10:00

Azureにデプロイ。
最初なぜかインスタンス起動でエラーが出て、30分ほどリソースグループごと作り直したりしてた。

  • とりあえず1度ベンチマークを流す
  • 「isudaってなんだ? 増田か!!」
  • 「メモリ7GB近くあって結構いろいろのせられそうだね」
  • nginx.conf見て「isudaとisutarがマイクロサービスっぽくなってる」
  • githubにコードを上げておく
  • dumpを取る
  • ローカルで動かす

最初のインスタンス起動トラブルで焦ったものの、アプリケーションが動き出してからは比較的スムーズにいった。
この時点では作戦通り、まだアプリケーションを読み始めていない。

11:00

事前に準備をしていた解析ツールを入れて導入検証を進める

この辺の作業は

ISUCON予選突破を支えたオペレーション技術 - ゆううきブログ

ゆううきさんのブログを参考に用意していた。
特にデプロイスクリプトミドルウェアごと再起動するあたりは、忘れがちなので用意しておいてよかったと思う。

作戦会議の結果、特に事前に話していた作戦とは差分なく

みたいな配分で作業していくことになった。
ここまでは予定通り。

12:00 - 15:00

各自の改善を入れて少しずつスコアが上がる。しかしこの時間帯はスコアが1万にものらない。 以下のようなことを試した。

  • nginx -> unicornunixドメインソケット化
  • staticなファイルをnginxで返す
  • unicornのworkerを増やす 5 -> 10程度
  • istarをisudaに統合
  • MySQLにインデックス追加
  • keyword_pattern, htmlifyのロジック改善
  • ispamは解析始めると時間がかかりそうなのでいったん放置

ロジック改善以外はそこそこ効果があった。 正規表現部分だけどうしようもなくて、徐々にキャッシュの方に話がいきはじめる。

15:00 - 18:00

15:00すぎから、少し作業を止めて話し合い、キャッシュ導入を検討しはじめる。

最初は htmlifyまるごとredisにキャッシュ。たしかこれでスコアが1万を超えた。

そこからまた少しスコアが伸び悩み、再度ロジック改善に走る。
その間に htmlifyの処理を細分化し、さらに細かくキャッシュしていく。これでスコアが2万を超える。

また17:00の時点で一度インスタンスごと再起動してベンチマークが走りきるか確認。

キャッシュを進めていくと今度はまたstaticなファイルを返すのに詰まりはじめたり
ファイルディスクリプタを使い切ったりしたのでカーネルチューニング。

最終的に終了直前に keyword_patternの結果をredisにキャッシュしたあたりで3万オーバーのスコアが出て終了。

終了後

近所のすしざんまいに行って反省会

個人的には

  • キャッシュ戦略の引き出しが足りなかった
  • 正規表現周辺のロジック改善にこだわりすぎた
  • /initializeの5秒を使えず

あたりに後悔が残ってます。

今年けっこう気合い入ってただけに悔しい。本戦出場の壁高い。

しかしISUCON楽しい。出題チームの方々もおつかれさまでした。

表参道.rb #14 でLTしてきた

シャドウプロキシのkageを使って安心してFuelPHP -> Railsに移行した話をしました。

表参道.rb #14 ビアガーデン風編 - connpass http://omotesandorb.connpass.com/event/36622/

今回の会場はSansanさんのオフィスでした。
13Fにオフィスがあって、LT中におそらく神宮球場のであろう花火が見えたりしてよかったです。

今年は仕事で使った技術について色々話せるといいなと思います。 よろしくお願いします。