Sansan Tech Blog

Sansanのものづくりを支えるメンバーの技術やデザイン、プロダクトマネジメントの情報を発信

Google Cloud Next 2019 @San Francisco に行ってきました!(後編)

DSOC Infrastructure Group の 大澤 です。

GW も明けてしまいましたが、Google Cloud Next 2019 参加レポート後編です。

前編はこちら。 buildersbox.corp-sansan.com

Istio in Production: Day 2 Traffic Routing

www.youtube.com

Istio がとても気になっていたので聞きに行きました。サービスメッシュの定義、Istio の概要から入り、サイドカープロキシとして使われている Envoyや Istio のアーキテクチャといった、Istio 入門用セッションでした。

Istio 導入のメリットとして、Traffic Management をメインに解説されていました。
Istio がプロキシとして、コンテナ同士の通信の間に入るため、パーセント指定のトラフィック分割やコンテンツベースルーティングができます。デモではヘッダーの有無でリリース成果物(V1, V2)にリクエストを振り分けていました。

Circuit Breaker という、あるマイクロサービスでエラーが多発するようになったら即座にエラーを返して全体システムへの影響を軽減する仕組みや、意図的に障害を発生させることで障害発生時のテストを行い、障害に強いシステムを目指す Chaos Testing があります。極稀にですが、マネージドサービスや自社/他社の API サービスを信頼している前提でシステム設計されているのを目にすると、Netflix ではあえて日常的に障害を発生させていますし、適度に障害を起こすことがシステム全体の健全な運用に必要だと思いますね。

面白いと思ったのがアウトバウンド(Egress)はデフォルトですべてロックダウンされているとのことです。たとえクラスタ内であっても通信先が必ずしも信頼できるわけではない、メッシュ内のサービスであっても、メッシュ外サービスと同じように考える(ゼロトラスト)とのことです。同じネットワーク内だと多種多様な通信をしていると余計な通信まで許可してしまいがちになりますが、最小限に許可するのがベストなんですよね(当たり前)。

Kubernetes では Deployment によるローリングアップデートの場合、1% のトラフィックを新しいバージョンに流して問題が発生した場合にすぐに戻すことは難しいです。Istio ではトラフィックを細かく制御できるため、Canary Release(一部のトラフィックだけ新しいバージョンに流すこと)が可能です。

Istio で採用しているサイドカーによって、アプリケーションとネットワークが分離されているため、開発言語やライブラリに依存することなく、トラフィック制御や通信の暗号化、デプロイなどを実現できるのも大きなメリットです。

Canary Deployments With Istio and Kubernetes Using Spinnaker

www.youtube.com

DSOCでは Spinnaker を採用しているので個人的にとても楽しみなセッションだったのですが、参加できなくなってしまったので動画を見ました。

前半は Kubernetes の Canary Release を実現するためにビルトインの Deployment を使ったやり方と Istio を使ったやり方について、後半はデモを用いて Spinnaker による Canaery Release 及び分析の自動化の紹介でした。

Canary Release する場合、普通であれば現行バージョン(ここでは V1 とします)に新バージョン(V2 とします)の二つが存在し、V2 に少しずつトラフィックを流していくことを想像すると思います。リリースの準備が整ったかどうかについて分析する場合、ベースラインとなる現行バージョンと比較する必要がありますが、現行バージョンV1は少なくとも新バージョンV2より長時間稼働しているうえに、新バージョンV2とは流れるトラフィック量が異なるため、正確に比較することが難しいです。そのため、新バージョンV2と同じ数のインスタンス数(Kubernetes であれば Pod 数)分だけ、新たにベースライン用V1として作成し、新バージョンV2と同じ量のトラフィックを流すことで正確に比較できるようにします *1

Kubernetes にビルトインされた、Deployment はラベル付けなど余計な作業なしでデプロイができますが、トラフィック量の分割が難しいです。例えば、三つの Deploymentに分けて Pod 数で調整することである程度トラフィックを分散できますが、本当にトラフィックが意図した量で分散できているかはロードバランサの特性に依存します。Istio によってパーセンテージのトラフィック分割ができますが、サブセットを追加・削除したり Canary の重み付けを変える場合は設定ファイルを都度修正しなければならず、人間の手だとミスが生じやすいため自動化が必要になります。

Spinnaker であれば、Template や Expression(式)によって設定や重み付けをパラメータ化することで、動的に設定を生成することができます。デモでは 10% だけ Canary Release した後に分析しパスできれば、 20% リリース&分析、30%、40% ・・・ と徐々に増やすパイプラインになっていました。規模を拡大すると、潜在的な問題(メモリリークや I/Oが遅いバグなど)が発生する可能性があるため、影響範囲が大きいサービスなどは徐々に拡大していった方が安全ですね。

Day 4

Cloud Next は三日間で終わりましたが、Google 社のご厚意で Mountain View にある Google Cloud Space にて Google 社員といくつかのテーマでセッション&ディスカッションさせていただく機会をいただきました。

f:id:ohsawa0515:20190507022800j:plain Android のコードネームにちなんだ菓子、マスコットが置かれている名物スポットで撮影

最初は Google のイノベーションについて。自由にやらせることで出てくる、とりあえずトライしてみることが大事とのこと。例えば有名な 20% ルールは Google のミッションから外れなければ上司に相談の上、予算も必要最小限の範囲内で、業務時間を充てることができます。プロトタイプを何度も作って改善していくことでプロダクトは成長していき、たとえプロダクトがコケたとしても別プロダクトとなって生まれ変わることもあるそうです。

Google のミッション やコアのカルチャーは創業以来変わっておらず、個人的に最も好きだと思ったのが「自分の仕事に関係なくても助けてくれる」カルチャーです。Google に入社後はいろんな人が毎週一時間程度 OJT をやってくれるのでキャッチアップも問題なくできたとのことで、今度は自分が助ける側として自然にそういうことができる環境になっているとのことです。助けることで大きなインセンティブはないですがピアボーナスはもらえます。ピアボーナスによって、自分のコアを離れてヘルプをしていける、その結果自分の技術度が広がっていくこともあるそうです。弊社も Unipos を導入していますが、お互いに感謝や、称賛し合ったりする流れは感じています。

SRE についてのセッションでは、可用性、信頼性を 100% にする必要がなく、人間の心臓でさえ、可用性が 99.95%、たまに心拍が止まっても支障がないという話があり、とても共感しました。 バックエンドサービスであってもエンドユーザがいるのであれば、SLO は設定すべきで、会社のオペレーション、ビジネス、Devチームの三つ巴で決め、四半期に一度見直すのが理想です。

他にもハイブリッド/マルチクラウド、新サービス(Cloud Run)、BigQuery のアップデート情報の解説など、とてもボリューミーで疲れましたが有意義な時間となりました。

おわりに

海外カンファレンスに参加するのは初めての体験でしたが、とても楽しかったですし、刺激をたくさんもらいました。
英語はCTOの藤倉に頼りきりになってしまったので、また行くことがあるときに備えてもう少し勉強しておきます。

*1:詳細は Netflix のブログを参照

DSOCコーポレートサイトを動かしているインフラ

導入

DSOCインフラチームの藤田です。
皆さんはDSOCのブランドサイトをご覧になったことはありますでしょうか? 自分の所属している部署のブランドサイトがこんなにかっちょいいなんて、テンションアゲアゲです。ちなみにサイトのコンセプトなどはこちらで紹介されていますので気になる方は読んでみてください。今日はこのイケてるデザインのほうではなく、インフラ部分を紹介しようと思います。

このサイトのインフラはAWS100%で構成されています。サーバレスやデプロイパイプラインなどの要素を盛り込み、できるだけ運用負荷を下げるように構築しました。

本記事では各種サービスの細かい設定方法などは省略しており、「DSOCブランドサイトはこんな構成でできてます」ということを紹介するのが目的です。また、AWSを日頃から使っている方にとっては当たり前の内容が多いかもしれませんがご勘弁ください。

構成図

f:id:oneal-desu:20190501165441j:plain

CloudFront, S3

バックエンド部分はAmazon CloudFrontとAmazon S3を使ってサーバレス(色んな捉え方がありますが、ここではAmazon EC2を利用しないという意味)構成となっています。S3のみでも簡単に公開できますが、HTTPS接続に対応できないため今回はCloudFrontも利用しています。これはAWSで静的サイトを作成するときによく使われるパターンだと思います。

ここで1つ問題なのがS3とCloudFrontを連携する際、S3のWeb Hostingの設定を有効にするかどうかです。たとえば /team/ のようにディレクトリへアクセスが発生したときは /team/index.html を返してくれるのがWEBサーバの一般的な動作だと思います。Apacheでは DirectoryIndex ディレクティブでこの機能を実現しています。

S3ではWeb Hosting機能を有効にすることでこれを満たしてくれるのですが、これを有効化するとセキュリティ観点から以下のデメリットがあります。

  • CloudFront-S3間がHTTPでしか通信できない
  • CloudFront Origin Access Identityが使えない

AWS設備内の通信ではありますが、全てHTTPSで完結させたかったのでなにか方法はないかと考え、Web Hosting機能の代替としてLambda@Edgeを使うこととしました。

Lambda@Edge

Lambda@Edgeは簡単に言えば「CloudFrontへのリクエスト・レスポンス時に実行できるLambda関数」です。リクエストのURIやヘッダー、ボディーを操作することができるので、今回はオリジンへのリクエストの際、宛先URIが /team/ , /team のような場合に /team/index.html へ置き換える関数を作りました。こちらはクラスメソッドさんの素晴らしいブログを参考にしています。

exports.handler = async (event) => {

    var request = event.Records[0].cf.request;

    const olduri = request.uri;

    if (olduri.match(/\/$/)) {
        var newuri = olduri.replace(/\/$/, '\/index.html');
    } else {
        var newuri = olduri.replace(/\/[^.\/]+$/, '$&\/index.html');
    }

    console.log("Old URI: " + olduri);
    console.log("New URI: " + newuri);

    request.uri = newuri;

    // Return to CloudFront
    return request;
};

CodePipeline, CodeBuild

本サイトは更新頻度が高くなると想定されたのでCodePipelien, CodeBuildを使ってデプロイパイプラインを構築しました。
CodePipelineは様々なアクションを組み合わせてパイプラインを構築できます。今回は利用していませんが、手動承認機能が地味に便利です。CodeBuildはYAMLファイルで定義した処理をコンテナ上実行することができ、ビルドやテストに利用することができます。定義するのはLinuxコマンドなので特別な記述方法を覚える必要はありません。

デプロイの流れは以下のとおりです。
(同じように master ブランチにcommitがあれば本番環境へのデプロイが走ります。)

  1. GitHubの release_stg ブランチにcommit
  2. CodePipelineでcommitを検知、パイプラインが起動
  3. CodeBuildで静的ファイル作成, S3にアップロード, CloudFrontキャッシュクリア

CodeBuildはCodePipelineから受け渡されたリポジトリのコミットに配置してある buildspec.yaml にしたがって処理を実行します。

version: 0.2

env:
  parameter-store:
    CLOUDFRONT_ID: "/path-to/cloudfront-id"
    S3_BUCKET_NAME: "/path-to/s3-bucket-name"

phases:
  pre_build:
    commands:
      - echo Build started on `date`
  build:
    commands:
      - docker-compose build
      - docker-compose up -d
      - docker-compose exec -T app /bin/sh -c "npm install"
      - docker-compose exec -T app /bin/sh -c "npm run generate"
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Upload files to s3
      - aws s3 sync ./dist/  s3://${S3_BUCKET_NAME}/ --delete
      - echo Clear cloundfront chache
      - INVALIDATION_ID=$(aws cloudfront create-invalidation --distribution-id ${CLOUDFRONT_ID} --path '/*' | jq -r .Invalidation.Id)
      - while [ $(aws cloudfront get-invalidation --distribution-id ${CLOUDFRONT_ID} --id ${INVALIDATION_ID} | jq -r .Invalidation.Status) != "Completed" ]; do echo waiting for complete && sleep 5s; done

今回のCloudFrontのIDやS3バケット名のように環境毎に扱うリソースが異なる場合、Systems Managerのパラメータストアを参照するようにすれば各環境で同じようにビルド、デプロイを行うことができます。

まとめ

今回はDSOCブランドサイトのインフラについて説明しました。細かい部分で言えば、Route53, ACM, AWS WAFなども利用していますが細かすぎるので今回は省略します。
AWSで実装するにしてもこれ以外に色んなやり方があると思います。1つの例として参考にしていただければ幸いです。

フィード機能におけるDynamoDBの選定理由と活用法

こんにちは。Sansanのサーバサイドエンジニアの荒川です。今回はSansanのアプリに導入されてるフィード機能について、特に中心でもあるDynamoDBについてアーキテクチャを交えながら技術的側面を解説していこうと思います。

Sansanアプリにおけるフィードとは?

f:id:ad-sho-loko:20190508094747j:plain
Sansanのフィード機能

登録した名刺に関する人事異動やその会社のニュースなどが流れる機能です。営業先や取引先のニュースが受動的に流れるため、ご活用いただいているユーザも多いかと思います。「隣の部署のAさんの名刺交換した人、面識あるぞ」「数年前お会いしたBさんが部長に昇進されたんだ」とビジネスレベルでの気づきを与えてくれます。

少々イメージしにくいかもしれませんが、SNSでのニュースフィードをイメージしていただくと良いかもしれません。

※2019年05月09日時点での機能のため、今後は機能の改修が入る可能性があります

フィードの全体像

以下がフィード機能のアーキテクチャ図になっています。詳細は割愛しますが、バッチサーバと記述している部分がありますが、実際は内製のメッセージング機構を利用しています。その他にも冗長化されたりしていますが、本題と逸れるので簡略化しています。

f:id:ad-sho-loko:20190507142234p:plain
フィード機能を支えるアーキテクチャ

  1. PostgreSQLに格納されたイベント情報をもとに定期的にフィードが作成され、作成したフィードはDynamoDBに格納される
  2. DynamoDBに格納されたフィードを、別のバッチからAWSのSNSを介してモバイルへプッシュ通知を送る *1
  3. (リクエストがあれば)APIサーバはDynamoDBとPostgreSQLから返却するデータを作成してレスポンスを返却する

ご覧の通りフィード機能では一部データをAWS上のDynamoDBに保管しています。フィード作成にあたっては、発生した名刺交換のイベント、ニュースソースなどから取得したデータを紐づけます。そして一日に2回(平日の朝と夕方)バッチが起動して作成されたフィードのプッシュ通知を送ります。

さて、フィードを支える技術としてDynamoDBが中心となっていることがお分かりになると思います。 前提の説明はここまでにして、以後はDynamoDBの説明とフィード機能における選定理由を解説します。

*1:一日に2回実行され、CloudWatchがトリガーになっています

続きを読む

RubyKaigi 2019 でいいねと思ったノベルティ

Eight の本田です。

4/18(木)〜20(土) に福岡で開催された RubyKaigi 2019 に参加してきました。

RubyKaigi とは

年に一度開催される Ruby コミュニティ主催の国際カンファレンスです。

Rubyist による Rubyist のためのお祭りみたいなものです。

Sansan は RubyKaigi に企業スポンサーとして参加しており、今回はエンジニアとブース担当をあわせて17名で参加しました。

ノベルティ

RubyKaigi では協賛している 40 社を超える企業がブースを出しており、様々なノベルティを配布していました。

ノベルティというとシールや T シャツが思い浮かぶかと思いますが、今回の RubyKaigi では各企業さんが様々なノベルティを出していて面白かったです。

そこで、本記事では私がもらって嬉しかったノベルティを3つほど紹介しようと思います。

続きを読む

【Techの道も一歩から】第19回「itertoolsを使おう」

こんにちは。 DSOC R&D グループの高橋寛治です。

桜の花びらが散り春の時候を感じる今日このごろ、スマブラだけでなくコーディングの季節もやってきたなと思っております。

意気込んでコーディングを始めたときに、たとえば2階層のリストを1階層に変形したり、条件に沿うものだけをリストから抽出したりする場合に、自分で実装してしまいがちです。

そのようなリストの各要素に対する処理について、高速かつメモリ効率のよい実装である itertools が Python の標準モジュールとして実装されています。 今回はこの itertools や言語処理に取り組んでいる際の適用例などを紹介します。

イテレータ

イテレータとは、公式マニュアルに書かれている通りですが、データの流れを示すオブジェクトです。 Pythonの内部処理としては、イテレータオブジェクトの __next__() メソッドが呼ばれる、もしくは next() に渡された場合に、次のデータを返します。

実際のコードを見てみましょう。 itertools.count というのは、start で値が始まり、呼び出すごとに step の値分加算された値を返すというイテレータです。

>>> import itertools
>>> iter_count = itertools.count(start=0, step=1):
count(0)
>>> iter_count.__next__()
0
>>> iter_count.__next__()
1
>>> next(iter_count)
2
>>> for c in itertools.count(start=0, step=1):
...:     if c > 2:
...:         break
...:     print(c)
0
1
2

ここではいったん for で読み取る場合には、 __next__() が呼ばれると考えてください。 呼ばれるごとに加算された値が返っていることがわかるかと思います。 この itertools.count ですが、次のコードとほぼ等価です(公式マニュアルより引用)。

def count(start=0, step=1):
    n = start
    while True:
        yield n
        n += step

ここで yield 式が出てきました。 これは、 next()__next__() が呼ばれるたびに、 yield で指定された値を返します。 上の例ですと、 while による無限ループ内で yield が記されており、 next() のたびに n += stepyield n を繰り返します。 このように yield 式を持つ関数は、ジェネレータと呼ばれます。

続きを読む

【ML Tech RPT. 】第6回 不均衡データ学習 (Learning from Imbalanced Data) を学ぶ (3)

f:id:ssatsuki040508:20181210005017p:plain
DSOC 研究員の吉村です。最近はシーバスフィッシングにハマっています。長らく釣れない日々を送っていたのですが、つい最近久しぶりに釣り上げてから、調子がややよくなってきました。

さて、前置きはこのくらいにして、今回も不均衡データ学習の話です。今回は、不均衡データに対して異常検知手法を用いる方法について説明をします。ちなみに前回、前々回の記事をまだ読まれていない方はそちらから見ていただけると嬉しいです。
buildersbox.corp-sansan.com
buildersbox.corp-sansan.com

不均衡データ学習に異常検知手法を用いる場合の仮定

f:id:ssatsuki040508:20190425211752j:plainf:id:ssatsuki040508:20190425211806j:plain
二次元平面上にデータ点をプロットした様子の例。
(左図) 分類問題として対処すべきと考えられるデータの分布。
(右図) 異常検知問題として対処すべきと考えれるデータの分布。
前々回の記事から、不均衡データ学習におけるアプローチの一つとして異常検知手法を紹介していますが、異常検知手法は不均衡データ学習に対する一般的な対処法ではありません。
不均衡データ対策として異常検知手法が有効に働くのは、少数派のデータ点が特徴量空間上でクラスタを作っていない場合であると考えられます。この時には、少数派のデータを"滅多にない"、"普通ではない"データ(つまり、異常データ)と考えることで異常検知手法の適用ができます。
ここで私が言いたいことは、あくまでも不均衡データ学習を異常検知の問題設定とみなせるときにのみ、異常検知手法が威力を発揮するのであり、異常検知手法は不均衡データ学習全般に対する汎用的な対処法ではないということに注意すべきであるということです。本記事の趣旨は不均衡データ学習に用いるための手法を知ることなので、今回は時系列ではない異常検知問題に対処するための手法についていくつか紹介します。

続きを読む

© Sansan, Inc.