Sansan Tech Blog

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

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つの例として参考にしていただければ幸いです。

© Sansan, Inc.