2017年度 新卒技術部研修 〜社内ISUCON参加編〜

はじめまして、技術部新卒の池田です。

毎年、カヤックの技術部の新卒研修では、研修の最後に『社内ISUCON』を行います。
このエントリーでは、今年の研修最後の社内ISUCONについて新卒参加者サイドから振り返ります。

社内ISUCONのお題とチームについて

ISUCONのルールとしては与えられたサーバーで稼働しているWebApplicationを『いい感じにスピードアップする』という競技で、競技時間は10:00 ~ 17:00の間の7時間です。

お題

今年の社内ISUCONのお題は、ボケてlikeな JokerというWebApplicationです。

  • ボケて(bokete)  クローン
  • RubyのSinatraで作成
  • DBにはMySQLを使用
  • KVSにはRedisを使用
  • ログイン・ログアウトがある
  • 画像のUpload機能がある

等、WebApplicationの基本のたくさん詰まったお題で、 私含む新卒社員達は事前に2週間の研修で取り扱ってたものです。

チーム編成

社内ISUCONの開催前に、事前にチーム編成の発表が有りました。
チームは2人1組で組まれており、先輩チームが3チームと新卒チームが7チームでした。
また、チーム編成発表と同様に、各チームに渡されるサーバーの構成の発表もありました。 実際のチーム編成は下の表のようになってます。

チーム番号 チーム名 新卒  構成
1 チームイケイケ   8コア1台
2 チームせりまち   8コア1台
3 チームイシビア   8コア1台
4 チームすずきヴォン   8コア1台
5 チームダリかわら    8コア1台
6 チームさるたに     8コア1台
7 チーム新卒のフリ    8コア1台
8 チームmixed contents 2コア4台
11 模範解答チーム 8コア1台
12 チーム実力主義 2コア4台

11番の模範解答チームは、講師の先輩のチームで新卒チームと同じ構成でリアルタイムに模範解答をいただけるそうです
ちなみに私はチームイケイケで、 相方は同じく新卒の入江でした。

社内ISUCON開始。与えられたWebApplicationは・・・

10:00に開始の合図とともに、
『まず1度ベンチマークをやってみましょう。』 という案内がありました。
その結果はスコア0
『では、できたチームはトップページを開いてみてください。』
f:id:ikeda-masashi:20170713173632p:plain

Oh…504! まともに開けないものでした。

f:id:ikeda-masashi:20170713173809p:plain

Oh…ループクエリ! 100+1回クエリーが発行されてると言う状態でした。
最終的には、このループクエリどうにかしないといけないですね。

はじめに取り組んだのは開発環境構築

さて、一通りの説明が終わり各チームが作業をはじめました。
30分後にはスコア1000以上のチームが出てきました。
周りからは『ここにこうやってINDEXを張ってどうだ!』みたいな声が聞こえたりします。

そんな状況の中、私達チームイケイケがやっていたのは環境構築でした。 手順のダイジェストは以下です。

1. サーバーへログインするための鍵を作る

(server)$ssh-keygen -t rsa

2. 現在のアプリケーションをGit管理化

(server)$ cd ~app/joker
(server)$ git init

3. 別のディレクトリでbereリポジトリ作成

(server)$ cd ../
(server)$ mkdir joker.remote.git
(server)$ cd joker.remote.git
(server)$ git clone --bare ~app/joker

4. bereリポジトリをリモートリポジトリとしてローカルにクローン(ssh経由)

sshでログインする設定を終えてから(ssh app_ikeikeでログイン可能に) 

(local)$ git clone ssh://app_ikeike/~/joker.remote.git

5. ローカルに開発用の環境を構築

(local)$ bundle install --path vender/bundle

6. ローカルで簡易ベンチマークテストをできるようにする

(local)$ brew install ab
(local)$ bundle exec rackup config.ru -p 5000
(local)$ ab -n 100 -c 100 http://localhost:5000/

7. bereリポジトリにHook用のスクリプトを追加し、デプロイ自動化

(server)$ cd ~app/joker.remote.git/hooks/
(server)$ vim post-receive
(server)$ chmod +x post-receive

post-receiveの中身にはservice restart jokerとか入れて、更新反映に必要な処理を入れていました。
appのユーザーの権限とか、post-reciveをtypoしてとかで色々トラブルが発生しつつも
ローカル環境でのベンチマークテストとPushで自動更新が出来るようになりました。
ここまで、1時間30分かかりお昼前後で『お腹が空いたぽ〜』の状態になってました。
その間のチームイケイケはもちろんスコア0の状態でした。
先輩方から『チームイケイケ元気ですか?』と心配される状態で前半戦を終了しました。
この時点でTop(ただし1位の模範解答チームを除く)は8000点超え。自分たちは0点のまま。

後半戦。巻き返しの肝は・・・

『ループクエリどうにかしよう!』
相方はフロントサイドエンジニアで、テンプレートのerbファイルを編集し始めました。
自分はサーバーサードエンジニアなのでアプリのapp.rbを編集し始めました。
その結果は動かなくなりました。 (原因はindex.erbapp.rbの変数名の不一致)

相方と話し合い、意思疎通して同時に編集するのは難しいと悟った私たちは方針を変えました。 * 相方は研修で習ったチューニングを本番サーバーでひたすら実施していく。 - unicorn.rbworker_processesをあげる。

pid "joker.pid"
listen "127.0.0.1:8080"
worker_processes 32
stderr_path "stderr.log"
stdout_path "stdout.log"
  • アプリケーションサーバーに渡されている静的ファイルの場所を変える。
  • /etc/init.d/joker内の一文を export RACK_ENV = productionに修正する。
  • mysqlinnodb_buffer_pool_sizeを増やす。
  • mysqlmax_prepared_stmt_countを増やす。
  • 自分はアプリケーションのコードやクエリ関係をどうにかしていく。
    • TOPページの激重100+1クエリをINNER JOINでつなげて、テンプレートからのクエリ呼び出しを消し去る。
    • Indexを張る。

f:id:ikeda-masashi:20170713173831p:plain

完全並行作業で相方が何をやっていたのかは正直把握してないのですが、
Topページのループクエリを消し去るだけで、2位との差は2000点くらい。 ( 模範解答チームはこの時点で同じ時間で18000点くらいで、2位と6000点くらいの大差をつけていました。) Topページのループクエリを消した後は他のページのチューニングをしていました。
チューニングしたら必ずローカルベンチマークを回して、スコアが下がる修正は未然に防ぎました。
INNER JOINしすぎかな?と思い2クエリに分けてみたりしたのですが、
クエリ回数が増える修正は、確実にスコアが落ちるようです。

実際にサーバーに反映してベンチマークする場合、
データがたくさん詰まった重い状態かつ他のチームとのキュー入れ競争なので、そこそこ時間がかかります。
ローカルでベンチマークできることで効率的にチューニングが進められました。

アプリケーションのコードの変更反映もgit pushだけスムーズに変更を反映できました。
前半戦の下準備が、実った瞬間です。

まとめ

今回社内ISUCONに参加して学んだのは、3つ。

  • 開発環境って大事ですね。
  • 意思疎通って難しいですね。
  • ループクエリはパフォーマンスの敵

カヤックでは一緒にgit logを綴るエンジニアも募集しています!

2017年度 新卒技術部研修 〜講義編〜

はじめに

新卒技術部研修にするにあたって昨年の研修を振り返り

  • フロントエンジニア、フロントエンジニア(Unity)、サーバーエンジニア、と様々な職能の新卒メンバー全員が受ける研修でGo言語を使うのは配属後に使わない人がいるのでもったいない
  • 各講義毎にそれぞれ異なったお題を出すと、講義で学んだことがどのように関係してくるのか関係性を把握するのが大変そうだった

という反省がでました。

そこで今年は

  • カヤックではサーバー構築にChefを使用しているため、配属先がRuby以外の言語で開発していてもRubyを使う機会があり無駄にはならないということからRubyを使用する
  • 与えられたお題のWebApplicationを段階を踏んで作っていく体系的に学べるカリキュラムにする

ということになりました。

実際の研修ですが、以下の流れで研修をすすめていきました。

  • アプリを作る上で必要なgit,Rubyの基礎的な講義
  • Webアプリをつくるために基本となるTCP/HTTPの講義
  • Sinatraで作ったアプリでデータを保存するためにDBについての講義
  • KVSについて講義をし、ユーザー認証の機能を作る実習
  • 作ったアプリを公開するためにAWSの基本的な操作の講義と、サーバー構築の実習
  • お題のアプリにセキュリテイ的な穴を開けたものを用意し、実際に自分で攻撃してみる実習

この記事では各講義でどのようなことを行ったのかの概要を各講師にまとめてもらいました。

これから研修を行う方々の参考に少しでもなればと思います。

Git編

今回の研修は全体的に、Github上で課題の説明や提出を行っていくため、まずはgitを自由自在に扱うための講義を行いました。

  • Gitとはなにか
  • 実習
    • cloneする
    • 自分のブランチを切って作業
    • 現状の確認とpush
  • Github
    • Pull Requestとは
  • コンフリクト解決

Ruby編

  • Rubyの基本文法
  • gem, bundler の使い方
  • erb, slim の書き方
  • Rubyでurlにアクセスする
  • Rubyで正規表現
  • RubyでFileを扱う

TCP/HTTP編

TCP, HTTPの基礎について解説しました。

  • OSI参照モデルの紹介
  • TCPとHTTPの仕組み

講義に加えて実際にTCP, HTTPを見て触る実習を行いました。

  • TCP通信している様子をパケットキャプチャしてみる
  • telnetコマンドでクライアントとしてHTTPをしゃべってみる
  • ncコマンドでサーバーとしてHTTPをしゃべってみる

普段何気なく利用しているHTTPの仕組みを知ることができたということで、なかなか好評だったようです。

DB編

RDBMSについての概念的な説明と、その中でも社内で多く採用されているMySQLの特性と使い方について説明しました。

  • RDBMSについての説明
  • MySQLについての解説
    • クラスタインデックスの構造と特性
  • MySQLのデータベースとテーブルの関係とCREATE文の書き方、読み方
  • SELECT, INSERT, UPDATE, DELETEなどの基本的なSQLの説明

また実習として、大量のデータを入れたテーブルを用意し発行されるクエリから適切なインデックスを貼るべきカラムを調べて実際に張った上で速度が向上するかという体験も行いました。

  • インデックスの張り方
  • インデックスの使われ方とEXPLAINの結果の見方
  • 大量にデータを入れた際のフルスキャンした場合ととインデックスを使った場合を体験してみる

KVS編

KVSとは何か、RDBMSとの違いは何かを解説しました。

  • 適材適所でデータベースを選択すること
  • KVSの利用が有効なユースケース
  • 管理されていないKVSは魔窟

また、KVSの例としてRedisを用いて実習を行いました。

  • 基本的なコマンド(GET/SET/DELETE)
  • 集合操作(LIST/SET/SORTED SET)
  • TTLの設定

Redisを実環境上で使用する場合の注意点についても触れました。

  • コマンド実行時のブロック
  • Redis実行に必要なメモリ量
  • 永続化方法の違いによるデータ消失耐性とパフォーマンス

WebApp編

Ruby で Web アプリケーションを実装してもらいました。最初から Sinatra を使うと内部でどのようなことをしているのかわからないので、

  1. socket ライブラリを使って HTTP サーバを実装
  2. Rack で HTTP サーバを実装
  3. Sinatra で Webアプリケーションを実装

という順で進めました。実装したWebアプリケーションは、とあるWebサービスの簡易版で、社内ISUCONでも使用しています。

フロント編

ここまでに制作したWebアプリケーションのデザインをあてていく形で、 kayac-html5-starter を使ったフロント開発の講義・実習を行いました。配属後にフロントエンジニアの配属にならないメンバーにも、最近のWebフロントがどのように開発されているのかを体感してもらうことが主目的でした。

ここまでの流れとはやや独立した内容であること・サーバーサイドをメインで勉強していてフロントに慣れていない(あるいはその逆の)人もいたことから、新鮮な気持ちで取り組んでもらえたようです。

サーバー構築編

サーバー構築篇は、2016年の「ガチ授業をしてそのあと各自が演習するスタイルでやったらサーバーになじみのないフロントエンドエンジニアがみんなお手上げになってしまった」という反省を生かし、講師が実際にサーバー構築する様子を画面に映して、各自が手元で同じことを行って自分の環境を構築していく、ハンズオン形式で行いました。

具体的な流れとしては、以下のような感じです。

  • まず全部入り構成のEC2を作って1台で動かす
    • AWS ConsoleでEC2を起動
    • 自分のアカウントを作ってsudoersを設定
    • 課題のアプリをEC2上にデプロイ(ただしミドルウェアがないので起動しない)
    • ruby、mysql、redisをインストールし、データベースを作ってスキーマを投入
    • 無事1台構成で動くことが確認できました!
  • マネージドサービスとしてRDSとElasticacheを利用する構成に変更
    • 課題のアプリがRDSとElasticacheを使うように設定変更
    • これでストレージ関連をEC2から分離できたので、EC2を増やしてスケールアウトできるようになりました!
  • 別のEC2で同じ構成のEC2をchefで構築
    • 講師が用意した色々足りないchefレシピを実行
    • serverspecで足りない点が指摘されるので、各自レシピを書く
    • 徐々にヒントを出していき、最終的にserverspecで全てテストが通って課題完了

2017年はこれを2日間で行いました。

フリーダムに各自ガンバレ方式だった2016年とは違い、全員同じ手順をやっていくことで進度の足並みが揃いました。また、副次的効果としてサーバーが得意な人がフロントが得意な人にやり方を教えたりするなどよい効果がありました。

セキュリティ編

セキュリティの講義では実装によって簡単に脆弱性を生み出せてしまう、脆弱性の危うさを体験してもらうために

  • XSS
  • CSRF
  • SQL Injection
  • OS Command Injection

の4つの脆弱性について触れました。

もともと研修内でつくっていたアプリを修正(改悪?)し、挙げた4つの脆弱性が再現できる状態のアプリを用意しました。

講義では、各脆弱性ごとに

  1. 説明する脆弱性がどのようなものかの説明
  2. 用意したアプリをつかって実際に脆弱性を再現してみる
  3. 脆弱性の回避方法の講義
  4. 用意したアプリを講義を受けて終始し、脆弱性の再現ができないことを確認する

という流れで行いました。

実施の脆弱性を再現させるところでは

  • DOMを書き換え外部リソースを読み込む (XSS)
  • 入力済みのコメントを全て削除してみる (SQL Injection)

などを実際にやってもらうことで、簡単に脆弱性が生み出せ攻撃がされてしまうということを体験してもらうことが出来ました。

ログ編

ログの講義でアプリケーションはログを適切に吐こうというのを分かってもらうためにGoogle Spread SheetからCSVファイルにするというスクリプトを中途半端に実装した状態で渡し、最後まで実装してもらうということをやりました。

講師が作った、一見何の変哲もないライブラリを読み込みこんでいたためログが全く表示されない状態のため 解説のタイミングで「うわーーーー」、「なるほど…」という阿鼻叫喚な声が聞こえてきました。

その日の研修の振り返りにも

  • ログがでるという当たり前の大切さが見にしみた
  • 使うライブラリはきちんと調べたい

というようなことが書かれていたのでおおよそ狙い通りになったのではないかなと思いました。

トラブルシューティング編

トラブルシューティング実習では、運用しているWebアプリケーションに障害が発生した場合に、どうやって原因を特定し解消するのか、という講義と実習を行いました。

実際のトラブルは、お客さんの環境から自分らの使っている環境、通信経路のうちどこに発生しても障害に繋がりますが、今回の実習ではアプリケーションが動作しているサーバ(EC2インスタンス)の内部だけを対象としました。

原因追及のための各種stat系のコマンド、ログの出力場所などを講義、実習後、各自に割り当てられたサーバに対して講師が人為的にトラブルを発生させます。

課題の内容

  1. システムのどこに問題が発生しているのかを調査し、修正してください。以下の3項目を、発見、修正した問題点ごとに、手元に記録してください
  2. 発見した問題点
  3. 問題を確認できた理由(このログのこの部分、とかこのコマンドの実行結果から、とか)
  4. 修正するために対応した内容
  5. 修正後、また同じことが起きないように、システムを修正してください

講師が発生させたトラブル

  • Webアプリケーションのファイアップロード機能を利用し、攻撃用シェルスクリプトを画像ファイルに偽装してサーバに送り込む
  • Webアプリケーションのログイン機能にある OS Command Injection 脆弱性を利用し、先に配置した攻撃用 シェルスクリプトを実行する
  • シェルスクリプトの内容は下記のようなものです
    • MySQLのテーブルを (バックアップを取った上で) DROP
    • Redis に SHUTDOWN コマンドを発行して停止させる
    • god を利用して、stress コマンドを実行して永続的に負荷を掛ける

当然、その時点からWebアプリケーションはエラーを起こしますし、top コマンドなどでCPUを消費している stress プロセスを見つけて kill しても god が復活させます。

アクセスログ、アプリケーションログから

− ある時点でファイルがアップロードされていること - 同じIPアドレスから、ログアウト→ログインが行われていること

を見つけ、

  • Redisのログでその時刻に shutdown が発生していること、
  • その時刻から普段はいないはずのプロセス god が存在して、子プロセスに stress が動作していること

を確認し、アップロードされたファイルの内容を確認すれば、OS Command Injection の脆弱性があってそれを実行されたようだ、ということが分かるでしょう。

攻撃者は親切にもテーブルのバックアップを取ってくれているのでそれを書き戻し、Redisを起動し、god と stress をまとめて kill すれば、とりあえずは復旧できます。しかし、それだけではまた同様の攻撃を受けてしまうので、アプリケーションのソースコードを確認し、脆弱性を修正する、というところまでできれば、という実習でした。

さいごに

このような形で研修を行いました。

多少躓くところはあっても参加者全員がWebApplicationを作るために必要な知識や、技術、 更には、実際の仕事で気をつけなければいけないセキュリティの怖さも学ぶことができました。

そして、いよいよカヤックの研修恒例の社内ISUCONに続きます。

今年はどのような問題で、どのような結果になったのでしょうか?!

次の記事をお楽しみに!!

新卒じゃないけどカヤックにちょっと興味があるな・・・という方はこちらへ!

もちろん新卒採用もやってます!