開発 | AI tech studio https://cyberagent.ai AI事業本部は、サイバーエージェントグループのデジタルマーケティング分野におけるサービス開発を行う組織です。 Wed, 21 Dec 2022 03:46:27 +0000 ja hourly 1 https://wordpress.org/?v=5.3.26 アプリ運用センター 内定者バイト「Four Keys計測による生産性の可視化」 https://cyberagent.ai/blog/pr/culture/18301/ Tue, 20 Dec 2022 09:41:48 +0000 https://ai-blog-admin.cyberagent.ai/?post_type=blog&p=18301 はじめに

初めまして、23卒エンジニア職内定者の丸山司と申します。

2022年9月7日から1ヶ月弱、サイバーエージェントのAI事業本部にある小売DX アプリ運用センターという部署で、サーバーサイドエンジニアとして内定者としてアルバイトをさせていただきました。

私は、開発者の生産性を向上させるためのあらゆるDevOps技術に興味があり(トランクベース開発、フィーチャーフラグ、自動テスト、リリースエンジニアリング等)、上記のようなDevOps技術を1つ経験することと、現場で働く中でエンジニアとしての目標を引き直すことを目的に内定者バイトに参加しました。

この記事では、実際に行った業務と学んだことについてまとめたいていきたいと思います。

アプリ運用センターとは

 

サイバーエージェントでは現在、小売/行政/医療などのドメインにおいて様々な企業様と提携し開発を行うDX事業に取り組んでいます。

アプリ運用センター(通称アプ運)はその中でも小売事業を展開されている企業様向けにアプリ開発/データ基盤構築をはじめとした開発を行う事業部です。POSシステムやAIカメラ、ビーコンなどの多岐にわたるテクノロジーを連携し、アプリ単体ではなく購買体験全体のUXを高めるアプリ開発を実現して行きます。(詳しくはこちらでご確認ください)

取り組んだこと

今回のインターンでは、バックエンド開発チームのFour Keysの計測と可視化を行いました。

Four Keysとは

Four KeysとはDevOps Research and Assesment (DORA) チームが実施した 6 年間の研究で明らかにしたソフトウェア開発チームの健全性を示す以下の4つの指標です。[1]

  • リードタイム:組織による正常な本番環境へのリリース頻度
  • デプロイ頻度:commit から本番環境稼働までの所要時間
  • 変更失敗率:デプロイが原因で本番環境で障害が発生する割合(%)
  • 復元時間:組織が本番環境での障害から回復するのにかかる時間

DORAの研究結果によると、Four Keysは開発チームの生産性だけではなく、サービスや企業の収益にも強い正の相関があると結論づけています。[2]

また、Four Keysは、CI/CDなどのDevOpsの実践と組織の開発文化が成熟しているチームほど優れた数値となっていることが明らかにされています。Four Keysの数値をきっかけに、開発チームの文化やDevOpsの実践について振り返ることが計測の目的です。

[1] エリート DevOps チームであることを Four Keys プロジェクトで確認する | Google Cloud Blog

[2] Nicole Forsgren Ph.D 他 『Lean と DevOps の科学』

計測の概要・流れ

現状、計測可能な「デプロイの頻度」と「変更のリードタイム」を計測しました。インシデントを管理できていないため、変更の失敗率や復元時間については、計測不可能でした。(実質、Two Keysです)

  • GitHub Webhookから送信されるイベントを集計して計測
    • mainブランチへのプッシュイベント
    • GitHub Actionsのデプロイワークフロー完了イベント

計測基盤の実装

計測基盤は、OSSであるGCP/fourkeysをベースに構築しました。

  • GitHub Webhook から送信されるイベントを受け取るサーバー(Event Handler)
  • イベントをパースしてBigQueryにログを吐くサーバー(Data Parser)
  • 各指標を可視化するGrafanaダッシュボード(Dashboard)

既存のOSSをベースとして、一部修正を行いました

  • 既存のOSSでは受け付けていない、GitHub Actionsの実行イベントをBigQueryに保存する処理を追加(Data Parser)
  • Grafanaダッシュボードを社内IPからのみアクセスできるアクセス制限を追加(Dashboard)

エリート DevOps チームであることを Four Keys プロジェクトで確認する | Google Cloud Blog より拝借

アプリ運用センターならではの計測

BtoBtoCという業態に由来する長い仕様決定のリードタイムがボトルネックとなり、頻繁なリリースが現状求められていません。短期的には、継続的なインテグレーションを目的として、マージを基準とした指標も計測した方がいいと考えました。1つの修正のサイズ(PRの大きさ)を小さくすることや、コードレビューを速くすることなどにより、マージを基準とした指標を改善することができます。

計測後の運用

私が内定者バイトを終えた後、「誰もダッシュボードの見方や活用方法がわからない」「継続的に数値を振り返えることを誰もしない」という懸念がありました。そこで、Four Keysがどう重要であるかやダッシュボードの見方、計測内容から改善が必要な箇所の提案などのドキュメントとして残しました。

Four Keys自体は、明確な開発のボトルネックを見つけることはできないので、開発プロセスを改善して、メトリクスの変化を評価するという改善サイクルを継続的に行っていく必要があります。入社後に、運用・改善を任せてもらって、チームの課題と対峙したいと思います。

学び・感想

内定者ランチで学びがたくさん

サイバーエージェントには、内定者バイトの学生と先輩エンジニアがランチに行って、技術や社内のことなどざっくばらんに話す文化があります。今回のインターンでは、AI事業本部含めて、社内の多くの先輩エンジニアとランチに行って、記事や書籍では知ることのできない技術的なお話をたくさんすることができました。

特に、私が最近気になっている技術・考え方であるトランクベース開発を実践されている江頭さんに、たくさん質問をさせていただきました。トランクベース開発は開発のアウトカムを向上させるプラクティスですが、多くの技術が下支えしていることを学びました。

技術的な広がり

ソフトウェアの設計やコード規約などのチーム課題、CI/CDの実現、リリースエンジニアリングなど、さまざまな技術が開発の効率を上げるために地続きでつながっていると感じました。

例えば、トランクベース開発で目的とする「重要なデプロイの心理的ハードルを下げる」ためには、Progressive Deliveryなどのリリースエンジニアリングを充実させたり、自動テストの適用範囲を広げたりなど密接に関わっています。ソフトウェアエンジニアとして大成するためには、繋がりを意識しつつ、技術習得をしていくことが大事だと感じました。

技術以外の部分・小売DXの面白さ

今回のFour Keysの計測をする中で、開発の生産性は技術的な部分に限らないことも感じました。例えば、企業様とやり取りをする業態の場合、企業様とどう仕様をすり合わせるかといった部分が開発のアウトカムを向上させる上で重要となります。

開発チームのマネージャーである後藤さんとお話した際に、「BtoBtoCという業態でのアジャイルを実現したい」とおっしゃっていて、小売DXドメインの面白さを垣間見ました。(後藤さんのインタビュー記事でも同様の内容をおっしゃられています)

最後に

アプリ運用センターはエンジニアの挑戦を歓迎している雰囲気があり、今回少し難しい課題でも挑戦させていただきました。また、技術習得にも積極的で、コミュニケーションもとりやすく、非常に働きやすい環境でした。メンターの坂上さんをはじめ、先輩の社員の方のサポートのおかげで、充実した1ヶ月を過ごすことができました。短い間でしたがお世話になりました!ありがとうございました!

]]>
ミニマムにはじめるイベントストーミング https://cyberagent.ai/practical-introduction-to-your-first-event-storming Fri, 09 Oct 2020 02:55:41 +0000 https://ai-blog-admin.cyberagent.ai/?post_type=blog&p=13996 こんにちは。AIRTRACK の豊田です。今回は技術マネジメントの話をします。

先日、チーム内でコロナ対策を万全に講じてイベントストーミングを実施してみました。

社内でもあまり行われていない取り組みのようなので、イベントストーミングとはなにかについて簡単に説明し、どのような課題を解決する手法なのかを見ていきます。加えて、実施にあたってどのような点について注意したのかといった点について、簡単に書きたいと思います。

この記事は実践寄りの感想になるため、イベントストーミングのやり方そのものについては深くは掘り下げていません。

AIRTRACK とは

サイバーエージェントの AI 事業本部で、主に広告配信や小売向けのソリューションを提供しているプロダクトです。2014年から創業を開始し、以降さまざまなクライアントを獲得しながら発展してきたプロダクトで、現在では AI 事業本部の小売 DX 部門を支えるプロダクトです。

AIRTRACK が挑戦するのは、まだまだ確固たるソリューションが存在しない小売業界のデジタル化(DX という言葉が最近ははやっています)という領域です。チラシのデジタル化です。独自のアルゴリズムを用いて、消費者が必要としているモノに関する広告を届けられるように日夜工夫しています。

少し開発体制の話をさせてください。

開発チーム全体はデータサイエンティストも含めると10名程度、ビジネスサイドを含めると15名程度の規模をもつプロダクトです。私はそのうちのテックリードを務めており、私以外にはエンジニアリングマネージャーを務める方がいます。私自身の職責はソフトウェアアーキテクトと技術周りのマネジメント・戦略設計です。エンジニアリングマネージャーは、ピープルマネジメント(とカタカナでいうとカッコいいが要するにエンジニアの人的側面の管理)やプロダクトの開発プラン、優先順位決めを行っています。

開発プロセスの特徴としては、スクラム開発を採用しているという点があげられます。スクラムの原則である「検査・透明性・適用」を重要視したプロダクト開発をしており、スプリントは1週間単位で回されています。認定スクラムマスターも在籍しています。

AIRTRACK 開発チームマネジメントの哲学は、「誰もがどのような業務であってもできるようにする」です。これにより、たとえば新卒であっても(多少サポートを受けながら)シニアがこなすようなレベルの仕事をすることができます。また、サーバーサイドエンジニアであってもフロントエンドもインフラも触るということが可能になっています。いわゆる暗黙知や属人化をできるだけ減らすマネジメントをしていきたいと考えています。そこにスクラム開発が生きてくるのです。

イベントストーミングとは

『Introducing EventStorming』p. 14 に記載されている図。

イベントストーミングとは、いわゆるワークショップ形式のアクティビティです。このアクティビティを通じて、ドメインイベントを中心としたドメインモデルを作っていきます。

このアクティビティの最大の目標はモデルを組み上げることです。ドメイン駆動設計をご存知の方であれば、ドメインモデルがどのような形になっているのかを落とし込めるアクティビティだと思うと想像がつきやすいかもしれません。

ドメインモデルを組み上げる作業は、私が思うに開発の中でもっとも難しいプロセスで、とくにステークホルダー間で認識を合わせ続けるのは普通に開発をしていると至難の業です。イベントストーミングはこの問題に立ち向かいます。

イベントストーミングでは、まず対象とするドメインで起こる「ドメインイベント(以下、イベント)」を抽出していきます。イベントというのは、過去に起きた出来事です。要するにたとえば、広告配信システムなら「案件を入稿した」「配信を開始した」「配信が中断した」などといった出来事がイベントとなります。これをまずは抽出していきます。加えて、イベント以外にも「コマンド」「外部システム」「方針」「読み取りモデル」といった要素も、ビッグピクチャの中に加えていきます。

そして抽出した要素たちをタイムラインに沿って過去から未来に並べていくことで、最終的な成果物(ビッグピクチャと呼びます)が作り上げられます。つまり、このアクティビティの結果、複雑な現実の業務全体をシステムの言葉に落とした成果物ができあがり、のちの実装時にはこの絵を参照しながら開発を進めていくことができるようになるのです。

ユーザーストーリーマッピングとは違い、かなりシステム側によったアクティビティです。議論を進めていく内容も、システムにどう落としていくかという観点を中心に進められていきます。このアクティビティを見た瞬間に、これはドメイン駆動設計をかなり意識しているなと感じました。実際、関連する用語が多々登場します。

一般的なイベントストーミングの実施手順については『Introduction EventSourcing』という本や、Qiita のこの記事、あるいはこの記事などに詳しいです。

イベントストーミングで解決したかった問題

私が今回イベントストーミングを実施するにあたって解決したかった課題は下記です。

  • 今回実装の対象とするドメイン(業務内容)がそもそも複雑で、結局何をしたらよいかについて誰も全体像を描けていなかった。
  • 業務を行うためのシステムを実装するにあたって、自分たちがどのようなドメインをシステムに落とそうとしているのか、実施前には整理できていなかった。
  • また、直近でチームの人数が増えてきたが、それに伴ってドメインの知識が一部の人に偏って蓄積されてしまっており、属人化が進んでいた。

こうした課題に対して、イベントストーミングは非常に有効な手法なのではないかと以前から考えていました。百聞は一見にしかずということわざがありますが、最終的にひとつのビッグピクチャを全員で作り上げるこのプロセスは、まさに百聞は一見にしかずなのではないかと考えたのです。これは AIRTRACK の開発チーム運用の哲学ともマッチします。

結果的に、上記にリストアップした課題に対して、イベントストーミングは大きな効果を発揮し始めたように思います。まだイベントストーミングの結果は実装途中で、本当の効果が検証できるのはこれからですが。

実施した結果、

  • 実施した本人がまず、ドメインの全体像を手に入れられた。
  • メンバーと事後にドメインについて話をした際に明らかに以前と比べて理解度が違っていると感じられた。
  • 参加者が楽しそうだった。

といった効果をすでに感じています。参加者にとって実りある会だったのであれば、ひとまず開催した価値はあったと思っています。

ただ一方で、こうした活動は一過性のものとせず、継続して行ってこそはじめて改善活動になっていきます。私は次に、どうしたらスクラムチームの開発プロセスにイベントストーミングという新しいプロセスを自然に統合していけるかについて思案しています。

事前検討

ガイドに載っているものをそのまま実施したとしても、組織にフィットするとは限りません。また、一度きりのものにしたくはなかったので、先ほど紹介した本や先駆者の記事を参考に、継続的に行えるよう会をデザインし直しました。これがうまくいっていれば、私は次回「継続のコツ」のような記事をアップしていることでしょう(笑)。

参加者は絞り、時間は1時間にした

参加者と時間についてはかなり絞りました。『Introducing EvnetStorming』内では、何日間にもわたって行われているようですが、その時間はありませんでした。なので、

  • 参加者は全体で6名程度。
  • 時間は1時間。

で行いました。そのため、今回実施する対象としたドメインもかなり小さいものを採用しました。

イベントストーミングで重要なのは参加者を正しく選定することです。「その場で」結論を出し切りたいです。なので、

  • ファシリテータ: 会議の進行や参加者から質問を引き出したりする人。
  • ドメインエキスパート: イベントストーミングで扱う業務領域に詳しい人。
  • 開発者: 実際にその業務内容をシステムに落とす人、あるいは日常的にアプリケーションの開発に携わっている人。

の3つの役割の人たちをその場に集める必要があります。

今回は、データサイエンティストが広告配信において AB テストを行う際に、どうしたら彼らがやりたいことを実現できるかについて考える必要がありました。したがって、ドメインエキスパートはデータサイエンティストということになります。なので、彼らに参加してもらうことにしました。

ファシリテータは、今回がはじめての実施だったため、イベントストーミングについてある程度理解している必要がありました。なので、私が担当しました。本来であれば、ファシリテータは適切な問いを投げかけて議論を深める役割を担うので、イベントストーミングもわかっていて、かつドメインに関する知識もある程度ある人がよさそうではないかと考えています。

イベント、集約、Issue に絞って実施することにした

今回実施するにあたって、イベントストーミングをフルパッケージでは実施しないという手を取りました。これは FOLIO 社の方が発表されていたスライドの中で採用されていた手法で、初回を実施するにあたって非常にスマートに実施できそうだと感じたため採用しました。

イベントストーミングには、External System や Command といった多くの要素が存在しています。初めて実施するにあたって、1時間しかない中でこれらすべてを参加者に理解してもらいあげきるのは無理だと考えました。少々説明しなければならないことが多いためです。

したがって、スライドで紹介されていた「イベント」「集約」と「Issue」をあげるという手法を採用しました。これにより、最低限果たしたい目的は果たせました。ただ今後、要素は徐々に増やしていきたいと考えています。

事前に予行演習で課題を発見した

さらに1時間のワークショップを実施する前に、特定の小さなドメインに絞って2名で軽くイベントストーミングを実施しました。

その結果、思いつく限りのイベントを列挙する前に、イベントの始点となる付箋を何枚か用意しておくと、議論の起点となってよいのではないかというフィードバックを得られました。これも採用し、当日は始点をあらかじめ何枚か用意した状態でスタートさせました。

当日、始点の用意は発想の起点になったようです。このおかげで、私が見つけられていなかった始点も参加者がたくさん発見していきました。

ミニマムに実施した際のプロセス

上記をふまえながら、下記プロセスを最終的には採用しました。

  1. ファシリテータは「始点」を用意しておく。
  2. まず思いつく限りのイベントを列挙する。同時に議論の余地がある話を Issue として貼る。
  3. タイムラインに載せる。
  4. イベント間の依存関係を矢印で結ぶ。
  5. 集約を発見し、命名する。

ファシリテータは「始点」を用意しておく

先ほども説明したとおり、予行演習で発見した「始点」の確保を行いました。これを行うことによって、そのドメインに関してあまり知識をもっていなかったとしても、議論に参加しやすい状態を担保しました。

「始点」は本当になんでもよいです。たとえば広告配信であれば、「広告配信ボタンを押した」「広告を入稿した」といった文言がひとつの始点になりえます。そこから派生するイベントをあとは列挙していくだけです。

始点は最初にファシリテータが用意したもの以外に、参加者が自由に追加することもできます。もちろん時間内に会議を収め、成果物をすばやく作り上げるために「始点以外は貼り出さないようにしてください」と絞ってみてもよいでしょう。ここはファシリテータの腕の見せ所かもしれません。

始点の確保の最大の目的は、ドメインに関してあまり知識をもたない人でも参加できるようにするという点です。少なくとも始点があれば、参加者は質問を考えることができます。これが重要なのではないかと考えています。

まず思いつく限りのイベントを列挙する

思いつく限りのイベントを列挙する

オレンジのふせんでイベントを列挙し、列挙している最中に疑問に思ったことはピンクの付箋で貼っておく

先ほど説明した始点を起点にし、まず思いつく限りのイベントを参加者に列挙してもらいます。あげる内容は、ドメインに明らかに存在しないものでない限りはどんな些細なイベントであっても構わないです。オレンジの付箋で貼っていきます。

この段階では相当数のイベントが列挙されていくため、横に広いホワイトボードを用意しておくことをおすすめします。

また、先ほど説明した Issue もこの段階で貼っていきます。ガイドに従ってピンクの付箋を貼るようにしました。

タイムラインに載せる

先ほど列挙したイベントを、左から右に時間が流れるように順番を並び替えていきます。まず、まとめられるものをまとめ、不要だと思ったものは削除するというように整理をしていきます。

まとめられるもの同士をまとめる

まとめられそうなものに対してコメントをしながら、整理していく

次に、まとめられそうなものや、不要そうなものを整理しきったあと、時系列順になるように位置を調整してさらに整理します。

タイムラインに載せた

整理しきった結果を左から右に時系列順になるように再度並べ直す

無理に依存関係を整理する必要は、この時点ではありません。それは次のプロセスで行うためです。

イベント間の依存関係を矢印で結ぶ

依存関係を矢印で結ぶ

各イベントが、どのイベントに対して依存しているのかを示す矢印を書き込んでいく

タイムラインに一通り載せ終わったところで、お互いに依存し合うイベント同士を矢印で結び合います。(実施当日は私が勘違いしていた&あとで知ったのですが、『Introducing EventStorming』の中にはこのフェーズは登場してきていません。ただ、意外に有用なように思いました。)

基本的には始点の付箋から次の付箋、次の付箋…というように矢印が続いていくはずですが、中にはスタート地点がわからないものもあるかもしれません。途中からある特定の時間帯や条件を満たすと発生するイベントがそれです。その際は、「…→」というような矢印を採用してみました。

スタート地点がわからないものには、特定のアクターが関与しているかもしれません。今回は行いませんでしたが、そうした特定のアクターについても整理して記載しておくと、規模の大きめなプロジェクトでは役に立ちそうに感じました。

集約を発見し、命名する

集約を発見する

集約を見つけ出し、黄色い付箋とグルーピングの印をつける

依存関係を結び終わると、「これらのイベントはお互いに強く依存しあっていて、整合性を保つ必要がありそう」というイベントのまとまりを発見できるかもしれません。これは集約と呼ばれるもので、ひとまとまりに処理を仕切る単位として認識しておくべきものです。

まとまりあったグループを線で囲います。囲い終わったあとに、もう一度そのまとまりで正しいのかを見て、正しそうであればひとつひとつ名前をつけていきます。これがモデルとして発見されていくのです。

次回以降

やはりフルパッケージで行いたいです。本来であれば Command や Policy なども採用して、もう少し細かく議論することも可能なはずです。

今回の抽出されたイベントは、やや粒度が大きめの内容が多かったので、この精度で十分間に合うのかどうかは気になっています。これはイベントのみをリストアップする手法の弱いポイントなのかもしれないと感じました。

また、実装に落とす際にシームレスなものかどうかはまだわかっていません。弊プロダクトのシステムは、イベントストーミングの中で使用される用語から連想される CQRS + ES のアーキテクチャを採用できているわけではありません(したいけど)。こうしたシステムに対しても効力があるのかは、検証の余地がありそうです。

まとめ: 設計を”タンジブル”にする

最終的にできあがった成果物

今回行ってみた結果、複雑な業務やドメインイベント、ふるまいの関係性が、このようにひとつのホワイトボードで眺められる形にまとまった。中身はお見せできないのでモザイクで🙏

ドメインモデリングのような抽象作業は、何も手を打たないと抽象的な文章が羅列され、結局参照されないものができあがってしまいます。議論も口頭で行うと空中戦になりがちで、論点も散りがちです。その結果、システムやドメインに詳しくない人は、途端に議論への参加権を失います。これは健全な姿とは言えないでしょう。

こうした問題に対するキーワードとして「タンジブル(tangible)」というものがあります。この単語はノンネイティブには少し難しいですね。語源はラテン語の tangere で、この単語は英語に直すと touch です。これに -ble が付くので、touchable (触れられるもの)くらいの意味合いだと予想が立ちます。

タンジブルというのは要するに「触って感知できる状態にすること」です。モヤモヤしたものを実体化するという意味合いがあります。

モデリングにおいては、図表に起こしてひと目で見てわかる形にしておくというのが表現としてはわかりやすいでしょうか。モヤモヤしたモデルという存在を、図表という人間が目で見て観測可能な形にして用意しておく。これが設計をタンジブルにするという作業です。

イベントストーミングではまさにこの設計をタンジブルにする作業を行います。この手法が優れているのは、イベントを扱うのに特化して設計されているという点です。タイムラインに過去から未来に向けて項目を並べるというシンプルな作業ではあるのですが、これがイベントをタンジブルにするのにピッタリの作業です。

AIRTRACK では、こうした設計をタンジブルにするアクティビティを今後も設計し、積極的に開催していきたいと考えています!

 

]]>
新規プロダクトでKubernetesを中心にCloudNativeなアーキテクチャのインフラを導入した話 https://cyberagent.ai/blog/pr/kubernetes/10219/ Mon, 26 Aug 2019 13:44:39 +0000 https://cyberagent.ai/techblog/?p=4500 はじめに

アドテク本部Airtrackチームの横山(@nnao45)です。

チーム内ではScala書いたり〜K8Sと遊んだり〜AWSったり〜しています。
この度Airtrackチームの新規プロダクトでKubernetesを採用し、本番環境に投入したのでその知見を共有させた頂きます。

おしながき

  • 目指したアーキテクチャ
  • Kubernetes周り
  • ミドルウェア
  • CI/CD
  • 監視
  • 感想

目指したアーキテクチャ

妥協はしないが身の丈にあったコンテナプラットフォーム って感じですかね。

Kubernetes周り

サービスレイヤひとまわり

Airtrackチームで広告系完全新規プロジェクトの立ち上げに
Kubernetes中心にクラウドネイティブなアーキテクチャ意識して設計しました。

描れている4つの六角形のやつが今回僕らの広告系のプロダクトでサービスとして動かしているサーバで、このうち2つをフロント、2つをサーバサイドの関係にあります。

このサービスを中心に組み立てるにあたって設計で迷った次の点をまずは紹介させて頂きます。

お悩みポイント

  • 結構スモールスタートだけど、なんでk8sで採用したの?
  • KubernetesのサービスはIngress?Nodeport?
  • ログの置き場所をどうするか in AWS

結構スモールスタートだけど、なんでk8sで採用したの?どうだった?

とりあえず全く新規で始めるので、ワークロードはコンテナで考えたかったのは間違いないです。インフラの依存性を安全に切り離せるのはもちろん、特にDockerはアプリをリリースするのとインフラの(上澄みの)ライフサイクルを併せられるので、アプリエンジニアの手に馴染みやすいのも大きな利点です。

そしてAirtrackではほぼ全てのコンポーネントがAWS上にあり、必然的にECS Fargateも選択肢に入ってきました。
正直、ぶっちゃけFargateでも良かったです。EKS vs Fargateは以下のブログが詳しい。

https://www.bmc.com/blogs/aws-ecs-vs-eks/

ALBからpodに落ちるまでのホップ数(EKSというかKubernetes)や、他のAWSサービスとの連携などはFargateの方が良いでしょうね。

結果的にEKSを採用した理由として、

  • OSSコミュニティの盛り上がり、拡張性の強さ。
  • そこまでレイテンシにシビアなサービスではなかった
  • @nnao45君がKubernetesの方が圧倒的に慣れてた、また他のメンバーのKubernetesへの勉強も進んでいて、社内事例も多かったので。
  • 採用面で強く出れそう。なお、AirtrackチームではKubernetesをバリバリ触ってみたいエンジニアを絶賛募集していまs(ry
  • 将来的にオンプレやGKEへの移行という話になってもスムーズに行きそう & 他のチームでGKEとかオンプレk8s動かしてた人も入りやすくなりそう

という少々生々しい感じで、多分性能面というよりかはチームビルディングの過程による色が強かったかもしれません。
また載せたサービス群がそれほど多くないのでK8Sではtoo muchではなんていう懸念もありましたが、
後述するCD面やクラスターの拡張性の面でこの規模でさえK8Sから得られる恩恵は意外に大きかったです。

KubernetesのサービスはIngress?Nodeport?

うーんどっちもどっちって感じでした。
「Ingressはまだ使うな」という論と「alb-ingress-controller強いからイケる」という論があり、難しかったです。
alb-ingress-controllerは僕の予想それでもポツポツと本番投入しているプロダクトは出てきてるんじゃないかな?と思っています

https://github.com/kubernetes-sigs/aws-alb-ingress-controller

annotationでalbの設定を投入できたり、証明書を紐付けたりできるみたいで悪くなさそうですね。
ただ、僕は個人的にはEKSとしてはNodeportによるサービスバインドが優れていると感じました。

1. AWSの世界とKubernetesの世界を分離したかった

これは非常に重要な観点と感じていて、チームとして大きなメリットを得ることができます。
IngressによりALBまで管理することによって「Kubernetesのmanifestに何かしらのアクションをしないとALBができない(=サービスコントロールができない)」という状態になります。

IngressでALBをmanifest管理できるのは確かにメリットかも知れませんが、ALBを「AWSリソースを立てる」という手立てで管理にしておくとVM時代と同様な手順で「AWSが触れる人」でもサービスコントロールできるようになります。
マイグレのような対応をするときにクラスターを複製するときに、ALBも複製されたり色々面倒だったりします。

2. 適当なローカルクラスタを作りやすくなる。

例えばminikubeで本番環境と全く同じ検証環境を作成したくなっても、VMとDBだけ用意すればよく、そしてVMとDBは頑張れば1個の物理マシンで済ませられるので検証に便利です。
クラウドベンダのLBがあること前提の環境にしてしまうより差分が少なくなるはずです。

ログの置き場所をどうするか in AWS

当然標準出力だろという話に思えた。

https://12factor.net/logs

しかし、ローンチ時点でサーバサイド内で分析したいログとそうでないログがあり、それをログの出力内容としてjson化してタグで分別するのが厳しそうでした。
よって分析ログだけ物理ログとしてどっかに置いておいてfluentdで飛ばそうとなりました。しかし・・・。

AWS EKS, Volumes architecture in a statefull app. in multiple AZ’s

単純にEBSマウントしログを置くとAZ跨ぎで実装することが出来ません。
今回、僕らの環境ではマルチAZな構成にしているのでこれにぶち当たってしまいました・・・。

マルチAZ構成でのPersistent VolumeはEFSによって構成すれば、マウントするpodがどこのAZにいても大丈な構成にしました。
今後、このEFSがAZをまたぐ構成がクリティカルな負荷になる可能性はありますが、その時はnode taintも視野に入れて採用に至りました。

しかし・・・・・・

https://github.com/kubernetes-incubator/external-storage/tree/master/aws/efs

なんとこの公式のEFS Dynamic Provisioner、動かない >< 理由がわからん。
頑張ってググったところ、helmの方が動くようなので、此方のEFS Dynamic Provisionerを使用しました。

https://github.com/helm/charts/tree/master/stable/efs-provisioner

これにより、分析ログはこのEFS上に配置してfluentdでtailすることによって解決しました。

最後に注意点ですが、こういうkubelet配下にないログの場合はローテーションを考えなければいけません。

今回の構成では他のプロダクトでも使っているJavaの伝統的なログマネージメントライブラリのlogbackを使って実現しています。

ミドルウェア

ミドルウェア周りは大量にあるので、特筆点のみ紹介します。

Aurora(データ移行)

リレーショナルなデータベースにはAuroraを選びました。強いからです。

https://aws.amazon.com/jp/rds/aurora/
インフラ観点で特別な事はしてないのですが、今回特筆する点といえばproduction→stagingへのデータ移行の点でしょうか。

今回の環境では結構インプレッションの上下が激しく、データに関してもstagingはproduction同等ものを用意しました。
ここに関してはそのうち①いつでも発火できるジョブ②早朝に回るバッチとしてdockerコンテナがrunする仕組みをkubeのCronJobで実現する予定です。

docker自体はシェル叩くだけのコンテナですが、Auroraでは叩くシェルも非常に単純なシェルで実現できます。
何と、ほぼ全てのデータ移行がSQL4行で終わっています。

# テーブル名を全部持ってくる。
SHOW tables FROM ${DATABASE_NAME}
# テーブルごとにproductionのデータをs3に保存する。
SELECT * FROM ${DATABASE_NAME}.${TABLE} INTO OUTFILE S3 's3://${S3_AURORA_BUCKET}/${TABLE}/table' OVERWRITE ON
# テーブルごとにstagingのテーブルをTRUNCATEする。
TRUNCATE TABLE ${DATABASE_NAME}.${TABLE}
# テーブルごとにstagingのテーブルをs3からロードする。
LOAD DATA FROM S3 PREFIX 's3://${S3_AURORA_BUCKET}/${TABLE}/table' INTO TABLE ${DATABASE_NAME}.${TABLE}

このS3にSELECT文のデータを送り、LOADするのはAuroraバージョンv1.13から使えるんだそう。

ネットでは色々情報が錯綜してますが、今回使用したAurorav2.03系でもこの機能は(上記のクエリを発行するだけなら)使えます。
そんなとこです。

Botkube

僕が気に入ってるミドルウェアの一つにbotkubeがあります。

https://github.com/infracloudio/botkube

僕自身コントリビューションもしているので是非使ってみてください。

このツールはslackからreadonlyなオペレーションをchatで投げれるのと、kubernetesのイベント監視をしてくれます。小回りがきいて便利です。

 

bokube君のおかげで、kubectlをslackからクラスタに投げたり、getコマンドをチームで共有、履歴保存できて重宝しています。

Fluentd

fluentdですが、野良プラグインが使いたかったりしたので、今回は野良レポジトリのを応用してビルドしております。

fluent-plugin-docker

FROM fluent/fluentd:latest-onbuild
WORKDIR /home/fluent
COPY /entrypoint.sh /home/fluent/entrypoint.sh
ENV PATH /home/fluent/.gem/ruby/2.3.0/bin:$PATH

# We need root access to read container logs
USER root
RUN apk --no-cache --update add build-base ruby-dev && \
gem install fluent-plugin-s3 fluent-plugin-docker && \
rm -rf /root/.gem/ruby/2.3.0/cache/*.gem && gem sources -c && \
apk del build-base ruby-dev && rm -rf /var/cache/apk/*

ENTRYPOINT ["sh", "entrypoint.sh"]

<追記 20190828>

↑の fluent/fluentd:latest-onbuild は古いイメージを使っているとご指摘いただいたので、asspで直しました😌いんたーねっつはあったけえ

FROM fluent/fluentd:v1.7-debian-1
WORKDIR /home/fluent
COPY entrypoint.sh /home/fluent/entrypoint.sh
ENV PATH /home/fluent/.gem/ruby/2.3.0/bin:$PATH

# We need root access to read container logs
USER root
RUN buildDeps="sudo make gcc g++ libc-dev" && \
    apt-get update && \
    apt-get install -y --no-install-recommends $buildDeps &&  \
    sudo gem install fluent-plugin-s3 && \
    sudo gem install fluent-plugin-docker && \
    sudo gem sources --clear-all && \
    SUDO_FORCE_REMOVE=yes \
       apt-get purge -y --auto-remove \
                     -o APT::AutoRemove::RecommendsImportant=false \
                     $buildDeps && \
    rm -rf /var/lib/apt/lists/* && \
    rm -rf /tmp/* /var/tmp/* /usr/lib/ruby/gems/*/cache/*.gem

ENTRYPOINT ["sh", "entrypoint.sh"]

参考:https://github.com/fluent/fluentd-docker-image

</追記 20190828>

参考:https://github.com/ccpgames/kubernetes-fluentd-s3

余談ですが、コンテナで動かすfluentd_confを書く場合は、環境変数を埋め込める記法を覚えておくとセキュアです。

<match **>
  @type s3
  log_level info
  s3_bucket "#{ENV['S3_LOGS_BUCKET_NAME']}"
  s3_region "#{ENV['S3_LOGS_BUCKET_REGION']}"
  s3_object_key_format %{path}%{time_slice}/cluster-log-%{index}.%{file_extension}
  path "#{ENV['S3_LOGS_BUCKET_PREFIX']}"
  buffer_path /fluentd/log/s3-buffer

  flush_interval 60s

  time_slice_format %Y/%m/%d
  time_slice_wait 10m
  utc

  <instance_profile_credentials>
    ip_address 169.254.169.254
    port       80
  </instance_profile_credentials>

</match>

#{ENV['<環境変数名>']} を使うことによって、環境変数から引っ張ってこれます。

Gatling as a Service

少し番外編として負荷試験ツールも紹介しておきます。

以下の理由でgatlingをas a serviceとして作りたいって思いたちました。

  • 負荷試験サーバってtoo fatの金食い虫
  • 別にずっと流す必要がない
  • 環境が汚れがち

つーことで作りました。

構成としては図にするまでもなかったのですが、Codebuild + Gatling + Dockerです。

つまりCodebuildでdocker runするだけです。

他のAWSのサービスではなくCodebuildでGatlingを実行する理由ですが、

  • lambdaはログがだるい
  • aws batchみたいにECSサービスを立ち上げるまでもない
  • aws barchみたいに定期的に実行したいわけでもない
  • 「dockerで」揮発的にジョブ実行したい(移植可能な状態にしたい)
  • gatling自体がシェルとかsbt越しで実行される事を期待されている

な感じです。

記念に使ったDokerfileを置いておきますが、インストールしてるだけです。

# Gatling is a highly capable load testing tool.
#
# Documentation: https://gatling.io/docs/3.0/
# Cheat sheet: https://gatling.io/docs/3.0/cheat-sheet/

FROM openjdk:8-jdk-alpine

# working directory for gatling
WORKDIR /opt

# gating version
ENV GATLING_VERSION 3.0.3

# create directory for gatling install
RUN mkdir -p gatling

# install gatling
RUN apk add --no-cache --update tzdata zip curl wget bash libc6-compat && \
  mkdir -p /tmp/downloads && \
  wget -q -O /tmp/downloads/gatling-$GATLING_VERSION.zip \
  https://repo1.maven.org/maven2/io/gatling/highcharts/gatling-charts-highcharts-bundle/$GATLING_VERSION/gatling-charts-highcharts-bundle-$GATLING_VERSION-bundle.zip && \
  mkdir -p /tmp/archive && cd /tmp/archive && \
  unzip /tmp/downloads/gatling-$GATLING_VERSION.zip && \
  mv /tmp/archive/gatling-charts-highcharts-bundle-$GATLING_VERSION/* /opt/gatling/ && \
  rm -rf /tmp/*

# setup TZDATA
RUN cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
    echo "Asia/Tokyo" > /etc/timezone

COPY entrypoint.sh /opt/gatling/entrypoint.sh
COPY user-files /opt/gatling/user-files

# change context to gatling directory
WORKDIR  /opt/gatling

# set environment variables
ENV PATH /opt/gatling/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
ENV GATLING_HOME /opt/gatling

ENTRYPOINT ["/opt/gatling/entrypoint.sh"]

参考: https://github.com/denvazh/gatling/blob/master/3.0.3/Dockerfile

cloudbuildにbuildが走るとslackに通知してくれてレポートも飛ばしてくれるようにしてます、普通にzipコマンドで固めてcurlで飛ばしてるだけですがこうすると便利なのでオススメです(^^)!

CI/CD

今回の構成では以下のようなCI/CD基盤としました。GitOpsですね(正確には少し違うのだけれど)。

ほぼ図に全て書いてあるので、今回も以下の特筆点だけ。

  • ぼくがかんがえたちょっとつよいぎっとおぷすもどき
  • CircleCIからk8sレポジトリへはプルリク、ただしdevelopmentはマージコミット
  • マニフェスト管理にはkubectlのみ、helm、kustomizeは使わない
  • kubectl発火はCodepipeline/Codebuild

app、k8sどちらもdevとstgとprdを表すブランチへのマージコミットを起点にCI/CDが発火されます。

ぼくがかんがえたちょっとつよいぎっとおぷすもどき

  • 大部分のオペレーションからkubectlの排除
  • アプリを触る人が、Githubだけでリリースが完結する
  • manifestの変更だけでもリリースできる
  • リリースの可否もslackを見ればいい(=デバック以外でkubectlを使わせない)
  • git pushからリリースまで大部分をマネージド、自動化することで開発者へのストレスをゼロに近づける

ほぼほぼこれらは実現でき、非常に好評でした(^_^)v

CircleCIからk8sレポジトリへはプルリク、ただしdevelopmentはマージコミット

最初、ScalaとVueのコンテナのコードのテストだけをCircleCIに任せていたのですが、
ここからLambdaとか何か生やしたりするのが面倒だったのでCircleCIにdocker buildまでさせてECRにプッシュしています。
注意点はECRにイメージがあるかどうか怖いので、CIの最後にそのコンテナのpullにより実在することを保証しています

devはプルリクじゃなくてマージコミットを(=アプリのCIがOKならそのままクラスタに反映)した理由は、単純に「devはリリースタイミングはCIが通ったら行ったらいつでも」であったので、それならプルリクにする必要がなかったからです。

また余談ですが、実はプルリクに工夫をしていまして、何とそのイメージタグにどういうアプリの変更が含まれたかの情報を入れています。

これスミマセン、アプリレポジトリ側のCIの時で使っているシェル芸です…!

BEFORE_MERGED_COMMIT_HASH=$(git log -1 --pretty=format:"%P" | awk '{print $1}')
git log --pretty="%H %s" ${BEFORE_MERGED_COMMIT_HASH}...${CIRCLE_SHA1} --no-color | sed "s@^@https://github.com/xxxx/yyyy/commit/@g" > tee /tmp/commit_list.md

詳しくは長くなるので書きませんがこれは凄い便利なのでこの情報をつけてプルリクを自動生成すると運用する人に凄い喜ばれます✌︎(‘ω’✌︎ )

echo -e "${COMMENT}\n\n## Git History\n$(cat /tmp/commit_list.md)" | hub pull-request -b ${ORG}:${MERGE_BRANCH} -h ${ORG}:${BRANCH} -F -

マニフェスト管理にはkubectlのみ、helm、kustomizeは使わない

これは賛否両論なとこでしょう^^;

個人的に今回の規模であれば、helmなどによるテンプレーティングよりもむしろ単純に kubectl apply -f できない、また反映される実物はレポジトリ内で閲覧できないという点の方デメリットに感じての事です(ちなみにhelm templateでmanifestを持ってきたりしてはいます)。

マニフェストの内容も変えるとしたらイメージタグやレプリカくらいなので、これはこれで良かったと感じています。

またアプリのビルドが成功したらマニフェストのイメージを変更するわけですが、これも実はkubectlコマンドでできます。

kubectl set image \
        -f <イメージを変えたいマニフェスト> \
        <イメージを変えたいデプロイメント>=<変えたいイメージ> \
        --local \
        -o yaml

–localでapiと通信しない、-o yaml でyamlで出力する、これを使う事でsedでシェル芸しなくてもイメージ変更することができます。

 

kubectl発火はCodepipeline/Codebuild

色々考えましたが、AWSメインで要件を満たしていたので採用しました。

ブログを書いてる時点ではcodepipelineによってsourceステージを指定すると、master以外のブランチを指定できるようになるのでcodepipelineからcodebuildを呼び出す構成になっています。

工夫点としては、k8sのmanifest側のディレクトリ構成が

❯❯❯ tree -NC ./
./
├── kubesec
│   ├── hoge_secret.yaml
│   └── piyo_server_secret.yaml
└── manifests
    ├── mayo_dep.yaml
    ├── sio_cm.yaml

みたいにしております。今回kubesecを使っていて、単純にkubectlをぶちかませばいい構成ではないので、最終的に発火しているbuildspec.yml(=dockerのENTRYPOINT)はこんな感じです。

version: 0.2

phases:
  build:
    commands:
       - aws eks update-kubeconfig --name <EKSのクラスター名>
       - kubectl get all --all-namespaces
       - kubectl apply --dry-run -f manifests && kubectl apply -f manifests
       - ls -1 kubesec | while read manifest; do echo '---'; kubesec decrypt kubesec/$manifest;done | kubectl apply -f -
       - kubectl get all --all-namespaces

ポイントは、

  • applyする前に kubectl apply --dry-run してテストしておく。
  • 4行目、シェル芸をかまして kubesec decrypt をかましてから、複数のyamlを一枚の標準出力にするために文頭に --- 入れて〜最後に kubectl apply しています。これでkubesecで kubesec encrypt されて暗号化されたマニフェストをおくだけで展開できます。

そんな感じだわよ

監視

AWSとkubeの監視についてはDatadogに一任をしています。
datadog-agentはhelm templateでmanifestだけ持ってきてkubectlで適用しているだけです。

ダッシュボードですが、datadogはワンクリックでこんな凄いダッシュボードが出来上がります・・・はっきり言ってこれは神

How to monitor Kubernetes + Docker with Datadog

感想

俺たちの旅はまだ始まったばかりだ!!@nnao45、ならびにAirtrackチームの次回作にryって感じで、とりあえずの初期状態は作れたかなと思っております。
プロダクトが大きくなる見込みも十分にあるので、もし大きくなりアーキテクチャを見直すことになったらまた記事を書きたいなと思っております!

]]>
木星を継ぐもの 〜JupyterLab よこんにちは〜 https://cyberagent.ai/blog/research/10260/ Wed, 20 Mar 2019 05:05:30 +0000 https://cyberagent.ai/research/?p=2045

AI Lab で主に NLP を担当している張です。
突然ですが、まだ Jupyter Notebook で消耗していませんか?

2011 年に IPython Notebook (現 Jupyter Notebook) の登場1によって研究やデータサイエンスの環境は一変しました。
それまではソースコードの実行やグラフの出力、メモやノートなどをすべて別々に取っておく必要があったものが、このツールによって一箇所にまとめて表示できるようになり、管理や閲覧が一元化され圧倒的に便利になりました。IPython Notebook の登場は研究者やデータ分析する人に革命的な効率改善をもたらしたのです。まさに働き方改革です。
2019 年現在、初登場から約 8 年もの年月が過ぎました。この 8 年の間に Python のバージョンは 3.2 から 3.7 にあがり、GPU は GTX 500 番台から RTX 2000 番台になり、NLP における state-of-the-art な言語モデルも RNNLM 2 から GPT-2 3 へと進化しました4。そんな中 Jupyter は過去に留まったままでしょうか?否、 Jupyter もこの激しい変化に適応するための新しい武器を粛々と開発していたのです。Jupyter Notebook で使いづらかった点を改善し、より利便性を向上させた後継、それが JupyterLab なのです。
しかし悲しいかな、まだその存在を知らない方が多くいらっしゃいます。そこで、 Jupyter Notebook との違いと活用方法に主眼を置きながら、その良さをあまねくご紹介できたらと思います。

もくじ

  1. 画面
    1. 画面構成
    2. 画面操作
    3. ダークモードとプレゼンテーションモード
    4. CSV / TSV のテーブル表示と JSON の展開可能な表示
  2. セル操作
    1. セルの D&D
    2. 補完機能
    3. マルチカーソル
    4. セル内検索、正規表現検索
  3. ファイル操作関係
    1. ファイルダウンロード
    2. 共有リンク
  4. 拡張機能(extensions)
  5. おすすめの運用方法
  6. さいごに
  7. 参考

1. 表示関係

画面構成

JupyterLab になって一番はじめに目につくのはなんと言っても画面構成でしょう。ディレクトリ画面も ipynb 実行画面も全てがシングルページだった Jupyter Notebook とは違い、JupyterLab では画面がマルチタブ・マルチペインな構成に一新されました。

トップレイヤーでは左側にサイドバー、右側に実行画面がある2ペイン構成になっています。
サイドバーではディレクトリ移動や実行中のファイル操作、さまざまなコマンド、拡張機能タブなどと多くの補助的な機能が備わっています。
右側にある実行画面では Notebook やエディタ、ターミナル、出力画面などと数多くの編集・実行可能画面が自分好みなペイン・タブ構造に配置できるようになっています。
え?シングルページの方が集中できる?ご心配なさらずに。左のサイドバーは非表示可能なので、それを閉じればいつものシングルページな Jupyter Notebook に早変わりです。さらにメニューバーの View から Single-Document Mode なるモードも選択可能で、それを実行すればタブバーも消失するのでもう完璧に Jupyter Notebook と化します。

画面操作

具体的な画面操作についてご紹介していきます。

画面の配置変更は、タブをドラッグすると分割する方法が青い枠線で提示されるので、自分がやりたいようにタブをドラッグします。カーソルを画面の左、右、上、下、中央に重ねることによって、ペインに分割することやタブに統合することなどの挙動になります。下の GIF から分かるように、動作は非常にスムーズでストレスはほとんどありません。

また、タブ・ペイン化できるのはなにもファイルだけではありません。ターミナル画面や拡張機能画面、ipynb 内で実行する際の出力まで可能です。その自由度の高さを下の GIF で表現してみました。

この実行出力を別タブ・ペインで表示できるのは非常に便利で、上の GIF のように画像を表示させながら色んな処理を施したりできます。

他にもいくつか便利なタブ・ペイン化できる機能が JupyterLab に備わっています。そのひとつが同じ ipynb の複数画面表示です。自分もこの機能には大いにお世話になっています。例えば長くなった ipynb の上の方のコードを修正したいときはわざわざ上までスクロールするのはめんどくさいですよね?そういうときはペインを上の部分と下の部分のふたつの分割をすればその手間もなくなります。

もうひとつの有用な機能が、Code Console と呼ばれるものです。これは ipynb 内の環境を引き継いた IPython インタラクティブシェルと言えば伝わりやすいでしょうか。これは ipynb の編集中に、スペース部分の右クリックメニューから「New Console for Notebook」を選ぶことで開くことができます。ipynb に残したくない検証や、軽いアドホックな処理を行うのに最適です。ただし、同じ ipython カーネルを使っている以上、名前空間は ipynb と共有されるので、くれぐれもお気をつけください。

ダークモードとプレゼンテーションモード

Jupyter Notebook を使っている方なら少なくない方が拡張機能を入れてダークモードにしたり、プレゼンテーションモードを有効化しているのではないでしょうか。自分もそのうちの一人でした。それもそのはず、仕事の性質上、パソコンの画面と長時間に渡ってにらめっこをするのが日常化している我々にとって、明るい画面はそれだけで我々をムスカ大佐たらしめる要因になり得るのです。そのため、ダークモードといった目に優しい機能は必要不可欠です。そんな方に朗報です。なんと!JupyterLab ではダークモードとプレゼンテーションモードが標準機能として組み込まれています!(Cheers and applause)
これで拡張機能を入れたり探したりする時間をデータ分析や研究に費やせますね!

ただプレゼンテーションモードは、横幅や文字サイズをプレゼンテーション用に拡大したりするけど RISE5 みたいにスライド化されるもののではないのでご注意ください。

CSV / TSV のテーブル表示と JSON の展開可能な表示

他にも便利な表示に関するアップデートがあります。今までの Jupyter Notebook で CSV や JSON ファイルを開くと生データが表示されていましたが、それを不便に思ったことはありませんか?研究やデータ分析で扱う多くのフォーマットは CSV や JSON です。それらをきれいに表示できることは正しくデバッグすることやミスを減らすことに繋がります。そこも JupyterLab は対応してくれています!CSV や JSON ファイルを開くと、表形式や展開可能な形式にレンダリングされます。

画面構成に比べると小さな改善ではありますが、日々データを扱う身としては利便性の向上に繋がる重要な改善だと思っています。

2. セル操作

セルの D&D

JupyterLab になって、セル周りの操作もかなり便利になりました。その中でもセルをドラッグ・アンド・ドロップ(以下 D&D)できるようになったのはちょっとした驚きかもしれません。それも同じファイル内だけでなく 6、異なるファイル間でも可能なのです。

これでファイル間でのコードのコピーが楽になりますね。

補完機能

個人的にかなり嬉しいことのひとつに、JupyterLab でのコード補完の挙動がアップデートされたことが挙げられれます。というのも、以前 Jupyter Notebook を使っていたときは、一応 TAB キーで候補リストが出るものの、その移動は方向キーの↑↓でしか動かせませんでした。vim の jk でもなく、emacs の ctrl-p, ctrl-n でもなく方向キーです。いちいちキーボードのホームポジションから手を離さなければならないのが地味にストレスでした。しかしそれももう悩まされることはありません。JupyterLab では補完機能が一新されました。

TAB キーを押すと候補リストがでてくるところまでは同じですが、python の場合はリストの各項目の左にその項目が関数なのか変数なのかキーワードなのかなどの情報が表示されています。さらに、肝心の補完操作ですが、入力したキーと候補の中からマッチする項目が選択されていきます。

最初はちょっと慣れないかもしれませんが、使っていくうちにただ上下移動する方式よりも速く入力できるようになります。

マルチカーソル

これも待ち望んだ機能のひとつだと思います。自分がマルチカーソルに出会ったのは sublime text 2 というエディタを初めて使ったときでした。あのときはこんな便利な機能があったとは!と多大なる衝撃を受けたものでしたが、それが今やあらゆるエディタや IDE の標準装備として組み込まれています。マルチカーソルを含め、他にも多くの斬新な機能を搭載させた sublime text 2 はエディタ文化の一時代を切り開いたと個人的には思っています。
閑話休題。そんなデファクトスタンダードな機能になりつつあるマルチカーソルを、JupyterLab ではついに搭載しました。

ただし注意点がいくつかあります。まずマルチカーソルが有効なのは単一のセル内のみです。複数のセルに渡ってマルチカーソルを行うことができません。次に、マルチカーソルでは、BackSpace のような時々対応していないキーがあったりします。というのも、マルチカーソルの状態で BackSpace をしても、ひとつのカーソルしか動きません。BackSpace の場合は、Mac / Linux ユーザであれば ctrl-h で同じ挙動をさせることはできますが、他にも効かないキーがあるかもしれないのでご注意ください。

セル内検索・正規表現

これに関してはどれくらいの希望度があったのかわかりませんが、使ってみると意外と便利な機能と言えます。従来の検索に / を前後に追加すると正規表現での検索に早変わりです。ただリアルタイムでヒット箇所が出るわけではなく Enter キーを押して初めて該当箇所がハイライトされる仕組みになっています。

検索はできるのに置換機能はなかったり、単一セル内でしか使えなかったり、放置するとハイライトが残りっぱなしになったりなど、まだ使い勝手の向上する余地がありますが、なんだかんだ便利な機能です。

3. ファイル操作関係

ここでは軽くファイルの操作について書きます。Jupyter Notebook もそうですが、近頃共用サーバ上で Jupyter を起動して特定のグループのメンバーから使用可能にさせたり、遠隔からでもアクセスできるようにしたりする運用が増えています。この運用方法自体は非常に利便性が高いが、Jupyter Notebook だといくつか不便なことがあります。

ひとつがファイルのダウンロードです。実験で作られたモデルファイルや評価の出力画像など、なんだかんだでダウンロード処理がしたい場合が多々あります。Jupyter Notebook を使っていたときはわざわざ scp などで転送するか、Google Storage などのクラウドホスティングのストレージに保存したりしていました。しかし JupyterLab からはその面倒なことをしなくて済みます。サイドバーのファイル操作画面でファイルの右クリックメニューでダウンロードできちゃいます。しかも複数選択7もできちゃいます。

他にも右クリックメニューから共有 URL のコピーとかファイルを複製するなどの機能も追加されていたりするので、ぜひ使ってみてください。

拡張機能(extension)

ここではいくつかお世話になっている便利な拡張機能をご紹介します。

jupyterlab-variableInspector [link]

個人的に一番お世話になっているのがこの拡張機能です。これはアクティブになっている ipynb の変数を一覧にして表示してくれている拡張機能です。変数名だけでなく、その型やサイズ、内容まで表示してくれるのでかなり重宝します。numpy の shape や pandas の shape も見れるので、行列計算が多いニューラルネットワークをゴニョゴニョする人やデータを分析する人にはかなり助けとなる拡張機能です。

また、Jupyter を使っている方なら一度は経験しているのが名前空間のカオス化じゃないでしょうか。関数やクラスにせずにただただ変数を宣言していくとグローバル名前空間があっという間に混沌としていきます。そこから何かを計算するときに予期しない動作をして失敗したり、なにより一番怖いのが予期しない動作に気づかないことです。そのためにもこういった宣言されている変数を見ることができる機能は非常に有用だと思っています。

ただこれにもひとつ注意点があり、Unicode 文字の扱いがいまいちなのです。例えば a = 'あ' と宣言してもインスペクタ上では \u3042 になってしまいます。以前これを Issue に上げたら製作者の方が str などの文字列には対処してくれました。が、pandas の DataFrame などには未対応のままとなっております。みなさんプルリクチャンスですよ。

jupyterlab-go-to-definition [link]

PyCharm などの IDE でおなじみのコードジャンプです。Alt + Click でその変数の宣言文までジャンプしてくれます。

ipynb はその性質上、図表なども表示できるが全体的に長くなってしまいがちなのでめちゃくちゃ便利です。

その他

他にも多くの拡張機能がありますが、自分が主に使っているのは上のふたつだけなので詳しく書いてみました。
いくつか自分は使っていないけど便利そうな拡張機能もご紹介してみます。

  • jupyterlab_templates [link]
    • テンプレートを作ってそこからファイルを新規作成できる拡張機能
  • jupyterlab_tensorboard [link]
    • JupyterLab 上で tensorboard を開ける拡張機能
  • jupyter-matplotlib [link]
    • matplotlib の出力グラフをぬるぬる動かせる拡張機能
  • jupyterlab_code_formatter [link]
    • 散らばっているコードをきれいにしてくれる formatter

世に存在する拡張機能は github のトピックや個人の awesome-jupyterlab などで追うといいかもしれません。

おすすめの運用方法

基本的に @lain21 さんの記事を参考にして下図のようにリモートサーバに JupyterLab サーバが入ったカスタマイズ済み Docker イメージを実行して運用しています。

この方法で得られるメリットはいくつかあります。

  • どこに行ってもネット環境さえあれば実験サーバにアクセスできる
  • 環境構築が容易
  • 統一性のある環境が作れる
  • GCP との接続を切っても Jupyter の実行が中断されない

ただしあまりメモリをいじめすぎると docker コンテナが落ちてしまうので気をつけましょう。

参考までに、自分で構築せず既存のサービスとして使いたいのであれば Google の Colaboratory や Cloud Datalab、Miscrosoft の Azure Notebooks などの選択肢があります。どれも Jupyter Notebook のクローンではありますが、Colaboratory なら GPU/TPU が条件付きで使える、Datalab なら他の GCP サービスとの連携が容易といったそれぞれ独自のメリットがあります。自分でサーバを建てずに手軽でパワフルな環境を利用したい際は一考する価値があるかもしれません。

さいごに

よい Jupyter ライフを!

参考


  1. Fernando Perez: The IPython notebook: a historical retrospective ↩
  2. Mikolov et al., Recurrent neural network based language model, INTERSPEECH 2010 ↩
  3. Radford et al., Language Models are Unsupervised Multitask Learners ↩
  4. なのにアーマードコアの新作はまだ出ません ↩
  5. Jupyter Notebook をプレゼンテーション用スライドみたいに表示してくれる拡張機能 [link] ↩
  6. 同じファイル内だったらショートカットのほうが馴染みあるし速いかもしれないですね ↩
  7. なにげに shift キーを使っての複数選択というお馴染みな操作ができます ↩
]]>
Pachydermによるデータ管理とパイプライン https://cyberagent.ai/blog/research/10253/ Mon, 12 Nov 2018 03:40:39 +0000 https://cyberagent.ai/research/?p=890 AI Labの谷口です。
11月に入って急に寒くなってきましたね。
以前のSeldonに関する記事でもあるようにAI LabではResearch Engineeringの観点から様々なミドルウェアの調査などをやっています。
この記事ではデータのバージョン管理+パイプラインを提供するミドルウェアのPachydermを紹介します。

データを用いた研究をやっていく中で重要な要素の一つに「データの再現性」というものがあります。機械学習はその大部分のアルゴリズムがモデルを構築するデータに依存しますが、モデルを構築した際に利用したデータがわからなくなってしまった場合、そのモデルをもう一度再現することは非常に困難となります。また、特徴エンジニアリングなどをやっていく過程で一つのデータに対して複数の処理を様々なパターンで行うことがほとんどです。Pachydermはこのような問題に対してgitライクなデータのバージョン管理とデータ処理のパイプラインで解決することが出来ます。

Pachydermとは

PachydermはKubernetes上で動作するデータのバージョン管理+パイプラインを提供するミドルウェアになります。ソフトウェアは、OSSで公開されているものとは別にEnterpriseバージョンも提供されているようです。
Enterpriseバージョンでは、

  • フルUI (各種可視化と設定画面など)
  • アクセスコントロール
  • 管理者

などの機能が追加されているようです。
下の画像がEnterpriseバージョンで提供されるUIの画面です。

用語説明

Repository

データソースの置き場になります。Exapmlesなどを見ていると例えば学習データとテストデータなどがあっても別々のRepositoryにしているので、データの種類というよりは用途別でRepositoryを作成するのが良さそうです。
Repositoryには後述するBranchとCommitが含まれています。この3つの名前を見るとおそらく大半の人は「git」を連想されると思いますが、Pachydermのバージョン管理は「git for data」なものをイメージするとわかりやすいです。

Commit

変更があった場合に記憶される単位で、gitのcommitとほぼ同義です。
各CommitにはParent Commitというどのコミットから処理されて出てきたものかを示すものが記憶されています。

Branch

Commitの流れの単位で、Parent Commitは同じBranchの前のCommit IDが記憶されています。
基本的にこれもgitと同じなのですが、mergeという概念がないため少し異なると言えそうです。

Pipeline

Pipelineはデータに対するあらゆる処理の一つの単位で、データは必ずPipelineを経由して、次のRepositoryに格納されます。また、Pipelineは1つのRepository/Branchを入力として利用できるだけではなく、複数のRepository/Branchを入力にすることも可能です。更にはデータが入力になるのではなく、cronをトリガーにして実行することも出来ます。

機械学習での利用例

※こちらの例は公式が用意しているものを解説しているだけです

全体像

ここで、

  • 円柱で表現されているものがRepository
  • 四角で表現されているものがPipeline

になります。
traningが学習データを格納しているRepositoryで、attributesが予測したいデータを格納するRepositoryになります。今回は特徴抽出などは行わず直接モデルを作っているので、modelというPipelineからmodelというRepositoryを作っています。(Pipelineから作成されるRepositoryの名前はPipelineの名前と同じになります)生成したモデルが格納されているmodelと予測したいデータが入っているattributesの二つを入力にして、inferenceのPipelineが予測値を計算します。計算された予測値はinferenceというRepositoryに格納されていきます。

実際のコード

Repositoryへのデータ格納や取り出しなどは基本的にCLIから行うことができます。ここではPipelineのコードに焦点を当てて、解説していきます。Pipelineの処理はjson形式で記述されており、下記のようになります。

{
  "pipeline": {
    "name": "model"
  },
  "transform": {
    "image": "pachyderm/iris-train:python-svm",
    "cmd": [
    "python3",
    "/code/pytrain.py",
    "/pfs/training/",
    "/pfs/out/"
    ]
  },
  "parallelism_spec": {
    "constant": "1"
  },
  "input": {
    "atom": {
      "repo": "training",
      "glob": "/"
    }
  }
}

"input"で入力されるRepositoryを指定しています。"transform"ではdockerのimageをpullしてきて、そこで入力に対して実行したい処理を記述しておきます。このとき、/pfs/training/や/pfs/out/というものが出てきますが、

  • /pfs/{repository_name}/で入力されるrepositoryのデータをここにマウントされる
  • /pfs/out/に出力されるデータはPipelineの結果として生成されるRepositoryの/として格納される

のような動作をします。このようにVolumeをマウントすることで、Pipeline間でもデータのやり取りを実現しています。

また、入力を複数取るinferenceのPipelineでは"input"の部分に"cross"というキーを利用しています。

"input": {
  "cross": [
    {
      "atom": {
        "repo": "attributes",
        "glob": "/*"
      }
    },
    {
      "atom": {
        "repo": "model",
        "glob": "/"
      }
    }
  ]
}

詳しいPipelineの書き方は公式のこちらにまとまっています。

まとめ

研究や機械学習のモデリング、データ分析のイテレーション(PDCA的な意味)を加速させるために様々なミドルウェアなどを調査しているのですが、今回はPachydermについて紹介しました。公式の解説に留まりましたが、基本的な使い方は非常に簡単で、RepositoryとPipelineの設計もシンプルでわかりやすい設計になっているため、今後採用できる場所で使ってみようと思っています。

]]>
[Kubernetes] オンプレでも GKE Like な Ingress を使うために 自作 Ingress Controller を実装してみた https://cyberagent.ai/blog/pr/kubernetes/3758/ Thu, 30 Nov 2017 23:00:54 +0000 https://cyberagent.ai/techblog/?p=3758 こんにちは。
CIA の青山 真也です。

この記事は Kubernetes Advent Calendar 1日目の記事になります。

ちなみに CyberAgent Advent Calndar 1日目の記事も「GKE 互換のオンプレコンテナ基盤 AKE (Adtech Container Engine) 誕生秘話とアーキテクチャ完全公開!」というテーマで書いており、まるで夏休み最終日の気分です。
弊社のオンプレコンテナ基盤 AKE の話を詳細にまとめてありますので、こちらも是非お読み下さい。

今回は、SSL 終端や L7 パスベースルーティング及び L7 ロードバランシングを行ってくれる Ingress リソースの話をします。
GKE の Ingress が最高すぎるんですが、GKE の Ingress に負けない使い勝手になりました!

 

はじめに

みなさんは GKE 等で Ingress リソースを作成したことがある方が多いかと思います。

詳しくは後ほど話しますが、GKE で Ingress リソースを作ると非常に簡単に Ingress リソースを作ることができ、しかも分かりやすいと言った特徴があります。
AWS などでも alb-ingress-controller 等で ALB 連携できる Ingress Controller などが用意されています。

しかし、OpenStack 環境やベアメタル環境などでは Ingress を使う場合には手間が掛かる(しかも初心者には分かりづらい)nginx-ingress-controller や nghttpx-ingress-controller を利用する必要があります。

今回は、OpenStack 環境やベアメタル環境などの Kubernetes でも GKE Like に Ingress を利用できる方法をお伝えできればと思います。

 

Ingress の基礎知識

Ingress は L7 LoadBalancing を行なうリソースです。

たとえばこのような Deployment と、

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: sample-ingress-apps
spec:
  replicas: 5
  selector:
    matchLabels:
      ingress-app: sample
  template:
    metadata:
      labels:
        ingress-app: sample
    spec:
      containers:
        - name: nginx-container
          image: zembutsu/docker-sample-nginx:1.0
          ports:
            - containerPort: 80

このような Service を事前に作っておきます。

apiVersion: v1
kind: Service
metadata:
  name: svc1
spec:
  type: NodePort
  ports:
    - name: "http-port"
      protocol: "TCP"
      port: 8888
      targetPort: 80
  selector:
    ingress-app: sample

 

Ingress では NodePort の Service を登録しておく必要があります。

その後、下記のような Ingress リソースを登録することで L7 LoadBalancer が作成され、L7 LoadBalancer から svc1 に対して転送されるような形になります。
余談ですが、spec.backend はデフォルトの転送先になります。また、SSL 終端を行なう場合には spec.tls を設定しておいて下さい。

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: sample-ingress
  annotations:
    ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - host: sample1.example.com
    http:
      paths:
      - path: /path1
        backend:
          serviceName: svc1
          servicePort: 8888
  backend:
    serviceName: svc1
    servicePort: 8888
  tls:
  - hosts:
    - sample1.example.com
    secretName: tls-sample

 

図にするとこのような感じです。(/path2 宛を svc2 に転送する設定も入っている場合)

仕組みとしては、L7 LoadBalancer から Service に向けて転送するような形になっています。
GKE の場合と Nginx Ingress Controller の場合で結構仕組みはことなりますが、今の時点では結果的には L7 LoadBalancer から Service に向けて転送するということだけ抑えておいて下さい。

 

Ingress と Ingress Controller の違い

Ingress と Ingress Controller という似たような名前が登場してきていますが、これら 2 つは全くの別物です。

  • Ingress
    • Kubernetes のリソースの1種
    • ただの定義にすぎないため登録しただけでは何も起こらない(イメージ的には Custom Resource Definision に近い)
  • Ingress Controller
    • Ingress リソースの定義を読んで L7 ロードバランサを用意する

通常はこの Ingress リソースを登録しても Kubernetes に登録自体はされますが、何も起こりません。

GKE ではマネージドマスターで見えなくなっていますが、デフォルトで Ingress Controller が配備されているため GCLB の L7 LoadBalancer が作成されているだけです。

 

GKE の Ingress

GKE の Ingress の場合、Google Cloud LoadBalancer 上に「特定のパスから全 Kubernetes Node の NodePort へと転送する設定」が登録されます。
また、SSL 終端なども GCLB 上で行われます。

Ingress の場合には DSR 構成ではないため、SNAT と同じようにクライアントの IP Address が欠損してしまいますが、HTTP Header の X-Forwarded-For にクライアントの IP Address があるため、必要に応じて利用して下さい。

 

厳密には、GCLB > NodePort Service > Pod という経路をたどるため、GCLB が Kubernetes Node に割り振った後、Kubernetes Node から Kubernetes Node へと転送される可能性がある点に注意して下さい。
より低レイテンシを求める場合には、NodePort Service を作成する際に spec.externalTrafficPolicy を Local に設定して下さい。
この設定を行った場合には、NodePort Service が受けたトラフィックは必ず自分の Pod へと転送するようになります。
但し注意点が 2 つあります。

1つ目は、Kubernetes Node 上に転送できる Pod が無い場合は 別の Kubernetes Node に転送するといった柔軟な対応はしてくれません。spec.externalTrafficPolicy を Local に設定した場合は、必ず 各ノードに Pod が居るようにするか、NodePort に GCLB の転送設定を書き換えて下さい。

2つ目は、Ingress には関係ありませんが、spec.externalTrafficPolicy を Local にしていても、内部のコンテナから NodePort を叩くと、別の Kubernetes Node にもバランシングされる点に注意して下さい。仕様なのかはわかりませんが、iptables のエントリ的に ClusterIP 相当のチェインに吸い込まれるようです。

さすがは GKE といったところでしょうか。非常に分かりやすく理に適った構成だと思います。

 

Nginx Ingress Controller

Nginx Ingress Controller では、L7 LoadBalancer の代わりに Nginx Pod を作成し、Nginx で SSL 終端やパスベースのルーティングを行います。

また、Nginx Pod を作成すると書きましたが、Nginx Ingress Controller = Nginx Pod となります。そのため、負荷分散を行なう場合には Nginx Ingress Controller (Nginx Pod)を Deployment などでスケールさせることで実現します。
展開方法については詳しくはここでは書きませんが、各リポジトリの examples を見ると良いかと思います。

さらに、GKE の Ingress では/path1 に来たリクエストを svc1 の NodePort に転送していましたが、Nginx Ingress Controller では、/path1 に来たリクエストを直接 Pod に転送してくれるといった違いもあります。

この状態だと Nginx Pod に来たリクエストを分散対象の Pod にL7 LoadBalancing や SSL 終端・パスベースルーティングなどを行ってくれることはわかると思います。

しかし、このままではグローバルなエンドポイントを持たないため、nginx Ingress Controller の Pod に向けた “type LoadBalancer” Service を作成して上げる必要があります。
“type LoadBalancer” が利用できない場合などは、nginx Ingress Controller の Pod に向けた “type NodePort” などを作成することでも代用は可能です。
また、クラスタ内部でしか使わない場合には “type ClusterIP” でも問題ありません。

 

整理すると、L4 LoadBalancer > Nginx Ingress Controller Pod > 各 Pod という経路を辿ります。

 

Nginx Ingress Controller が GKE には勝てない部分

Nginx Ingress の使いづらいポイントはいくつかあります。

前述の通り、エンドポイントは Nginx Ingress Controller Pod への Service を作ることで実現します。
そのため、kubectl get ingress などした場合に、IP Address などは書かれておらず、自分自身で管理しなければなりません。
言い換えるならば、Ingress リソースを作成した後は、その Ingress リソース用に Nginx Ingress Controller Deployment を作成し、Service で紐付けてあげる作業が必要です。
さらに、Ingress 上には Service の IP が登録されるわけではないので、確認したい場合には対応する Service を探さなければならないと言った手間が発生します。

 

例えば、GKE 上ではこのような表示になるのですが、

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS           PORTS     AGE
sample-ingress    sample1.example.com   xxx.xxx.xxx.xxx   80, 443   3h
sample-ingress2   sample2.example.com   xxx.xxx.xxx.xxx   80, 443   3h

 

Nginx Ingress の場合にはこのような表示となってしまい、Address を確認することができません。
これは Nginx Ingress を利用している場合には、Ingress リソースの Status が更新される処理が存在しないためです。

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS   PORTS     AGE
sample-ingress    sample1.example.com             80, 443   3h
sample-ingress2   sample2.example.com             80, 443   3h

 

 

また、GKE の場合には複数の Ingress リソースを作成した場合に、それぞれの Ingress リソース毎に GCLB が作成されます。
これらは別のエンドポイントとなるため分離することが可能です。

 

 

一方で nginx ingress Controller の場合には、Ingress リソースに何も指定しないで複数作成した場合、Ingress Controller はどちらの Ingress リソースの定義も吸収します。したがって、上記の様なことを行おうと思っていたにも関わらず、両方共 エンドポイントを分けることは可能だとしても、中身は全く同じになってしまいます。

 

Nginx Ingress Controller を上記のように分離したい場合には、Ingress Controller と Ingress リソースの紐付けを行ってあげる必要があります。

Ingress Controller 側には –ingress-class HOGE の起動オプションを渡して起動します。また、Ingress リソース側に関しては annotation で kubernetes.io/ingress.class: HOGE を指定して作成します。
この作業をすることにより、Ingress Controller が管理する Ingress リソースを判別することができるようになるため、分離を行なうことが可能です。

 

自作 Ingress Controller

前述のおさらいですが、Nginx Ingress Controller を利用する場合、GKE に比べて大きく 3 つの使い勝手が悪い部分があると考えられます。

1.Ingress リソースの状態を確認しても Status は何も分からない

2.Ingress リソースを作成した後に、Nginx Ingress Controller Pod を立てて Service も作成する必要がある

3.Ingress リソースの分離性を保つために Ingress リソースと Nginx Ingress Controller に識別子を設定する必要がある

 

今回はこの問題を解消するべく、Ingress Controller を自作しました。
GKE の方が良いとは言わせません。オンプレも頑張ってるんだぞー。

Deployment Controller など様々なコントローラ系のソースを参考に作成しました。
Controller 系統を自作する場合には、イベントハンドラの機構などは用意されてはいますが、実装量は結構あるため type LoadBalancer のようにお気軽に作れるものではありませんでした。

イベントハンドラとしては、特定の Kubernetes API Resource に対して「追加」「変更」「削除」の処理が行われたタイミングで呼ばれます。
Deployment Controller であれば、追加時に ReplicaSet の追加を行なう・変更時に ReplicaSet から ReplicaSet へのローリングアップデートを実施、削除時は自身の保持する ReplcaSet を削除するといった処理を行なう必要があります。

cache.ResourceEventHandlerFuncs{
                        AddFunc:    c.onAdd,
                        UpdateFunc: c.onUpdate,
                        DeleteFunc: c.onDelete,
                })

 

また、各関数には追加削除時はそのリソースの情報、変更時は変更前後のリソースの情報が渡ってくる形になります。
今回の GKE Like な Ingress Controller では この obj interfece{} は obj.(*ext.Ingress) として Ingress リソースとして扱うことができる構造体として渡ってきています。

func (c *GKELikeIngressController) onAdd(obj interface{}) {
func (c *GKELikeIngressController) onUpdate(oldObj, newObj interface{}) {
func (c *GKELikeIngressController) onDelete(obj interface{}) {

 

GKE Like な自作 Ingress Controller の仕組みはわりとシンプルな構成になっており、Nginx Ingress Controller を上記のように手動で使う部分を全て自動化する Controller として実装しています。

onAdd が呼び出された際には、下記の 4 つの工程を行います。

  • Ingress リソースの annotation に “kubernetes.io/ingress.class” = NAMESPACE_INGRESSNAME を埋め込む
  • Ingress リソースに対応する Ingress Controller の Deployment を作成(–ingress-class = NAMESPACE_INGRESSNAME)
  • 上記の Deployment に対する “type Loadbalancer” Service を作成
  • Ingress リソースの Status に LB の IP と ClusterIP を反映

リソースの作成には kubernetes/go-client を利用しています。
例えば、Ingress の場合にはここにCreate()、Delete()、Update()、UpdateState() といったインターフェースが定義されています。

また、全てのリソースに共通することですが、onAdd 関数内で複数回リソースを更新するとエラーが発生するため、1回だけにする必要があります。
さらに、各リソースの Status に関しては Update() 関数ではなく UpdateStatus() 関数を利用しないと更新があっても無視される点に注意して下さい。
今回の場合だと、Ingress リソースの Status.LoadBalancer.Ingress 構造体を更新することで、LoadBalancer の IP と Cluster IP を登録しています。

onUpdate が呼び出された場合には特別な作業は行いません。作り込み次第ですが、 IP 変更時の変更処理程度だと思います。
Ingress リソースの更新に関しては、Nginx Ingress Controller の Deployment が変更を反映してくれるため、この部分に複雑な処理が必要ないのは多いなメリットだと思います。

onDelete 時には Deployment と Service を削除します。

 

以上の実装をすることで、GKE Like な Ingress Controller を作成することが出来ました。
GKE と違う部分としては、Internal から Ingress を叩く場合には ClusterIP を利用したほうが良いため、Ingress の IP にも一応入れています。

# kubectl get ing -o wide
NAME              HOSTS                 ADDRESS                      PORTS     AGE
sample-ingress    sample1.example.com   192.104.167.215,10.10.10.45   80, 443   3h
sample-ingress2   sample2.example.com   192.111.123.71,10.10.10.47    80, 443   3h

 

裏側的には Deployment と Service が配置されています。当然 annotation と ingress-class は指定された状態でデプロイされています。

# kubectl -n kube-system get deploy,service sample-ingress
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/sample-ingress   1         1         1            1           3h

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP   PORT(S)                      AGE
svc/sample-ingress   LoadBalancer   192.104.167.215   10.10.10.45   80:11652/TCP,443:44404/TCP   3h

# kubectl -n kube-system get deploy,service sample-ingress2
NAME                     DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deploy/sample-ingress2   1         1         1            1           3h

NAME                  TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)                      AGE
svc/sample-ingress2   LoadBalancer   192.111.123.71   10.10.10.47   80:32535/TCP,443:33424/TCP   3h

 

物理 Ingress Controller

Nginx Ingress Controller を利用する形で GKE ライクな Ingress Controller を自作しましたが、結局のところパフォーマンスを最大化するには L7 を捌ける Hardware LoadBalancer が欲しいなと思います。
現状の弊社の LoadBalancer では L7 を全て捌き切るのば難しいため、実装を見送りましたが、Hardware LoadBalancer を使った Ingress も実現することは容易なため、機会や利用感を見つつ検討したいと思っています。

 

Nginx / Nghttpx Ingress Controller の違い

Nginx を利用しているか Nghttpx を利用しているかの違いがありますが、Nghttpx Ingress Controller は Nginx Ingress Controller を参考に作られているため、基本的には同じだと思います。
Nghttpx の方は ZLab 様が公開してくれており、HTTP/2 に対応しているため、gRPC にも利用できるといった特徴があります。
また、今回実装した GKE Like Ingress Controller はどちらも選択できるようにしています。

 

以上、Ingress Controller を作ってみたお話でした。
KubeCon から帰ってきたら、じきに OSS で公開しようかな―と思っています。

]]>
エンジニアの組織に必要なエージェンシーチームとは? https://cyberagent.ai/blog/no_use/bi/10204/ Mon, 13 Nov 2017 09:22:23 +0000 https://cyberagent.ai/techblog/?p=3565 こんにちは!
株式会社サイバーエージェント
アドテクスタジオ
アドテク本部
技術戦略室
Central Infrastructure Agency
エージェンシーチームの鷹雄です。

今回はアドテクスタジオのIT総務的なエージェンシーチームの役割について紹介しようと思います!

 

業務内容ですが、主に下記のような作業を行っています。

・検証端末の貸し出し管理業務
・購買業務
・コスト情報の管理・収集・可視化
・資産管理業務
・アカウント・ライセンス管理業務

基本的には総務的な立ち位置な役割なのですが、
エンジニアの人数が200人を超える組織では総務的なポジションにも技術力が求められます。

まずはそれぞれの業務についてご紹介しましょう!

・検証端末の貸し出し管理業務

検証端末とは、実際に広告が正しく表示されているかチェックするための実機を用意しています。
サイバーエージェントグループでは、STF(https://openstf.io/)というスマートフォンをリモートで検証できるシステムを社内向けに公開しています。利用できる端末の数も多いですし、何しろリモートで表示チェックが行えるのはとても便利です。

ただ、最近では動画広告や位置情報広告などリモートで検証するのが難しい要件が増えてきており、物理検証端末も数多く必要になります。

アドテクスタジオでは実際の自社の広告配信データから、シェアが高い携帯端末を抽出して定期的に購入しています。

少し前まではサンプリングしたログからプログラムを実行してトレンドをチェックしていたのですが、最近ではBigQueryによるデータ分析でシェアを出すようにしています。

大量のログデータから今どんな機種がユーザーに使われているかなどが一瞬で出てくるので便利ですね。

検証端末は今では古い端末から、新しい端末まで東京オフィスだけでも250台程度保持しており、沖縄のデバックチームでも相当な台数を保持しています。

この規模だと少し少ないように見えるかもしれませんが、「検証端末管理者の会」というサイバーエージェントグループの検証端末管理者が入っているチャットグループで端末の在庫状況を共有し、貸し借りを行ったりもしています。

全体的に見るとiOS系のシェアが多いため、iPhoneの新機種が発売されるとすぐに纏まった台数を購入し、バージョンを固定化したりして同じ端末でも幾つかのバージョンのバリエーションを保持しています。

これによって、何か問題が発生したときに原因の切り分けなどがしやすくなります。

今回のiPhoneXは予約合戦が激しく、1台しか予約できなかったため
頑張って担当者が行列に並んで確保しました。。。

・購買業務

購買ではSSLやドメインなどの購入、各種ライセンスの包括契約など購買に関わる業務です。

SSLはサービスの種類や重要度、セキュリティなどの要件によってDV,OV,EVを選んで購入しています。

一般的な証明書ではDV証明書にComodo Essential SSL (Wildcard)を3年($200くらい)で購入するようにしています。

広告系のサービスだと大体3年以上は続くのと、証明書の切り替えにはそこそこの人的コストと、更新ミスによるサービス停止のリスクが掛かるため、出来るだけ長く取得しています。

OVやEVではcybertrustの証明書を使うことが多いですね。

 

ドメインは最近ではRoute 53で購入することにしています。

更新が自動なのと、そこそこ安いのと、安心して任せられるのが大きいですね。

また、利用しているAWSアカウントに合算されて請求されるのも便利です。

そのままRoute53をすぐ使えるのも便利ですね。

 

ライセンスの購入などはグループ全体でボリュームライセンス契約を行い、費用を削減したりしています。

例えばTableauやDatadogなど、サイバーエージェントグループ全体でまとまった数をコミットして契約をすることにより、良い物を割安で使えています。

サイバーエージェントでは全社的に何かのツールをトップダウンで契約することはありません。

これは各部署には高い裁量が移譲されているため、何かしらのツールを強制して使わせることが出来ないためです。

このためボリュームディスカウント契約の交渉などがとてつもなく難しい環境なのですが、勉強会を開催したり社内報を書いたりすることで認知を上げて良いツールを広めたりして、広がったタイミングで契約を行ってます。

Tableau,Datadog以外にも各種包括契約を行い、費用の削減と合わせて利便性の高いツールの利用を拡大しています。

 

・コスト情報管理・収集・可視化

クラウドを使っているとコストの管理が難しいと考えている会社は多いのでは無いでしょうか。理由として、見込みが立てづらかったり、請求が月初に分からなかったり。オンプレを使っていても各種資産の減価償却やネットワーク費用やラック費用の利用実態に応じた費用計算などコスト計算や、翌月の費用予測などはとても大変です。

今まではExcel、Google Spreadsheetを利用して費用計算をしていたのですが、最近では一部BIツールのTableauを利用し始めています。

APIで自動的にデータを収集し、デイリーで各クラウドの費用を集約し、各事業責任者にTableau Serverで共有しています。

 

・資産管理業務

サーバーなどの資産チェックはある程度の規模になると棚卸しも一苦労です。
現在は各サーバーにQRコードシールを貼り付けており、棚卸しの速度も以前に比べると格段に向上しました。

 

・アカウント・ライセンス管理業務

意外に大変なのがアカウント管理です。

入社や移動なども多いですし、サービスが次々に立ち上がったりする環境では一般的にアカウントの管理が疎かになりやすいです。

そこで、現在では出来るだけSingle Sign Onを利用するようにシフトしています。
サイバーエージェントではSingle Sign OnにPing Identity(https://www.pingidentity.com/en.html)を導入しており、各種システムとのつなぎ込みが容易です。

ですが、残念ながらSSOに対応していないサービスも存在します。

その場合はAPI経由でユーザー一覧を取得し、社内データベースと連携することで移動による権限の付与、剥奪などを自動化しています。

 

アカウントと同様にライセンスの管理も重要です。

基本的にユーザー数がライセンス数よりも多くなることは無いのですが、逆にライセンス数が過剰に余ってしまうケースが存在します。

これは人員の移動や、ライセンスを使わなくなった時などに起こりえます。

このため、ライセンスの棚卸しは頻繁に行う必要があります。

現在では殆どが自動化され、一定期間利用しないライセンスは回収しています。

これも社内データベースと連携することによりかなり精度が高くなりました。

ライセンスの無駄を減らす取り組みでは他にもJetbrainsのライセンスサーバーの導入なども行っています。
※https://www.jetbrains.com/help/license_server/getting_started.html
JetbrainsはIntelliJやRubyMine、DataGripなど高度なIDEを提供している会社で、エンジニアであれば誰しもが一度は触ったことがあるのでは無いでしょうか。

商用ライセンスがそこそこ高く(All Products Packが1年で$649ドル)、導入に躊躇してしまう方も多いかと思うのですが、Jetbrains社ではライセンスサーバーを提供しており、購入するのは同時起動数のライセンスだけで済むようになっています。

ライセンスサーバーを導入してから無駄なライセンスを購入しなくて良くなったのと、同時起動数だけで済むため、計算したところ年間100万以上のコスト削減に繋がっていました。

 

このような感じで、IT総務を担当しているエージェンシーチームでは技術で時間・費用コストを抑えると共に、エンジニアの開発を支える動きをしています。

まだまだ紹介出来ていない取り組みもいっぱいありますがまたの機会にでも!

 

]]>
OpenStack Heat で Resource Plugin を自作してみる https://cyberagent.ai/blog/no_use/network/2594/ Tue, 05 Sep 2017 08:36:21 +0000 https://cyberagent.ai/techblog/?p=2594 こんにちは。
Central Infrastructure Agency の青山真也です。

今回は OpenStack Heat で自作の Resource Plugin を作ってみたお話です。

弊社では昨今のコンテナの需要拡大に伴い、プロダクション環境向けに Kubernetes (& Docker Swarm) on OpenStack を実現する GKE like なコンテナ基盤を構築しました。

コンテナ基盤を作るにあたって、Heat でプロビジョニングする部分があり、
OS::Heat::AutoScalingGroup / OS::Heat::ResourceGroupを利用しています。
また、クラスタメンバに対する負荷分散では物理のロードバランサを使っているため、
現状ですとクラスタメンバの増減に伴いロードバランサの Pool Member を入れ替えてあげる必要があります。

 

heatplugin

 

方法としては、

  1. Heat の API を一定間隔でポーリングして、クラスタメンバに変更があった場合に LB の変更処理を行う
  2. Heat Resource として定義し、 Scale In/Out イベントをトラップして LB の変更処理を行う

の2つがあると思います。

1つ目の方法では、下記のような問題が存在しています。

  • 無駄な API アクセスが多い、反映までに遅延がある
  • 自前でポーリングサーバを用意してあげる必要がある
  • 追加や削除などの進行情報の管理を作り込む必要がある

2つ目の方法では、これらの問題が解決されるだけではなく、
Heat のテンプレートとしてコード化もされるため、管理がしやすいといった特徴もあります。

Heat で Plugin を自作する際には、heat.engine.resource.Resource のサブクラスとして定義し、
下記のメソッドを用意してあげる形になります。

  • handle_create()
  • handle_update()
  • handle_delete()
  • check_create_complete()
  • check_update_complete()
  • check_delete_complete()

最初の 3 つの handle_*() メソッドは、作成・更新・削除それぞれの処理を記述します。
Heat で扱う Nova Instance や Neutron Router などの OpenStack Resource は
作成する際に handle_*() 内で各 client library を使って非同期で実行します。
そのため、最後の 3 つの check_*_compute() メソッド内では、同様に各 client library を使って
それぞれの動作が完了したかをチェックする枠組みを提供しています。

 

このように Heat では Plugin を作るときにの枠組みとして、ライフサイクルを提供しているため、
“追加や削除などの進行情報の管理を作り込む必要がある” という点を解決することが可能です。

また、Heat の Resource として定義することで、Scale In/Out 時に handle_create() / handle_delete() が呼び出されます。
その際に、LB の Pool Member への追加や削除を行うといったようにイベントドリブンな処理が行われることとなり
“無駄な API アクセスが多い、反映までに遅延がある”、”自前でポーリングサーバを用意してあげる必要がある”という点も解決します。

弊社では “APIでネットワークを自動化するAXC” にあるようにすでにネットワーク機器の自動化の枠組みは導入されており、
Python ライブラリ化もされていたため、このあたりのつなぎ込みは容易でした。

 

さらに Heat には Constraint という枠組みも用意されており、各 Property の値などをチェックすることが可能です。
例えば、LB に追加する Pool Member の値が正しい IP Address の形式かどうかや、
nova に登録されているインスタンスかなどをチェックさせることも可能です。
ビルドインの Custom Constraint は Heat Orchestration Template (HOT) specification にあるほか、
簡単に自作の Custom Constraint を作成することも可能です。

 

また、依存関係を定義する際に利用する {get_resource: XXX } や {get_attr: XXX} などでリソースから値を取得できるようにするには、
_show_resource() を実装したり、heat.engine.attributes を インポートし、ATTRIBUTES を定義してあげることで実現可能です。

 

最後にこちらが完成した自作の Heat Resource です。

# Top level の Heat template に定義
resources:
  sample_pool:
    type: ADTECH::AXC::BIGIPPool
    properties:
      axcauthtoken: XXXXXXXXXXXXXXXXXXXX
      poolname: SamplePoolName
      mode: round-robin
      monitor: /Common/gateway_icmp

  sample_vs:
    type: ADTECH::AXC::BIGIPVS
    properties:
      axcauthtoken: XXXXXXXXXXXXXXXXXXXX
      vsname: SampleVSName
      ipaddr: 10.10.10.10
      poolname: SamplePoolName
      port: 80

# Top Level の Heat Template から呼び出される Auto scaling group の template に定義
resources:
  sample_poolmember:
    type: ADTECH::AXC::BIGIPPoolMember
    properties:
      axcauthtoken: XXXXXXXXXXXXXXXXXXXX
      poolname: {get_resource: sample_pool}
      ipaddr: {get_attr: [autoscale_server_eth0, fixed_ips, 0, ip_address]}
      port: 0
      ratio: 1

なお、OpenStack 系の Resource では、python-keystoneclient などの python client library を利用しているため、
client library を用意してResource Plugin から呼び出す形のほうがお作法的にはいいかもしれませんね。
ココらへんは興味がある方は OpenStack Heat のソースコードを読んでみてください。

]]>
How To Become A Successful Product in the US https://cyberagent.ai/blog/no_use/no_genre/2702/ Wed, 01 Mar 2017 01:02:45 +0000 https://cyberagent.ai/techblog/?p=2702 US Market Challenge

“巨大なUSマーケットで成功したい!”これはDynalyst, AdTech Studioだけの話ではありません。私は今現在DynalystでUS向けの開発をやっていますが、以前にはAMoAd International, CyberAgent AmericaでUS向けプロダクトに挑戦したことがあります。 特にCyberAgent Americaで働いた時(2012.3 ~ 2012.8)はいろんな国内の会社がUSマーケット特にSan Francisco Bay Areaに進出するGold Rush時代でした。

7ff240f7-b0da-99af-d742-a3acd72ee3d5

 

ただそこで成功したプロダクトや会社について私は1個も聞いたことがありません。みんな同じく言ってたのは“USは難しかった”です。

といっても今DynalystがUSにマーケットを展開している理由はMobile GameのRetargeting (Re-Engagement)プロダクトとしてUSで競争力があると判断してるからです。その判断をした一番の理由は日本のマーケットで売れてるからだと思います。普通に新規プロダクトを立ち上げて国内でトップ売り上げのゾーンに入る、もしくはそのゾーンに入る傾向があると、そこからさらに売り上げを伸ばすために一番考えやすい戦略はリージョン展開です。とくにUSは業界で一番大きいマーケットなのでリージョン展開する時に一番に注目される国です。もしリージョン展開が難しいプロダクトの場合は新規プロダクトを立ち上げて売り上げを伸ばすと思います。

 

CyberAgent Americaの反省点

“The Social Network”, “Steve Jobs”の映画では”Mark Zuckerberg”と”Steve Jobs”のStartupからの成功ストーリが実話基盤で制作されています。私たちと同じ業界なのでこの映画をみて“私たちもUSで頑張れば絶対成功するぞ”と夢を描くプロダクトや会社も多いと思います。

72f7918f-0f44-292d-abb4-3431ff239014

そこから実際に現地でビジネスをやってて一番よく言われていたのは”言葉の壁”です。

 

問題点は本当に言葉だったのか

CyberAgent AmericaでFree App King / Free App Dailyというアプリを開発した時はアメリカ人のセールスが5人で日本人のプロダクト責任者 1人、プロダクトManager 1人、日本のEngineerが4人いました。マーケティングやセールスに言葉の壁をなくすために現地のセールスを雇用したと思いますが、大きく成功できずにプロダクトは撤退となりました。

その時言われていた主な問題点は

  1. プロダクト責任者とアメリカ人のマネージメントができなかった。
  2. 雇用したセールスマンの顧客ネットワークが広くなかった。
  3. 雇用したセールスマンが広告業界の人ではなかった。

私が思う疑問点は

  1. その時のプロダクトがUSマーケットで需要はあったのか。
  2. プロダクトの競争力をあげるための競合戦略は正しかったのか。

 

当時、私は事業の戦略には関わってなかったので自分が問題だと思っていたものをちゃんと責任者が認知してプロダクトをマネジメントしていたかは分かりません。ただ、現地で業界のネットワークが広い人を雇用するのが難しいのは自明ですし、そういった人をみつければ一発勝負できる夢のような考え方より、それだけではなく自分たちのプロダクトの競争力をあげるためにUSマーケット調査と戦略に集中しつつ雇用のためにUSでのプロダクトビジョンをしっかり伝える部分が先行されなければなりません。

 

b732751e-7607-ae23-cb46-c6ad85b78caf

 

To Success in US Market

私たちは意図したかわかりませんが、LEANのプロセスに乗ったプロダクト開発を行っています。

8b1cb554-4de3-e768-8250-9d13d4d71fe4

 

USで1年、3年、5年続くプロダクトに成長していくためにはこの図をもっとも早いイテレーションで回すことです。US開発においての私たちは今CODEの状態でMEASUREに行けずIDEASに戻っています。足元はMEASURE > DATA > LEARNができるように戦略を立てるべきかもしれませんが、これは短期戦略にしかなりません。

競合に勝てずに1年・2年後に事業撤退はしたくありません。

本当にUS Mobile App Retargeting Product No.1になるプロダクトを作るにはこの図ができる組織を現地に作ってかつ顧客需要やマーケット戦略をなるはや決断して対応していくことです。このイテレーションが遅くなればなるほどUS事業成功は夢の話にしかなりません。

またネットワークの広い人材を雇用するのも難しいけど、雇用できたとしてもこれに依存するプロダクトは短期成果にしかつながりません。積極的なマーケティングとそこからチャンスがあった時にそれがチャンスだと判断でき、開発プロセスにのせていくことがもっとも重要です。

顧客需要に積極的に投資できず、現地人材に依存したプロダクトでUS No.1を目指してもそんなMagicのようなことは起きません。

 

最後に

私個人的な意見なので突っ込みはいつでも歓迎です。
次はUSだけではなくDynalyst For Gamesの未来について私からの意見を投稿したいと思います。

]]>
GitHubのプルリクのレビュワーを自動でいい感じに指名するプログラムを作った https://cyberagent.ai/blog/no_use/linux/2559/ Tue, 24 Jan 2017 09:50:57 +0000 https://cyberagent.ai/techblog/?p=2559 Dynalystの黒崎です。

GitHubのプルリクのレビュワーが自動で指名されるようになったら便利だろうかと思い、作って導入してみたので紹介します。

GitHubのレビュワー機能

ご存知の方も多いとは思いますが、GitHubにはプルリクエストのレビュワーを指名する機能があります。 (About pull request reviews)

pr-reviews-in-merge-box
画像の引用元: About pull request reviews

この機能があれば画像のように、レビュワーとして指定したユーザからOKが貰えるまでプルリクエストのマージがブロックされる仕組みです。

レビュワーをどうやって選ぶのか

誰かから依頼を受けて作業をした場合は、レビュワーとしてその人を指名する事が多いと思いますが、それ以外の場合はどうでしょうか。
自分が変更を加えた箇所の周辺をコミットした事がある人が詳しい可能性が高いので、その人に依頼する事が多いのではないでしょうか。

そう思っていたところ、LINEのエンジニアブログでgitのコミットログを用いてレビュワーを探すスクリプトの記事がありました。
git で reviewer を探すスクリプトを作りました/LINE Engineers’ Blog

簡単に言うと、

  1. 変更されたファイルに詳しい人が、レビュアーとして選出される。
  2. 変更されたすべてのファイルで、レビュアーの誰かがレビューできる。
  3. 無駄にレビュアーを増やさない。

というアルゴリズムです。

これを試しに実行してみたところ、おおよそ納得のいきそうなレビュワー候補が上がってきたので、このスクリプトをベースにGitHubのプルリクエストに対応させてレビュワーをアサインするところまで自動化してみました。

作ったプログラム

kurochan/github_reviewer_assignor

自分で書いたのは200行もないくらいですが、GitHubのAPIがわかりやすくて簡単なおかげですぐ作ることができました。

使い方

必要なのはgitとruby(試した環境は2.3)とoctokitというGitHub APIのラッパーのgemです。
config.rbにGitHubのAPIキーやgitのコミットログのメールアドレスとGitHubのユーザIDの対応を記述します。
GitHubのAPIでメールアドレスからGitHubのユーザIDが紐付けられればよかったのですが、できなかったのと、GitHubに登録していないメールアドレスでコミットされたものもたまに混じっていたりするので設定で持つことにしました。

$ sudo apt -y install ruby git-core
$ sudo gem install octokit
$ git clone https://github.com/kurochan/github_reviewer_assignor
$ cp github_reviewer_assignor/config.rb{.example,}
# edit config file
$ vim github_reviewer_assignor/config.rb

$ ruby github_reviewer_assignor/main.rb

 

実行すると、レビュワーが付いていないプルリクエストに対して、そのブランチをGitHubからfetchしてきて、ローカルでコミットの差分を取ってレビュワー候補(のメールアドレス)を出します。メールアドレスとGitHubユーザIDのマッピングがあればGitHub APIを叩いてそのユーザをレビュワーとして設定します。

今のところイベントをフックしたりはしていないので、cronで定期的に実行されるようにしています。

実際に使ってみる

先週より自分のチームの一部リポジトリで動かしはじめたので、”それっぽく”動いているのはわかりますが、”いい感じ”にアサインできているのかはまだわかりません。。。
ぱっと見た感じ、同じ人がアサインされていて、チーム歴が長い(コミット量が多い)人ほどレビューの負荷が偏ってしまう気がしてきているので、そのあたりはレビューの実績を記録して、レビュー回数で分散されるようにしたり工夫が必要かもしれません。

しばらく動かして、新たに知見が得られたらまた記事を書こうと思います。

]]>