Indeed におけるリリース工程の自動化

Indeed が急成長する中で、沢山の課題が生まれましたが、中でも特にリリース工程は、取り組みがいのあるものでした。ほぼ手動の工程はスケールできず、ボトルネックとなってしまいました。なので、私たちは独自のソリューションの開発を決めました。私たちが工程を自動化するなかで学んだ教訓は、ソフトウェアの質と開発者の善意を保っていきたいと考える、他の急成長中の組織も応用できるものとなりました。

私たちが辿った道のり

私たちのソフトウェアリリースの工程は、以下のように四つの主な目標があります。

  • どの機能がリリースされるのか理解していること
  • プロダクト間、チーム間での依存性について理解していること
  • リリース候補の中にあるバグを素早く修正すること
  • 追跡、分析、反復利用を可能にするため、リリースの詳細を記録すること:

そして、私たちの工程は最終的には以下のようになりました。

この工程は分かりやすかったのですが、多くの作業を必要としました。どんな状況だったかと言うと、4 件の新規機能を持つ 1 件のソフトウェアリリースに対し、100 回分のクリックと Git のアクションが必要でした。1 件の新規機能につき、およそ 13 回分のアクションが工程に追加されました。

こうした工程の確認を通して、私たちは、4つの主な問題を見つけ出しました。

  • リリース管理に時間がかかりすぎた
  • リリースの中身が正確には何なのかを理解しにくかった
  • 手動のステップが多すぎたため、エラーが発生する可能性が高かった
  • リリースをきちんと扱える知識を持つのはシニアマネージャーだけに限られていた

そうして、私たちは気づきました。自動化がもっと必要だ、と。

単純化すれば良いじゃない?

もちろん、工程を自動化するよりも、ただ単純化することもできました。しかし、私たちのもつ工程は、無くしたくない二次的な利益がありました。

データ 工程が与えてくれる多くのデータと指標のおかげで、継続して改善を重ねることが可能

履歴 工程があるので、何がいつリリースされたかを追跡することが可能

透明性 複雑ではあるものの、工程のおかげで各ステップを検証することが可能

自動化までの道

私たちは、工程の多くを自動化し、オーバーヘッドを減らせることに気づきました。そのために、既にあるソリューションをもっと上手に、賢くまとめる必要がありました。

私たちの工程は、以下の複数のシステムを使用しています。

  • Atlassian JIRA: 課題管理と追跡
  • Atlassian Crucible:  コードのレビュー
  • Jenkins: リリース候補のビルドとデプロイ
  • Gitlab:  ソース管理
  • 様々なビルドや依存性の管理ツール

これらのツールを置き換える代わりに、これらツールが各自コミュニケーションをとれるように、統一されたリリースシステムを作り上げることにしました。この統一したリリースシステムを、管制塔を意味する
Control Tower  (コントロールタワー) と呼ぶことにしました。

依存性の管理ツールとの統合によって、リリースマネージャー(RM)がライブラリ・アップデートを通して入ってくる新しいコードの追跡を出来るようになりました。RM は素早くコードの相互依存性を評価し、リリース内の変更の進捗を確認することができます。最後に、RM が全て確認した後、 Jenkins を通してビルドの実行が可能になります。

Control Tower のメイン画面からは、RM が全ての関連システムの詳細を確認することができます。変更は JIRA のイシューによってまとめられ、各変更項目はCrucible のコードレビュー情報と Git のレポジトリ・ロケーションのリンクを含んでいます。

自動化することで、リリース工程の中で必要だった人間同士のやりとりを劇的に減らしました。以下の画像は、グレイの色がついたボックスそれぞれが、削除された手動のステップを表しています。

自動化をすすめた後では、必要なクリックや Git のアクションの回数を、100 回以上から、15 回未満まで減らしました。新規機能を追加するのも、13 回のアクションを必要とするだけで、余分な仕事を増やすことがなくなりました。

Control Tower に興味を持った方は、是非 Indeed Eng テックトークをご覧下さい。Control Tower について、32:45 ごろから説明しています。

私たちが学んだこと

統一されたリリースシステムを作り上げる過程で、私たちはいくつか大切なことを学びました

教訓その1:
自動化すべきは欲しい工程でなく、既にある工程

私たちが最初にリリース工程の自動化に着手した時、私たちは、ついエンジニアがそういう状況でしてしまうことをしました。それは、始める前に、工程を可能な限り理解できるように、工程を学ぶ、ということでした。それから、またエンジニアがやりがちなことですが、それを改善しようとしました。

自動化を進める中で工程を修正する、ことは当然です。けれど、例え問題があったとしても既にテスト済みの稼働する工程のほうが、格好よく見えるテストされてない工程よりも好ましい、と言うことを私たちは学びました。自動化の最初の試みは、開発者たちが新しいやり方に馴染みがなかったので、反発を受けました。

教訓その2:
自動化は想像以上に有意義である

多くの人が工程を「自動化」と聞くと、人間による意思決定を削除して、設定したら後は忘れてしまうようなものだと考えがちです。しかし、場合によっては工程から人間のやりとりを削除できないこともあります。技術的に難しすぎる場合や、正しい結果になるのをきちんと確かめるために、人間の目でチェックしたい場合もあります。そういった状況でも、自動化が役立つのです。

場合によっては、自動化はデータを集めて表示し、人間が決断を下すスピードを上げてくれるのです。何かを選択する際に人間の手が必要な際にも、より良いデータを提供することで、さらなる情報に基づいた決断を下しやすくなる、ということに私たちは気づきました。

人間と機械のアクションの適切なバランスを決めるのは、自動化にとっての鍵となります。機械学習の技術を取り入れることで、人間による意思決定をさらに加速させるような改善も、将来行えるようになるでしょう。

教訓その3:
何がなんでも透明性が大事

エンジニアは、非効率なものを好みませんが、謎も同様に嫌います。「何故」「どうやって」などの洞察を与えることなく、全て行ってしまう、ブラックボックス化した工程は避けたいと考えました。

私たちは、できる限りログを取り、メッセージを使用して、十分な透明性を提供しています。どんな工程が実行されたか、そして何故それが実行されたかを、開発者が検証できるようにすることで、彼らが自動化のソリューションを信頼し、受け入れやすくしました。また、ログは、何か不具合が発生するようなことがあれば役立ちます。

そして、これから

新しいシステムの環境が整ったとはいえ、私たちは、これすらも改善できると考えています。次なるステップも、密かに取り組んでいるところです。

課題のステータス、完了したコードレビュー、ビルド/テストのステータス、そしてその他の外部の要素を監視できるアルゴリズムを、現在私たちは開発しています。リリースの準備ができた機能を、プログラムで理解することができるシステムを開発できるのです。そして、自動で適切なマージリクエストを作成し、リリース工程を進める事ができるのです。これは、さらに機能の作成と本番に送り出すまでの時間を削減してくれます。

機械学習の技術を利用して、意思決定のプロセスで使用する膨大な量のデータを取り込むことができます。これはリスキーなデプロイを指摘し、さらにテストに時間を費やすべきか、最小の監視だけでデプロイが可能かどうかを教えてくれるのです。

私たちのリリース管理システムは、顧客が望む品質を維持しながら、ソフトウェアのアウトプットを増やすための、重要なステップです。このシステムは、あくまでステップであり、最終的なゴールではありません。継続して工程を改善していくことで、そして進んで行く中で学ぶことで、私たちは、「さらに多くの求職者と仕事の橋渡しをする」という、最終的なゴールを目指しているのです。

 

 

 

Tweet about this on TwitterShare on FacebookShare on LinkedInShare on Google+Share on RedditEmail this to someone

Statusを使用した機能の グレイスフルデグラデーション

以前の記事で、 Statusのライブラリを使用した、アプリケーションの堅牢なヘルスチェックを行う方法を説明しました。本記事では、

  • アプリケーションから主要でない機能を削除
  • データセンターのロードバランサから一件のアプリケーションのインスタンスを削除
  • DNSレベルでローテーションから全データセンターを削除

と言った作業を行ったことで停止(outage)した際に、アプリケーションを確認しデグレデーションを行う方法を書いていきたいと思います。

アプリケーションの動作状態の確認

Status ライブラリを使用すると、単一の依存性チェックと、システム全体の評価という、二種類のチェックをシステムに対して行うことが可能です。依存性というのは、システムが機能するために必要なシステムやサービスを指します。

単一の依存性チェックの間、DependencyManager は、依存性のIDを取得し、CheckResultに返す評価方法を使用します。

CheckResultは以下を含みます。

  • 依存関係の状態
  • 依存関係に関する一部の基本的な情報
  • 依存関係の状態を評価するのにかかった時間

CheckResultはJavaのenum(列挙型)で、OK, MINOR, MAJOR, OUTAGE などの一つです。OUTAGE のステータスは依存関係が使用できないことを示します。

final CheckResult checkResult = dependencyManager.evaluate("dependencyId");
final CheckStatus status = checkResult.getStatus();

アプリケーションの状態を評価する二つ目のアプローチには、システム全体を見るやり方があります。これは、高いレベルで全体のシステムがどう処理しているかという全体図を見せてくれます。一つのシステムが OUTAGE の状態にある場合、これはあるアプリケーションのインスタンスが使用不可能であることを示します。

final CheckResultSet checkResultSet = dependencyManager.evaluate();
final CheckStatus systemStatus = checkResultSet.getSystemStatus();

システムが正常でない場合には、システムに送ったリクエストを短絡させ、HTTP ステータスコード500 (“Internal Server Error”) を返すのが、多くの場合、最良とされます。下記の例では、Springでインターセプターを使用し、リクエストをキャプチャし、システムの状態を評価し、アプリケーションが停止している場合にはエラーの応答をします。

public class SystemHealthInterceptor extends HandlerInterceptorAdapter {
    private final DependencyManager dependencyManager;

    @Override
    public boolean preHandle(
            final HttpServletRequest request,
            final HttpServletResponse response,
            final Object handler
    ) throws Exception {
        final CheckResultSet checkResultSet = dependencyManager.evaluate();
        final CheckStatus systemStatus = checkResultSet.getSystemStatus();
        
        switch (systemStatus) {
            case OUTAGE:
                response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value());
                return false;
            default:
                break;
        }

        return true;
    }
}

依存関係の状態を比較

CheckResultSetCheckResult は、それぞれ現在のシステムの動作状態や、依存関係の状態を返す方法を持っています。CheckStatus を手に入れさえすれば、結果の比較ができる方法もいくつか出てきます。

isBetterThan() は、現在の状態が与えられた状態よりも良いかどうか判断します。これは他を含まない排他的な比較です。

CheckStatus.OK.isBetterThan(CheckStatus.OK)              // evaluates to false
CheckStatus.OK.isBetterThan(/* any other CheckStatus */) // evaluates to true

isWorseThan() は、現在の状態が与えられた状態よりも悪いかどうか判断します。この操作も、他を含まない排他的な比較になります。

CheckStatus.OUTAGE.isWorseThan(CheckStatus.OUTAGE)          // evaluates to false
CheckStatus.OUTAGE.isWorseThan(/* any other CheckStatus */) // evaluates to true

isBetterThan() と isWorseThan() の方法は、評価した依存関係が望ましい状態にあるかを確認できる、優れたツールです。しかし、残念ながらこれらの方法はグレイスフルデグレデーションを行うには、十分なコントロールが利きません。システムは、正常か、停止してしまっているか、のどちらかしかないのです。システムのグレイスフルデグレデーションを、さらにコントロールするために、新たに別の二つの方法が必要となりました。

noBetterThan() は、二つの状態のうち、正常でない方を返します。

CheckStatus.MINOR.noBetterThan(CheckStatus.MAJOR) // returns CheckStatus.MAJOR
CheckStatus.MINOR.noBetterThan(CheckStatus.OK)    // returns CheckStatus.MINOR

noWorseThan() は二つの状態のうち、正常な方を返します。

CheckStatus.MINOR.noWorseThan(CheckStatus.MAJOR) // returns CheckStatus.MINOR
CheckStatus.MINOR.noWorseThan(CheckStatus.OK)    // returns CheckStatus.OK

完全にシステム評価をする間、私たちはこれらの方法の組み合わせと、Urgency#downgradeWith() という方法を使用して、アプリケーションの動作状態のグレイスフルデグレデーションを行います。

停止した状態を調査できる機能を持つことで、依存関係の状態に基づいて、エンジニアは機能を表示するかどうかを動的に切り替えることが可能です。仮に、企業情報を提供する私たちのサービスがデータベースに到達できなかったとします。このサービスのヘルスチェックの状態はMAJOROUTAGE に変更します。私たちの求人検索のプロダクトは検索結果ページの右列にある、企業ページのウィジェットを外します。求職者と企業を結ぶ、核の部分となる機能には影響しません。

正常

正常でない(グレイスフルデグレデーション後)

サービスの動作状態に基づいて機能をコントロールできることだけがStatusの全てではありません。私たちは、フロントエンドのWebアプリケーションのインスタンスへのアクセスをコントロールするのにもStatusを使用しています。インスタンスがリクエストを処理できない場合、再び正常化するまで、ロードバランサからインスタンスを削除します。

インスタンスレベルでのフェイルオーバー

一般的に、本番環境にあるアプリケーションの複数のインスタンスを実行するのが広く推奨されています。これは、万一アプリケーション内の一件のインスタンスが停止した場合にも、リクエストを処理し続けるのを可能にすることで、システムを障害から復旧しやすくします。アプリケーションのこれらインスタンスは一つのマシン内にも、複数のマシンにも、そして複数のデータセンターにも、存在することが可能です。

Status ライブラリは、インスタンスが正常でなくなった際に、それを削除するようにロードバランサを設定することができます。下記の、一つのデータセンター内を表した基本例をご覧下さい。

  一つのデータセンター内のアプリケーションが全て正常に動作している場合、ロードバランサは、リクエストを均等に分散する。アプリケーションが正常な状態かどうかを判断するために、ロードバランサはリクエストをヘルスチェックのエンドポイントに送り、応答するコードを評価する。
インスタンスが正常でなくなった際には、
ヘルスチェックのエンドポイントは200番台以外のHTTPステータスコードを返し、トラフィックに応答しないように指示する。その後ロードバランサは正常でないインスタンスをローテーションから削除し、リクエストの受信を防ぐ。
インスタンス1がローテーションから削除され、データセンター内の他のインスタンスがインスタンス1のトラフィックに応答を開始する。各データセンター内では、複数のインスタスが停止した場合にも、トラフィックを処理できるように十分なインスタンスを設定している。

データセンターレベルでのフェイルオーバー

リクエストがデータセンターにすら送信される前、私たちのドメイン(例:www.indeed.com )はDNSを使用し、IPアドレスを解決します。私たちは、データセンター間で、トラフィックを地理的に分散する、グローバルサーバー・ロードバランサ(GSLB)を使用しています。GSLBがドメインを最も近くで利用可能なデータセンターのIPアドレスに解決し、その後、データセンターのロードバランサは上記で説明したように、トラフィックを転送し、フェイルオーバーさせます。

あるデータセンター全体がリクエストを処理できなくなったら、どうすればいいでしょうか?一件のインスタンスの場合のアプローチと同様に、GSLBは常に各データセンターの状態を(同じヘルスチェックエンドポイントを使用し)確認しています。GSLBが一つのデータセンターがリクエストを処理してないことを検知した場合、別のデータセンターにリクエストをフェイルオーバーさせ、ローテーションから正常でないデータセンターを削除します。繰り返しますが、こうして、停止状態の間ですら、リクエストの処理を確かにすることで、サイトを利用可能の状態に維持できるのです。

一つのデータセンターが正常でいてくれる限り、サイトはリクエストを処理し続けることが出来ます。正常でなくなったデータセンターに当たるユーザーには、この状態は、単にページの読み込みが遅くなったようにしか見えません。理想的でありませんが、処理されないリクエストにくらべれば、遅くてもエクスペリエンスを提供できる方がましでしょう。

最後の想定シナリオは、完全にシステムが停止した場合です。これは全データセンターが正常でなくなり、リクエストを処理できなくなった場合です。エンジニアというのは、こうした最悪の状況は回避しようとします。

Indeedが、完全なシステム停止に陥った場合には、私たちは各データセンターと各インスタンスにトラフィックを転送します。このポリシーは、オープンフェイルとして知られ、システムのグレイスフルデグレデーションを可能にします。各インスタンスは正常でない状態を通知してくるかもしれませんが、アプリケーションが何らかの形で作動する可能性があります。私たちは、どうにか何かが動いてくれる方が、全く動かなくなるよりも、ましだと考えるからです。

Indeedにもあなたにも役立つStatus

Statusライブラリは、Indeedで開発し、実行するシステムにとって、不可欠な部分です。私たちは以下の事にStatusを使用しています。

  • 迅速なアプリケーション・インスタンスとデータセンターのフェイルオーバー
  • コードが、トラフィックの大きなデータセンターに到達する前にデプロイの失敗を検知
  • 失敗するとわかっている作業をするのではなく、リクエストを素早くfailさせることで、アプリケーションの速さを維持
  • アプリケーションのサービスリクエストが、正常なインスタンスだけになっていることを確認し、サイトが利用可能であるように維持

Statusを始めるには、こちらのクイックスタートガイドを読み、サンプルを見てみてください。ご質問、ご不明な点は、Indeed GitHub Twitter までご連絡ください。

Tweet about this on TwitterShare on FacebookShare on LinkedInShare on Google+Share on RedditEmail this to someone

Indeed の新しいエンジニアリング・ マネージャーになったって? -まずは、コードを書いてもらおうか。

2016年の三月、ソフトウェアエンジニアのための「中途採用」のマネージャーとして、私は Indeed に入社しました。Indeed のエンジニアリング・マネージャーは、管理職としてのマネージャー業務に就く前に一般社員( individual contributor 以下本文では IC )として業務を始めます。私も、IC としてチームと仕事をすることで、より効率よくマネジメントをする準備ができました。

入社前には、どんな心構えをしておくべきかを数人のエンジニアリング・マネージャーと話しました。彼らは私に、入社後三ヶ月から六ヶ月間は、一般社員としての業務に就くとよいとアドバイスをくれました。それは、ユニットテストやコードを書き、変更の実行をし、コードレビューを行い、バグを修正し、ドキュメンテーションを書く、等を行う、ということでした。

このアプローチを聞いて、私はわくわくしました。なぜならエンジニアリング・マネージャーとして働いていたここ数年は、コードを書くなどの業務に残念ながらも貢献する機会がなかったからです。代わりに、私はコードレビューを行い、テクニカルデザインのレビューに参加し、チームの生産性を上げる機能やツールを作ることで、生き延びていました。

Indeed のエンジニア組織で、 IC として新しいマネージャーが就業を開始する際には、四半期の間、複数の異なるチームに参加するか、一つのチームに加わります。私の場合は後者で、収益の管理について取り組むチームに参加しました。

IC としての新入社員研修

私のマネージャーは、入社研修や、社内の wiki を使った自習スタイルの学習を行えるよう手助けしてくれました。新しい社員が、 Indeed で使用されるツールや技術について慣れ親しむために提供されたコンテンツ量に、私は感心しました。私の経験からですが、多くの企業は、便利なドキュメンテーションの作成や維持に対して投資していません。同僚のインディーディアン(Indeedian。社員のことをこう呼びます)[YI1] 達が快く質問に答えてくれたり、技術的な障壁にぶつかった際には助けてくれたりしたのも、同じくらい有益でした。新入社員にとって、こうした助けは本当にありがたいものでした。

そして、IC でいる間管理業務に関する責任はありませんでした。これも私には新鮮で… そして素晴らしかった!コードを書くのに集中できたからです。技術的な能力も積み上げられたし、しばらく使う必要のなかった、なまっていた思考プロセスの勘を取り戻すことができました。チームで使用されている手法やプロセスを観察し、どうやったら自分も同じように生産的になれるかを学びました。Git の使用についても、さらに深く学ぶ機会がありました。ユニットテストや DAO テストを書き、コードの範囲も増やしました。久々に、プロダクトの中の新機能のために、本番へ行くコードを書きました。

自分のチームが持つ 20 件の異なるプロジェクトにもっと早く自分を慣らすために、全てのコードレビューに自分を入れてもらうように頼みました。全てのプロジェクトには貢献できないとわかっていましたが、できる限り多くに触れたかったのです。私がこのお願いをする前には、開発者は通常数人をコードレビューに選んで、メインのレビュー担当者を一人選出していました。私は、全てのレビューに入れてもらっていたので、コードの変更とコードの改善方法を書いたチームメンバーのコメントを見ていました。コードレビューに書かれていたこと全てを理解したとは言いませんが、変更の種類への理解が深まりました。このアプローチは、マネージャーだけでなく、チームの新しいメンバー皆におすすめです。

他のアクティビティでも、自分のチーム以外の人と交流しやすくなりました。例えば、私は自分と面接をした人全員とランチ・ミーティングをいれました。これは、だいたい他のエンジニアリング・マネージャーとでしたが、プログラムマネージメントやテクニカルライティングのメンバーにも会いました。これらのランチ・ミーティングは、異なる職務の雰囲気をつかむことができました。各々がどう仕事を計画して優先順位を作っているかや、IC からマネージャーへ移行することへのそれぞれの考えなどを話しました。オンサイトで昼食(ところで、これが美味しいんです)を取ることで、エンジニアリング・チームの先輩たちや、他の部署の人とも出会うことができました。

マネージメント業務への移行

最初の四半期の終わりにさしかかる頃には、いくつかのプロジェクトに関わっていました。私のチームが所有する、重要なシステムのいくつかにも馴染んだところで、この頃、私は自分のマネージャーと管理業務への移行について相談しました。足場となっていく、しっかりした基礎を作れたね、と意見が一致したので、一対一の個人面談、四半期の評価、キャリア育成の相談を引き継ぎました。

技術にフォーカスし続けること

管理職を選ぶソフトウェアエンジニアの多くは、コードを書くのを諦めなければ、という考えに悩みます。しかし、リーダーシップを執る役職で、より重要なことは技術レベルでのチームへの参加です。この参加は、様々な形で行えます。Indeed のエンジニアリング・マネージャーは、抽象的なスキルと技術的な決定について、チームを指導します。マネージャー陣が、さらに深い技術への造詣を持っていれれば、彼らの役目もより効果的になります。

私も、ICとして働き始められたことで、チームの信用と尊敬を得られたのも嬉しいです。特に、Money チームのアクバー、ベン、チェン、エリカ、ケヴィン、リー、そしてリチャードには、感謝を述べたいと思います。

Tweet about this on TwitterShare on FacebookShare on LinkedInShare on Google+Share on RedditEmail this to someone