Medley Developer Blog

株式会社メドレーのエンジニア・デザイナーによるブログです

PrometheusでRailsアプリケーションのパフォーマンスが簡単に見れますという発表をしました

こんにちは。エンジニアの宍戸です。先日、社内勉強会「TechLunch」にてPrometheusを使ったアプリケーションのモニタリングについて発表する機会がありましたので、その内容を少しご紹介できればと思います。

Prometheus

先日2.0がリリースされたばかりの統合監視ツールです。監視対象からメトリクスを取得し、その情報をGrafanaと連携してグラフィカルに表示したり、アラートマネージャ機能を利用してメトリクスの状況に応じて通知を作成することなどが可能です。また、PrometheusはPull型のメトリクス収集形式をとっていますが、サービスディスカバリ用の設定を入れることで、対象のサーバ群の増減の度に作業すること無く、監視対象を管理することができるなど、現在のアプリケーション稼働環境にあった運用が可能なものとなっています。(Prometheus自体に関する情報はかなり多くの方が記事を書かれているので、詳細はそちらにお任せしたいと思います🙏)

Prometheusは前職時代にもお世話になっており、そのときにはGoで書かれたサーバのモニタリングに利用していました。当時から便利に利用していたこともあり、弊社のプロダクトはRailsを利用しているため、今回はPrometheusを利用して、Railsアプリケーション自体のパフォーマンスに関するメトリクスを取得してみました。

アプリケーションパフォーマンスのモニタリング

アプリケーション自身のパフォーマンスの情報を定常的にモニタリングしておく事は、潜在的ボトルネックの発見や、アクセス状況の変化、リリース単位でのパフォーマンスの変化(改善/悪化)を早期に発見することに繋がると考えています。

アプリケーションのモニタリングは有料ツールを含めてかなり多くの選択肢があります。NewRelicのAPMは代表的なツールの一つで、実は現時点ではこちらを利用してモニタリングを行っています。またその他様々なツールでも同じようなインサイトを得ることができます。アプリケーションパフォーマンスのモニタリング(マネジメント)ツールについてはこちらに良くまとまっていましたので、合わせて見ていただくと良いかもしれません。

メトリクスについて

Prometheusも様々な機能がありますが、監視対象となるサーバはExporterと表現されます。このExporterに対して、Prometheus Serverがデータを取りに来る格好です。

https://github.com/prometheus/client_rubyというライブラリが公式に提供されています。こちらにあるPrometheus::Middleware::CollectorPrometheus::Middleware::Exporter という2つのRack middlewareを利用することで設定に数行追加するだけで以下に記載した情報をPrometheus Serverから利用する準備が整います。

key 意味
http_server_requests_total アプリケーションの処理したリクエスト総数
http_server_request_duration_seconds_bucket あるエンドポイントの要求処理時間のヒストグラム
http_server_request_duration_seconds_sum 上記の合計値
http_server_request_duration_seconds_count あるエンドポイントへのリクエストの総数
http_server_exceptions_total アプリケーションで発生した例外の合計数

ここで出て来るバケットというのは特定の範囲のデータを格納する領域のようなものです。 ヒストグラムの計算などの際に利用します。

これらを用いて、

  • 直近10分のエンドポイント毎のレイテンシ
    • rate(http_server_request_duration_seconds_sum{path=~"/api.*"}[5m]) / rate(http_server_request_duration_seconds_count{path=~"/api.*"}[5m])
  • APIのエンドポイント毎のステータスコードが200以外となったレスポンスの数
    • sum(http_server_requests_total{job="rack-example", code!~"^2..$", path=~"/api.*"} offset 10m ) by (path)

などをPromQLというPrometheusの独自クエリ言語を用いて集計することができます。デフォルトで提供されるダッシュボードで即座に確認できるのでとても便利です。

実際に発表した際のスライドはこちら。 speakerdeck.com

まとめ

今回はPrometheusを利用したRailsアプリケーションのモニタリングについてご紹介しました。

Prometheusがどうという話はあまりできませんでしたが、ここまで手軽に準備できるもんかというのは正直びっくりしました。他のツールを利用していない状態でアプリケーションの運用をしている場合には、特にRails等であれば簡単に監視を始められるので、導入を検討する価値はあるのではないかと思いました。

現在は比較的安定してサーバを稼働させることが出来ていると思いますが、日頃からこういったメトリクスを見つつ、今後の安定運用につなげていきたいと思います。

フロントエンドエンジニアがIonicを触ってみた〜メドレーTechLunch〜

こんにちは。開発本部の大岡です。オンライン診療アプリ「CLINICS」の開発を担当しているエンジニアです。2017年6月にメドレーに転職してきて初めて気づきましたが、僕は人見知りでした。

メドレーでは、定期的にTechLunchという社内勉強会を実施しています。今回僕が担当になりましたので、その時の内容をご紹介させていただければと思います。テーマは「フロントエンドエンジニアがIonicを触ってみた」です。

色々な事情を考えずに好き放題言っています。個人的にウェブアプリが好きということもありウェブアプリよりの偏ったことを書いていると思います。ご了承ください。

なぜIonicを触ろうと思ったか

ウェブアプリ・ネイティブアプリ(このエントリーではiOS/Android各プラットフォーム固有の言語を使って開発したものを指しています)それぞれにメリットデメリットはあると思いますが、ゲームのようなグラフィックごりごりのものでなければウェブアプリで十分に感じています。

最近では、両方に展開しているもののネイティブアプリにコストを割いてしまっているのか、モバイルのウェブアプリのUIが本当にひどかったり最適化されていないと感じる例があります。最適化もしてないし、導線はネイティブアプリに向けてるのにネイティブアプリの方が使われてるじゃん!って言われてもウェブアプリの気持ちになると「そりゃそうでしょ。。。」って感じです笑

とはいえ、いくらウェブアプリが好きでもネイティブアプリを作らざるをえないとなった場合にiOS/Androidそれぞれ作ることが適切なのかなと思ってしまいます。そこでクロスプラットフォーム開発できるものを探していると、自分のスキルセットに合いそうなものがいくつかありました。その中で今回は、Ionicを触ってみることにしました。

今回TechLunchで発表したスライドは以下です。

speakerdeck.com

スライドと重複してる箇所もありますが、テキストでも解説してみます。ご興味がありましたら以下もどうぞ。

ウェブアプリとネイティブアプリ

ウェブアプリとネイティブアプリはよく比較されますが、その違いは以下のような感じかなと思います。

項目 ウェブアプリ ネイティブアプリ
動作速度 遅め 早め
バイスの機能 使えるものもある 利用可
開発コスト 普通 多め
審査 無し 有り
インストール 不要 必要

動作速度は遅めとはいえ、ゲームではなくECサイトのようなものであれば、そこまで気にするほどではない思います(作りにもよると思いますが)。デバイスの機能に関してはChromeなら割と使えます。開発コストはワンソースでいけるのでネイティブアプリよりは優れているかなと思います。

あとは何と言っても、ブラウザとURLさえあればどこからでも使えるっていうのがメリットです。最近ですとウェブアプリをネイティブアプリっぽく動かすProgressive Web Application(以下PWA)というワードもよく目にするようになりました。PWAは動作速度の面やプッシュ通知・オフラインでも利用できたりとウェブアプリの弱点を補ってくれるのですが、PWAに必要な技術であるService Workerの実装がされていなかったりと環境により十分に恩恵を受けることができない場合があります。

個人的に以下のようなパターンの場合

  • ネイティブの実装が必要
  • 重要だけど機能に更新がはいることがほぼない
  • アプリを使う上で毎回必要なわけではない

アプリによっては、この部分だけアプリに切り出して、ウェブアプリで必要になったら呼び出すとかでもいいのかなと思います。

上記のようなことをネイティブアプリとして実現できるものにApach Cordova(以下Cordova)があります。ウェブアプリはWebView(ネイティブアプリ内でウェブページを表示する部品みたいなもの)に表示し、ネイティブ機能はプラグインとして提供されているのでそれをJavaScriptから呼び出すことが可能です。

Ionicとは

ウェブの技術を用いてネイティブアプリの開発を可能にするフレームワークです。 主な特徴は以下です。

実際に触ってみた

公式のGet startedにもあるようにブラウザでアプリを表示するまでは3ステップです。 ※Node.jsがインストールされていることが前提

# 1. ionicとcordovaのインストール(この時のIonic CLIは3.18.0)
 $ npm install -g cordova ionic

# 2. アプリケーションの作成
$ ionic start [アプリ名] [テンプレート]

# 3. アプリケーション起動
$ cd [アプリ名]
$ ionic serve

テンプレートもよく見る形のものは用意されているのでこだわらなければサクッとそれっぽいものが作れます。下の画像はtabsテンプレートを使用しました。

f:id:medley_inc:20171122191851g:plain

Android/iOSの開発環境が整った状態ですと以下のコマンドで、エミュレータを起動しネイティブアプリとしてインストールすることができます。

# 1. 対象のOSを追加
$ ionic cordova platform add [ios/android]

# 2. エミュレータ起動かつインストール
$ ionic cordova run [ios/android]

# 2.5 ウェブアセット更新と連動してアプリ更新させる場合
$ ionic cordova run [ios/android] --livereload

ここまでは本当に簡単です。起動時に--livereloadオプションをつけるとウェブのアセットが更新されるとアプリ内の画面も更新されるので便利です。ネイティブの機能を利用したくなった場合も多くのプラグインが提供されているのでウェブの知識だけでもある程度のアプリは作れると思います。

便利そうだし簡単にアプリ作れそう!となりますが、やりたいことがプラグインで提供されていなかった場合は自分で作成しないといけません。プラグインの作成はiOS/Androidそれぞれで作らないといけないため知識もそれぞれ必要です。そして、このプラグインを作るのがウェブの知識だけだと割と苦戦します。

SkyWayを利用してビデオチャットアプリを作ってみた

SkyWayとはNTTコミュニケーションズが提供しているWebRTCを利用したビデオチャット等ができるサービスです。今回このSkyWayを利用して、Androidのみですがビデオチャットアプリを作ってみました。

Androidプラグイン作成

SkyWayのプラグインがなかったのでプラグインを作成します。画面のあるプラグインの作成に結構はまりました(基本的なプラグイン作成の説明は、検索すればすぐに見つかると思うので省略させていただきます)。解決して思うようには動いたのですが、これでいいのかわかりません。いい感じの解決法があったら教えてください。

今回はSkyWayのAndroidサンプルコードに少し手を入れて利用させて頂きプラグインを作成してみました。ざっくりディレクトリ構成は以下のようになりました。

    plugin-skyway
    ├── platform
    │   └── android
    │       └── AndroidStudioで作成したプロジェクト(SkyWayサンプルソース)
    └── plugin
        ├── package.json
        ├── plugin.xml
        ├── src
        │   ├── android
        │   │   ├── cordova-plugin-skyway.gradle
        │   │   ├── MainActivity.java
        │   │   ├── PeerListDialogFragment.java
        │   │   ├── SkyWay.java        
        │   │   ├── layout
        │   │   │   ├── activity_main.xml
        │   │   │   └── fragment_dialog_peerlist.xml
        │   │   └── libs
        │   │       └── skyway.aar
        │   └── ios
        └── www
            └── SkyWay.js

plugin-skyway/platformに各OSのプラグイン用プロジェクトがある感じです。plugin-skyway/pluginがIonicのプロジェクトにインストールされるディレクトリです。

はじめは、plugin-skyway/plugin/src/androidにAndroidStudioで作成したプロジェクトを置いてました。しかし、Ionicのプロジェクトに作成したプラグインをスンストールすると不要なものまで含まれてしまったので、plugin-skyway/platform/androidにプロジェクトを作成し必要なファイルのみをplugin-skyway/plugin/src/androidにコピーすることにしました。

AndroidStudioでプラグインを開発する際は、一度IonicのプロジェクトをAndroidでビルドし、Ionicプロジェクト内のplatforms/android/CordovaLib/build/outputs/aar/CordovaLib-debug.aarプラグインのプロジェクトで取り込まないといけないようです。

いざIonicプロジェクトにインストールしてビルドするとConstraintLayoutが無いとかSkyWayのSDKのバージョンがどうとか怒られますが、Androidの開発の仕組みが理解できていなかったので下記の「パッケージRは存在しません」というエラーに時間を取られました。

    .../MainActivity.java:258: エラー: パッケージRは存在しません
                    Canvas canvas = (Canvas) findViewById(R.id.svLocalView);

のようにR.javaがないと怒られます。 AndroidStudioからGUIでポチポチと画面を作るとR.javaというのができいい感じに解決してくれるようなのですが、今回のようにxmlのみを移動させるだけだとR.javaが作られません。

そこでR.id.svLocalViewのようなR.の箇所を下記のように置き換えて、やっとビルドが通るようになりました。

getResources().getIdentifier("svLocalView", "id", getPackageName())

最終的に以下のようなものができました。 SkyWayボタンをタップするとネイティブの画面が立ち上がり、AndroidのSkyWay SDKを利用してテレビ電話をすることができます。 クローズボタンをタップするとネイティブ画面が終了し、WebView部分に戻ってきます。実際触ってみた感じフルネイティブアプリと違和感なく感じます。

f:id:medley_inc:20171122192030g:plain

※アニメーションGIFの容量が重くなったので相手側でのコード入力中の空白時間を削ったり編集した影響で左下の緑と赤の画面が飛んだりしてますが、実際は滑らかです

まとめ

今回触ってみたというより指先が触れた程度ですが、全然Ionicでもいけそうな雰囲気を感じました。普通に触ってる分にはネイティブ部分なのかウェブ部分なのかわからなかったです。ただし、銀の弾丸ではないので、実際に開発に導入する場合はメリット・デメリットをちゃんと把握する必要があります。 ウェブアプリ寄りのことを言ってはきたものの、結局はウェブだろうがネイティブだろうがどんなツールを使おうが、使ってくれる人が求めるものを提供できればいいと思います。今後も何かあったときの引き出しのために色々なツールなど触っていこうと思います。

お知らせ

12/6(水)、こんなイベント(というか飲み会)をやります。 今回のブログの話が詳しく聞きたい、医療ヘルスケア領域の開発ってどんな感じだろう、社会貢献性の高いプロダクトに関わりたい、など思っているエンジニア・デザイナーの方、ビール片手に開発の話で盛り上がりませんか?

www.wantedly.com

その日は予定が入っているんだけど話を聞きたいという方は、こちらの「話を聞きたい」ボタンからどうぞ。

www.wantedly.com

提供価値によって異なるデザインプロセス

最近PS4グランツーリスモスポーツをやり始めて、自宅のネット環境の遅さに気づいたデザイナーのマエダです。前回はDLSについてご紹介させていただきましたが、今回はメドレーに入社して感じた「デザインプロセスの違い」について自分なりにまとめてみました。

あとで読みたい人向けに、エレベーターピッチ風にまとめると、

[ CLINICS ] というサービスは
[ 患者と医療機関向け ] それぞれサービスを提供しているが
[ 提供価値の違い ] によって
[ デザインの役割が異なる ] ことに気づいた

特に [ 医療機関向け ] は
[ UIが重要 ] となり
[ 伝えることを目的としたWebサイト ] とは違って
[ UIデザインの良し悪しがプロダクト全体の品質に関わる ] ため
[ 事業や技術を理解 ] した[ デザインオリエンテッド ] が求められる

というような内容です。

ユーザーによって異なる提供価値

メドレーに入社してから、オンライン診療アプリ「CLINICS(クリニクス)」というサービスのデザインを主に担当しているのですが、患者と医療機関側で提供しているプロダクトの内容が異なります。

f:id:medley_inc:20171116131612p:plain

  • オンライン診療の内容を伝え、利用を促すためのWebサイト(患者・医療機関双方)
  • 患者がオンラインで通院を行うためのアプリ
  • 医療機関側がオンラインで遠隔診療を行うためのシステム

サービスの入り口となるWebサイト

Webページの役割としては、オンライン診療というものがどういったもので、CLINICSを利用するとどういう課題解決につながるのか、というサービスの特徴を患者と医療機関双方に「伝えるためのデザイン」が必要となります。

デザインするにあたって注力すべきポイントは、装飾やイメージ画像などシンボリックなデザインとストーリー性のある導線設計をすることで、視覚的に特徴を伝わりやすくし、またコンバージョンポイントへ誘導するため、ボタン等は目立たせるなど、適切に情報を伝え、行動を促すためのデザインが重要になります。

患者がオンライン診療を行うためのアプリ

アプリは患者がオンライン診療をするための「病院検索」→「予約」→「問診」→「診察」→「決済」の機能をシームレスに繋げるためストレスのかからないユーザー体験を提供することが重要です。

そもそもオンライン診療のメリットとして、待ち時間の軽減や、落ち着いた環境で診察ができるなどが挙げられるため、そこに至るまでのユーザー体験を台無しにしてしまうUIでは元も子もなくなってしまいます。

アプリでは「伝えるデザイン」よりも「機能的なデザイン」が必要になりますが、ユーザーの行動を途切れさせないよう、不必要な要素や導線を極力排除して、非常にシンプルなUI設計をおこなっており、機能自体を主張しないデザインを心がけています。前回このブログでもとりあげたDLSも、そういった設計思想のもと開発を進めていたからこそ実現できたと思っています。

f:id:dev-medley:20171117113501p:plain

医療機関向けの遠隔診療システム

医療機関側に提供しているシステムは、オンライン診療を行うためのツールです。 Webサイトのように「伝えるデザイン」やコンバージョン重視ではなく、「より機能的に使いやすいデザイン」が重要になります。 このような画面をBootstrapのようなUIテンプレートそのままの見た目で構築してしまうと、技術的に素晴らしいものができたとしても、使い勝手が悪く貧弱な機能と見られかねないため、デザイナーが管理画面のUI設計に携わることは、ビジネス的にも非常に重要な要素です。

特にCLINICSの医療機関向けのシステムは、実際にオンラインで患者の診察を行うツールのため、医療機関側が診療行為をつつがなく終えられるようなUI設計が重要です。

MVP的な手法で、とりあえずリリースして検証を重ねていくというスタンスがとれないため、リリースするまでに機能的に不備がないか、医療従事者が迷わず正しく使えるUI設計になっているかなど、社内や実際の医療機関のテストを繰り返し、試行錯誤を経てリリースするという、プロダクトデザインをしている感覚になり「伝えるデザイン」とは違った思考でデザインに取り組んでいます。

機能重視なサービスこそ、直感的にシンプルなUIが重要

このようにCLINICSという1つのサービスを構成する要素の中でも、ターゲットユーザーや提供価値の違いによって、デザインアプローチや重要視する視点が異なります。

たとえば、自分も業務でよく利用するプロトタイピングツールのinVisionもログイン前は、利用シーンやより魅力的なサービスであるということを伝えるためのデザインをしており、ログイン後は直感的にわかりやすいシンプルなUI設計で、ログイン前後でサービスのUIが異なります。

inVisionも機能は豊富ですが、プロトタイピングを行う上で非常にシンプルな操作性を提供しており、無駄な説明や導線がなくても直感的に使えるUIは、継続して利用できる安心感にもつながり、見習うべきUIだなと思っています。

f:id:medley_inc:20171116131754p:plain

(inVisionの画面の違い)

CLINICSの医療機関向けシステムも受付管理やスケジュール、オンライン診療を行う機能など複数の機能をひとつのサービスとして提供しているため、UI的に複雑になりかねません。複雑な機能をまとめ、画面上にシンプルに落とし込めるかどうかというのを吟味し、作っては壊し、作っては壊しを繰り返しします。

これならいける!とおもったUIも、機能面での見落としなどがあったりすると導線に矛盾が生じたり、シンプルに表現したつもりが、逆に使い勝手の悪いUIになってしまったり。UIを考えるというのは、感性に訴えるデザインとは違ったよりロジカルな思考が必要で、デザインしながら四苦八苦して、ぶつぶつ独り言を言うことが多くなりますw

デザインオリエンテッドなプロダクト開発

Webサービスであればいかに目標としているコンバージョン率を高めるかどうか、分析〜調査〜開発というサイクルをベースとした運用になりますが、特に医療機関向けのシステムの場合は、ムダな機能や使いづらいUIだとサービス的に不安要素を抱かせる要因にもなり、ビジネスの成功可否に直結します

そのため、リリースまでにUIを磨けるだけ磨き、実際に医療機関の現場に出向いてどういうフローで診察を行っているのかなどヒアリングしたり、出来上がった機能を試してもらうなどし、十分に検討した上でリリースしています。こうしたデザインを重視した開発が行える点はメドレーだからこその開発体制かもしれません。

このようなデザインオリエンテッドなプロダクト開発を行う上で重要なポイントは、事業理解とエンジニアとの密な連携です。表面的なデザインではなく、実際に使われる利用シーンを想像しつつも、体験することが難しい医療サービスだからこそ、前提の知識やヒアリング、ユーザーテストなどが重要となりますし、機能的な部分に関してはエンジニアと仕様について議論したり、ユースケースを踏まえてどのようなUIに落とし込むべきかを考えながらデザインに落とし込んでいきます。インタラクティブな表現などinVisionでも表現しきれない細かい動きや導線などは、実装時にエンジニアに伝わりやすいようフロントエンド部分のコーディングを自ら行うことでコードを通じてエンジニアとコミュニケーションが取りやすくもなるので、フロントエンドも把握しておくことは重要です。

まとめ

個人的には「伝えるデザイン」と「機能的なデザイン」で、明確に思考をわけて考えてデザインしてきたわけではなかったのですが、提供すべき価値の違いによって左脳と右脳それぞれ使い分けてデザインしているかもしれないということに気付かされました。これは以前デザイナーの小山がブログで書いたシステム1(自動的に直感で動く”早い思考”)とシステム2(手動で論理的に動く”遅い思考”)が自分の中で振り子のように行き来してるだろうなと感じたので、興味のある方は「思考とデザインスキル」も読んでもらうとわかりやすいです。

developer.medley.jp

最後に

ふだんビールばっかり呑んで適当な人とレッテルを貼られているマエダですが、今回は真面目なことを書いてみましたがいかがでしたでしょうか。このブログを書いてて正直疲れたので、システム2が働いてるに違いないと思います。こんな私と一緒に仕事がしたい、呑みたいというデザイナーやエンジニアさん。応募お待ちしております。

www.wantedly.com

www.wantedly.com

「フロントエンド開発に再入門する」タスクフォースの進め方

こんにちは。開発本部の宍戸です。 メドレーでは定期的に、テーマに沿って組織の技術的な底上げを行うための機会(タスクフォースと呼んでいます)を行っています。そのタスクフォースの1つとして先日、フロントエンド開発力のベースアップを目的としたタスクフォースを行いました。本記事では、その取組みについてご紹介したいと思います。

背景

メドレーには現在20人弱のエンジニアが在籍しており、その約半数がサーバーサイド出身者です。また普段の開発においては、一つの機能をフロントからサーバーサイドまで一貫して一人が担当するケースが多くあります。サーバーサイド出身者のフロントエンド開発のスキルセットには多少ばらつきはあるものの、普段の開発業務ではレビュー等でそれぞれサポートしつつ開発を行っています。

しかし、フロントエンドの基礎的な部分や最新の流れまで聞かれると不安なメンバーも少なくありません。フロントエンド出身のメンバーが主導し、改めて基礎や最新情報に関して整理・フォローを行うことで、組織全体のフロントエンドの開発力を高めていきたいと考えました。

タスクフォースの目的

今回のタスクフォースは『フロントエンドの基本や最近のトレンドに関して学ぶ』ことで『(フロントエンド開発における)技術選定、設計、実装ができる基礎を身につける』、そしてこれらをもとに『新規のプロジェクトで設計段階から自走できるようになる』ことを目的としました。

その中でも特にここ数年、変化の流れが早かったJavaScriptを中心にトピックを選定しました。

参加者は、これまでサーバーサイド開発を中心に行ってきたメンバー数名です。背景でも触れたとおり、業務経験はそれぞれある前提でのスタートであったため、基礎をみっちりというよりは、基礎的な話から最近の話題までを一通り確認しながら、各自の持っている知識の整理と土台を固めることで、今後の設計や技術選定を行う際の指針を得ることを目的としました。

進め方

今回のタスクフォースは期間を3ヶ月と定め、毎週1時間程度集まって行いました。

毎週、事前に講師陣が選んだ資料を読んでおき、当日は講師陣が参加者の不明点、疑問点に対してフォローアップするという形式で進めました。その他には、社内のプロダクトでの利用事例なども交えながらテーマに関する質問会のような形で進むことが多かったです。

また毎回のタスクフォースの時間のあとに、参加者がその日の内容をまとめた議事録形式の資料を作成し、参加者全員と共有することで、その日に話された内容や、それぞれの理解度を再度確認するようにしました。

内容

およその流れは上記の通りですが、約3ヶ月でどのようなテーマに触れてきたのか、一部をご紹介します。

フロントエンドの基礎

序盤はこちらの資料を利用させていただきながら進めました。

ブラウザの仕組み、HTML/CSSの基本的な話のおさらい、JavaScriptの話に関連して、これまでに出てきたAltJSについてもいくつか特徴や何故流行ったのかなどについて読み込みました。

この中でも、ブラウザの仕組み: 最新ウェブブラウザの内部構造でDOMの解析、レンダリング、レイアウトといったブラウザ内部で具体的に何が行われているのかといった話はここで確認できてよかったという声が多くありました。

JavaScript(基礎〜ES2015以降)

JavaScriptの話題への導入編としてこちらを資料として読み込みました。

このパートではJavaScriptの基礎は、あえてES3〜5をベースにすることでJavaScriptと他言語との違い・特徴を再確認していきました。上記の内容を踏まえ、今では使わない書き方などについてはその理由も確認しながら進めていきました。

その後はLearn ES2015 · Babelを参照しながら、Promise, Classなどは普段の開発でも当たり前のように利用しているものの、ES2015以降での書き方はES5だとどのようになっていたかもここで同時に学習していきました。(Playground ・ TypeScriptで、その雰囲気を見ることが出来ます)

Screen Shot 2017-10-27 at 16.14.49.png (424.4 kB)

モダンJavaScript

これまでの知識を踏まえ、モダンなJavaScriptの利用(実装)例として、jser.github.iopreact-wwwのソースを読んでいきました。

これまでのJavaScriptの言語自体に関する内容から、ライブラリを利用したコンポーネント間でのデータフローや、コンポーネントのライフサイクルに関する部分まで確認していきました。また初見のプロジェクトであればどのあたりから目を通していくか、などコード全体の読み方についても講師陣からアドバイスがありました。また時折出てくるDOM APIに「なんだっけこれ・・・?」などとなりつつもコードを紐解いていくことで改めてフロントエンドJavaScriptの特徴的な部分を垣間見ることができたように思います。

また最後に、現在開発に利用されているツール群について、Qiita:JavaScriptフレームワーク選定の議論を参考に確認しました。それぞれのツールがどのような背景で使われている(あるいはいない)のかなども合わせて確認をしました。

ここまでのテーマを振り返りつつ、JavaScriptが言語としてどのように変化してきたかを考えた時、webpackやTypeScriptがなぜ使われるのか、ようやく腹に落ちたように思います。また上記資料も、どのようなケースで何を選択するのかや、アプリケーションの寿命とライブラリやツールの寿命といった運用フェーズで理解していく必要のある事項にも触れられており、非常に参考になりました。

実際にやってみて

今回のタスクフォースでは、3ヶ月という期間の中で、1ヶ月半の時点と、全体終了後に振り返りを実施しました。 その中で、参加メンバーからは

  • 業務においては必要な部分に絞った調査で終わってしまったり、周りのコードを参考にしたりすることでなんとなくできた気になってしまっていたが、今回改めて基礎的な知識を学習する、復習する時間としてできてよかった。
  • 断片的でまばらな知識しか持っていなかった部分が資料を読み込むことで理解が深まった
  • 数年前にバズってあとで読まない「あとで読む」ブクマ記事をきちんと読めてよかった
  • フロントエンド経験の長い講師陣の生きた知見(ツラミなども含めて)を聞くことができたのはありがたかった

などの感想が出ました。 私自身も参加していましたが、個人的には基礎部分を改めて固めることができた機会だったように感じています。 また、普段は新しいツールなどの情報のキャッチアップへの意識が向きがちですが、今回のタスクフォースで言語・ツールの変遷を一度通して見ることができたことで、新しく現れる技術がどういった課題を解決するものなのか、これまでに似たツールがあったのかどうかなどを調べる指針が得られたことはよかったのかなと思っています。

一方で、今回のタスクフォースの中では「コードを書く」時間は作りませんでした。これについては振り返りの中でも話題となりましたが、それについてはTodoMVCなどを参考に、自分で手を動かしてみることが必要だろうと今後の課題として挙げられました。このあたりは実務以外での個々の頑張りが必要になってくる部分かと思います。

まとめ

「サーバーサイドエンジニアのフロントエンド開発力の底上げ」をテーマとしたタスクフォースについてご紹介しました。こういった形で、同じようなスキルセットのメンバーが集まり、お互いにわからない部分について話をしたり、経験豊富なメンバーから、個人の経験を含めて話を聞くというのは、改めて非常に貴重な機会だったように思います。

こういった取り組みを繰り返していくことで、個々で尖った部分はもちろん伸ばしつつ、全体の底上げを行いながら、よりよいプロダクト開発に繋げられればと考えています。

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

CircleCI2.0に移行してビルド実行速度を向上

こんにちは。開発本部の稲本です。医療介護の求人サイト「ジョブメドレー」の開発を担当しているエンジニアです。

最近ジョブメドレーではCircleCI2.0への移行を行いました。移行の方法はもちろん、その際に調べたこと、CircleCIの新機能を利用してどうだったかなどを書いていきたいと思います。

課題感

弊社では、全プロダクト(CLINICSMEDLEY介護のほんねジョブメドレー)でCircleCIを利用しています。

ジョブメドレーではCIによるテスト実行に37分前後掛かっていました(コンテナを2つ利用した実行時間です)。 さらに、開発メンバーが増えて来たこともあり、CIのリソースが足りなくなり開発効率が落ちかねない状況でした。

まぁよくある話ですよね。

コンテナを増やすというのも解決策の一つとしてはあるのですが、速度の改善に期待が出来ると評判も良かったのでCirclecCI2.0へ移行しました。

CircleCI2.0への移行メリット

基本的には速度の改善に期待が出来る、というのが大きなメリットではありますが、公式では以下のように記載されています。

f:id:medley_inc:20171024100735p:plain

抜粋ですが大きな特徴としては以下の点でしょうか。

  • First-class Docker Support: DockerのネイティブサポートとDockerレイヤーキャッシュの導入
  • Workflows: ビルド、テスト、デプロイをジョブとして柔軟に管理できるようになった
  • Advanced Caching: キャッシュの保存とリストアをイメージ、ソースコード、依存関係に対して行うことができるようになった。

この辺りの機能を活用しCIの速度改善へ繋げてみたいと思います。

ジョブメドレーのアプリケーション構成

移行の前提として、ジョブメドレーのアプリケーション構成について記載します。

f:id:medley_inc:20171024100814p:plain

フロントエンドのビルドをyarn+webpackで行い、生成したアセットをpublic/assetsへ吐き出し、manifestファイルのパスをrailsのhelper経由で取得し読み込んでいます。(Rails4.2.x)

このような構成のアプリケーションをCirlceCI2.0でビルド、テスト、デプロイ出来るようにしていきます。

config.ymlの全体像

今回、作成したconfig.ymlはこのような形になりました。

ざっくりは

  • build: bundle install, yarn install
  • code_analyze: rubocop, brakeman, scss-lint
  • rspec
  • deploy: capistrano

を行っており、ブランチによってどのジョブを実行するのかをworkflowsを利用して使い分けています。

詳しい解説は以降、記載していきます。

defaults: &defaults
    working_directory: ~/job-medley
    docker:
      - image: circleci/ruby:2.4.2-node-browsers
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
      - image: circleci/mysql:x.x.x
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
      - image: redis:x.x.x
        environment:
          TZ: /usr/share/zoneinfo/Asia/Tokyo
version: 2
jobs:
  build:
    <<: *defaults
    steps:
      - checkout
      - restore_cache:
          key: job-medley-app-{{ checksum "Gemfile.lock" }}
      - run:
          name: bundle install
          command: bundle install --jobs=4 --path=vendor/bundle
      - save_cache:
          key: job-medley-app-{{ checksum "Gemfile.lock" }}
          paths:
            - vendor/bundle
      - restore_cache:
          key: job-medley-yarn-{{ checksum "yarn.lock" }}
      - run:
          name: Yarn install
          command: |
            echo "Node $(node -v)"
            echo "Yarn v$(yarn --version)"
            yarn config set cache-folder ./yarn_cache
            echo "Yarn v$(yarn cache dir)"
            yarn install
      - save_cache:
          key: job-medley-yarn-{{ checksum "yarn.lock" }}
          paths:
            - node_modules
            - yarn_cache
      - persist_to_workspace:
          root: ~/job-medley
          paths:
            - ./*
  code_analyze:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run rubocop
          command: bundle exec rubocop
      - run:
          name: run brakeman
          command: bundle exec brakeman -qz
      - run:
          name: run scss-lint
          command: bundle exec scss-lint
  rspec:
    parallelism: 2
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - restore_cache:
          key: job-medley-elasticsearch
      # rspecでesのコマンドを一部実行しているため、primary container側へinstall
      - run:
          name: Elasticsearch install
          command: |
            wget https://download.elasticsearch.org/elasticsearch/elasticsearch/elasticsearch-x.x.x.tar.gz && \
              tar -xvf elasticsearch-x.x.x.tar.gz && \
              if [ -z "`elasticsearch-x.x.x/bin/plugin -l | grep analysis-kuromoji`" ]; then \
              elasticsearch-x.x.x/bin/plugin -install elasticsearch/elasticsearch-analysis-kuromoji/x.x.x; fi
      - save_cache:
          key: job-medley-elasticsearch
          paths:
            - ./elasticsearch-x.x.x
      - run:
          name: database create
          command: bundle exec rake db:create
          environment:
            RAILS_ENV: test
      - run:
          name: run test
          command: |
            circleci tests glob 'spec/**/*_spec.*' \
              | circleci tests split --split-by=timings --timings-type=filename \
              | tee -a /dev/stderr \
              | xargs bundle exec rspec \
              --profile 100 \
              --format RspecJunitFormatter \
              --out rspec/rspec.xml \
              --format progress
          environment:
            RAILS_ENV: test
            TEST_CLUSTER_COMMAND: elasticsearch-x.x.x/bin/elasticsearch
      - store_artifacts:
          path: artifacts/
      - store_test_results:
          path: rspec/
  deploy_qa:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run deploy
          command: |
            sh scripts/init_deploy.sh
            BRANCH="${CIRCLE_BRANCH}" bundle exec cap develop deploy deploy:restart
  deploy_only:
    <<: *defaults
    steps:
      - attach_workspace:
          at: ~/job-medley
      - run:
          name: run deploy
          command: |
            BRANCH="${CIRCLE_BRANCH}" bundle exec cap production deploy deploy:restart
workflows:
  version: 2
  workflows:
    jobs:
      - build
      - code_analyze:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - rspec:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - deploy_qa:
          requires:
            - code_analyze
            - rspec
          filters:
            branches:
              only: develop
      - deploy_only:
          requires:
            - build
          filters:
            branches:
              only: /^sandbox.*|^master$|^staging$/

DockerImageの選定

元々、Dockerを使わずにCIを回していましたが、CircleCI2.0へ移行するに辺りDockerへの利用に切り替えました。

Specifying Container Images

DockerImageはDockerHubへ登録されているCircleCI公式のもを利用しました。

アプリケーションの一部でReactを使用しており、フロントのビルドはyarn+webpackを利用しています。その為、以下のimageを選択しました。

  • circleci/ruby:2.4.2-node-browsers
    • nodeのインストールと、E2Eのテストに必要なソフトウェアがインストールされています。
    • ※詳細はこちらの記事を参考にさせていただきました。

その他、現在利用しているMySQLのバージョン、ElasticCacheRedisのバージョンと合わせたimageを選択しました。

注意点としては、複数のDockerImageを利用する場合、一つ目に指定したimageがprimaryとして扱われます。

以下の例ですと、Rubyのimageを最初に指定し、MySQL、Redisのimageを指定していますが、MySQLコマンド自体はRubyのimageに含まれていないため、Rubyコマンドを実行できてもMySQLコマンドを実行することは出来ません。

※詳細はこちらに記載されています。

docker:
  - image: circleci/ruby:2.4.2-node-browsers
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo
  - image: circleci/mysql:x.x.x
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo
  - image: redis:x.x.x
    environment:
      TZ: /usr/share/zoneinfo/Asia/Tokyo

また、用意されているimageをカスタマイズする必要がある場合は、Dockerでカスタムimageを作り、publicで良ければDocker Hubへ登録、privateが良ければAmazon EC2 Container Registryへ登録しておくことで呼び出すことも可能になっています。

※Using Custom-Built Docker Images circleci.com

※Using Private Images circleci.com

buildの設定とcache

CIで実行するアプリケーションのbuildに関してです。

checkoutgithubからコードをcheckoutし、その後の定義でアプリケーションのインストール、キャッシュ保存、キャッシュの展開を行っています。

steps:
  - checkout
  # Rails application setup
  - restore_cache:
      key: job-medley-app-{{ checksum "Gemfile.lock" }}
  - run:
      name: bundle install
      command: bundle install --jobs=4 --path=vendor/bundle
  - save_cache:
      key: job-medley-app-{{ checksum "Gemfile.lock" }}
      paths:
        - vendor/bundle
  • save_cache: keyにGemfile.lockを指定することでキャッシュキーとして扱い、pathsに設定したパスをキャッシュするようにしています。
  • restore_cache: keyと一致するキャッシュがあれば、save_cache時に指定したパスを展開し直しています。
  • run: こちらはrailsのアプリケーションインストールしているだけです。

CircleCI1.0よりもキャッシュ管理を柔軟に行えることがわかります。

circleciコマンドによるRspecの並列実行

rspecによるテストの実行に関してです。 circleci コマンドを利用することでテストの並列実行を効率的に行うことが出来るます。

今回は --split-by=timings --timings-type=filename のオプションを指定し、ファイル名ベースでの分割でテストを実行します。

- run:
    name: run test
    command: |
      circleci tests glob 'spec/**/*_spec.*' \
        | circleci tests split --split-by=timings --timings-type=filename \
        | tee -a /dev/stderr \
        | xargs bundle exec rspec \
        --profile 100 \
        --format RspecJunitFormatter \
        --out rspec/rspec.xml \
        --format progress
    environment:
      RAILS_ENV: test
      TEST_CLUSTER_COMMAND: elasticsearch-x.x.x/bin/elasticsearch
- store_artifacts:
    path: artifacts/
- store_test_results:
    path: rspec/
  • store_artifacts: 以前からもある機能ですが、テスト結果の成果物を保存するパスになります。
  • store_test_results: こちらはテストの実行結果を保存しておくことで、コンテナ間でrspecの実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、workflowsを利用するとサポートされないようです。

※参考: https://circleci.com/docs/2.0/configuration-reference/#store_test_results

このような形でArtifactsが保存されています。 f:id:medley_inc:20171024101642p:plain

また、artifactsにはcoverageとcapybaraのscreenshotなどを保存しています

- simple_cov
    if ENV['CI']
      SimpleCov.coverage_dir File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'coverage')
      SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
        SimpleCov::Formatter::HTMLFormatter
      ]
      SimpleCov.start do
        add_filter '/vendor/'
        add_filter '/spec/'
        add_filter '/config/'
        add_filter '/db/'
      end
    end
  • capybara
    • Capybara.save_and_open_page_path = File.join(ENV['CIRCLE_WORKING_DIRECTORY'], 'artifacts', 'capybara') if ENV['CI']

※CircleCI2.0からCIの環境変数が変わっています。詳細は以下のリンクへ記載されています。 circleci.com

Workflowsの設定

CircleCI2.0からWorkflowsの利用が可能になりました。 circleci.com

コンテナ毎に分割しジョブを実行することで更なる並列実行の効率化、及び、ジョブ間の依存関係まで設定できるようです。 ジョブメドレーではコードの静的解析に少し実行時間が掛かっていることから、多少の改善を図れると考え単純に使ってみたかったこちらの機能を利用してみました。

workflows:
  version: 2
  workflows:
    jobs:
      - build
      - code_analyze:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - rspec:
          requires:
            - build
          filters:
            branches:
              ignore: /^sandbox.*|^master$|^staging$/
      - deploy_qa:
          requires:
            - code_analyze
            - rspec
          filters:
            branches:
              only: develop
      - deploy_only:
          requires:
            - build
          filters:
            branches:
              only: /^sandbox.*|^master$|^staging$/
  • build: 各ジョブで実行前に行っておく処理を定義
  • code_analyze: rubocop、brakeman、scss-lintなどの静的解析処理を定義
  • rspec: アプリケーションのテストを定義
  • deploy: デプロイに関する処理を定義

ジョブメドレーでは、ブランチ管理にGit-flowを採用していますが、それとは別にsandboxというテスト環境を用意し運用しています。 developブランチでコード解析やテストをクリアしたコードだけ、masterへ反映し、masterではテストフェーズなしにdeployする構成を取っています。 極力、CIのリソースを節約するように各ブランチごとで実行する処理を分けています。

各ブランチの運用は以下の通りです。

  • feature:
    • コード解析、テスト実行
  • sandbox:
    • デプロイのみ実行(一時レビュー用ブランチ)
  • develop:
    • コード解析、テスト実行、デプロイ実行
  • master:
    • デプロイのみ実行

上記の例では、

  • code_analyze:
    • sandbox、master、stagingブランチ以外は実行
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。
  • rspec:
    • sandbox、master、stagingブランチ以外は実行
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。
  • deploy_qa:
    • developブランチでのみ実行
    • requiresで依存関係を指定し、code_analyze、rspecが正常に終了しなければ実行されないようになっています。
  • deploy_only:
    • sandbox、master、stagingブランチのみ利用するジョブ
    • requiresで依存関係を指定し、buildが正常に終了しなければ実行されないようになっています。

どのようなworkflowが出来あがるのか、以下に例を示します。

  • featureブランチの例: f:id:medley_inc:20171024101757p:plain

  • developブランチの例: f:id:medley_inc:20171024101818p:plain

  • master、sandboxブランチの例: f:id:medley_inc:20171024101852p:plain

今回の例だとブランチ毎にworkflowを変えているため、ignore、onlyの書き方で意図せず振り分けされないように考慮は必要ですが、柔軟にworkflowを作れることがわかると思います。(やり過ぎると読み解くのが大変になりそうですね)

Workflowsに関してはこちらに色々なパターンの組み方が記載されているので、こちらを読むとより理解が深まると思います。

ジョブ間でのデータ共有

ジョブを分けてビルドする=何回もアプリケーションの初期化が必要なんじゃないか? と当然疑問に思う点ではありますが、それに対する解決策も用意されています。

build:
  steps:
    ===省略===
    - persist_to_workspace:
        root: ~/job-medley
        paths:
          - ./*

deploy_qa:
  <<: *defaults
  steps:
    - attach_workspace:
        at: ~/job-medley
  • persist_to_workspace: 指定したパスにあるデータを一時的に保管してくれます
  • attach_workspace: 保管済みのデータを展開してくれます

この機能により、ビルドプロセスで生成したものを各ジョブで実行するコンテナへ渡すことが出来ます。 ただし、そもそもビルドプロセスでキャッシュを入れていることもあり、これ自体の効果は殆どありませんでした。 コンパイル済みのデータを受け渡す際には効果を発揮しそうですね。(公式でもそのような利用を想定していそうです)

改善結果

肝心の速度改善結果です。結果は以下の通りになりました。

改善前: CircleCI1.0

  • rspecの実行時間: 26:59 f:id:medley_inc:20171025162233p:plain

以下は、CircleCI2.0へ移行しただけの結果です。このケースではworkflowsを利用していません。

改善後: CircleCI2.0

  • rspecの実行時間: 19:40 f:id:medley_inc:20171024102115p:plain

CircleCI1.0からCircleCI2.0へ移行することにより、約12分程テストの実行時間を短縮することが出来ました。 Workflowsなど特に利用していない、かつ、ビルドフェーズの実行時間も関係しないため、CircleCI2.0を利用するだけで単純にテスト実行速度の向上を見込めることがわかると思います。

続いてWorkflowsを利用した結果です。

改善後: CircleCI2.0 with Workflows

  • rspecの実行時間: 21:14 f:id:medley_inc:20171024102148p:plain

結果は、Workflowsを利用しないケースと、利用したケースでは、Workflowsを利用したほうがrspecの実行時間は長くなってしまいましたが、build-code-analyze-rspecの実行に掛かったトータルの時間に差は見られませんでした。

これは、「circleciコマンドによるRspecの並列実行」のセクションへも記載した通り、store_test_resultsがサポートとされないことにより、コンテナ間での分散が最適化されていない為です。

コンテナ間でrspecの実行時間にばらつきが出ないよう、対象のファイルを最適に振り分けてくれるようなのですが、Workflowsを利用するとサポートされないようです。

実行時間にばらつきが出てしまい、code_analyzeのジョブを分散することで見込んでいた改善時間(約3分)とばらつきにより発生したテスト実行時間のロス( (20:01 - 14:57) / 2 = 2:32 )が大体同じであるため、トータルでの実行時間に差が出ない結果となりました。

ばらつきを出さない方法や、ジョブの分け方については今後も工夫してみたいと思います。 また、フロントエンドのテストをもう少し厚くしていきたいと考えているので、フロントエンドのテスト、サーバサイドのテストをWorkflowsを上手く使いながら分散していければ良いのかなとも思っています。

さいごに

移行に際して、CircleCI2.0の移行ガイドを読みながら進めていましたが、基本的な記法の変更、timezone、environmentの定義方法の変更、variableの変更などが多々有り、ドキュメントを結構読み込まないとどこに何が定義できるのか把握できませんでした。

また、Workflowsの組み立てなど公式に良いサンプルは沢山あるのですが、依存関係の定義を色々試すのに苦労した気がします。

※素直に小規模なアプリを用意して、ローカルでcircleciを実行してみた方が効率良く進められたかもしれません。

※ただ、公式のドキュメントCommunityForumをしっかり読めば余すことなく情報は合ったので非常に助かりました。

CircleCI2.0でどのような事が出来るのか、それはどのように行えるのか。 この記事がその概観をつかむ助けになれば良いなと思っています。

参考リンク

CircleCI2.0へ移行するにあたり、以下の記事を参考にさせていただきました。ありがとうございます。 qiita.com medium.com

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。

開発本部のセキュリティ知識を底上げする、タスクフォースの進め方

ジョブメドレーの開発運用を担当している新居です。
メドレーでは開発本部のメンバーの技術力底上げや課題解決を目的とした短期プロジェクト(タスクフォースと呼んでいます)を実施しています。この取り組みの一環として、6〜8月はセキュリティ知識の底上げを目指した「セキュリティタスクフォース」を実施しました。今回は、その取り組み内容を紹介します。

背景

現在、メドレーの開発本部には約20名のエンジニアが在籍しており、それぞれ多種多様な開発経験やスキルセットを持ったエンジニアが集まっています。
そして、前職ではフロントエンド専門でやってきたエンジニアもサーバーサイドの開発を行ったり、またその逆のケースもあったりと、各自の専門領域にとらわれないスタイルでの開発を行うことも多々あります。

課題

そういった背景の中、エンジニアが増えていくにつれエンジニア間のスキルセットに差が生まれ、ばらつきが見られるようにもなっています。今後、更に組織が拡大していくのに伴い、こうしたエンジニア間のスキルセットの差も大きくなっていくことが懸念されます。
 
また、メドレーは医療ヘルスケア分野に向けてサービス提供を行っており、多くの個人情報を扱っていることはもちろん、医療・介護という領域の性質上、セキュリティには非常に気をつかう必要があります。データベースに入れて保守・運用している個人情報などの取り扱いや、システム改善や新機能開発などを行うときには必然的にセキュリティにも配慮して開発を進める必要があります。
 
そして背景のところでも述べたように、元々フロントエンド専門のエンジニアがサーバーサイドの開発に関わるケースや、そもそもサーバーサイド開発の経験が浅いエンジニアも中にはいて、サーバーサイドのセキュリティに関して自信がなかったり、具体的な対策方法がすぐに出てこないこともあるという課題がありました(当然ですが実際の開発ではPRなどでレビューして問題が起きないように対応しています)。
 
そこで、そういったメンバーのセキュリティ知識の底上げを行い、エンジニア間のスキルセットの差を縮めていくことが必要であると考えました。

取り組み

上述した通り、
  • スキルセットにばらつきや差が見られる
  • 医療ヘルスケア分野は特にセキュリティに気を使う必要がある
  • セキュリティに関して自信がない、具体的な対策方法がすぐに出てこないこともある
といった課題感から、まずはフロントエンド専門でやってきたメンバーやサーバーサイド開発の経験が浅いメンバーをメインターゲットとして、セキュリティ知識の底上げをやっていくことにフォーカスしました。
 
目標としては、ウェブアプリケーション開発における最低限のセキュリティ知識や対策方法をしっかり再整理し、さらに開発で使っているRuby on Rails上でどのように対策するべきかを押さえるというところを目標におきました。

形式

形式としては、参加者に事前に教材の対象範囲を読んできてもらい、隔週開催のTechLunch(社内勉強会)終了後の約20分を利用して、内容の簡単な説明や補足、質疑応答、議論などを行う場(フォロー会)を設けました。
 

教材

以下を使用しました。
 
メイン教材:「IPAの安全なウェブサイトの作り方」
 
サブ教材:「Railsセキュリティガイド」
 
メイン教材の「IPAの安全なウェブサイトの作り方」は、IPAが届出を受けた脆弱性関連情報を基にして作られており、セキュリティを考慮したウェブサイトを作る上での最低限の知識が整理できるだろうということで採用しました。
 
サブ教材の「Railsセキュリティガイド」は、Railsではどのようにセキュリティの問題を回避しているのかといった方法が解説されており、実際の開発のときにどうすれば良いのかといったことが押さえられるだろうということで採用しました。
 
実際の開催スケジュールと、フォロー会の対象範囲はこちら。

f:id:dev-medley:20170925122130p:plain

取り組みを終えて

取り組みを終えて感じたこととしては以下になります。

技術的な観点

  • SQLインジェクションXSSCSRFなどのメジャーな攻撃手法を参加者間で再整理できた
  • 技術的に不安なところなどは「ここはこうですよね?」という感じで確かめ合うことができた
  • 参加者が前職での経験談などをシェアしてくれる場面もあり、知見の共有ができた
  • セキュリティに詳しいエンジニアも交えて話すことで、随所で効果的にツッコミをいただき、濃密な議論ができた
  • ベテランエンジニアからは、昔流行った某サービスの某セキュリティ系障害の有名事例なども共有され、過去の歴史を知ることができる場となった

技術以外の観点

  • 普段はチーム毎に黙々と仕事に取り組んでおり、チームを跨いであるひとつの話題(今回はセキュリティ)についてみんなと会話する機会は少ないので、知識の底上げはもちろんのこと、コミュニケーションの場としても良かった
今回のように質疑応答や議論ができる場を設けることにより、他のメンバーの経験や知見も効果的に共有することができ、教材の読み合わせや講義形式では得られない知識も共有でき、取り組みとしてはうまくいったかなあと思います。

まとめ

ということで、今回はセキュリティタスクフォースについてご紹介しました。
 
セキュリティの知識はだれかひとりが押さえていれば良いというものではなく、開発に関わるエンジニア全員が最低限は押さえておく必要があると思います。
 
組織の拡大と共に、日々のプロダクトの運用・開発も大切ですが、それらを支えるエンジニアの知識の底上げなどの開発以外の部分もより大切になってきますし、そういった取り組みが組織力を高めていくのではないかと思います。

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

医療や介護とは全く違う業界で経験を積んできたエンジニア・デザイナーが多いですが、こうした定期的な勉強会などで必要知識をインプットしながら開発しています。

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております!

runitが便利なので、使い方を紹介した話〜メドレーTechLunch〜

メドレー開発本部のnakataniです。

開発本部で定期的に開催している勉強会「TechLunch」で、runitというunixのプロセススーパバイザについてお話しました。 その内容について紹介させていただきます。

runit自体は特に目新しい技術ではなく(Linuxbusyboxに収められていたりする枯れた技術です)、大して難しい話題でもありません。

ただ、個人的には便利に使っている手放せないツールであり、もしスーパバイザというものの存在を知らずに使わずにいる人がいると勿体無いなあという思いから、TechLunchのテーマとして取り上げた次第です。

runitとはなんなのか

プロセスをデーモンとして立ち上げて、プロセスが死んでも再度起動し続けてくれるツール郡です。C言語で開発されています。

Linuxなどのunixではたいてい標準でinit, Upstart, Systemd, launchdなどのスーパバイザが組み込まれています。 runitはそれらと同じような位置づけのものです。

qmailの作者であるdjbが作ったdaemontoolsの後継のプロダクトです。

runitがあると何が便利なのか

■マシンが起動しているかぎり、プロセスを動作させ続けることができる

マシンを立ち上げたあとに、起動コマンドを叩いたり、プロセスが落ちたときに再起動をする必要がありません。 また、フォアグラウンドで動作するプロセスを起動した後に、端末を切り離す操作をする必要もありません。

ただ、これは他のスーパバイザでも同じことが実現できます。

■その場しのぎで作ったスクリプトを、ほぼそのままデーモン化できる

Shell, Ruby, Perl, Haskell どのような言語で作ったスクリプトであっても、 シェルなどで実行可能なファイルがあれば、それをそのままデーモンとして実行することができます

スクリプトの標準出力をそのままログファイルとして扱うことができる

svlogdというプログラムが、デーモンの標準出力をログ化し、ローテーションなどの面倒も見てくれます。 自作のデーモンが思った通りに動かない際のデバッグが容易です。

macOSへの導入方法

macOSに導入するための手順を記載します。詳しくはスライドやrunitのドキュメントなどを理解して使うようにしてください。

XcodeやHomebrewをmacOSに導入していることが前提です。

/serviceをrootのrunitディレクトリとして設定する手順

$ brew install runit # macports feels better.
$ sudo mkdir /service
$ sudo cat <<EOF |sudo tee /Library/LaunchDaemons/runit.plist
<?xml version='1.0' encoding='UTF-8'?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd" >
<plist version='1.0'>
  <dict>
    <key>Label</key><string>runit</string>
    <key>ProgramArguments</key>
    <array>
      <string>sh</string><string>-c</string>
      <string>PATH="/usr/local/sbin:/usr/local/bin:$PATH"
    exec '/usr/local/bin/runsvdir' '/service'
      </string>
      <string>;</string>
      <string>--pid=exec</string>
    </array>
    <key>Debug</key><false/><key>Disabled</key><true/><key>KeepAlive</key><true/>
  </dict>
</plist>
EOF
$ launchctl load -w >/Library/LaunchDaemons/runit.plist
$ launchctl list | grep runit

自作スクリプトをデーモンにする手順

$ cd /service/
$ sudo mkdir -p hello/log # logを同時につくるとrunsvdirがlogの準備もする
$ cd hello
$ sudo cat <<EOF |sudo tee run
#!/usr/bin/env ruby
# 自作スクリプト
while true
  puts("hello ruby #{Time.now.to_i % 100}");
  STDOUT.flush();
  sleep(1);
end
EOF
$ sudo cat <<EOF |sudo tee log/run
#!/bin/sh
exec svlogd -ttt .
EOF
$ sudo chmod 755 run log/run
$ tail -F log/current # ログが見られる。
$ sudo sv st . # daemonの状態を確認する。
run: .: (pid 35517) 46s; run: log: (pid 34727) 456s
$ sudo sv st /service/hello/ # ディレクトリの指定方法は自由。
$ sudo sv t . # TERMシグナルをdaemonに送る
$ sudo sv st .
run: .: (pid 35589) 1s; run: log: (pid 34727) 470s # 起動時間が1sになってる。

まとめ

結局のところ、「使えばわかるし使わないと便利さがよくわからない」というのが正直なところです。 そのため、TechLunchにおいては、使うための手順を時間をかけて解説をするようにしました。

speakerdeck.com

みなさんも興味があれば、ぜひ導入して使ってみてください。

僕自身、開発マシンであるmacbook proにrunitを入れて、開発環境のRubyやMongoDB, Elasticsearch, Nginxなどのサーバ群、 定期的に動かしたいちょっとしたスクリプトなどをrunitで管理しています。

異なる設定のサーバ群を一つのマシンに同居させる場合も、設定ファイルを分けて別ポートで立ち上げたりしています。

以前のプロジェクトでは本番環境をrunitで構築したこともありますし、今のプロジェクトでも、たまったゴミデータを削除し続けるスクリプトをrunitで対応してそのまま放置(放置してもOKなくらいメンテナンスフリー)していたりします。

最近はクラウドやコンテナ技術が活況であり、環境を抽象化しようという流れがあります。しかしながら、そもそもプロセスやUNIX OS自体が環境を抽象化するための技術群です。そういった基本的な技術と仲良くすることで、物事がシンプルになることがあるのではないかと考えたりしながら、日々開発に取り組んでいます。

お知らせ

メドレーでは、医療介護の求人サイト「ジョブメドレー」、医師たちがつくるオンライン医療事典「MEDLEY」、口コミで探せる介護施設の検索サイト「介護のほんね」、オンライン診療アプリ「CLINICS」などのプロダクトを提供しています。これらのサービスの拡大を受けて、その成長を支えるエンジニア・デザイナーを募集しています。

www.medley.jp

メドレーで一緒に医療体験を変えるプロダクト作りに関わりたい方のご連絡お待ちしております。