アクトインディ開発者ブログ

子供とお出かけ情報「いこーよ」を運営する、アクトインディ株式会社の開発者ブログです

S3のフォルダーが日毎にちゃんと作られているかLambdaで監視する

こんにちは!!こんにちは!!
インフラエンジニアのyamamotoです。

最近、しいたけ栽培セット にハマっています。モリモリしいたけが生えてくるのでとっても面白いです!
生のしいたけというのも普段なかなかお目にかかれないので、秋の味覚を楽しめてイイですよ!

しいたk…ログがちゃんと出力されているか?

当社のあるプロダクトではAWSを使用していますが、ログをAthenaで読むために、S3に日毎のフォルダーに分けて出力するようにしています。
ログの出力のしかたについては過去の記事をご参照ください。 tech.actindi.net

ログって使わないときはつい放置しがちになりますよねー。いつの間にか出力が止まっていたりしても気づかなかったり。
そこで、ログが毎日ちゃんと出力されているか監視するために、スクリプトを書いてLambdaで監視するようにしてみました。

コードの説明

ざっくり、コードはこんな感じになりました。

const {
  S3_BUCKET_NAME,
  S3_CHECK_PATTERN1,
  S3_CHECK_PATTERN2,
  S3_CHECK_PATTERN3,
  SNS_TOPICARN,
  SNS_REGION,
  SNS_MESSAGE_TITLE,
  SLACK_URL,
  SLACK_CHANNEL,
} = process.env;

const AWS = require('aws-sdk');

const s3 = new AWS.S3();
const sns = new AWS.SNS({
  apiVersion: '2010-03-31',
  region: SNS_REGION,
});

const checkPattern = [S3_CHECK_PATTERN1, S3_CHECK_PATTERN2, S3_CHECK_PATTERN3];

function formatDate(date, format) {
  let ret = format;
  if (!ret) ret = 'YYYY-MM-DD';
  ret = ret.replace(/YYYY/g, date.getFullYear());
  ret = ret.replace(/MM/g, `0${(date.getMonth() + 1)}`.slice(-2));
  ret = ret.replace(/DD/g, `0${date.getDate()}`.slice(-2));
  return ret;
}

async function checkS3Object(bucket, pat) {
  const params = {
    Prefix: formatDate(new Date(), pat),
    Bucket: bucket,
    MaxKeys: 1,
    StartAfter: formatDate(new Date(), pat),
  };

  console.info(`Checking S3 log directory ${bucket}/${params.Prefix}`);
  const res = await s3.listObjectsV2(params).promise();
  if (res.KeyCount === 0) {
    console.info('Data listobj: ', res);
    return 1;
  }
  return 0;
}

async function checkS3Status(bucket, pattern) {
  let ret = 0;
  let res = 0;
  for (let i = 0; i < pattern.length; i += 1) {
    if (pattern[i]) {
      res = await checkS3Object(bucket, pattern[i]);
      if (res === 1) {
        ret = `${bucket}/${pattern[i]}`;
        break;
      }
    }
  }
  return ret;
}

async function publishSNS(arn, title, message) {
  const params = {
    Message: message,
    Subject: title,
    TopicArn: arn,
  };

  try {
    await sns.publish(params).promise();
  } catch (err) {
    console.error('Error publishing: ', err);
  }
}

async function publishSlack(slackurl, slackchannel, message) {
  const { IncomingWebhook } = require('@slack/webhook');
  const slackwebhook = new IncomingWebhook(slackurl, {
    channel: slackchannel,
  });

  try {
    await slackwebhook.send(message);
  } catch (err) {
    console.error('Error Slack: ', err);
  }
}

exports.handler = async () => {
  const ret = await checkS3Status(S3_BUCKET_NAME, checkPattern);
  if (ret !== 0) {
    const msg = `Error! S3 log directory ${ret} is not known!`;
    await publishSNS(SNS_TOPICARN, SNS_MESSAGE_TITLE, msg);
    if (SLACK_URL !== '') {
      await publishSlack(SLACK_URL, SLACK_CHANNEL, msg);
    }
    console.error(msg);
  }
};

フォルダー名の形式を受け取って、それにマッチしたものが無いとSNS→メールと、Slackで通知するようになっています。
当社の例ではフォルダー名は「/year={年}/month={月}/day={日}/ 」なので、日付をフォーマットに当て込む関数を簡易的に使っています。
また、AWSのオブジェクトは非同期で扱うのでasync・awaitを使って受け渡しています。

意外と迷ったのが、フォルダーの存在有無をどう確認するか、というところです。
S3のフォルダーの有無を確認するには、s3.listObjectV2() メソッドを使用しています。

あと、SlackのWebhookを使った通知については、下記を参照しました。
https://slack.dev/node-slack-sdk/webhook

Lambdaに仕込む

これを、Lambdaに仕込みます。
トリガーとして CloudWatch Event を使い、毎日特定の時間に実行するようにします。日毎の場合、日付が変わってある程度時間が過ぎてからにしています。
リソースについては、監視するS3オブジェクトに対する s3:ListBucket 権限と、SNSの sns:Publish 権限が追加で必要になります。
あと、コードの冒頭にある環境変数を設定する必要があります。

さいごに

アクトインディでは、しいt…Webエンジニアを募集しています! actindi.net

システム開発・運用に関連する各種サービスのステータスページまとめ

2019/08/23 発生したAWSの障害事故は当社のサービスにも影響しました。 幸いサービスが長時間に渡って止まるなどの大きな影響はなかったものの、一時アクセスしにくい状態になりました。

普段、AWSがダウンするなんてことはほぼ考えないで使っているので、突然止まると何が起こったのか把握するのに思わぬ時間がかかることがあります。

とりあえず、これはなんかおかしいと思ったらTwitterで検索してみるのが手っ取り早かったりするんですが、 何が起こっているのか、あるいは起こっていないのかを正確に知るためには利用している各サービスの公式情報を得るべきだと思います。
大抵のサービスでステータスページが用意されています。まずはそれをチェックすることからかと思います。

いい機会なのでAWSだけでなく、システム運用、開発によく使われ、依存しがちなサービスのステータスページをまとめてみました。
多くのサービスでステータスページの内容をRSSフィードやTwitterでも情報が提供されています。
利用サービスのフィードをSlackにでも流すようにしておけば、いち早く障害を察知するのに役立つと思います。

なお、サービスのチョイスは当社で使っているものの他によく使われていそうかなと思ったものを選びました。
私の独断です。
サービスの分類も私の独断と偏見です1。悪しからずご了承ください2

ステータスページへのリンクとRSSやTwitterへのリンクをフィードとして示しています。

IaaS,PaaS系サービス

システム、サービスをまるっと載せてしまえるサービスを提供しているものを分類しました。

これらはモノリシックなサービスというわけではなく、様々なユースケース、システム要件に対応するサービス群を提供するプラットフォームです。
ダウンされるとたちまちサービスの稼働に重大な影響が発生する可能性が高く、その稼働状況には特に気を配るべきサービスかと思います。

サービス名 ステータスページ フィード
Amazon Web Service(AWS) AWS Service Health Dashboard ECS
RDS
S3
Google Cloud Platform(GCP) Google Cloud Status Dashboard
Microsoft Azure Azure の状態
Heroku Heroku Status
Saleforce Trust Status

AWSは各リージョンx各サービスごとのRSS配信が用意されています3。表には代表的な3サービスのフィードを載せました。他のサービスはステータスページを見てください。 AWS Healthというサービスもあるので利用を検討してみてもいいかと思います。

Baas系サービス

システムの一部機能を担うバックエンド系のサービスです。
サービスにクリティカルな影響があるものが多いのでその稼働状況に注意したいサービス群です。

サービス名 ステータスページ フィード 概要
Firebase Firebase Status Dashboard GoogleのBaas
Stripe Stripe System Status @stripestatus 決済サービス
Paypal PayPal Status Page 決済サービス
Auth0 Auth0 Status Page @auth0status 認証サービス
OneSignal OneSignal Status @OneSignalStatus プッシュ通知配信サービス
Fastly Fastly Status CDN

Auth0はRSSフィードも提供していますが、テナントIDを含める形でRSSフィードのURLが決まります。詳しくはステータスページを参照してください。

開発系Saas

サービスの稼働には直接関わらないけれど、物によっては開発プロセスの重要な一部を担うサービスです。

ライブラリ・パッケージエコシステム系サービス

各言語のパッケージマネージャがライブラリやパッケージを取得元とするサービスです。
落ちているとライブラリ等が取得できずにビルドできなくなったりするので、本番サービスにすぐに影響することはないですが、開発プロセスにとっては重要なサービスです。

サービス名 ステータスページ フィード
RubyGems RubyGems.org Status @rubygems_status
npm npm, Inc. Status @npmstatus
Docker Docker System Status RSS Feed

DockerはDockerhubを含んでいます。

VCSサービス

ソースのバージョン管理システムのリモートリポジトリを提供するサービスです 。
落ちているとソースを取得できず、ビルドに失敗したりするのでやはり開発プロセスにおいて重要なサービス群です。

サービス名 ステータスページ フィード
Github GitHub Status @githubstatus
GitLab GitLab System Status @gitlabstatus
Atlassian Bitbucket Atlassian Bitbucket Status

CIサービス

落ちているとビルドやデプロイ処理が止まってしまうなどの影響がある重要サービスです。

サービス名 ステータスページ フィード
CircleCI CircleCI Status @CircleCIstatus
Travis CI Travis CI Status @traviscistatus

監視・モニタリングサービス

サービスそのものや、開発プロセスを止めたりしないものの、システムの状態がわからなくなるので大いに困るサービスです。

サービス名 ステータスページ フィード
Mackerel Mackerel Status @mackerelio
Datadog Datadog Status @DatadogOps
New Relic New Relic Status
Sentry Sentry Status @getsentry
Airbrake Airbrake Status @airbrake
Google Analytics Analytics ステータス ダッシュボード

※単なる公式Twitterアカウント。ステータス情報以外も投稿されています。

業務系Saas

開発業務をサポートするサービスです。
開発プロセスそのものにとってクリティカルではないと思いますが、使えないと作業効率を大きく落としてしまいます。
ただ、Slackのダウンは高度にChatOpsを実現していると効率低下で済まないかもしれません。

サービス名 ステータスページ フィード 概要
G Suite G Suite Status Dashboard オフィススイート+α
Slack Slack System Status @SlackStatus チャットサービス
Chatwork1 メンテナンス・障害・その他 チャットサービス
Zoom Zoom Status ビデオチャットサービス

ステータスページを作れるサービス

紹介したサービスのいくつかでも使われていますが、Statuspage | Hosted Status Pages for Your Companyを使えばステータスページを作成できます。
自社のサービスで作りたくなったら利用を検討するといいと思います。

最後に

すべてを自前で賄うとというのも難しいので多かれ少なかれこれらのサービスを使っているかと思います4
利用しているサービスはプロダクトやチームそれぞれですし、依存の度合いも違うと思います。
止まることもあると思って完全な障害対策を用意しておけると理想です。
しかし、障害が発生する頻度と発生時の損失、障害対策を維持するコストのバランスを考慮し最適な費用対効果を見いださないといけません。その結果、プラットフォームの障害には甘んじないとならない状況、箇所もあると思います。
だから、せめて迅速に状況を把握できるように障害情報を得る方法を確認しておきたいと思って調べてみました。

アクトインディではエンジニアを募集しています。 actindi.net


  1. 様々なサービスメニューを提供していて、分類しづらいサービスも多く難しかったです。

  2. いわゆるステータスページはありません。要望は上がっているようです。status.chatwork.com が欲しい – Chatworkに関するご意見・ご要望

  3. 「あのサービスも入れてあげて!」とかはてブのコメントに書かれると追加するかもしれません。

  4. リージョンごとの全サービスフィードもあれば便利なのになぁと思うのは私だけでしょうか。

  5. 利用したほうが可用性が高かったりしますし。

AWS CloudFormation入門編:IAMユーザーを管理してみる

こんにちは!!こんにちは!! インフラエンジニアのyamamotoです。

AWSのCloudFormation、使ってますか?
すでに当ブログでも記事になっていますが、布教のために改めて題材にしたいと思います。

これが驚くほど便利なので、だまされたと思って一度使ってみてください!

CloudFormationのスゴイメリット

CloudFormationには、主に下記のようなメリットがあります。
1. インフラのコード化
2. 変更の場合は差分のみ更新される
3. 実行時に不具合があったらロールバックしてくれる
4. まるっと削除できる

1.については最近のトレンドですね。AWSのリソースを用意するのにYAMLやJSONで設定(テンプレートといいます)を書いておけるので、Gitなどにコードとして残しておけます。また、同じ環境や似た環境を作るのもコードから起こすので、コンソールの操作と違って楽にできますね。
2.はCloudFormationの方で、自動的に設定の差分だけを反映してリソースを追加・削除してくれるので、変更しない部分に影響を与えずにうまいこと更新することができます。
3.もCloudFormationがよきにはからってくれる点で、リソースの追加・削除の際にコンフリクトなどの問題が発生したら、作業する前の状態まで自動的に環境を戻してくれます。
最後に4.は、CloudFormationで作ったリソース群は「スタック」という単位で管理されていて、スタックで追加したリソースだけをまるっと削除できるので、他の環境に影響を与えずにスクラップアンドビルドが簡単にできます。

CloudFormationでIAMのユーザー管理をしてみる

前述のような特長から、CloudFormationを使うのに一番メリットがあるのは、IAMのユーザー管理だと(勝手に)思っています。
ユーザーの追加や削除、グループの変更や権限設定は頻繁に行われるので、CloudFormationのメリットが生かされるのではないでしょうか。

ということで今回はCloudFormationのお試しで、ユーザー管理を取り扱ってみます。
すでにmorishitaがAWS CDKでIAMユーザーを作成するネタを披露していますが、こちらはプレーンなCloudFormationなので比較材料になると思います。

ここでは、簡単に下記のようなユーザー環境を構築してみたいと思います。

  • グループは以下の通り
    • デザイナー
      • AWSのコンソールにはアクセスできない
      • 作業環境構築のため、ECRのリポジトリにだけアクセスできる
    • カスタマーサポート
      • AWSのコンソールにアクセスできる
      • ユーザーサポートのため、Athenaにだけアクセスできる
  • ユーザーは以下の通り
    • hogehogeさん
      • デザイナー
    • fugafugaさん
      • カスタマーサポート

これをCloudFormationの設定にすると、下記のようになります。

Resources:
  # グループdesigner:デザイナー
  # 権限:AWSコンソールアクセス不可。ECRのイメージダウンロード権限
  GroupDesigner:
    Type: "AWS::IAM::Group"
    Properties:
      GroupName: "designer"
      Path: "/"
      Policies:
        - PolicyName: "ECRRepository"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action:
                  - "ecr:GetAuthorizationToken"
                  - "ecr:BatchCheckLayerAvailability"
                  - "ecr:GetDownloadUrlForLayer"
                  - "ecr:DescribeImages"
                  - "ecr:BatchGetImage"
                Resource:
                  - "*"

  # グループcustomersupport:ユーザーサポート
  # 権限:AWSコンソールアクセス、Athenaの操作権限
  GroupCustomerSupport:
    Type: "AWS::IAM::Group"
    Properties:
      GroupName: "customersupport"
      Path: "/"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonAthenaFullAccess"

  # ユーザーhogehoge氏
  # 権限:designerグループ。コンソールアクセス不可。要キー作成
  UserHogehoge:
    Type: "AWS::IAM::User"
    Properties:
      Path: "/"
      UserName: "hogehoge"
      Groups:
        - "designer"

  # ユーザーfugafuga氏
  # 権限:custemersupportグループ。コンソールアクセス可
  UserFugafuga:
    Type: "AWS::IAM::User"
    Properties:
      Path: "/"
      UserName: "fugafuga"
      Groups:
        - "customersupport"
      LoginProfile:
        Password: "initialpassword"
        PasswordResetRequired: true

各設定項目などはマニュアルなどで調べてみてください。ただ、マニュアルは2000ページ以上あってけっこう大変です…… https://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-ug.pdf

CloudFormationのコンソールを触ってみる

そして、これをCloudFormationのコンソールに登録します。 もちろんCLIにも対応していますが、コンソールでも簡単に作業することができます。

CloudFormationのコンソールを開いたら、「スタックの作成」をクリックします。
f:id:yamamoto-kazuyasu:20190813122746p:plain

「スタックの作成」フォームが出てくるので、「テンプレートのアップロード」を選択して、上記テンプレートのYAMLファイルをアップロードして「次へ」をクリックします。
f:id:yamamoto-kazuyasu:20190813122846p:plain

「スタックの名前」には他とかぶらないわかりやすい名前をつけます。わかりにくい名前だと、スタック更新や削除などのときに間違えてしまうので要注意です。
f:id:yamamoto-kazuyasu:20190813122929p:plain

「スタックオプションの設定」については現状そのままにしておきます。
f:id:yamamoto-kazuyasu:20190813122946p:plain

確認画面ではフォーム下部にあるチェックボックスを入れないとエラーになります。チェックを入れて「スタックを作成」をクリックします。
f:id:yamamoto-kazuyasu:20190813122956p:plain

するとYAMLに記述したリソースをモリモリと自動的に作成してくれます。
f:id:yamamoto-kazuyasu:20190813123013p:plain

スタック作成後、IAMの管理画面で見ると、グループやユーザーが作られていることがわかります。
f:id:yamamoto-kazuyasu:20190813123033p:plain f:id:yamamoto-kazuyasu:20190813123107p:plain

また、指定した権限設定も反映されていますね。
f:id:yamamoto-kazuyasu:20190813123131p:plain f:id:yamamoto-kazuyasu:20190813123048p:plain

今まで作ったものをすべてまるっと削除する場合は、スタック詳細画面で「削除する」をクリックします。
するとYAMLで作成されたリソースがサクサク削除されていき、スタックを作成する前の状態に戻ります。
f:id:yamamoto-kazuyasu:20190813123147p:plain

さいごに

いかがでしょうか?こうやって見ると、CloudFormationって意外と簡単で便利ですよね。
コードとして残りますし、巻き戻しも簡単。コンソールでポチポチリソースを作成するより圧倒的に使いやすいと思います。
CloudFormationオススメですよ!

アクトインディではAWSを愛するインフラエンジニアを募集中です!
ぜひお問い合わせください!

AWS CDK で IAM ユーザを作ってみる

morishitaです。

AWSのリソースを作るときにはコンソールをポチポチやるよりは Cloud Formation を使ったほうが楽に感じるようになってきました。
そんなところに aws-cdk が GA になりました。Typescript で Cloud Formation スタックが書けるということで気になっていました。
ちょっとググると DyanamoDB とか Lambda を使ってサーバレスアプリケーションを作りました的な記事が出て来るのですが、今回はちょっと地味にIAMユーザを作ってみました。

背景

当社ではほとんどのプロダクトで Docker Compose による開発環境を用意しています。
更に、いくつかのプロダクトでは本番DBのデータ1を含むDockerイメージを毎日ビルドしてECRにプッシュしています。それを Pull すれば、ほぼ最新の本番DB同様のデータをローカルの開発環境で利用できるようになっています。
アクトインディではデザイナが Slim のテンプレートを書くので、デザイナーも開発環境を作ります。DBのイメージを Pull するにはECRにアクセスできる必要があるので、そのためのデザイナにもIAMユーザを振りだそうということになりました。

そのときは次の様な要件で YAMLでテンプレートを作って Cloud Formation スタックを作りました。

  • デザイナ用グループを作る
  • グループに ECR から Pull できるだけのポリシーをつける
  • そのグループに各デザイナ用の個別のIAMユーザを作成する
  • IAMユーザには AccessKeySecretAccessKey も発行する

やってみて思ったのは、同じ様なIAMユーザを作るのに繰り返しのシンタックスがないので不便だということでした。
コピペしてちょっと変えていくだけで増やせるのですが、そのコピペがイマイチだなぁと。

でも、Typescript で書ける CDK ならもっとスッキリ書けるのでは、ということ入門からやってみました。

AWS CDK のインストールとスタックの初期化

CDKのCLIはnpmパッケージです。
それをインストールして、iam-users という名前で CDK プロジェクトを初期化するには次のコマンドを実行します。
言語は、 Python、Java、.NET、TypeScript から選択できます。
私は TypeScript を指定します。

$ npm install -g aws-cdk             # CDKのインストール
$ mkdir iam-users
$ cd iam-users
$ cdk init app --language=typescript # CDKプロジェクトの初期化

ちなみに、このときインストールされたCDKは 1.3.0 でした。

cdk init appの結果、次のファイル群が生成されます。

iam-users/
├── README.md
├── bin/
│   └── iam-users.ts        # CDKコマンドが実行する
├── cdk.json
├── cdk.out
├── lib/
│   └── iam-users-stack.ts  # 実装対象
├── node_modules/
├── package-lock.json
├── package.json
└── tsconfig.json

実装

さて、IAMユーザを作るための実装に入りますが、その前にIAMのリソースを扱うには更に追加のモジュールが必要です。
次のコマンドで @aws-cdk/aws-iam を追加できます。

$ npm install -S @aws-cdk/aws-iam

CDKが実行するのは bin/iam-users.ts です(実際にはトランスパイルされたJS)。
bin/iam-users.ts を見てみると、lib/iam-users-stack.ts をインポートしてインスタンス生成をしているだけです。
ということで、lib/iam-users-stack.ts が実装対象となるファイルということです。

次の様に実装しました。
AWSの提供するサンプルを参考に実装して見たので、コンストラクタに全コードを書いてしいましたが、適宜メソッドに切り出しても構わないだろうと思います。

import { Construct, Stack, StackProps, CfnOutput }  from '@aws-cdk/core';
import { Group, Policy, PolicyStatement, User, CfnAccessKey } from '@aws-cdk/aws-iam';

const account = 'your-account-id';
const repositoryName = 'your-repository-name';

const groupName = 'Group01';
const userNames = ['User01', 'User02', 'User03'];

export class CdkLessonStack extends Stack {
  constructor(scope: Construct, id: string, props?: StackProps) {
    super(scope, id, props);

    // ポリシーステートメントの定義
    const policyStatement = new PolicyStatement({
      resources: [`arn:aws:ecr:ap-northeast-1:${account}:repository/${repositoryName}`],
      actions: [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:DescribeImages",
        "ecr:BatchCheckLayerAvailability",
        "ecr:GetRepositoryPolicy",
      ],
    })
    // ポリシーの定義
    const policyName = 'AllowGetImagesFromECR';
    const policy = new Policy(this, policyName, { 
      policyName,
      statements: [policyStatement],
    });
    // グループの定義
    const group = new Group(this, groupName, { groupName });
    group.attachInlinePolicy(policy);

    // ユーザとアクセスキーの定義
    userNames.forEach((userName) => {
      const user = new User(this, userName, { userName, groups: [group] });
      const key = new CfnAccessKey(this, `${userName}Key`, { userName: user.userName });
      new CfnOutput(this, `${userName}AccessKey`, { value: key.ref });
      new CfnOutput(this, `${userName}SecretAccessKey`, { value: key.attrSecretAccessKey });
    })
  }
}

変数userNamesforEachで回して複数のユーザを作っています。
このuserNamesを変更するだけでユーザの作成、削除ができるはずです。
YAMLに比べるととてもスッキリ書けたのではないかと思います。

ビルド、デプロイ

さて、実装できたところで、これを実行してリソースを生成するのですが、 デプロイ前にはビルドが必要なので次のコマンドを実行します。

$ npm run build

ビルドは単に tsc が実行されるだけです。これで Typescript のコードがトランスパイルされ、Javascriptのコードが出力されます。

ビルドが済んだら次のコマンドでデプロイできます。

$ cdk deploy

実行結果は次の様に出力されます。

f:id:HeRo:20190816082305p:plain
cdk deployの出力

Cloud Formationのチェンジセットが作られてリソースが生成される様子が出力されています。
お、狙い通りuserNamesの要素数分、IAMユーザが作成されたようです。

すでに、Cloud Formationに慣れていてYAMLでも確認したいという場合にはcdk synthを実行すれば出力されます。

生成されたリソースの確認

AWSコンソールで生成されたものを確認してみます。

Cloud Formation スタック

AWSコンソールを確認するとCloud Formation スタックが作成され、コード通りのリソースが作成されています。

f:id:HeRo:20190816082346p:plain
Cloud Formation リソース

狙い通り、AWS::IAM::USER が3件作成されています。

また、Cloud Formation で AWS::IAM::AccessKey を作った場合、Outputで出力しないとsecretAccessKeyの値が見れないですが、それもちゃんと出力されています。

f:id:HeRo:20190816082418p:plain
Cloud Formation 出力

Outputに出力してしまうとsecretAccessKeyがいつでも見れてしまうので、スタックを参照できる人の管理が重要です。更にCFnで作るユーザの権限を大きくしすぎないようにするといいと思います。

余談

本当はCloud FormationのOutputにせず、console.logで出力するだけにしたかったのですが、無理そうです。
ユーザを作っているforEachの中で次のコードを追加してみました。

console.log({ name: user.userName, key: key.ref, secret: key.attrSecretAccessKey});

しかし、出力されたのは値ではなく、次の様にCloud Formationスタック内での参照を表しているであろう文字列のみでした。

  {
    name: '${Token[TOKEN.24]}',
    key: '${Token[TOKEN.28]}',
    secret: '${Token[TOKEN.27]}'
  }
  {
    name: '${Token[TOKEN.36]}',
    key: '${Token[TOKEN.40]}',
    secret: '${Token[TOKEN.39]}'
  }
  {
    name: '${Token[TOKEN.48]}',
    key: '${Token[TOKEN.52]}',
    secret: '${Token[TOKEN.51]}'
  }

これを見ても、CDKがやってくれるのは Cloud Formationのテンプレートを作って、スタックに反映するところだけで、リソース自体はCloud Formationが作っているのだなと思います。

IAMリソース

次に作られたIAMリソースをそれぞれ確認してみます。

まずはグループ。
インラインポリシーも指定した名前でちゃんとできています。

f:id:HeRo:20190816082449p:plain
IAM グループ

ポリシーの中身もこの通り。

f:id:HeRo:20190816082531p:plain
IAM インラインポリシー

ユーザも次の通り作られています。もちろん3件作られています。

f:id:HeRo:20190816082559p:plain
IAMユーザ

グループに所属し、ポリシーも割り当てられています。

削除

要らなくなったら次のコマンドで削除できます。

$ cdk destroy

f:id:HeRo:20190816082635p:plain
cdk destroyの出力

IAMのリソースの場合、残るものもなく綺麗サッパリなくなります。

メリット・デメリット

メリット

  • Typescriptは書きやすい
  • エディタによるサジェストも便利すぎる
  • 環境変数による条件分岐や繰り返しなどの制御構文を活用できる

デメリット

  • npm パッケージのアップデートが面倒そう

所感

AWSの各リソースの知識はやはり必要なものの、慣れないJSONやYAMLの仕様と格闘するより、使い慣れたTypescriptで書けるのがやりやすいです。 私はVSCodeを使っているのですが、マウスオーバしたメソッドのコメントも参照できます。 各メソッドの引数はインタフェースが定義されているでどんな値を設定すればいいかもわかりやすいです。

f:id:HeRo:20190816082732p:plain

文字列の連結もCloud FormationのDSLとして用意された使いづらい関数を使わなくてもよく、普通に文字列中の変数展開ができ楽々です。

デメリットはこれといったものが思いつかなかったのですが、強いて言うならCDK自身、そして依存しているnpmのアップデートに追従するのがちょっと面倒そうかなということくらいですかね。

とか思っていたら早速CDKの更新が!

*************************************************
*** Newer version of CDK is available [1.4.0] ***
*** Upgrade recommended                       ***
*************************************************

まあ、この程度の面倒は無視できるくらいCDKを使うとCloud Formationの作成、運用が楽になるのではないでしょうか。

もう私はYAMLに戻れないかも。

参考

最後に

アクトインディではエンジニアを募集しています。 actindi.net


  1. もちろん、開発環境用にはメアドやユーザ名、パスワードなどの情報は書き換えてマスクしています。

Amplify Console の Branch Autodetect でステージング環境を自動で作り放題

morishitaです。

以前、Nuxt.js のSPAの稼働環境としてAWS Amplify Console を紹介しました。

tech.actindi.net

とても便利に使っているのですが、先日、次の機能追加があり、更に便利になりました。

aws.amazon.com

早速使って見たので紹介します。

Branch Autodetect ってどんな機能?

Amplify Console ではブランチ毎に専用環境が用意され、そのブランチにPushするたびに自動的にデプロイされる機能があります。 これまではどのブランチをその対象にするかを事前に設定する必要がありました(Amplify用語ではブランチを接続するといいます)。
今回追加された Branch autodetection はその名の通り事前設定なしで、ブランチ毎の環境を作ってデプロイしてくれます。
全ブランチが対象というわけではなく、事前にブランチ名のパターンを設定しておきます。するとそのパターンにマッチするブランチをリポジトリにPushするだけで新たな環境を自動的に追加しデプロイしてくれます。

ということは開発中に作業ブランチ毎に本番とは独立した環境=ステージング環境が自動で作れるということです。
いくつもの開発が並行で走っても互いに干渉しないステージング環境で動作確認ができます。

設定してみる

アプリの設定の「全体」で設定できます。 下の方にまだ翻訳もされてない追加ほやほや項目がそれです。

f:id:HeRo:20190801081757p:plain
全体設定

編集モードに入ると設定できます。 有効にしていない状態では隠れていますが、Branch autodetection のスイッチを Enabled にすると次の項目が表示されます。

  • Branch autodetection - patterns
  • Branch autodetection - access control

f:id:HeRo:20190801081729p:plain
Branch autodetection の設定項目

Branch autodetection - patterns

ブランチ名のパターンを設定する項目です。 * にすると全ブランチが対象になりますが、まあ普通全ブランチを対象にする必要はないと思うので、個別ステージングがほしい場合のブランチ名のプリフィックを決めるといった運用をすることになるかと思います。

当社では d/〜から始めるブランチでステージングを用意する運用をしようと思うので d/** と設定します。

UI上の例では "*", "*/**"のようダブルクオートでパターンを囲っていますが、実際の設定では不要。というか付けるとマッチしなくなってしまうのでご注意を。

Branch autodetection - access control

access control はBASIC認証の設定です。usernamepassword を設定します。
ステージングとして利用するなら設定しておくほうがいいでしょう。開発途中をパブリックに公開したくないでしょうし、うっかり検索エンジンにインデックスされてしまうと本番サービスに悪影響が生じます。

ブランチをPushしてみる

では、上記の設定にマッチするブランチをリポジトリにPushしてみるとどうなるか見てみます。

Pushしてちょっとすると次の様に新たな環境が追加され、プロビジョニングが始まります。

f:id:HeRo:20190801082832p:plain
プロビジョニングの開始

で、処理を待っているとビルド->デプロイ->検証と進み、アクセスできるようになります。
URLは Amplify Console が用意するドメインのサブドメインとなります。
サブドメイン名は基本的にブランチ名となりますが、/(スラッシュ)は -(ハイフン)に変換されます。

アクセスしてみると設定したBASIC認証でアクセス制限もされています。

アプリの設定の「全般」のブランチリストにも追加されています。

f:id:HeRo:20190801083021p:plain
ブランチリスト

カスタムドメインに自動でブランチを追加してはくれないようです。
追加したければ自分でカスタムドメインの設定にサブドメインとして割り当てる必要があります。
まあ、開発中に使うステージングなのでドメインは気にしないケースがほとんどではないかと思います。 別サブドメインのサービスとCookieを共有したいなどの要件があると別ドメインのステージング環境を含めちょっと工夫が必要になるかと思います。

作業ブランチをマージするとどうなるか?

一応、「本番稼働ブランチ」という設定項目があるので、masterにマージしたらそのブランチの接続を自動削除してくれないかなぁと淡い期待を抱いていたのですが、 残念ながら何もしなければ一度できた環境はずっと存在し続けます。

放置すると使い終わったステージング環境が大量に残ってたという事態になりそうです。アクセスしなくても不要なストレージコストもかかるので削除していくほうがいいでしょう。
業務終了時間にパターンにマッチするブランチの接続を全消去するとか、master にマージされたタイミングで始末する仕組みを考える必要がありそうです。
ちなみに AWS CLISDK でも Amplify Console の操作がすでにサポートされています1

まとめ

このようにちょっと設定を追加してやるだけでステージング環境作り放題が実現できます。

Amplify Console はアプリケーションの構成によっては DynamoDB などバックエンドも作るので、どんどん環境を増やすってわけにはいかないアプリケーションもあるかと思います。
しかし、私が今やっているプロダクト は Nuxt.js の SPA のフロントエンドだけなので、気軽に増やしても構わないかなと思っています。
一方、使い終わった環境の後始末は要検討です。Lambdaで実装して定期実行しようかな。

最後に

アクトインディではエンジニアを募集しています。 actindi.net

Codebuild のランタイムのアップデートでハマった

morishitaです。

先日、別のことでハマっていたときに「ビルド環境が古いせいではないか?」と思ってしまい焦ってCodebuild のランタイムを変更したら余計にハマったという話です。

当社でのCodeBuildの利用状況

本題に入る前にアクトインディでのCodeBuildの利用状況を紹介します。

主な用途は次のとおりです。

  • Dockerイメージのビルド
    • 本番サービス用イメージ
    • 開発環境用DBイメージ
  • 自動テスト(RSpec、Prontoなど)の実行

本番サービス用DockerイメージのビルドはCodePipelineに組み込まれています。
開発環境用DBイメージというのは、毎日本番DBのバックアップをリストアして作っているものです。 各エンジニア、デザイナーがローカル開発環境で使うもので、プルすればほぼ最新の本番データをローカル環境で動かせます1

自動テストについては次のエントリで紹介しています。

tech.actindi.net

tech.actindi.net

ランタイムを最新にしたら動かなくなった!

さて、本題です。

Webpacker+Workboxの設定変更をしていてローカル環境での実行ではうまく設定どおりに動作するのですが、本番適用するためDockerイメージをビルドするとうまく動作しないという事象を調べていました。

設定の仕方やコマンドの実行順序、実行タイミングなどいろいろ試してみましたがうまくいかず困っていました。
Dockerイメージのビルド中の処理なのでCodeBuildのランタイムが原因であることは冷静に考えれば少ないはずです。
でも、うまくいかない原因を見つけられず焦ってしまい実行環境を疑ってしまいました。

ビルドに使っている CodeBuild の設定を確認すると利用しているイメージがaws/codebuild/docker:17.09.0となっていました。

f:id:HeRo:20190731085509p:plain
CodeBuildの環境設定(変更前)

Dockerのバージョンが 17.09.0 とやや古い。「まさかこれが原因?」と思って変更しようとしました。
で、イメージの上書きをクリックして見ると…。

f:id:HeRo:20190731085703p:plain
仕様が変わっていたCodeBuildの環境設定

あれ、しばらく触ってなかったら設定項目が変わっている。
なにより、イメージのバージョニング体系が変わっている。

でもまあ、些細な仕様変更だろう。
最新を選択すればいいだろうと次の様に設定を変更してしまいました。

項目 設定値
ランタイム Standard
イメージ aws/codebuild/standard:2.0
イメージのバージョン aws/codebuild/standard:2.0-1.11.0

で、再度CodeBuildを実行すると、エラーで止まってしまいました。

f:id:HeRo:20190731085840p:plain
ビルド失敗
11秒で止まっているとは実質起動すらしてない様子。

しかも、設定変更前のイメージaws/codebuild/docker:17.09.0 はもう選択できません。
もとに戻すという退路も絶たれていました。

うわー、余計にハマった!
もともとサクッと終わる瞬殺タスクだと思っていたのに…。

buildspec.ymlに変更が必要だった

ログを見てみると次のエラーメッセージが出ていました。

Phase context status code: YAML_FILE_ERROR Message: This build image requires selecting at least one runtime version.

ふむふむ、ランタイムバージョンを少なくとも1つしてする必要があるらしい。

ドキュメントを確認してみると次の様に変わったようです。

以前は、Ruby、Node.js、Dockerなど使いたい言語やツールごとにイメージが用意されていたと思います。
それが、イメージが整理されたらしく、バージョンを選択してどの言語を使うのかは buildspec.yml で指定するように変わったようです。

ドキュメントには次の様に書かれています2

Ubuntu Standard イメージ 2.0 を使用する場合、buildspec ファイルの runtime-versions セクションでランタイムを指定できます。

「指定できます」と書かれていますが、先のエラーメッセージを見ると1つは「指定しなければならない」ようです。

選択できるランタイムのバージョンは次のとおりです。

プログラム言語 ランタイムのバージョン
Ruby 2.x
Python 3.x
PHP 7.x
Node.js 8.x、10.x
Java 8、11
Golang 1.x
.NET Core 2.x
Docker 18.x
Android 28.x

今回は Dockerが使えればいいので、buildspec.yml に次のように設定を追加しました。

version: 0.2

env:
  variables:
    〜 省略 〜
 phases:
  install:
    runtime-versions:
      docker: 18
  pre_build:
    〜 以下、省略 〜

あと、もう1つ。
Dockerイメージ作る場合にはCodeBuildの環境設定の特権付与にチェックを入れるのも重要です。これにチェックを入れないとDockerが動きません。

これで元通り動くようになりました。
やれやれ。

まとめ

新しいCodebuildのランタイムイメージのDockerファイルはGithubで公開されています。

それによると次のようにアクティブにメンテするのは standard 2.0 だとと書いているので、今後は standard 2.0を選択したほうがいいと思います。

The following images are actively maintained by AWS CodeBuild, and are listed in the CodeBuild console.

  • standard 2.0

standard 2.0 にアップデートする際には buildspec.yml の変更も必要なのでご注意を。

参考

最後に

アクトインディではエンジニアを募集しています。 actindi.net


  1. “ほぼ"というのは1日1回ビルドなので若干タイムラグがあるのと、メアドなどのセンシティブなデータはマスクされているという意味です。

  2. https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-available.html

AWS Chatbotを触ってみた

morishitaです。

AWSで様々なサービスを使っていて、常々不便に思っていたことがあります。

それは Slackに通知できない ということです。

もちろん、Lambda を使って Slack にメッセージを送るのは、難しくないしすぐできます。実際、Cloud Watch Alert の重要なものは SNS(Simple Notification Service) + Lambda で Slack に通知していたりします。

先日、紹介した Amplify Console はメール通知機能を備えており、ビルド、デプロイの成否をメールで通知してくれます。Amplify Consoleの様に開発プロセスの一部に組み込まれるサービスにとって通知のニーズが必要とはAWSもわかっているなぁと感心しました。
しかし、一方で「その手段がメールだけって…」と軽い失望を覚えました。

tech.actindi.net

Amplify Console のような比較的新しいサービスにおいてもそんな感じなので、AWS的にはあんまりやる気ないのかなと思っていました。

大きな手間でもないのだけど、だからこそIncoming WebhookのURL設定するだけ「デプロイ終わったよ」くらい送れる程度のことでいいからできればいいのに。

と思っていたところ、このAWSのミッシングピースを埋めるかもしれないサービスが発表されました!

aws.amazon.com

2019/07/29時点でベータ版とのことですが、すでに東京リージョンでも使えるようです 1

試してみました。

早速使ってみる

まあ、Slackになにか通知できるだけのサービス、そんなに難しくはないだろうとドキュメントを読むのもそこそこに触ってみました。

チャットサービスの選択

Chatbot Consoleにアクセスするとチャットサービスを選択ができます。

次の選択肢があります。

  • Amazon Chime
  • Slack

Amazon Chime はオンライン会議・チャットサービスとのことです。
AWSにはこんなサービスもあるのかと思いつつ、選択するのはもちろん Slack です。

f:id:HeRo:20190729083055p:plain
チャットサービスの選択

Slackアプリケーションのインストール許可

Configure client ボタンをクリックして進むと、Slack に飛ばされて、Slack アプリケーションのインストール許可を求められます。

f:id:HeRo:20190729083155p:plain
Slackアプリのインストールダイアログ

ブラウザでログインしている Slack アカウントがあればそれが表示されます。 右上のプルダウンで別のアカウントを選択することも可能です。

許可するインストールされます。 Slack側のアプリリストにも次のように追加されています。

f:id:HeRo:20190729083244p:plain
Slackアプリに追加されている

チャネルの設定

インストールOKするとChatbotの設定に戻ります。 設定は3つのパートに分かれています。

まずは、Slack Channel の設定。

f:id:HeRo:20190729084005p:plain
Slack チャネルの設定

Private を選択すると Private channel ID を直接入力します。 Public を選択すると接続した Slack にあるチャネルのリストから送信先のチャネルを選択できます。

続いて、IAMロールの設定。

f:id:HeRo:20190729084039p:plain
IAMロールの設定

既存のも選択できますが、Create an IAM role using a template を選択すれば、必要な権限を持ったロールを作ってくれます。

最後にメッセージのソースとなる SNS(Simple Notification Service)のトピックの選択。

f:id:HeRo:20190729084102p:plain
SNSトピックの設定

ChatbotSNS のサブスクライバーの1つになるので、SNS のトピックを選択します。 既存のものからの選択になるので、適当なのがなければ予め作って置く必要あります。 複数のリージョンの複数のトピックを指定することも可能です。

Configure ボタンをクリックして設定完了です。

選択した SNS トピックをコンソールを確認すると、サブスクリプションに Chatbotのエンドポイントが追加されています。

f:id:HeRo:20190729084228p:plain
SNSトピックのサブスクリプション

テストメッセージ送信

とりあえず、疎通確認としてなにか送ってみようと SNS のコンソールからメッセージを発行してみましたが、一向に Slack のチャネルにはメッセージが届かない…。

試しに、メールのサブスクライバーを追加してメッセージを発行してみると、メールにはちゃんと届きます。
しかし、Slack には何度送っても届かない。

IAMロールも Chatbot のコーンソールが作ったものを使っているので権限不足ってこともないいだろうし、何が悪いのか…。

と、ここでドキュメントに立ち返ってみると、現状 Chatbot がサポートしてるサービスが限られているようです。
Using AWS Chatbot with Other AWS Services - AWS Chatbotに書いてありました。

次のサービスがサポートされています。

  • Amazon CloudWatch
  • AWS Health
  • AWS Budgets
  • AWS Security Hub
  • Amazon GuardDuty
  • AWS CloudFormation

これら以外のサービスからメッセージを送信してもしれっと無視されるようです。

EC2に負荷をかけてみた

なるほど。ならばということでEC2インスタンスを実験用に作って負荷をかけてみることにしました。

作ったインスタンスには次のようなアラームを設定しました。

f:id:HeRo:20190729084430p:plain
Cloud Watch Alert

そして、stress コマンドで負荷をかけて見ると…。

来ました!

次のようなメッセージが Slack の設定したチャネルに届きました。

f:id:HeRo:20190729084508p:plain
Slackに送られたAlert通知

タイトル部分が該当する Cloud Watch Alert へのリンクになっていて、詳細を確認しやすくなっています。
グラフもついて、いまLambdaで自前で送っているメッセージより見やすいかも。

そして負荷をやめると次のメッセージが。

f:id:HeRo:20190729084538p:plain
Slackに送られたOK通知

あれ、こちらはちょっとグラフの内容がおかしいような。 ベータサービスだから?

まとめ

  • ChatbotSNS 経由で各種サービスの通知をSlackに送信できる
  • 特に CloudWatch AlertSlack に送信するのは簡単
  • 今は送信できるサービスが少ない

最初は SNS のどんなメッセージも送信できると思ったので、ちょっと期待通りではなかったですが、今後拡充されればもっと便利なサービスになるのではと思います。

参考

最後に

アクトインディではエンジニアを募集しています。 actindi.net


  1. AWS Chatbot自体はリージョンに属さいないグローバルサービスのようです。

Nuxt.jsのSPAをAmplify Consoleでホストする。めっちゃ簡単だった!

morishitaです。

以前、次のエントリを書きました。

tech.actindi.net

このとき作ったNuxt.jsのアプリケーションはその後、 リニューアルして2019年4月からはいこーよ!こどもBIRTHDAYとして利用しています。

https://birthday.iko-yo.net/birthday.iko-yo.net

今後は対象地域と施設を増やして拡充していく予定です。
それに伴い開発が活発になるのでステージング環境や 施設の方に予め確認いただくプレビュー環境もちゃんと準備する必要が出てきました。

上記エントリで紹介した環境を拡張しても良かったのですが、 せっかくなので東京リージョンでも使えるようになったAWS Amplify Consoleを使ってみました。

aws.amazon.com

Amplify Consoleとは

AWSが開発しているAmplifyというフレームワークがあります。
ReactやAngular、Vue.jsのアプリケーションをフロントエンドとして、 AppSync、DynamoDB、Cognitoなどのバックエンドの構築までをCLIで行えます。
フルスタックのサーバレスアプリケーションの構築から継続的なデプロイまでできてしまう超便利ツールです。

Amplifyフレームワークでは大量に設定やAppSyncのスキーマ定義のファイルが生成されます。
一部の生成ファイルは一緒に生成される.gitignoreでリポジトリに入れないように設定されます。
複数人で開発するとなるとリポジトリに入らないファイルを個別に持つのも良くないので Amplify CLIを実行する環境、生成されるファイル群を管理する仕組みを作る必要があります。
それがちょっと面倒だなぁと思っていました。

Amplify Consoleはそんな面倒を解決してくれるサービスです。
GithubなどのリポジトリにPushすると連携して、ビルド、デプロイしてくれます。
デプロイに利用するサーバ等を直接管理することのないフルマネージドのサーバレスサービスで次の機能をしてくれます。

  • 複数のブランチでそれぞれ別個にビルド、デプロイが可能
  • カスタムドメインで運用でき、サブドメインもつけ放題
  • ブランチごとにBASIC認証をかけられる
  • Amplifyフレームワークのアプリケーションならばバックエンドも構築できる
  • フロントエンドはCDNを利用して配信してくれる

プロトタイピング程度なら便利で使えるなぁという印象だったAmplifyをプロダクションでも使ってみようかなと思わせる、Amplify Consoleはそんなサービスです。

Amplify Consoleでアプリを作ってみる

いこーよ!こどもBIRTHDAYのアプリケーションの構成は次のとおりです。

  • Nuxt 2.8.1 + @nuxt/typescript
    • mode: 'spa'の静的サイト
  • Typescript 2.8.0

これをビルド・デプロイするまで流れを説明します。

アプリを設定する

最初にリポジトリと接続します。
リポジトリサービスを選択すると、OAuthの認証を促されるので従います。

f:id:HeRo:20190610110648p:plain

リポジトリとブランチが選択できるようになるので選びます。

f:id:HeRo:20190610110707p:plain

アプリ名をつけて、ビルド設定を行います。
React、Angular、Vue.jsなどは自動で認識してビルド設定を自動で作ってくれます。
ソースにGulpfileを含んでいたのでそれを使うと判定されたようです。

f:id:HeRo:20190610110725p:plain

独自にビルド処理を定義するならば、リポジトリにAmplify.ymlを作成します。

前述のGulpfileはビルド成果物のS3へのコピーなどといったデプロイ処理を行っています。それはAmplify Consoleに任せるので、次の処理だけするように変更したAmplify.ymlを用意しました。

  • yarn install
  • yarn run build
  • スラックへの通知(scripts/notify-deploy-to-slack.js

実際のAmplify.ymlは次のとおりです。

version: 0.1
frontend:
  phases:
    preBuild:
      commands:
        - yarn install
    build:
      commands:
        - yarn run build
    postBuild:
      commands:
        - node scripts/notify-deploy-to-slack.js
  artifacts:
    baseDirectory: dist/
    files:
      - '**/*'
  cache:
    paths:
      - node_modules/**/*

上記ファイルを追加して、再度アプリを作成しようとすると次のようにAmplify.ymlが認識されます。
環境変数API_URL_BROWSER1も設定します。
環境変数はアプリの作成のときでなくてもあとでいつでも追加できます。
特定のブランチでのみ値を変更することも可能です。

f:id:HeRo:20190610110750p:plain

最後に設定内容を確認します。

f:id:HeRo:20190610110808p:plain

すると処理が開始されます。

f:id:HeRo:20190610110826p:plain

しばらく待つと、処理が完了します。

f:id:HeRo:20190610110845p:plain

スクリーンキャプチャの下のリンクからデプロイしたアプリケーションにアクセスできます。
今後、対象ブランチはリポジトリにPushするたびにビルド・デプロイされます。

以上でSPAを継続的にビルドしデプロイする環境ができました。
簡単ですね。

リダイレクト設定

実はここまでの設定ではクライアント側でリクエストパスのルーティングを行うSPAの場合にはちゃんと動かないと思います。
ドメインルートからの遷移では問題ないのですが、それ以外のパスへダイレクトアクセスされると実体のファイルがないので Not Found になります。

これを解決するためにリダイレクト設定を行います。
左ペインの「書き換えて、リダイレクト」というを選択します(機械翻訳でしょうか?変な日本語ですね)。

デフォルトでは次の画像のように設定されています。

f:id:HeRo:20190610110908p:plain

編集・追加はWebフォームでできるようになっています。

f:id:HeRo:20190610110924p:plain

「テキストエディタで開く」と次のようなダイアログ中にJSONで設定が表示されます。この中で編集できます。

f:id:HeRo:20190610110940p:plain

ただ、いまいち使いづらいので、別のところで書いた設定をコピー&ペーストするほうがいいと思います。

Nuxt.jsのSPAではどう設定すればいいか?

クライアントでルーティングを行うSPAではどのパスへのアクセスもアプリケーションのエントリポイントとなるHTMLファイルを返す必要があります。
一方、CSSその他のファイルはそのまま処理してレスポンスする必要があります。

公式ドキュメントリダイレクトを使用する - AWS Amplifyに一応、SPAの場合の設定例が記述されているのですが、それではうまく動作しませんでした。

次のように設定するとうまく動作しました。

[
    {
        "source": "</^((?!.(json|css|gif|ico|jpg|js|png|txt|svg|woff|ttf)$).)*$/>",
        "target": "/",
        "status": "200",
        "condition": null
    }
]

これで、Amplify Consoleによる Nuxt SPAのビルド、デプロイ、配信が完成です。

独自ドメイン設定

さて、ここまでの操作ではデプロイしたものはAmplify Consoleが自動的に割り当てたドメインで配信されます。
開発環境として使うならいいのですが、本番環境で使うには カスタムドメインを設定して独自ドメインでの配信できるようにしなければなりません。

今回はbirthday.iko-yo.net で公開するので、それを設定します。
すでにRoute53のゾーンがあるので、それを選択します。

f:id:HeRo:20190610111054p:plain

サブドメイン名と対応するブランチを指定するとサブドメインも作り放題で増やせます。

設定後、処理が完了するまで、結構時間がかかる場合があります。ちょっと気長に待ちます。
終わるとRoute53にもCNAMEレコードが追加されています。

f:id:HeRo:20190612084039p:plain

以上で本番環境を独自ドメインを使ってAmplify Consoleで運用できます!

ブランチの追加

今回、Amplify Consoleを使ってみようと思った動機はmasterだけでなく複数のブランチで個別の環境を用意したいからでした。
それを実現するためにブランチを追加してみます。

「ブランチの接続」から別のブランチを追加すると簡単に任意のブランチを別環境にデプロイできるようになります。 f:id:HeRo:20190610111003p:plain

一度設定するとそのブランチへPushすると自動的にデプロイされます。
テストやステージングの環境を作り放題です!

ステージングはBASIC認証をかけたい

本番環境以外は関係者だけが見られるように制限したい場合も多いと思います。
Amplify Consoleならそれも簡単です。

左ペインの「アクセスコントロール」を選択するとBASIC認証の設定を行えます。
全ブランチに共通のusername/passwordを設定できますし、 ブランチごとに別々のusername/passwordを設定することもできます。
社内の関係者向け、社外の関係者向け使い分けることも簡単です。

この機能がAmplify Consoleで一番気の利いた機能だと個人的には思いました2

Amplify Consoleを探る

さて、ここまでSPAアプリケーションを設定してデプロイするまでを説明しました。とても便利なAmplify Consoleですが、どんな仕組みで動いているのでしょうか。

AWS Amplify コンソールのドキュメントには記述はないですが、コンソールに表示される値やログから推測すると次のAWSのサービスが使われているようです。

  • CodeBuild
  • S3
  • CloudFront
  • CloudFormation

NuxtのSPAを S3+CloudFrontでホストする。デプロイはCodeBuildで自動化でやったことと大体同じことがサービス化されているんだなぁと思いました。

これらのリソースが使われていると思われるものの、それぞれのサービスのWebコンソールには現れません。 一般的な管理とは別にAmplifyがプライベートに管理する環境でプロビジョニングされるようです。
なので、利用時には上記のリソースに関しては全く気にすることはありません。

配信にはCloudFormationが利用されているようなのでパフォーマンスやスケーラビリティも心配はなさそうです。

Amplify Consoleの気になるところ

Amplify Consoleはとても簡単で便利に使えるサービスです。
今の所、不満な点は殆どありません。
強いて上げるとすれば次の3点でしょうか。

  • 反映されるまでにちょっと時間がかかる設定がある
  • 標準で用意されている通知手段がメールのみ
  • 料金がちょっと割高っぽい

前者は裏で動いている仕組みが多そうなので仕方ない面もあるとは思うのですが、 カスタムドメイン設定とアクセスコントロール設定は反映に時間がかかる印象です。

AWSはもう個別のサービスに通知機能を実装しないでもいいのではと思います。
SNSのトピックにパブリッシュできればあとはよしなにできるので。
欲を言えばSlackへの通知をサポートしてほしいところです。

コストは?

さて、機能面では控えめに言って超オススメなAmplify Console、気になるコスト面ではどうでしょうか?

S3+CloudFront+CodeBuildで同様の仕組みを構築するとしてコストを比較してみました。 すべて東京リージョンでの料金です。

コスト項目 Amplify Console S3+CloudFront+CodeBuild
ビルド(USD/分) 0.01 CodeBuild: 0.01
ストレージ(USD/GB月) 0.023 S3: 0.025
ホスティング(USD) 43.9 CloudFront: 33.8

Amplify Consoleのビルド環境は4 vCPU, 7GB memoryと表示されるのでそれに相当する CodeBuildの料金と比べました。しかし、Nuxtのビルド程度なら一番小さい2 vCPU, 4GB memoryのCodeBuildで十分なのでビルドについては割高だと思います。

ストレージはAmplify Consoleのほうがややお得。しかし全体に占める割合が小さいと思われます。

ホスティングは1MB/リクエストのサイズのページを10000PV/日、30日、HTTPSで配信したときの概算料金です。
これはトラフィックが増えれば(=サイトが成長すれば)支配的なコストとなると思います。
Amplify Consoleはやはりちょっと割高ですね。

まとめ

NuxtのSPAを S3+CloudFrontでホストする。デプロイはCodeBuildで自動化で AWSの各種サービスを自前で連携させて環境を作ったのがバカバカしくなりました。
それほどAmplify Consoleは簡単に使え、便利なサービスです。

運用に関する作業コストははとても小さいので、それが配信のホスティングコストを相殺できると考えられるならオススメサービスです。

最後に

いこーよ!子供BIRTHDAYは毎月第3土曜日にその月生まれのお子様が入場無料になったりするお得クーポンを配布しているサービスです。
夏には地域を拡大してリニューアル予定なのでぜひご利用ください。

https://birthday.iko-yo.net/birthday.iko-yo.net

アクトインディではそんなパパ・ママ向けのお得サービスを一緒に開発したいエンジニアを募集しています。

actindi.net


  1. https://axios.nuxtjs.org/options#browserbaseurl

  2. CloudFront+S3でこれをやろうとするとLambdaを作って設定したりしないといけないので面倒ですよね。

Cloudformation 入門してみました

morishitaです。

これまで、なんかめんどくさそうでCloudfrmationは避けてきました。

ElasticBeanstalk や Serverlessフレームワークは裏側でCloudFormationが動くので、間接的には使ってきました。
デフォルトで用意されないリソースを追加するのにちょっとだけYAMLの定義追加する程度はやりました。
それなりに便利だと恩恵を感じてはいたのですが、直接使ったことはありませんでした。

ドキュメントの多さに、どこから手をつけていいかわからないとっつきにくさにかまけて避けていたのです。

ですが、「すぐいこ」の開発でイチからインフラを構築する機会がありついに入門してみました。

2021年03月 追記

私自身はもう CloudFormation で新規にインフラを構築することはないと思います。というのもCDKのほうが圧倒的に便利で、メンテナンスしやすいからです。

CDKについてのエントリはいくつかあるのでこちらもどうぞ御覧ください。 tech.actindi.net

CloudFormationを使ったインフラ構築の流れ

CloudFormation スタックのテンプレートをGitで管理したいので、Webコンソール上でなくローカルでテンプレートを作成します。

次の流れの繰り返しでインフラを構築しました。

  1. YAMLでCloudFormation スタックのテンプレートを作成する
  2. AWS CLIを使ってテンプレートをスタックに適用する

ちょっとづつ変更しては上記を繰り返しました。

環境の準備

まず、CLIでCloudformationスタックを操作するために AWS CLIを準備します。 AWS CLIのセットアップについては割愛します。

そして、エディタには Visual Studio Codeを使いました。 加えて次の拡張も使います。

vscode-cfn-lintを使うために、cfn-lintもインストールします。macOSだと、Brewでインストールできます。

$ brew install cfn-lint

vscode-cfn-lintによってとてもテンプレート作成が効率化されました。
テンプレートにエラーがあった場合にはスタックを更新しようとしたときに検出されロールバックされます。
が、このサイクルを繰り返すのは時間がかかって辛いです1。 設定項目の階層や名称、設定値の間違いの類ならvscode-cfn-lintでテンプレートの記述中にミスが指摘されるので、実際に適用する前に修正できます。
かなり効率が上がります。

cfn-lintの指摘を解消すれば、ほぼ記述ミスに由来するエラーはなくなります。 リソース間の矛盾のような論理的なエラーの修正に集中できます。

CloudFormationのとっつきにくさとはじめの一歩

CloudFormationのとっつきにくさの原因を私は次の様に思っています。

  1. ドキュメントが多すぎて、途方に暮れる
  2. インフラをまるっと作ろうとするとCloudFormation以前にインフラ設計の知識が必要
  3. VPCからインフラを構築していくとアプリケーションの動作を見れるまでの道のりが遠い
  4. よくわからないので既存のインフラを壊してしまわないかと怖い

1.についてですが、一旦ドキュメントを読んでよく理解してからなんて考えは捨てました。
CloudFormationはAWSの多くのサービスを扱えるのでドキュメントが膨大です。
全サービスを使うわけでもないと思うので全部読む必要なんて一切ありません。
必要な部分だけ読めばよく、この項目ってどんな意味だろうと調べるためのリファレンスだと割り切って接することにしました。

2.は既存のインフラリソースをコピーしてCloudFormationで作ってみることから始めてみました。

3.ですが、1つのWebアプリケーションを動かすためには意外と多くのリソースが必要になります。VPC、Subnet、SecurityGroup、EC2インスタンスetc。最初はそれらをまるっとCloudFormationで作れたらどんなに楽なんだろうかと想像して始めるのだろうと思います。
ですが、最初から気負って始めるとアプリケーションをデプロイしてその動きを確認できるところまでが遠くて挫折しがちです。
一部でもできるところから取り入れることにしました。

4.を克服するためには他のシステムが可動していない別リージョンや別VPCで始めて経験を積んでいけばいいのですがそうすると一部から始めるという戦術が取りにくいです。
それに練習のためであって本質的には必要のないものをCloudFormationの恩恵を感じる前に作ろうとするのはなかなかのストイックさが求められます。

これらを踏まえて、私がおすすめするCloudFormationのはじめの一歩はCloudWatch ダッシュボードを作ってみることです。

Cloudwatchのダッシュボードを作ってみる

AWSでシステムを運用しているとCloudwatch ダッシュボードでシステム状態を確認できると便利です。
同じようなAWSのリソース構成で複数のシステムを運用しているとCloudwatchのダッシュボードもやはり同じようなものを複数作っているのではないでしょうか。

CloudwatchのダッシュボードをWebコンソールで作っていくのは結構面倒なものです。
でも、CloudFormationを使えば既存のダッシュボードを複製してちょっと変更すれば楽できます。

Cloudwatch ダッシュボードをどう作ろうと運用しているサービスのインフラには何も影響しませんし、あれば役立ちます。
それに既存のダッシュボードがあればそれを複製するようなCloudFormationのテンプレートを簡単に作れます。

CloudFormationによるCloudwatch ダッシュボードの作成について以下に説明します。

テンプレートの作成

まず、既存のCloudwatch ダッシュボードの定義をコピーしてテンプレートを作ります。

サンプルとして次のようなEC2インスタンスのCPU使用率とネットワークトラフィックをグラフ化するダッシュボードがあるとします。

f:id:HeRo:20190610090632p:plain

このダッシュボードの定義を取得するには アクション>ダッシュボードの編集を選択します。

f:id:HeRo:20190610090658p:plain

次のようなダイアログが開きます。 その中にJSONで記述されたダッシュボードの定義が表示されるのでCopy Sourceボタンをクリックしてコピーします。

f:id:HeRo:20190610090715p:plain

コピーしたJSONをCloudFormationテンプレートで利用します。 そのテンプレートは次のようになります。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  EC2InstanceId:
    Description: 'EC2 Instance Id'
    Type: String

Resources:
  CloudWatchDashBoard:
    Type: AWS::CloudWatch::Dashboard
    Properties:
      DashboardName: EC2 Instance Metrics
      DashboardBody: !Sub |
        {
          "widgets": [
            {
              "type": "metric",
              "x": 0,
              "y": 0,
              "width": 24,
              "height": 6,
              "properties": {
                "view": "timeSeries",
                "stacked": false,
                "metrics": [
                    [ "AWS/EC2", "CPUUtilization", "InstanceId", "${EC2InstanceId}" ]
                ],
                "region": "ap-northeast-1",
                "title": "CPU Utilization"
              }
          },
          {
            "type": "metric",
            "x": 0,
            "y": 6,
            "width": 24,
            "height": 6,
            "properties": {
                "metrics": [
                    [ "AWS/EC2", "NetworkIn", "InstanceId", "${EC2InstanceId}", { "stat": "Average", "period": 60 } ],
                    [ ".", "NetworkOut", ".", ".", { "yAxis": "right", "period": 60 } ]
                ],
                "view": "timeSeries",
                "stacked": false,
                "region": "ap-northeast-1"
            }
          }
        ]
      }

Resources.CloudWatchDashBoard.Properties.DashboardBodyの値がダッシュボード定義でコピーしたJSONです。 ただし、EC2インスタンスのIDはParameters.EC2InstanceIdでパラメータ化して汎用性を高めています。 これを変えるだけで、別のEC2インスタンスについて同じダッシュボードを量産できます。
ちなみに!Subは変数を展開するためのCloudFormationの関数の省略形です。

シェルスクリプト

このテンプレートでスタックを作って更新していくのですが、AWS CLIへ渡す引数が少なくないので直接実行するのではなく、シェルスクリプトを用意したほうが便利です。

パラメータへの値のセットもシェルスクリプトからだとテンプレートの汎用性がより高まります。

上記テンプレートでCloudfrmationスタックを作成・更新するためのシェルスクリプトの例を次に示します。

#!/bin/bash

SCRIPT_DIR=$(cd $(dirname $(readlink $0 || echo $0));pwd)
CF_FILE_NAME="file://${SCRIPT_DIR}/cw-dashboard.yml"

CF_STACK_NAME='CloudwatchDashboard'
EC2_INSTANCE_ID=<YOUR EC2 INSTANCE ID>

case $1 in
  'create')
    CFN_SUB=create-stack;;
  'update')
    CFN_SUB=update-stack;;
  'changeset')
    CFN_SUB=create-change-set;; # より慎重にするにはこちら。
  * )
    echo 'Please set "create" or "update"'
    exit 1;;
esac

aws cloudformation $CFN_SUB \
--stack-name ${CF_STACK_NAME} \
--template-body ${CF_FILE_NAME} \
--parameters \
ParameterKey=EC2InstanceId,ParameterValue=${EC2_INSTANCE_ID} \
| jq .

上記シェルスクリプトは引数に応じて、AWS CLIのサブコマンドを切り替えます。 スタックが存在しないときには引数createを与えます。
スタックの更新時には引数updateを与えます。こjの場合、AWS CLIとしては aws cloudformation update-stack が実行されます。これによりスタックが更新され、変更が実リソースに反映されます。

チャンジセットがおすすめ

サービスに影響するようなCloudFormationスタックを変更する場合、無理な変更はロールバックされるとはいえ、いきなりリソース変更が適用される更新は勇気が必要です。 より慎重にするには上記シェルスクリプトに引数changesetを与えて aws cloudformation create-change-setを実行するほうが良いでしょう。 これはチェンジセットを作るサブコマンドです。 チェンジセットはスタックの変更箇所を適用せずに確認できる機能で、Dry Run のように使えます。 チェンジセットを作って変更箇所をWebコンソールでチェックしてから適用するというフローができます。

次のステップは?

簡単な例で説明しましたが、メリットを感じられたなら少々ハードルの高いことでもモチベーションを保てると思います。

既存のシステムと同様のシステムを構築する機会があるとチャンスです。 インフラ設計はすでにあるので、それらをどうCloudFormationで作っていくかに集中できます。 既存システムに加えるものは悪い影響を与えないか心配ですが、新規なら気にせず作業できます。 ぜひ、CloudFormationで構築してみましょう。 たとえ、CloudFormationに挫折してもWebコンソールでポチポチやるという逃げ道もあるのであまり気負わずに。

既存に参考になるリソースがある場合、AWS CLIでその設定内容を取得してみるといいと思います。例えばaws ec2 describe-instances などリソース情報を取得するCLIのレスポンスで示される属性はCloudFormationでのプロパティキーとほぼ一致しています。なので、どのキーにどのような値を設定すれば同じリソースが作れるのか参考になります。

このエントリの最初の方で一旦ドキュメントは置いておきましょうと述べましたが、プロパティやその設定値についてわからないことがあればドキュメントをそれらのキーワードで検索して調べてみれば良いと思います。 闇雲に膨大なドキュメントと格闘するより効率良いです。

また、次のドキュメントにはテンプレートのサンプルが示されているので、まずはコピー&ペーストで構築してもいいでしょう。

まとめ

今回、CloudFormationを使ってみて、良かったと思った点は次のとおりです。

  1. AWSのWebコンソールでポチポチ設定するより楽
  2. リソースの変更が記録されるようになった
  3. ノウハウを共有しやすい
  4. 試しに作ってみたリソースをCloudFormationスタック削除するだけで全消去できる

1.は業務で作るリソースである以上、リソースを作れば作り方や設定内容を何らか記録する必要があります。 ならば、そんなドキュメントを作るよりcloudformationテンプレートのほうがトータルで楽だと思います。 ドキュメントと実態のズレもほぼないですし2

2、3.はインフラのコード化の恩恵です。 テンプレートをコードレビューし合うようになったことで、他のメンバーが書いたテンプレートを見て学びやすくなりました。

3.は検証環境をつくるのが楽だということです。CloudFormationテンプレートを作りながら 試す場合もそうですし、機能やパフォーマンス検証のため本番と同じ構成、同じ設定のリソース群を一時的に作って すぐに捨てることができます。

最後に

アクトインディではインフラのコード化はまだ道半ばですが 一緒にやってみたいエンジニアを募集しています。


  1. 複数のリソースを作って行くときなどは途中まで作られたものがロールバックで作されます。それは結構時間がかかったりします。

  2. 適用していないテンプレートと実リソースには当然ですがズレが生じるのでご注意を。

AWS CloudWatch Logs に貯めこんだログをどうにかしようとしてハマった話

こんにちは!!こんにちは!!
インフラエンジニアのyamamotoです。

AWS CloudWatch Logs に貯めこんだログを、Kinesis Data Firehose を使って S3 に保管し、Athenaで検索しよう、と思ったらいろいろつまづいたのでまとめてみました。

きっかけ

当社の新プロジェクトで、ログをどげんかせんといかん、という話に。

ひとまず CloudWatch Logs に保存しておいて後でどうにかしようと思ったのですが、検索するにも保管するにも良くないので、S3に保管してAthenaで読めたらいいよねー、ということになりました。

しかし CloudWatch Logs のログを S3 に出そうとすると、手動での実行か、Lambdaでゴニョゴニョしないといけなさそうです。

もっとスマートに、逐次出力できないものか、と思って調べてみたところ、Kinesis Data Firehose を使ってストリーミングできるということが判明。
すごい。さすがはAWS。

ことの一部始終

Kinesis Data Firehoseのストリームを作成

まず、Kinesis Data Firehoseのストリームを作成します。
ここら辺は参考になるサイトがいっぱいあるので、そちらを見ながら設定。

CloudWatch Logsをサブスクリプション

CloudWatch Logsにはサブスクリプションという、外部出力の機能があります。
しかし、Kinesis Data Firehose に出力するにはコンソールからではできず、AWS APIで操作しなければなりません。
すごくないよAWS……

こちらのサイトを参考に登録しました。ありがとうございます。
https://christina04.hatenablog.com/entry/cloudwatch-logs-to-s3-via-firehose

ここまでは順調に進みましたが、この先でつまづきました。

S3に出力されるも……

ログがS3に出力されるようになりました。ただ、ログの内容がなんか変……

{"messageType": "DATA_MESSAGE","owner": "677754094491","logGroup": "/aws/elasticbeanstalk/hogehoge-rails","logStream": "production/rails/c6c65f81-1c88-4c02-8407-2e1d8bce69fa","subscriptionFilters": ["All"],"logEvents": [{"id": "34657843470841441528834034625987848118641882362345488384","timestamp": 1554111450640,"message": "{\"method\":\"GET\",\"path\":\"/hogehoge/products\",\"format\":\"json\",\"controller\":\"Hogehoge::ProductsController\",\"action\":\"show\",\"status\":200,\"duration\":1.41,\"view\":0.72,\"transaction_id\":\"bcfa33d20729b874d4f8\",\"params\":{},\"type\":\"RAILS\",\"host\":\"foobar.hogehoge.com\",\"remote_ip\":\"192.168.1.11\",\"ua\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36\",\"request_id\":\"d2c5fd8c-a207-4fe1-b030-e58439073ace\",\"session_id\":null,\"timestamp\":\"2019-05-21T08:03:50+00:00\",\"timestamp_jst\":\"2019-05-21T17:03:50+09:00\"}"}]}

見た感じ、生ログが Cloudwatch Logs のログで包まれ、複数のログも全部が1行にまとまっていました。
Athenaで読み込ませるには、1行に一つのログがある形式にしないといけないとのことなので、このログをバラす必要があります。

Kinesis Data Firehose の Transform 機能

そこで、Kinesis Data Firehose にあるTransform機能を使うことにしました。
一言で言うと、流れてきたログをLambdaで加工できる機能です。

これを使ってログをバラそう思い、流れてくるログをキャッチしてみると、なにやらbase64っぽい文字列が……
単純にbase64デコードしても、バイナリになっていて中が読めない。ううむ。

調べてみると、CloudWatch Logがサブスクリプションとして出力されるログは、gzip圧縮されてBase64エンコードされて送られてくるようです。な、なんと……

これを解凍してJSONで読み込み、生ログ部分だけ取り出すスクリプトをLambdaに用意しました。

const zlib = require('zlib');

exports.handler = (event, context, callback) => {
  const output = event.records.map((record) => {
    const buf = zlib.gunzipSync(Buffer.from(record.data, 'base64'));
    const cwlogs = buf.toString('utf-8');
    const cwlogsparsed = JSON.parse(cwlogs);
    let ret = '';

    for (let i = 0; i < cwlogsparsed.logEvents.length; i += 1) {
      ret += `${cwlogsparsed.logEvents[i].message}\n`;
    }

    return {
      recordId: record.recordId,
      result: 'Ok',
      data: Buffer.from(ret, 'utf8').toString('base64'),
    };
  });
  callback(null, { records: output });
};

肝心のログは「logEvents」配列の「message」フィールドに入っており、その他の部分はCloudWatch Logsの情報になっているようです。
「logEvents」配列を for で回して「messages」フィールドの文字列だけを取り出し、改行を付けています。

で、

{"method":"GET","path":"/hogehoge/products","format":"json","controller":"Hogehoge::ProductsController","action":"show","status":200,"duration":1.41,"view":0.72,"transaction_id":"bcfa33d20729b874d4f8","params":{},"type":"RAILS","host":"foobar.hogehoge.com","remote_ip":"192.168.1.11","ua":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.86 Safari/537.36","request_id":"d2c5fd8c-a207-4fe1-b030-e58439073ace","session_id":null,"timestamp":"2019-05-21T08:03:50+00:00","timestamp_jst":"2019-05-21T17:03:50+09:00"}

こんな感じに生ログが一行に一つだけになりました。
この形式であればAthenaで読み込むことができます。

なお、Kinesis Data Firehoseのオプションでgzip圧縮して保存できるので、この段階では無圧縮で渡しています。

Kinesis Data Firehoseでのデータ保存

Athenaではパーティションという機能があるので、これを活用する形で Kinesis Data Firehose でも意識してデータを保存するようにしてみました。

Kinesis Data Firehoseのストリームの設定で、指定したS3のパスの下に /year=年/month=月/day=日/ のディレクトリを掘り、その中にログを出力するようにしています。
※Firehoseの仕様上、UTCで日付が振られます

S3 bucket: hogehoge
Prefix: logs/rails/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/ 
Error Prefix: logs/error/year=!{timestamp:yyyy}/month=!{timestamp:MM}/day=!{timestamp:dd}/!{firehose:error-output-type}

この形式にしていると、Athenaでパーティションを作成するときに便利になります。

また、データはオプション設定でgzip圧縮して保存します。

Athenaの設定

Athenaでは、下記のSQLを使ってテーブルを作成しました。
RDBと違ってデータはS3にあってテーブルはいくらでも作り直せるので、いろいろ試行錯誤してみるのもいいかもしれません。

CREATE EXTERNAL TABLE IF NOT EXISTS hogehoge.rails (
  `type` string,
  `timestamp` string,
  `timestamp_jst` string,
  `method` string,
  `path` string,
  `controller` string,
  `action` string,
  `status` string,
  `duration` float,
  `view` float,
  `transaction_id` string,
  `request_id` string,
  `session_id` string,
  `params` string,
  `host` string,
  `remote_ip` string,
  `ua` string,
  `sql` string
)
PARTITIONED BY(
  year int,
  month int,
  day int
)
ROW FORMAT SERDE 'org.openx.data.jsonserde.JsonSerDe'
WITH SERDEPROPERTIES (
  'ignore.malformed.json' = 'true'
) LOCATION 's3://hogehoge/logs/rails/'
TBLPROPERTIES ('has_encrypted_data'='false');

そして、パーティションをS3から読み込ませます。
テーブル名左の点メニューから「Load Partition」を選択します。すると、自動的に年月日ディレクトリを読み込みパーティションを作ります。

あとはAthenaでコマンドを実行してデータを取り出せばいい感じです。

ログの流れ

最終的に、下記のようなログの流れになりました。

CloudWatch Logs
 ↓ サブスクリプション
 ↓
 ↓ Logは、JSON形式でCloudWatch Logsの情報が付与されて、
 ↓ gzip圧縮されBase64で文字列化されて送られるので解凍
 ↓
 ↓ Kinesis Data Firehose にある Transform 機能(Lambda)を使い、
 ↓ CloudWatch Logsの情報をはがして純粋なログのみを1行ずつJSONで受け渡す
Kinesis Data Firehose
 ↓ 年月日ディレクトリにデータを保存
 ↓ gzip圧縮オン
 ↓
S3
 ↓
 ↓ 定義したテーブルに読み込まれる
 ↓
Athena

最後に

アクトインディでは、楽しくAWSのソリューションを活用して楽しいサービスを作るエンジニアを募集しています!