20201207のAWSに関する記事は23件です。

サーバレスについてまとめてみた

サーバレスとは

サーバについて考える必要なくアプリケーション開発が行える。
仮想サーバ構成の場合以下について考える必要性がある。

  • サーバーの維持コスト
  • アカウントのアクセス制御範囲
  • デプロイ方法について
    etc..

サーバレスアーキテクチャが生まれた背景

オンプレミス(物理サーバ)
→データセンターの仮想サーバ
→クラウド環境の仮想サーバ
→サーバレス

インフラ構成はオンプレミスの物理データサーバを利用していた時代からサーバレスまで変化しました。
この背景はアプリケーションのアーキテクチャ設計が関係していると思われます。

アプリケーション開発のアーキテクチャ設計は
モノリシックアーキテクチャ
→サービス思考アーキテクチャ(SOA)
→マイクロサービスアーキテクチャ
のようにサービス、ロジック、インフラが密結合に設計されたアーキテクチャ構成から疎結合化したアーキテクチャに変更されてきました。
今回紹介するサーバレスはマイクロサービスを構築する上で利用しやすいサービスになります。

サービス責任モデル

オンプレミス クラウド Faas マネージドサービス
アプリケーション ユーザー ユーザー ユーザー クラウドベンダー
ランタイム ユーザー ユーザー クラウドベンダー クラウドベンダー
ミドルウェア ユーザー ユーザー クラウドベンダー クラウドベンダー
OS ユーザー ユーザー クラウドベンダー クラウドベンダー
仮想基盤 ユーザー クラウドベンダー クラウドベンダー クラウドベンダー
ハードウェア ユーザー クラウドベンダー クラウドベンダー クラウドベンダー

上記のようにサービス責任が問われます。
今回紹介しているサーバレスはこの表のFaas、マネージドサービスにあたります。

メリット

  • サービス開発の時間の短縮により、サービスロジックの構成に集中できる
  • 運用保守の省力化
  • 従量課金であるためインフラ維持コストがかからない

上記のメリットから以下のユースケースが想定される。

  • 既存機能の一部置き換え
  • 新規実装の概念実証
  • マイクロサービスとして定義したシステムの構築
  • キャンペーンサートなどの一時的機能
  • 管理サイトなどの特定の利用を行う機能

デメリット

  • モノリシックなアプリケーション開発には有効でない
  • トランザクションが頻繁に利用されるシステムに向いていない
  • 低レイテンシ(遅延)が求められるサービスに向いていない

従来のAWS構成との比較

  • 従来のEC2によるインフラ構成
    EC2_Diagram.png

  • APIGateway,lambdaを利用したサーバレスなインフラ構成
    lambda_Diagram.png

デザインパターン例

  • モバイルバックエンド AWS AppSyncを利用してリアルタイム処理。オフライン用件のモバイルシステム。
  • 画像処理・データ加工 S3,SQSと連携してイベント発生時にlambdaによる加工処理が実行される。
  • Amazon Alexaスキルの作成 音声認識時にlambda実行が行われる。

APIGatewayとlambdaを利用して簡単なAPIを作成してみる

今回はAPIGatewayのリクエストをトリガーにlambda関数を実行するAPIを作成してみます。

  • lambda関数の作成 AWSコンソールからlambdaを選択し、lambda関数の作成ボタンを押下します。 A979C71D-48B4-47F0-AF92-931AF9F2C799_1_201_a.jpeg

C047F395-A0EC-4C29-8808-DFCF894D56B0_4_5005_c.jpeg

「一から作成」を選択して
任意の関数名、記述言語を選択します。
3AF7F997-5678-4B5C-A336-BA59FCFC4697_1_105_c.jpeg

今回のLambda関数の他のAWSサービスへのアクセス許可を設定します。
E24B81A6-5792-44C3-92C6-8B8F976DAFE3_1_105_c.jpeg

ロール名は任意のロール名を入力して、
ポリシーテンプレートは
「基本的な Lambda@Edge のアクセス権限(CloudFront トリガーの場合)」を選択します。
このテンプレートには、CloudWatchへのアクセス権限が含まれているので、今回作成したロールが付与されたサービスは、CloudWatchにログを出力できるようになります。

上記のように入力したら、 「関数の作成」 ボタンをクリックします。

1B37322C-B0B9-45AE-AA96-4FA8E53979E3_1_105_c.jpeg

関数はサンプルをそのまま使います。
API Gatewayで作成したAPIがリクエストを受けると、Lambda関数が呼ばれて、そのリクエスト情報一式が1行目のeventに渡されます。
eventは今回は使わずに、APIには「"Hello from Lambda!"という文字列をレスポンスとして返すように」という指示を、処理結果として返します。

  • APIGatewayの作成

次にAPIGatewayの作成をしていきます。
RESTAPIの「構成」ボタンを押下します。
497BAF63-278E-4843-AB13-F2BCE0C83A3D_1_105_c.jpeg

新しいAPIを選択して、任意のAPI名を選択し、「APIの作成」ボタンを押下します。

606C3DA5-FE1A-42A3-9C73-D5A714BC31EF_1_105_c.jpeg

APIの作成ができたら、まずはエンドポイントを作成します。
アクションボタンを押下して「リソースの作成」を選択します。
A9791AB4-7528-4353-A285-C68344A1E632_1_105_c.jpeg

任意のリソース名を記入して、「APIGateway CORSを有効にする」にチェックを入れます。
チェックを入れると、testリソースの作成と共に、optionメソッドのエンドポイントが作成されます。
これによりブラウザからAPIを呼び出すことができます。
0E4FDAC7-2342-4415-B7E3-85A251CEE7ED_1_105_c.jpeg

testリソースに、GETメソッドのエンドポイントを作成します。
16C8A119-51D2-4420-BA30-6C23C4534E7E_1_105_c.jpeg

「GET」を選択してチェックをクリックします。
09968224-54BC-415F-88F6-9294CE7188F9_1_105_c.jpeg

GETメソッドのエンドポイントがリクエスト受けた時、どのように処理するか設定します。
統合タイプとして「Lambda関数」を選択して、「Lambda プロキシ統合の使用」にチェックをいれ、Lambda関数に先ほど作成したLambda関数を指定します。
A872CE7E-D268-4D67-9F44-DB2612924B6E_1_105_c.jpeg

「Lambda プロキシ統合の使用」にチェックを入れると、このエンドポイントに対するリクエスト情報一式を、Lambdaのevent変数(後述)に渡します。
保存ボタンを押すと、以下のようにAPIGatewayからlambda関数にアクセスする権限を与えるか確認がありますのでOKを押下します。
5A1EE569-FF1C-49C9-83E9-DAD24F26870A_4_5005_c.jpeg

lambdaに戻って「デザイナー」を選択して構成を確認します。
7403C0DB-6E22-44BC-A8F2-0092B94E3353.jpeg

最後にAPIGatewayのデプロイをします。
E2C3ECF8-8C38-43AD-A2AD-997F239D2EC3_1_105_c.jpeg

デプロイされるステージに「新しいステージ」を選択して、ステージ名を「production」としてデプロイボタンを押下します。
5E2F389F-66A4-43B6-963F-FCB3E1B063B0_1_105_c.jpeg

GETメソッドのエンドポイントを選択すると、エンドポイントのURLが表示されます。
URLにアクセスして想定通りの挙動をしているか確認してみます。
6EF026D2-A09A-41F7-9047-BAF6350E8815_1_105_c.jpeg

以下のように「Hello from Lambda!」と表示されれば完成です。
17CFE78E-3F58-456B-AD74-0821C1C2B92B_4_5005_c.jpeg

サーバーレスの背景、メリットデメリット、簡易的なAPIの作成と簡単にサーバレスに触れてみました。
今回紹介できなかったこと意外にもたくさんのできるととがあります。
参考記事にはこの記事以上の情報量、質があります。
この記事をみてもしもさらに調べてみたいと思った方がいましたら、是非とも一読してみてください!

参考

実践プロダクションサーバーレス - AWS CDK と TypeScript によるWebアプリケーション開発パターン
【初級】いまから始めるサーバレスアプリケーション | AWS Summit Tokyo 2019
サーバーレスシステムのチーム開発
API Gateway + LambdaでREST API開発を体験しよう [10分で完成編]

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

サーバレス(APIGateway,Lambda)についてまとめてみた

サーバレスとは

サーバについて考える必要なくアプリケーション開発が行える。
仮想サーバ構成の場合以下について考える必要性がある。

  • サーバーの維持コスト
  • アカウントのアクセス制御範囲
  • デプロイ方法について
    etc..

サーバレスアーキテクチャが生まれた背景

オンプレミス(物理サーバ)
→データセンターの仮想サーバ
→クラウド環境の仮想サーバ
→サーバレス

インフラ構成はオンプレミスの物理データサーバを利用していた時代からサーバレスまで変化しました。
この背景はアプリケーションのアーキテクチャ設計が関係していると思われます。

アプリケーション開発のアーキテクチャ設計は
モノリシックアーキテクチャ
→サービス思考アーキテクチャ(SOA)
→マイクロサービスアーキテクチャ
のようにサービス、ロジック、インフラが密結合に設計されたアーキテクチャ構成から疎結合化したアーキテクチャに変更されてきました。
今回紹介するサーバレスはマイクロサービスを構築する上で利用しやすいサービスになります。

サービス責任モデル

オンプレミス クラウド Faas マネージドサービス
アプリケーション ユーザー ユーザー ユーザー クラウドベンダー
ランタイム ユーザー ユーザー クラウドベンダー クラウドベンダー
ミドルウェア ユーザー ユーザー クラウドベンダー クラウドベンダー
OS ユーザー ユーザー クラウドベンダー クラウドベンダー
仮想基盤 ユーザー クラウドベンダー クラウドベンダー クラウドベンダー
ハードウェア ユーザー クラウドベンダー クラウドベンダー クラウドベンダー

上記のようにサービス責任が問われます。
今回紹介しているサーバレスはこの表のFaas、マネージドサービスにあたります。

メリット

  • サービス開発の時間の短縮により、サービスロジックの構成に集中できる
  • 運用保守の省力化
  • 従量課金であるためインフラ維持コストがかからない

上記のメリットから以下のユースケースが想定される。

  • 既存機能の一部置き換え
  • 新規実装の概念実証
  • マイクロサービスとして定義したシステムの構築
  • キャンペーンサートなどの一時的機能
  • 管理サイトなどの特定の利用を行う機能

デメリット

  • モノリシックなアプリケーション開発には有効でない
  • トランザクションが頻繁に利用されるシステムに向いていない
  • 低レイテンシ(遅延)が求められるサービスに向いていない

従来のAWS構成との比較

  • 従来のEC2によるインフラ構成
    EC2_Diagram.png

  • APIGateway,lambdaを利用したサーバレスなインフラ構成
    lambda_Diagram.png

デザインパターン例

  • モバイルバックエンド AWS AppSyncを利用してリアルタイム処理。オフライン用件のモバイルシステム。
  • 画像処理・データ加工 S3,SQSと連携してイベント発生時にlambdaによる加工処理が実行される。
  • Amazon Alexaスキルの作成 音声認識時にlambda実行が行われる。

APIGatewayとlambdaを利用して簡単なAPIを作成してみる

今回はAPIGatewayのリクエストをトリガーにlambda関数を実行するAPIを作成してみます。

  • lambda関数の作成 AWSコンソールからlambdaを選択し、lambda関数の作成ボタンを押下します。 A979C71D-48B4-47F0-AF92-931AF9F2C799_1_201_a.jpeg

C047F395-A0EC-4C29-8808-DFCF894D56B0_4_5005_c.jpeg

「一から作成」を選択して
任意の関数名、記述言語を選択します。
3AF7F997-5678-4B5C-A336-BA59FCFC4697_1_105_c.jpeg

今回のLambda関数の他のAWSサービスへのアクセス許可を設定します。
E24B81A6-5792-44C3-92C6-8B8F976DAFE3_1_105_c.jpeg

ロール名は任意のロール名を入力して、
ポリシーテンプレートは
「基本的な Lambda@Edge のアクセス権限(CloudFront トリガーの場合)」を選択します。
このテンプレートには、CloudWatchへのアクセス権限が含まれているので、今回作成したロールが付与されたサービスは、CloudWatchにログを出力できるようになります。

上記のように入力したら、 「関数の作成」 ボタンをクリックします。

1B37322C-B0B9-45AE-AA96-4FA8E53979E3_1_105_c.jpeg

関数はサンプルをそのまま使います。
API Gatewayで作成したAPIがリクエストを受けると、Lambda関数が呼ばれて、そのリクエスト情報一式が1行目のeventに渡されます。
eventは今回は使わずに、APIには「"Hello from Lambda!"という文字列をレスポンスとして返すように」という指示を、処理結果として返します。

  • APIGatewayの作成

次にAPIGatewayの作成をしていきます。
RESTAPIの「構成」ボタンを押下します。
497BAF63-278E-4843-AB13-F2BCE0C83A3D_1_105_c.jpeg

新しいAPIを選択して、任意のAPI名を選択し、「APIの作成」ボタンを押下します。

606C3DA5-FE1A-42A3-9C73-D5A714BC31EF_1_105_c.jpeg

APIの作成ができたら、まずはエンドポイントを作成します。
アクションボタンを押下して「リソースの作成」を選択します。
A9791AB4-7528-4353-A285-C68344A1E632_1_105_c.jpeg

任意のリソース名を記入して、「APIGateway CORSを有効にする」にチェックを入れます。
チェックを入れると、testリソースの作成と共に、optionメソッドのエンドポイントが作成されます。
これによりブラウザからAPIを呼び出すことができます。
0E4FDAC7-2342-4415-B7E3-85A251CEE7ED_1_105_c.jpeg

testリソースに、GETメソッドのエンドポイントを作成します。
16C8A119-51D2-4420-BA30-6C23C4534E7E_1_105_c.jpeg

「GET」を選択してチェックをクリックします。
09968224-54BC-415F-88F6-9294CE7188F9_1_105_c.jpeg

GETメソッドのエンドポイントがリクエスト受けた時、どのように処理するか設定します。
統合タイプとして「Lambda関数」を選択して、「Lambda プロキシ統合の使用」にチェックをいれ、Lambda関数に先ほど作成したLambda関数を指定します。
A872CE7E-D268-4D67-9F44-DB2612924B6E_1_105_c.jpeg

「Lambda プロキシ統合の使用」にチェックを入れると、このエンドポイントに対するリクエスト情報一式を、Lambdaのevent変数(後述)に渡します。
保存ボタンを押すと、以下のようにAPIGatewayからlambda関数にアクセスする権限を与えるか確認がありますのでOKを押下します。
5A1EE569-FF1C-49C9-83E9-DAD24F26870A_4_5005_c.jpeg

lambdaに戻って「デザイナー」を選択して構成を確認します。
7403C0DB-6E22-44BC-A8F2-0092B94E3353.jpeg

最後にAPIGatewayのデプロイをします。
E2C3ECF8-8C38-43AD-A2AD-997F239D2EC3_1_105_c.jpeg

デプロイされるステージに「新しいステージ」を選択して、ステージ名を「production」としてデプロイボタンを押下します。
5E2F389F-66A4-43B6-963F-FCB3E1B063B0_1_105_c.jpeg

GETメソッドのエンドポイントを選択すると、エンドポイントのURLが表示されます。
URLにアクセスして想定通りの挙動をしているか確認してみます。
6EF026D2-A09A-41F7-9047-BAF6350E8815_1_105_c.jpeg

以下のように「Hello from Lambda!」と表示されれば完成です。
17CFE78E-3F58-456B-AD74-0821C1C2B92B_4_5005_c.jpeg

サーバーレスの背景、メリットデメリット、簡易的なAPIの作成と簡単にサーバレスに触れてみました。
今回紹介できなかったこと意外にもたくさんのできるととがあります。
参考記事にはこの記事以上の情報量、質があります。
この記事をみてもしもさらに調べてみたいと思った方がいましたら、是非とも一読してみてください!

参考

実践プロダクションサーバーレス - AWS CDK と TypeScript によるWebアプリケーション開発パターン
【初級】いまから始めるサーバレスアプリケーション | AWS Summit Tokyo 2019
サーバーレスシステムのチーム開発
API Gateway + LambdaでREST API開発を体験しよう [10分で完成編]
サーバレスとは

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

fafafa


fafafafafafafa

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GitLab CEの設定ファイルをGitLab CE上でGit管理する

はじめに

タイトルでGitって3回も言ってる。
この記事は、「GitLab Advent Calendar 2020」8日目の記事です。

概要

GitLabの設定ファイルである下記の2ファイルをGitLab CE上でGit管理する手順をまとめました。

  • /etc/gitlab/gitlab.rb
  • /etc/gitlab/gitlab-secrets.json

また、同様の手順で、GitLab Runnerの設定ファイルも管理可能です。
私はrunnerのconfig.tomlのリポジトリは分けてGit管理しています。

  • /etc/gitlab-runner/config.toml

前提

記事の内容は下記の環境を前提としています。

  • OSはAmazon Linux (RHEL系)
  • GitLab CE v12.9
    • Omnibus GitLab

また、Gitリポジトリを置くプロジェクトの名前などは下記として説明します。
脳内でお好きな名前に置き換えてください。

  • グループ名: YOUR_GROUP
  • プロジェクト名: PROJECT_NAME
  • ユーザー名(git push/pull用): GITLAB_CONFIG_USER

私はリポジトリ名はgitlab-configとかgitlab-runner-configという名前にしましたが、お好きな名前にしてください。

何ができるようになるか

設定の更新/反映はどんな操作になるか

下記のようになります。

  • 設定の更新
    • ローカルのPCなどで設定ファイルの変更をGitリポジトリのmasterブランチにpush
  • 設定の反映
    • GitLabサーバー上で設定ファイルの変更をgit pullして反映(スクリプトで一発でpullできるようにします)
    • sudo gitlab-ctl reconfigureなど(いつものやつ)

最終成果物の構造の概要の説明

たとえば、/etc/gitlab/gitlab.rbをGit管理する時……

  • サーバー上の/etc/gitlab/にローカルリポジトリがある状態になる。つまり、/etc/gitlab/.gitがローカルリポジトリ。
  • 手元のMacbookなどにも、cloneしてきたローカルリポジトリがある。
  • bareリポジトリ(親のリポジトリ)は他のプロジェクトと同様にGitLab上で管理する。
    • つまり、/var/opt/gitlab/git-data/repositories/YOUR_GROUP/PROJECT_NAME.gitみたいなパスの下にbareリポジトリが自動で管理されるはず。

手順

サーバー上で操作をする前の準備

GitLab上で普通のプロジェクトを作成する

リポジトリの置き先となる場所を作っておきましょう。
YOUR_GROUPの下に配置して、名前はPROJECT_NAMEとします。

git pullするためのGitLabユーザーを作成する

なぜユーザーを作るかというと、git pullやgit pushをする時にユーザーに紐付いたsshの秘密鍵が必要だからです。
別に普段使っている個人のユーザーの秘密鍵でも動かすことはできますが、そのユーザーが退職した時に、サーバー上でのgit pullがいきなりできなくなっては困るはずなので、専用のユーザーを作っておきましょう。
名前はGITLAB_CONFIG_USERとします。

このユーザーはPROJECT_NAMEのメンバーにして、権限はMaintainerにしておきましょう。

GitLabユーザーの公開鍵・秘密鍵を発行する

上で設定したGITLAB_CONFIG_USERにログインして鍵を発行しておきましょう。後でサーバーに配置するのは秘密鍵の方です。

サーバー上の既存の設定をgit下に置く

GitLabサーバー上での作業です。

# rootとして作業
sudo su -
cd /etc/gitlab

git init

# 一応 --local でこのリポジトリだけの設定にしておく
git config --local user.name "あなたの名前を入れてください"
git config --local user.email "あなたのメールアドレスを入れてください"

# (重要! 初回コミット前に .gitignore を作成し、ignoreしたいファイルやディレクトリを書いておきましょう)
# ".gitignoreについて"の項目に一応例を載せておきました。

git add -A
git commit -m "Initial commit"

GitLab管理下に置くためにpushする

ローカルリポジトリへのコミットは終わったので、ここからはリモートリポジトリにpushするための準備です。(リモートとは言っても、localhost上にgitのサーバーはありますが……)

sudo su -
cd /etc/gitlab

# 同一サーバー上にリモートリポジトリがあるので、ホストはlocalhostにしておく
git remote add origin git@localhost:YOUR_GROUP/PROJECT_NAME.git
# 間違えてしまって再設定する場合は
# git remote set-url origin git@localhost:YOUR_GROUP/PROJECT_NAME.git

# (GITLAB_CONFIG_USERのGitLab push用秘密鍵を~/.sshに設置)
# (もちろんchmodして使えるようにしておいてください)

# push前の準備
eval "$(ssh-agent)"
ssh-add -k ~/.ssh/GITLAB_CONFIG_USERの秘密鍵

git push -u origin master

ここまでできたら、もうプロジェクト側に反映されたので簡単です。
後はローカルにgit cloneして色々変更を加えていきましょう。

git pullを簡単にできるようにするスクリプトをリポジトリに追加する

サーバーでgit pullするたびにいちいちsudo su -してeval "$(ssh-agent)"して……という作業は面倒くさいので、スクリプトにしてリポジトリに追加します。

git-pull.sh(これをリポジトリに追加する)
#!/bin/sh

# git pullを楽にするためのスクリプト。
# rootで実行する必要がある。

# GitLab側で認証できる秘密鍵がないとgit pullできない
eval "$(ssh-agent)"
ssh-add -k ~/.ssh/GITLAB_CONFIG_USERの秘密鍵

# git pull。もしgitlab-runner側の場合は"/etc/gitlab/"のところが"/etc/gitlab-runner/"になる
/usr/bin/git -C /etc/gitlab/ pull

動作確認

まずはサーバー内でスクリプトをpullしてきます。

sudo su -
cd /etc/gitlab

eval "$(ssh-agent)"
ssh-add -k ~/.ssh/GITLAB_CONFIG_USERの秘密鍵

git pull

無事pullできたら、一旦ログアウトしましょう。
ec2-userなどから、下記のコマンドでpullが問題なくできればOKです。

sudo /etc/gitlab/git-pull.sh

後はプロジェクトのREADME.mdに、説明を書いてあげると親切になると思います。お疲れ様でした!

注意点

最後に一つだけ注意すべき点を挙げておきます。

  • /etc/gitlab/gitlab-secrets.json

gitlab-secrets.jsonは、GitLabのアップデートがあった時に勝手に書き変わる可能性があるようです。アップデートで変更があったらサーバーの方からまた設定をcommitしてpushする必要があるので、それだけ覚えておきましょう。
Maintainerはmasterに直接pushできるようにしておいてもいいかもしれません。

付録

.gitignoreについて

私はこんな設定にしてみたという一例です。今のディレクトリの状態にもよると思うので、参考程度にしてください。

/etc/gitlab側

.gitignore
config_backup/
ssl/
trusted-certs/

/etc/gitlab-runner側

.gitignore
config.toml.lock
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS] AWSにRDSを設置

AWSにRDSを設置

今回も、本日(2020/12/07)、AWSにRDSを設置しましたよ、というだけの投稿です。

設置したデータベースエンジンはMySQL(バージョン8.0)です。

RDSはAWSが提供するリレーショナルデータベースのサービスですが、Amazon社がベストと考える状態に既にチューンナップされているわけですね。

逆に言うと、ユーザーが自由に設定を変えることはできないとも言えるようです。AWSユーザーが設定できる部分は以下の3つのようです。

RDSにおいて設定ができる箇所
・DBパラメータグループ
・DBオプショングループ
・DBサブネットグループ

本日も超個人的なメモでしかない投稿ですね。すみません。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SAMを使った環境毎のデプロイ方法のまとめ

はじめに

SAMを使ったサーバーレス環境の構築時に、1つのtemplate.yamlを使って、develop, staging, production毎に環境変数を切り替える方法を忘れるため、備忘もかねて記事にしました

目的

SAMのtemplateを使って、各環境毎に変数値を変えてlambdaを実行できるようにする

どうやるのか?

SAMテンプレートの中の、ParametersとMappingsを使うと、一つの環境変数でKey-Value方式で、複数の値を切り替えることができる

実例

まず、ParametersとMappingを記述する

Parameters:
  Environment:
    Type: String
    AllowedValues:
      - development # 開発環境
      - staging # ステージング環境
      - production # 本番環境
    Default: development

Mappings:
 EnvironmentMap:
   development:
     HELLO: 'developmen Hello World'
   staging:
     HELLO: 'staging Hello World'
   production:
     HELLO: 'production Hello World'

それを使いたい時は、以下のように指定すれば、環境変数がKeyになり、Valueが取得できる

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.lambdaHandler
      Runtime: nodejs12.x
      Events:
        HelloWorld:
          Type: Api
          Properties:
            Path: /hello
            Method: get
      Environment:
        Variables:
          SAMPLE_ENV: !FindInMap [ EnvironmentMap, !Ref Environment, HELLO]

動作確認

# ビルド
sam build

# 実行
sam local start-api --parameter-overrides Environment={書き換えたい環境変数}

アクセスして確認する

curl 'http://localhost:3000/hello'

CI/CDをどうするか?

CIに関しては、一つのtemplate.yamlをビルドするだけで良い
CDの時に、 --parameter-overrides を使って、Parametersを書き換えてデプロイするフローになる

❯ sam deploy --guided

Configuring SAM deploy
======================

        Looking for config file [samconfig.toml] :  Found
        Reading default arguments  :  Success

        Setting default arguments for 'sam deploy'
        =========================================
        Stack Name [SAMPLE-ENV]: 
        AWS Region [ap-northeast-1]: 
        Parameter Environment [staging]: ← ここで使いたい環境を指定するだけで良い
        #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
---- 省略 ---- 

まとめ

かなり雑にまとめてしまいましたが、templateのParametersだけを使う場合だと、環境変数が多くなると管理が大変になるので、Mappingsを活用すると使いたい環境を指定するだけで、セットアップができる

誰かのご参考になれば幸いです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

API Gateway + Lambda + DynamoDB サーバレスWebAPI開発まとめ

はじめに

スマホアプリのバックエンドとしてAWSの「API Gateway + Lambda + DynamoDB」でサーバレスなWebAPIを開発しました。
スクリーンショット 2020-10-20 22.19.22.png
現在、開発環境と本番環境を運用しています。
「API Gateway + Lambda + DynamoDB」の構築手法はたくさん紹介されていますが、知識ゼロの状態から構築するのは難しく、時間がかかりました。
この記事ではAPI GatewayLambdaDynamoDBそれぞれの概要と、私がサーバレスWebAPIを構築する上で参考になった記事を紹介します。

API Gateway + Lambda + DynamoDB

とりあえず「API Gateway + Lambda + DynamoDB」の構成でWebAPIを開発する際には以下が参考になると思います。
まずは簡単なサーバーレスアプリケーションを作成して感覚を掴むのが良いと思います。
初めてのサーバーレスアプリケーション開発 ~DynamoDBにテーブルを作成する~
初めてのサーバーレスアプリケーション開発 ~LambdaでDynamoDBの値を取得する~
初めてのサーバーレスアプリケーション開発 ~API GatewayからLambdaを呼び出す~

API Gateway

スマホアプリなどのクライアントからのHTTPリクエストは、API Gatewayで受け付けてLambda関数で処理されます。API GatewayではLamba関数に渡す前のデータ形式を調整し、Lambda関数から返ってきたデータの形式を調整してクライアントにHTTPレスポンスとして返却します。
スクリーンショット 2020-10-23 14.03.22.png

初期設定など

API Gatewayの設定、Lambdaへのパラメータの受け渡しなどについて、以下が参考になりました。
https://qiita.com/naoki_koreeda/items/c2a32198c86e8d9a5bb6

統合レスポンスやヘッダーの設定

統合レスポンスやステータスコードのLambdaでのエラーの正規表現の書き方、ヘッダーのマッピングの仕方などについて、以下が参考になりました。
https://qiita.com/naoki_koreeda/items/5351f8ab803db655b0b4

formでPOSTする際のマッピングテンプレート

「application/x-www-form-urlencoded」の形式でPOSTする際のマッピングテンプレートの作成に苦労しました。以下の記事を参考に作成しました。
https://dev.classmethod.jp/articles/sugano-013-api-gateway/

OpenAPI、Swagger形式でエクスポート

API GatewayでREST APIを作成および設定したら、API GatewayコンソールなどからOpenAPIファイルやSwaggerファイルにエクスポートできます。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-export-api.html
逆にOpenAPI、Swaggerファイルからインポートすることもできるので、ドキュメントを変更してそれを環境に反映させるのも簡単です。開発効率が向上します。

API Gatewayのドメインを変更したい

API GatewayではデフォルトのURLを使用する代わりに、任意のカスタムドメイン名を使用することができます。
API Gatewayでカスタムドメインを設定し、HTTPS化する際に以下が参考になりました。
https://dev.classmethod.jp/articles/api-gateway-custom-domain-ssl/

応答性を強化するためにAPIキャッシュを設定したい

APIキャッシュを有効にして、エンドポイントのレスポンスがキャッシュされるようにできます。
キャッシュを有効にすると、エンドポイントへの呼び出しの数を減らすことができ、また、APIへのリクエストのレイテンシーを短くすることもできます。
メソッド毎にのキャッシュを有効または無効にしたり、TTL期間を変更したりすることもできます。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/api-gateway-caching.html

開発環境、本番環境で使用するLambdaやDynamoDBを変更したい

2つの異なるHTTPエンドポイントが存在する場合に、API Gatewayのステージ変数を使用することで、異なるAPIデプロイステージのHTTPとLambdaバックエンドにアクセスできます。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/amazon-api-gateway-using-stage-variables.html

API Gatewayで設定したステージ変数をLambdaへパラメータとして渡すことで、ステージ変数を元にLambda内で使用するDynamoDBのテーブルを変更することができます。

Lambda

Lambdaはサーバーの準備・管理をしなくても、コードを実行できるサービスです。
今回の構成ではAPI Gatewayをトリガーにして実行されます。
スクリーンショット 2020-12-07 8.21.19.png

Lambda関数内でサードパーティのライブラリを使いたい

Lambdaでrequestsを使いたいなど、サードパーティのライブラリを使いたいときに、以下の記事が参考になりました。
https://qiita.com/Hironsan/items/0eb5578f3321c72637b4

Lambdaで環境変数を使いたい

Lambdaではキーバリュー形式で環境変数を設定することができます。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-envvars.html

Lambdaにデフォルトで設定されている環境変数は以下です。
https://qiita.com/ykarakita/items/c9b1ee133fe03bbd4f1e

LambdaでHTMLを返す

LambdaでHTMLを返す方法は以下が参考になりました。
https://qiita.com/yoshidasts/items/d58c555aed2693e99ae6

LambdaからLambdaを非同期で呼び出したい

Lambdaから別のLambdaを呼び出したいときはinvokeを使います。
呼び出す際に同期/非同期を設定することができます。
以下が参考になりました。
https://qiita.com/ume1126/items/8170a10fad6b21f0f54a

DynamoDB

DynamoDBはフルマネージド型のNoSQLデータベースサービスです。
Lambdaとの相性が良く、サーバレスなWebAPIではDynamoDBを使用するのが最も一般的です。
DynamoDBではパーティションキーを設定する必要があります。パーティションキーのみで一意にならない場合は、ソートキーを設定します。
完全一致検索、範囲検索、前方一致検索などが可能です。

以下の例ではパーティションキーに型番、ソートキーにメーカー名を設定しています。
imageURLやtypeなど、他の項目をキーにして検索したい場合は、グローバルインデックスを設定することで検索可能になります。
スクリーンショット 2020-12-07 19.52.53.png
Amazon DynamoDB とは - Amazon DynamoDB

AWSのDBには多くの選択肢が存在します。それぞれのユースケースなどは以下に記載されています。
https://aws.amazon.com/jp/products/databases/

DynamoDBのデータを自動で削除したい

DynamoDBに登録したデータを自動で削除したいことがあったので調べました。
リアルタイム性が重要でない場合、DynamoDBのTTL機能で自動削除できます。
https://dev.classmethod.jp/articles/try-dynamodb-ttl/

リアルタイム性が必要な場合は、AWSStepFunctionsを使って自動削除を実現できます。
https://qiita.com/se_fy/items/dfe5bccaca80deebfa1b\

AWSStepFunctionsについては以下が参考になりました。
https://dev.classmethod.jp/articles/step-functions-parameters/

DynamoDBのデータをインポート・エクスポートしたい

DynamoDBのデータはDataPipelineを使用してインポート・エクスポートをすることができます。
https://www.wakuwakubank.com/posts/694-aws-data-pipeline-dynamodb/

DynamoDBではデータをJSON形式でエクスポートしたときに、単純なキーバリュー形式ではなく、間にデータ型が入ってしまいます。
このようなJSONからデータタイプを排除する方法は以下が参考になりました。
https://bbh.bz/2019/12/09/delete-data-type-from-dynamodb-json/

CloudWatch Logs

CloudWatch Logsでは、ログの簡単な表示や特定のエラーコードまたはパターンの検索などが可能です。
Lambda関数には、CloudWatch Logsのロググループが付属しており、ログが自動で出力されます。
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/python-logging.html

ログデータを分析したい

CloudWatch Logs Insightsを使用することで、ログデータを簡単に分析することができます。
ログの操作にはクエリ言語を使用します。
CloudWatch Logs Insightsについては以下の記事が参考になりました。
https://qiita.com/sakochi/items/8e60654ffe36432c5228

CloudWatch Logs Insightsではクエリが保存できます。
https://hacknote.jp/archives/57401/

CloudWatch Logsのエラーログ内容をメールで送信したい

CloudWatch Logsで出力しているログファイルに特定の文字列がログに存在した場合に、メールで通知することができます。以下の記事が参考になりました。
https://dev.classmethod.jp/articles/notification_cloudwatchlogs_subscriptoinfilter/

メトリクスフィルターでのパターンマッチングについては以下が参考になりました。
https://dev.classmethod.jp/articles/cloudwatch-metricsfilter-filterpattern/

CloudWatch Logsのエラーログ内容をSlackに通知したい

Slackで通知する方法は以下が参考になると思います。
https://qiita.com/yahagin/items/2248287c5285cd1e9201

おわりに

私がサーバレスWebAPIを構築する上で参考になった記事を紹介しました。
これらの記事のおかげでサーバレスWebAPIを構築し、運用することができました。
この記事が、私のように初めてAPI Gateway、Lambda、DynamoDBに触れる方やAWSのサーバレス開発で困っている方の一助になれば幸いです。
この記事は自分用のメモでもあるので、今後運用する中で困ったことがあれば参考にした記事などを順次追加していきます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者のNewRelic。エージェントをインストールしてみる。

New Relicを使うに当たってサーバーにエージェントをインストールするとより詳細なメトリクスが取得できます。
また、「on-host integrations」というプラグイン群を追加すると、さらにサーバーでなにが起きているか詳しく知ることができます。
手始めのエージェントインストールと一部設定方法を残しておきます。

インストールした環境

  • AWS EC2
  • Amazon Linux2 or Windows Server2019

注意点

New Relicのバージョンによって管理画面の構成などが異なることがあります。

概要

手順はとても簡単です。
必要なことはサーバーへのSSHおよびRDPログイン。
Linux/Windowsともにコマンドを実行するだけです。
互換性や要件などは下記公式ドキュメントで確認してください。
https://docs.newrelic.co.jp/docs/infrastructure/install-infrastructure-agent/get-started/requirements-infrastructure-agent

手順 - エージェントインストール

① 管理メニューの「Add more data」を選択し、一覧より「Host operating systems」の対象OSを選択

adf0a659-8137-44b0-a8f4-69311b7d0a99.png

Linux(Amazon Linux2)の場合

スクリーンショット 2020-12-07 11.12.56.png

Windowsの場合

スクリーンショット 2020-12-07 13.26.31.png

② 対象のNewRelicアカウントおよびOSのバージョンを選択し、インストールコマンドを生成

Linux(Amazon Linux2)の場合

スクリーンショット 2020-12-07 11.15.20_02_22fd.png
スクリーンショット 2020-12-07 11.20.16.png

Windowsの場合

スクリーンショット 2020-12-07 13.27.46.png
スクリーンショット 2020-12-07 13.33.26_02.png

③ 対象のNewRelicアカウントおよびOSのバージョンを選択し、インストールコマンドを生成

あとは生成されたコマンドをLinuxならbashなど、WindowsならPowerShellで実行するだけです。
実行後、数分してからNewRelicの Infrastructure > hosts などでサーバーが表示されているか確認します。

手順 - プロセスメトリクス取得

下記設定ではOSで管理できる全てのプロセスに関するメトリクスを取得します。
プロセスをフィルターしたい場合は下記を参照
https://docs.newrelic.co.jp/docs/infrastructure/install-infrastructure-agent/configuration/infrastructure-agent-configuration-settings#include-matching-metrics

① newrelic-infra.ymlに有効化の設定を記述

1行追記するだけです!
簡単!
Linux: /etc/newrelic-infra.yml
Windows: C:\Program Files\New Relic\newrelic-infra\newrelic-infra.yml

license_key: 3***********L ←デフォルトでの記載行です

# ここから下を追記
# プロセス監視
enable_process_metrics: true

② エージェントを再起動

Linux(Amazon Linux2)

$ sudo systemctl restart newrelic-infra

Windows

PS C:\Users\Administrator> Restart-Service -Name newrelic-infra

③ 管理画面で設定反映を確認

スクリーンショット 2020-12-07 12.06.39.png
1行追加するだけで、サーバーで実行されているプロセスやWindowsサービスについてCPU使用率やメモリ使用率が把握できるようになります。
とても簡単。

他にもMySQLやapache固有のメトリクスを取得できたり、コンテナやdockerにインストールできたりします。
いろんな環境で触ってみたいと思います。

参考サイト:
https://docs.newrelic.co.jp/docs/infrastructure/install-infrastructure-agent/get-started/install-infrastructure-agent

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Step FunctionsとAPI Gatewayの統合【後編】

はじめに

前回の記事の続きで、APIGatewayを使いステップ関数を使用して、マイクロサービスをオーケストレーションし、AWSサービスにアクセスする方法について書いていきます。

マイクロサービスの自動化

まず前提として、Step Functionsに組み込まれている障害処理、意思決定の分岐、および並列処理を使用して、アプリケーションのバックエンドを調整しているとします。
開発者は、APIGatewayを使用してバックエンドマイクロサービスへのアクセスを管理しています。

APIGatewayを使う

要求、応答形式を標準化し、ビジネスロジックをルーティングロジックから切り離すのに役立ちます。
開発者が認証、スロットル、負荷分散などの責任を軽減できるようにすることで、複雑さを軽減します。

新しいAPIGateway統合を活用する

開発者はAPIGatewayエンドポイントを使用して、堅牢なワークフローを構築してマイクロサービスを調整できます。
これらのマイクロサービスは、サーバーレスまたはコンテナベースにすることができます。

次の例は、APIGatewayとStep Functionsを使用し、マイクロサービスをオーケストレーションして、AWSサービスにアクセスする方法を説明しています。

このアプリケーションのサンプルコード

2020-12-07_10h31_44.png

アプリケーションを実行する

GitHubリポジトリのクローンを作成します。

Bash
$ git clone https://github.com/aws-samples/example-step-functions-integration-api-gateway.git
$ cd example-step-functions-integration-api-gateway

AWS SAM CLIを使用して、アプリケーションをデプロイし、すべてのデフォルトのパラメーター入力を受け入れます。

Bash
$ sam build && sam deploy -g

image.png

これにより以下の17のリソースがデプロイされます。

  • Step Functions標準のワークフロー
  • 3つのリソースエンドポイントを備えたAPI Gateway REST API
  • 3つのLambda関数
  • DynamoDBテーブルを含む17のリソース

[StockTradingStateMachineArn]値をメモします。
これは、コマンドライン出力またはAWSLambdaコンソールの[アプリケーション/セクション]にあります。

image.png

ワークフローを手動でトリガーする

ターミナルウィンドウから、ワークフローを手動でトリガーします。

Bash
aws stepFunctions start-execution \
--state-machine-arn <StockTradingStateMachineArnValue>

応答は以下のようなものが出ます。
image.png
image.png

ワークフローが実行されると、APIGatewayから[/check]リソースへのGETリクエストを介して、Lambda関数が呼び出されます。

これにより、1〜100のランダムな株価値が返されます。
この値は、50未満かそれ以上かによって、購入または販売の選択で評価されます。

Lambda関数を呼び出す

販売状態と購入状態は、APIGateway統合を使用してLambda関数を呼び出します。
POSTメソッドを使用します。
"stock_value"は、 POSTリクエストです。
"transaction_result"で返され、"ResponseBody"に渡します。
最終状態は、遷移のログをDynamoDBテーブルに書き込みます。

AWSSAMテンプレートを使用したリソースの定義

ステップ関数リソースは、このAWSSAMテンプレートで定義されています。
"DefinitionSubstitutions"のフィールドは、ワークフロー定義にテンプレートパラメータを渡すために使用されます。

YAML
StockTradingStateMachine:
    Type: AWS::Serverless::StateMachine # More info about State Machine Resource: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/sam-resource-statemachine.html
    Properties:
      DefinitionUri: statemachine/stock_trader.asl.json
      DefinitionSubstitutions:
        StockCheckPath: !Ref CheckPath
        StockSellPath: !Ref SellPath
        StockBuyPath: !Ref BuyPath
        APIEndPoint: !Sub "${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com"
        DDBPutItem: !Sub arn:${AWS::Partition}:states:::dynamodb:putItem
        DDBTable: !Ref TransactionTable

ワークフローは別のファイル(/statemachine/stock_trader.asl.json)で定義されています。

次のコードブロックは、株価のチェック状態を定義します。
新しいリソースは、[arn:aws:states:::apigateway:invoke]APIGatewayのサービス統合タイプを意味します。

パラメータオブジェクトは、サービス統合を構成するために必要なフィールドを保持します。
パスと"ApiEndpoint"値によって、提供される"DefinitionsSubstitutions"のAWS SAMテンプレート内のフィールドです。
"RequestBody"の入力はアマゾン米国の言語を使って動的に定義されます。
[.$]フィールド名の末尾に"RequestBody"を指定するパラメータは、入力にJSONノードを参照するためのパスを使用します。

YAML
"Check Stock Value": {
  "Type": "Task",
  "Resource": "arn:aws:states:::apigateway:invoke",
  "Parameters": {
      "ApiEndpoint":"${APIEndPoint}",
      "Method":"GET",
      "Stage":"Prod",
      "Path":"${StockCheckPath}",
      "RequestBody.$":"$",
      "AuthType":"NO_AUTH"
  },
  "Retry": [
      {
          "ErrorEquals": [
              "States.TaskFailed"
          ],
          "IntervalSeconds": 15,
          "MaxAttempts": 5,
          "BackoffRate": 1.5
      }
  ],
  "Next": "Buy or Sell?"
},

展開プロセスは、ApiEndpoint値を検証します。
サービス統合は、"https:// [APIendpoint] / [Stage] / [Path ]"の形式で、parametersブロックで提供される情報から、APIエンドポイントのURLを構築します。

所感

APIゲートウェイでのステップ機能の統合の紹介と
REST API、HTTP APIの機能を、ワークフローステップから直接呼び出す機能をご紹介しました。

Step Functionsに組み込まれているエラー処理は、開発者がコードを削減し、ビジネスロジックを切り離すのに役立ちます。
これをAPIGatewayと組み合わせて、認証、スロットリング、負荷分散などの責任を軽減できます。
これにより、インフラストラクチャを管理することなく、APIGatewayを介してコンテナまたはLambda関数にデプロイされたマイクロサービスを自動化できます。

公式リンク

Step FunctionsからAPI Gatewayを利用する
AWS公式サイト

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Translate で翻訳の範囲指定ができるようになりました。

はじめに

この記事は株式会社ナレッジコミュニケーションが運営する Amazon AI by ナレコム Advent Calendar 2020 の 7日目にあたる記事になります。
ここでは、2020年11月5日にAWS Machine Learning Blog にて紹介されたAmazon Translate のアップデートについての紹介記事です。

Amazon Translate とは?

Amazon Translate はディープラーニングを利用したテキスト翻訳サービスです。
Amazon Translate の主な特徴としては以下の通りです。

■ディープラーニングを利用した自然な翻訳を提供。
■複数の言語に対応(2020年12月7日時点で55の言語に対応)
■アプリやwebサイトとの連携が可能
■リアルタイム翻訳・バッチ翻訳に対応

Amazon Translate の料金体系

Amazon Translate は従量課金制です。
課金対象の範囲は Amazon Translate が処理したテキストの文字数(空白文字も含む)になります。
料金には標準料金と無料枠があります。以下が詳細です。

■標準料金

  • 100万文字あたり15.00USD(1文字あたり0.000015USD)

■無料枠

  • 最初に翻訳をリクエストしてから12か月間利用可能。
  • 1か月200万文字の翻訳が無料
  • 無料枠を超えた場合は超えた文字数分が標準料金で請求。

今回のアップデード内容

今回のアップデートでは翻訳するテキストから翻訳しない範囲を指定できるようになりました。
以下の記事を参考に実際に試していきたいと思います。
参考記事:Amazon Translate now enables you to mark content to not get translated

今回は以下の文章のニューラル機械翻訳サービスを翻訳しない範囲においてみたいと思います。

Amazon Translate は、高速で高品質な言語翻訳を手ごろな価格で提供するニューラル機械翻訳サービスです。

AWSマネジメントコンソール上で操作

2020-12-07_16h40_36.png
範囲指定は以下のように行います。

<p translate=no> 対象範囲 </p> 

上記の画像の赤枠の指定した範囲が翻訳されず他のは英語で翻訳されました。

コマンドツールでの操作

2020-12-07_16h49_18.png
コマンドプロンプトを使用してAWS CLI で行いました

結果はjson方式で出しています。
上記の画像の通り、コマンド上でも指定した範囲では翻訳されずに行えました。

※以下のようにコマンド入力の際にOSによって違いがあるのでご注意ください。
Unix,Linux,MacOS 各行の終わりの行を「\」
Windows 各行の終わりの行を「^」

最後に

Amazon Translate はAWS SDKでの操作可能です。
Amazon Translateではpptxなどの様々な形式での翻訳や大量のテキストを一度に翻訳できるバッチ翻訳も利用可能なので試してみたいと思います。
興味がある方は無料枠を利用して試してみてはいかがでしょうか?

参考資料

Amazon Translate 開発者ガイド

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Workspace(旧称G Suite) アカウントのSAML認証を使ってAWS CLIの認証を行う

はじめに

この記事は2020年のRevCommアドベントカレンダー9日目の記事です。8日目は@marienplatzさんの 【超入門】android(kotlin)アプリにユニットテストを導入する でした。

こんにちは、RevCommでインフラエンジニアをしている工藤です。

AWSでのアカウント管理、皆さんどのようにされていますか?
エンジニアを始めとした社内のAWS利用者の加入、離脱に合わせて、IAM Userの発行/失効作業をするのは、頻度が高くなってくると中々大変な作業になります。
また、MFA設定についてもIAM User側で設定してもらう必要があるかと思いますが、Google Workspaceアカウントを使うと、Google Workspace側のMFAを使用することができます。
そこで、今回はGoogle WorkspaceアカウントのSAML認証を使ってAWS CLIの認証を行う方法について、触れていきたいと思います。

Google Workspaceアカウントを使ったAWS マネジメントコンソールの認証については、AWSさんがTechBlogで丁寧に解説して下さっていますので、こちらが参考にされるのが良いかと思います。
https://aws.amazon.com/jp/blogs/startup/techblog-saml-gsuite/

前提

  • 既にAWS マネジメントコンソールの認証ができている
  • AWS CLI v1最新版のインストールが完了している

実施した環境

OS: macOS Catalina 10.15.7
AWS CLI: aws-cli/1.18.190 Python/3.7.4 Darwin/19.6.0 botocore/1.19.30

認証方法

今回は2つの方法を試してみます

  1. aws-google-authというオープンソースツールを使用する方法
  2. AWS CLIのAssumeRoleWithSAMLを使用する方法

aws-google-authというオープンソースツールを使用する方法

https://github.com/cevoaustralia/aws-google-auth

aws-google-authをインストールして、認証する

流れとしては、こんな感じになります。
1. aws-google-authをインストール
2. aws-google-authを実行
3. 対話式で初期入力 or aws config設定
4. AWS_PROFILE設定 or awscli profile指定実行

aws-google-authをインストール

pip install aws-google-auth

aws-google-authを実行する

aws-google-auth

初回アクセス時は、下記のように入力を求められます

Google username: sample@example.com
Google IDP ID: 123456789 ※1
Google SP ID: ABCDEFGH ※1
Google Password: 

※1 IDP IP、SP IDは、AWS マネジメントコンソールへのリンクURLから取得可能できます
qiita_3.jpg

# 認証が通り、複数のIAM Roleを保持しているアカウントの場合、下記のように選択を求められます
# IAM Roleが1つの場合は聞かれない

Open the Google App, and tap 'Yes' on the prompt to sign in ...
[  1] arn:aws:iam::012345:role/dev-readonly
[  2] arn:aws:iam::012345:role/dev-administrator
[  3] arn:aws:iam::123456:role/prod-readonly
[  4] arn:aws:iam::123456:role/prod-administrator
Type the number (1 - 4) of the role to assume: 4

認証が通ると

Assuming arn:aws:iam::012345:role/dev-readonly
Credentials Expiration: 2020-12-07 06:41:56+09:00

認証が通ると設定情報は、aws configに記載されます

profileを切り替えて認証したい場合は、下記のように 予め設定する or aws-google-auth -p [profile] で対話形式設定するのいずれかの方法があります

予め設定する場合は、google_config.durationの値は、該当するIAM Roleの最大セッション時間 & Google Workspace側のユーザーのカスタム属性(Seesion Duration)の値と合わせます

~/.aws/config
[profile dev-readonly]
region = ap-northeast-1
google_config.ask_role = False
google_config.keyring = False
google_config.duration = 43200
google_config.google_idp_id = ABCDEFGH
google_config.google_sp_id = 123456789
google_config.u2f_disabled = False
google_config.google_username = sample@example.com
google_config.bg_response = None
google_config.role_arn = arn:aws:iam::012345:role/dev-readonly

[profile prod-readonly]
region = ap-northeast-1
google_config.ask_role = False
google_config.keyring = False
google_config.duration = 7200
google_config.google_idp_id = ABCDEFGH
google_config.google_sp_id = 123456789
google_config.u2f_disabled = False
google_config.google_username = sample@example.com
google_config.bg_response = None
google_config.role_arn = arn:aws:iam::0123456:role/prod-readonly

あとは、 export AWS_PROFILE or profileを指定して awscliのコマンドが実行できます

export AWS_PROFILE=dev-readonly
aws s3 ls
aws s3 ls --profile dev-readonly

AWS CLIのAssumeRoleWithSAMLを使用する方法

aws-cliの場合は、ブラウザも使う必要はありますが、SAML認証が可能です

流れとしては、こんな感じになります。
1. ブラウザでSSOのURLへアクセス
2. SAMLレスポンスをファイルに保存
3. assume-role-with-samlを実行
4. AWS_PROFILE設定 or awscli profile指定実行

ブラウザでSSOのURLへアクセスし、SAMLレスポンスを表示する

アクセス先のURLは、※1の画像にあるURLとなります。

SAMLレスポンスの取得方法は、下記のマニュアルを参照ください
https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/troubleshoot_saml_view-saml-response.html#chrome

SAMLレスポンスをファイルに保存

任意の場所にSAMLレスポンスの内容を書き込んだファイルを保存します。
※SAMLレスポンスには有効期限がありますので、ご注意ください

assume-role-with-samlを実行する

実際にご自身の環境で実行する場合は、下記の項目を変更してください

  • --role-arn: SAML認証に使用しているRoleのarn
  • principal-arn: IAMで作成したIDプロバイダーのarn
  • --duration-seconds: SAML認証に使用しているRoleに指定している最大セッション時間
  • "[dev-readonly]": 任意のプロファイル名に変更ください

コマンド見ていただくとわかりますが、認証情報を~/.aws/credentialsへ追記している為、2回目以降は該当のプロファイルを削除する必要があります

aws sts assume-role-with-saml --role-arn arn:aws:iam::012345:role/dev-readonly --principal-arn arn:aws:iam::012345:saml-provider/GSuite --duration-seconds 43200 --saml-assertion file://samlresponse.log |
jq -r '.Credentials' | awk -F':' '
BEGIN {RS="," ; print "[dev-readonly]"}
/:/{ gsub(/"/, "", $2) }
/AccessKeyId/{print "aws_access_key_id =" $2}
/SecretAccessKey/{print "aws_secret_access_key =" $2}
/SessionToken/{ print "aws_session_token =" $2 }
/Expiration/{ printf "aws_session_expiration =%s:%s:%s",$2,$3,$4}
' | sed 's/"//g' | sed '/}/d' >> ~/.aws/credentials

あとは、 export AWS_PROFILE or profileを指定して awscliのコマンドが実行できます

export AWS_PROFILE=dev-readonly
aws s3 ls
aws s3 ls --profile dev-readonly

まとめ

今回は、2種類の方法を使用して、awscliのSAML認証をしてみました。
awscli v2では、AWS SSOでのを使用しての認証がサポートされているので、こちらも試してみたいと思います。

明日は @series7 さんの「Github Actionsとserverless」に関する記事です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FTPサーバーとしてのS3とフォルダ振り分け

FTPサーバーもサーバーレスに

ファイルをFTPサーバーで受信するような旧来型のあるサービスをAWSに移行したときの話です。
移行後もFTPサーバーとしての EC2 を立てて…というのはなんだかなぁということで、S3 に配信することにしました。

このとき、まず仕組みとして考えたのが以下の構成。

  • (HTTP) CloudFront → API Gateway → Lambda → S3

見様見真似で構築自体は問題なくいきましたが、配信元システムの仕様でHTTPでのファイル配信では運用できないことが分かりました。

そこでFTPで配信する仕組みを構築すべく、AWS Transfer Family を検討しました。

AWS Transfer Family

従来はSFTPのみサポートしていたマネージドサービスですが、今年からFTP/FTPSをサポートするようになりました。まさに僥倖。
※FTPやめようよ…

構築について詳しくはこちらを参考に。
AWS Transfer for SFTPがFTP/FTPSをサポートしました!

Cloud Formation でテンプレートから構築されます。

  • Transfer for SFTP
  • API Gateway
  • Lambda

FTPの場合、認証はカスタムするしかないので、Secrets Manager を利用することにして、作成された Lambda から読み込むようにしました。
(Secrets Manager の読み込みはサンプルコードが生成されるのでそれを元に)

あとは、つくられた VPC のエンドポイントにアクセス確認。

問題なければ、配信元のシステムの設定も更新します。
これで無事、S3にFTP配信される仕組みが整いました。

配信されたファイルをフォルダに振り分ける

あとは元々、ファイル名から適切なフォルダに振り分ける(なければつくる)という処理が入っていました。
このままだとバケットのルートにファイルが溜まり続けるので、S3トリガーで動く Lambda を作成します。

トリガーは S3 ファイルPUT時。
ファイルを適切なフォルダに移動(コピー&削除)する処理を Lambda で記述します。

  • Pythonでの例
move.py
import logging
import urllib.parse
import boto3

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def handler(event, context):
    try:
        client = boto3.client('s3')
        bucket_name = event['Records'][0]['s3']['bucket']['name']

        src = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')

        ## ここで対象ファイルのチェックなど

        ## 向き先のフォルダ名を決めるなど

        dst = folder_name + '/' + src

        client.copy_object(Bucket=bucket_name, Key=dst, CopySource={'Bucket': bucket_name, 'Key': src})
        client.delete_object(Bucket=bucket_name, Key=src)

        logger.info("moved file: from[" + src + "]" + ", to[" + dst + "]")

        return True

    except Exception as e:
        logger.error(e)

        return False

トリガーを「すべてのイベント時」ではなく PUT に限定することで、このファイル移動処理での COPY のときにも起動されないようにしています。

これで、サーバーレスなFTPサーバー(?)ができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

子供たちのハイキングイベントでGPSマルチユニットSORACOM Editionを使って運営を楽にした話

これは何?

子供たちが参加するハイキングイベントを GPSマルチユニットSORACOM Edition (以降、GPSマルチユニットとします)を使って、現在位置を把握したり、距離や速度の履歴を可視化して運営を楽にした個人の趣味プログラミングの紹介です。
GPSマルチユニットからクラウドサーバ(AWS)にGPSデータを送付し、そのGPSデータを可視化するWebアプリを作った内容を簡単に解説します。「おもしろ要素」や「奇をてらって」的な要素は一切ありませんが、作りあげる過程の苦悩的なことをメモにしたので、読み物としてはイマイチかもしれません・・・

GPSマルチユニットの充電の様子
↑は、GPSマルチユニットを充電している様子です。

背景

「よき社会人を育成する」ことを目指した青少年教育ボランティア団体である ボーイスカウト では、夜を徹して子供たちがハイキングする恒例イベント(通称:オーバーナイトハイク)というのがあります。
子供たちだけで地図を見て歩くので、サポートする大人たちの想定しない道を進んでしまったりして 1 、迷子になるケースがあります。
今年はコロナがありキャンプなど密になりがちなイベントは敬遠されますが、ハイキングならソーシャルディスタンスを保って活動できるのでGoodです:blush:

「道を勘違いしたり」、「曲がる道を間違えたり」などなど夜歩く疲れも相まって「どうしてそうなった!:sweat:」と大人では考えにくいミスが起こり、「この子たちは、どこいったんだ!?:worried:」(当の本人たちは間違えたことに気付いていない)というトラブルが毎回発生します。

歩く子供たちにGPSを持たせておけば楽だ!と前々から考えていたのですが、個人で手軽に入手できるGPS発信デバイスがありませんでした 2 。そんな、ある日、SORACOMのページを見ていたら簡単に扱えそうなGPS発信デバイスとして「GPSマルチユニット」が発売されており、これは使わない手はない!と飛びつきました。

目的

子供たちのハイキングイベント(オーバーナイトハイク)を楽に運営する:vulcan:

内容

現在値と軌跡の確認画面

Google Mapを利用して、「現在誰が、どこに居るか」「どんな軌跡なのか」を表示しています。
軌跡を表示することで、「子供たちが犯す道の間違いの傾向(前のグループに釣られて間違えたコースをいってしまう等)」、「各グループがどのように歩くかといった戦略の違い(とにかく細い道を駆使し最短距離を目指すグループ、間違えないように大通りを選択するグループ)」も見えて面白いです。

移動軌跡.png

↑の画像の右図の3番のグループや6番のグループが何やら道を勘違いして、想定とは違うルートに行きかけていることがリアルタイムに分かります。特に3番のグループの場合は、想定ルートと大きくかけ離れているので、昔だったら「どこ行った!?」と車を出して総出で捜索されていたことでしょう。

速度や距離の記録

運営中には、子供たちのペース管理(疲れが見え始めてるとか、時速どれぐらいで歩いてるからどれぐらいで着く等の予測)が必要になります。
そのための元データとなるためのグラフです。

speed_and_distance.png

チェックポイントの到着・出発記録

ハイキングでは、スタートからゴールの間に複数のチェックポイントが設定されます。このチェックポイントの到着・出発を記録します。
本来であれば、ジオフェンス(緯度・経度と半径などで定める仮想的な境界線)を構築しにGPSログに応じて、自動的に到着・出発を記録したいところですが、そこまでは開発している時間がありませんでしたので、手動で記録しています。

CP履歴

技術的な話

全体構成

全体構成図

何故この構成となったのかは、次節以降で解説しますが、AWSをベースとする構成をとっています。

  • (GPSログ系)GPSマルチユニット → SORACOM Beam → AWS API Gateway3 → AWS Lambda → DyanmoDB
  • (管理系)管理用Webアプリ → AWS API Gateway → AWS Lambda → DynamoDB
    • 管理用WebアプリにAzureを使ってしまっている理由は後述します。

前提とした非機能要件的な話

全体構成を考える上で必要となりそうな非機能要件は下記のようにある程度洗い出しておきました。

  • 性能面を決めるファクターである参加人数とか関わる人数とか
    • 子供たちのグループの数は、10グループ想定=GPSマルチユニット10台。10台が1分間に1回ずつGPSデータの送付をする。(実際の参加は6グループまで減りましたが)
    • サポートする大人の数は、のべ15名程度。更新頻度的には、画面へはだいたい10秒に1回程度のアクセスか。
  • 認証と認可周り
    • (画面系・認証)サポートする大人が見る画面は、1日限りしか使わないのにメール&パスワードの登録は鬱陶しいので、簡単にログインできるようにしたい。(共通パスワードをかけるイメージ)
    • (API系/認証)リクエスト都度に正しい認証に基づくリクエストかどうかを検証する仕組みとする。

認証周りの仕組みについては、長くなるので、以降の記事で割愛します。

全体を構成する上での方針

今後継続的に使っていくことを考えて、GPSマルチユニットの購入費用は初期費用として仕方がないとしても、サーバ側の固定費はとにかく安く抑えたいと考えました。そこで、フロントエンドおよびバックエンドは次のような方針を立てました。

  • フロントエンドは、管理用Webアプリで、固定費を抑えるために静的サイトで運用できるように、 Vue.js や ReactJs を使って開発(願望)。
    • 結果、間に合わず、Javaを使ったフレームワークに頼ることに・・・
  • バックエンドは、固定費をとにかく抑えるため稼働しているだけで利用料を請求されるサービスは利用できないので、AWS API Gateway + AWS Lambda + DynamoDB を活用することにします。
    • 実装の仕組みは、Serverless Framework を利用して、API Gateway + Lambda の管理を楽にしようと思います。
    • 実装言語はJavaしか使えないマンなので、Javaを選定

各要素の実装で考えたこと

バックエンド

バックエンドは、大きく次の2種類があり、最終的には API Gateway → AWS Lambda → DynamoDB の経路となっています。

  • (GPSログ系)GPSマルチユニット → SORACOM Beam → AWS API Gateway3 → AWS Lambda → DyanmoDB
  • (管理系)管理用Webアプリ → AWS API Gateway → AWS Lambda → DynamoDB

(GPSログ系・管理系共通)AWS API Gateway → AWS Lambda → DyanmoDB

API Gateway は、 Serverless Frameworkを利用して、「管理画面用のAPI」と「GPSマルチユニットからのGPSログ受付のAPI」の大きく2種類作成しました。AWS Lambdaの実行環境に合わせて、Java11 を選択して、開発しています。

AWS Lambda の関数は、Serverless Framework が提供している サンプルコード に従って、下記のようにハンドラーをJavaクラスと serverless.yml に定義していきます。

SampleHandler.java
public class SampleHandler implements RequestHandler<Map<String, Object>, ApiGatewayResponse> {
    @Override
    public ApiGatewayResponse handleRequest(Map<String, Object> stringObjectMap, Context context) {
        // 処理を書く
    }
}
serverless.yml
service: sample-service

frameworkVersion: '1'

provider:
  name: aws
  runtime: java11
  stage: dev
  region: ap-northeast-1
  profile: sample-service-profile

package:
  artifact: target/sample-service.jar

functions:
  SampleHandler:
    handler: SampleHandler
    events:
      - http:
          path: /sample_handler
          method: post

AWS側のAPI Gatewayを作って、Lambdaを作って、API GatewayとLambdaを連携させる設定をして、といっためんどくさい設定は基本的に serverless framework にお任せで良いのですごく楽です。こちらは、 serverless deploy コマンドを叩くのみで良いのです。

serverless framework が適切に設定が実施できるよう(内部的にはAWS Lambdaの関数を作ったりしてくれるので)、
Identity and Access Management(IAM) での権限設定(権限追加)が必要です。最初から何を使うかよく分からなかったので、権限エラーが出る度に、トライ&エラーして権限を追加していく必要がありました。

「GPSマルチユニットからのGPSログ受付のAPI」を作ったまでは良かったのですが、「管理画面用のAPI」を必要な数分だけ作っていくと、API Gateway + Lambdaの数が増えてきてしまい、イマイチな状況 となりました。
そこで、汎用型のI/Fを用意して、内部的にAPIを呼び出すといったRPC(Remote Procedure Call)方式に切り替えました。gRPCのようにかっこよく実現したかったのですが、簡単に利用できそなものがない&時間が無くなってきたので、雑なオレオレRPC汎用呼び出しI/F(関数)を作って、内部的なロジックを呼び出す仕組みとしました。メソッドはすべてPOSTで、リクエスト&レスポンスは、BodyにJSON形式で埋め込む形式としています。

内部的な構造

個人的にはRESTful APIは、リソースベースが意識されすぎて設計しにくいので、RPC+ファサードパターン的に入力と出力を決めてアクション(画面の初期表示やボタンの押下などのイベント的な意味)に見合った処理を定義できるほうがやり易いと感じています。API Gatewayの役割は、マイクロサービスのコンテキストでいうところの BFF(Backend For Frontend) 的な役割を込めています。さらに内部の処理は、Lambdaに分割し、さらなるマイクロサービス化を図ってもよかったのですが、設定ばかりが複雑になるのと規模感が知れてるのと起動時間がデメリットなので1つのjarに閉じ込めてしまいました。

Swaggerを使って設計すれば、Swagger Codegen を使ってコード生成が行われ、フロントエンドとバックエンドで共通のDTO(Data Transfer Object)を自動生成できて便利だなと思うのですが、今回はオレオレでやってしまったので、いちいち手作業で作っていく必要があり大変でした。

上記以外にもDI(Dependency Injection)の仕組みがあった方がコードの見通しが良くなるので入れてみたかったのですが、ハマリそうだったのと、Lambdaの起動が遅くなる懸念もあり、スキップしました。せっかくなので、Javaのマイクロサービスフレームワークである MICRONAUT とか使うと面白かったのかなぁと思ったりしています。

(GPSログ系)GPSマルチユニット → SORACOM Beam 部分

GPSマルチユニット用のSIMグループを作成し、GPSマルチユニットをグループ化します。
その後、SORACOM のダッシュボードから GPSマルチユニット用のSIMグループ の設定から SORACOM Beam 設定の設定に行き、 API Gateway エンドポイント(URL) の登録をしました。

特にハマることもなく、この設定だけでデータを送れたので、楽ちんでした。
SIMグループの設定

フロントエンド(Webアプリケーション)

本当は、Vue.js または ReactJs で作りたかったのですが、開催までの時間が無くなってきたので、使い慣れてる JavaのWebアプリケーションフレームワークの1つである Spring Boot と Webアプリケーションのインタフェースを構築するためのフレームワークである Java Server Faces(JSF) を利用した開発に切り替えました。このせいでフロントエンドをデプロイするサーバが必要になったので、急遽 Azure App Service 4 を利用しています。

JSF は枯れた技術(人によってはオワコンと呼ばれます)ですが AdminFacesPrimeFaces といった素晴らしいUIコンポーネントがあり、Vue.js や ReactJs と同じようにコンポーネントを組み合わせるだけで、それなりのWebアプリができます。

JSFについてもっと詳しく・・・

POST/GET等のリクエストに応じたアクションベースではなく(Spring MVCのような)、画面表示時の動きはどうするか、ボタンが押下された場合にどう動くかのような振る舞いを定義するイベントベースで定義していきます(この点、Vue.jsやReactJSと同じような感じです)。振る舞いはすべてJavaで定義することができるため、npmのようなビルドシステムやパッケージ管理の配下に入ることも無く、Javascriptに触れることなく、画面の部分更新などSPA(Single Page Application)っぽいヌルッとした動きを持つそれなりのWebアプリが作ることができます。

一方で、JSFは、画面の構造をサーバ側に保持しておき状態変化に応じて常に同期(更新)していくステートフルな仕組みです。そのため、ボタン押下や画面更新の都度に、サーバ側との通信が発生することでVue.jsやReactJsに比べて動作はもっさりしますし、BtoCのようなスケーラブルなアプリケーションで利用するには工夫が必要(セッションを共有化するとかセッションに応じてアクセスするアプリケーションサーバを固定化する等)なフレームワークです。ある程度利用者が限定できるBtoBの業務システムなどに向いていると考えています。また、悪いことにドキュメントが少なく(特に日本語では)、とっつき易いフレームワークか、と言われれば否です。

JSF は、JBoss や GlasshFish などのJavaEEのアプリケーションサーバでしか動作しないイメージがあると思いますが、JoinFaces と呼ばれる Spring Boot と JSF をつなげるライブラリによって、Spring Boot上でのJSF利用を実現しています。

このWebアプリケーションはあくまで表示部分のみを担当し、バックエンドロジックは、JavaのRESTクライアントライブラリを用いて AWS API Gateway を呼び出すことで実現しています。

GPSマルチユニットについて

  • 基本的には、SORACOMが提供しているマニュアルを読めば一通りの事は理解できました。設定とかも戸惑うことも特にありませんでした。
  • GPSマルチユニットは、基本的には稼働しっぱなしを想定しているのだろうと思いますが、電源オフ操作が難しく(中央のボタンを4秒押すとマニュアルにはある)、うまく電源が切れない・・・ ので、いつもバッテリー切れまで放置しています。
  • GPSマルチユニットの起動直後は、GPSがうまく捕捉できずに、緯度・経度データ共に null となります。GPSを利用したい場合は、20~30分暖気運転(正しくGPSデータを捕捉するように電源を入れて放置)する必要があります。
    • 購入後に SORACOM Harverst で試運転している時に、どうやっても緯度・経度が null になり、色々調べていて時間を溶かしていたらそのうち緯度・経度データが連携されてきたので、「GPSの衛星を捕捉するまでに時間がかかるんだなぁ」と理解しました。
  • GPSマルチユニットのデータ送信間隔をAPIで設定したいのですが、今のところ(2020年11月現在)そのI/Fが存在しない?ようで、SORACOMのダッシュボードにアクセスしてポチポチするしかないのが面倒です。
    • 実際送信間隔をほぼほぼ変えることはありませんが、できれば自作アプリ内で完結したいな、とは思います。
    • 台数が多いと1台ずつの設定も時間がかかるので、APIで叩きたくなります。
    • コメントいただきましたが、 https://dev.soracom.io/jp/docs/api/#!/Group/putConfigurationParameters から頑張ればできるようです!
  • 最小データ送信間隔が1分なので、徒歩ならちょうどよい感じでデータを取得できます。しかし、車の場合は、1分間でそれなりの距離を進んでしまえるため(特に青信号が続く場合など)、間隔が空きすぎてスカスカな履歴データとなってしまいました。運転データとして取得するには使えないかもしれません。自転車だとどうでしょうか・・・
  • GPSの精度については街中のハイキングと言うこともあってか、ズレを感じることはありませんでした。

費用

個人利用にしては、GPSマルチユニットの支出が重すぎた・・・ テンション上がりすぎて買ったが若干後悔:joy:
そして、GPSマルチユニットの維持費の方がサーバ代より高いので、いろんなイベントで使ってもらって気持ち的にペイしていこうと思います!
(イベントに対して請求は0円で、個人の趣味です。今年は、特別定額給付金が出てるからちょっと無駄遣いしてもいいよね♪みたいなテンションで物を買ってしまってますが、軽く10万の出費を超えてしまってる気がします…:cry:

初期費用

項目 単価 数量 合計
GPSマルチユニットSORACOM Edition 11,000円 10台 110,000円(税抜)
SORACOM オンラインショップ送料 5 1,000円 2回 2,000円
合計 112,000円

運用費用

11月7~8日にイベントがあったので、その後、11月1日~11月10日時点での各サービスの費用を集計しました。

項目 合計
SORACOMへのイベント開催二日間の支払い分 135円×2日分
Azure App Service(B1プラン)利用料 2.03円
AWS Lambda利用料 0円
AWS DynamoDB 利用料(11月1日~10日) $0.33(≒34.64円)
AWS API Gateway 利用料(11月1日~10日) $0.28(≒29.39円)
AWS Cloud Watch 利用料 0円(無料枠内?)
Google Map API利用料 0円(無料枠内)
合計 336.06円

運営中にDynamoDBのReadキャパシティユニットをを引き上げた事が影響してか、有料になってしまいました。これは仕方ないかもしれません。
API Gateway は、イベント開始前からテストでデータを送ったりしていたのと、イベント終了後も稼働したまま放っておいたのもあってデータが2日間ぐらい送り続けられてしまい課金されてしまいました :crying_cat_face: 実際は無料の範囲内で収まるかもしれません。

Azure App Serviceを利用しているのですが、コストに2.03円しか表示されないのがイマイチ不可解です・・・ どさっと請求が来たら嫌だなぁとは思うもののダッシュボードから確認できるのは、2.03円のみでした。

冷静になって考えれば分かることですが(作るのが楽しくて冷静さを失ったわけですが笑)、SIMのステータスを「休止中」にしておいても、 1日10円×台数×日数分=約3000円/月 の出費が確定して、かなり痛い・・・ :money_mouth: リモートワークになって飲み会が激減したので、飲み会1回分だと思って我慢しようと思います。

改善点として、GPSマルチデバイス → AWS API Gateway部分をSORACOM Funkを使えば、Lambdaを直接実行できるため、API Gatewayの実行回数が減り、もう少し節約できるかもです。

今後取り組みたいこと

  • ジオフェンスを取り入れてチェックポイントの到着・出発の自動記録と出発・到着状況をサポートする大人のLINEグループへの通知(LINE BOT連携)をやってみたいなと思います。
  • 各チェックポイントやゴールへの到着予測時刻の算出。
  • 参加した子供たちへ渡せる記録みたいなのを自動で出力してあげる仕組みなどあると嬉しいかもしれない!?と思っています。
    • どこでどう間違えたのか、どうすれば次回上手くいくのかといった成長に繋げるための仕組み
  • 管理用Webアプリを Vue.js や ReactJs への移植。
  • 速度のグラフ表示が詰まりすぎて見にくいので、表示間隔を落とすなど改善は必要だと思いました。

プロジェクト規模感のメモ

個人プロジェクトの記録として、メモを残しておきます。

  • 9月に思い立って業務終了後の時間(できて3時間)、土日(2~4時間ずつ)+祝日をまるっと投入などで作り込みを行い、10月末には完成しました。
  • 管理画面はイベントの登録・変更やGPSマルチユニットの紐付け(登録)や変更などを行える様になっており、先に紹介した画面を含めて20画面ぐらいあり。
  • 総工数は、120時間(0.8人月)ぐらい。(1人日=8時間、1人月=20人日=160時間)
  • 規模感は、javaのコードだけで 10,000ステップ 程度 (うちテストコードが3,000行ぐらい)

まとめと感想

  • GPSマルチユニット + SORACOM Beam + AWS API Gateway + AWS Lambda + DynamoDB を利用して、位置情報監視システムが簡単に完成し、実際に役に立ちました。
    • GPSマルチユニットの維持費に比べたら、パブリッククラウドの利用料の安さ(というかタダ同然)は驚愕。サーバレスってすごい。
    • 年に数えるほどしか使わないサービスにおいては、サーバレス最強。(2回目)
  • 子供たちにとって直接の恩恵を受けることはありませんが、サポートする大人たちから「すごく便利になった」、「迷ったり、他のグループに負けないようにデットヒートしてる光景など子供たちが努力している様子が手に取るように分かる(涙もの)」との声を頂きました。
    • かっこよく言えば、ボーイスカウトのようなボランティア活動であっても DX(Digital Transformation)が必要ですね!
    • GPSが無い時代は、サポートする大人たちが車で見回りを行い、子供たちがどこに居るのかを随時連絡し合っていました。その時に比べてだいぶ運営が楽になりました。
  • GPSマルチユニットの数が10台~20台のオーダーであれば、現在値+軌跡を画面表示可能ですが、100台オーダーになればGoogle Map自体への描画やデータのロードが重くなりますので、もっと別の表示の方法を選択すべきでしょう。
    • Google Map の軌跡表示を間引くこと、直近1時間だけ表示するなどデータを少なくする工夫は必要かもしれません。
  • GPSマルチユニットからデータを受け付ける部分(SORACOM Beam → AWS API Gateway → AWS Lambda → DynamoDB 部分)は、1分間隔で10台が同時にデータ送付していても全然問題無さそうなので、このまま100台オーダーにスケールしても問題無い仕組みになっていると思います。
    • 仮にボトルネックが出るとしてもDynamoDBの書き込みキャパシティの問題なので、お金を出して書き込みキャパシティを引き上げて上げれば良いです。
  • やっぱり趣味プログラミング楽しい!!:heart_eyes:

免責事項

  • ボーイスカウトに関わりのある、ある大人の趣味的な取り組みであり、ボーイスカウト全体を代表する意見や取り組みではありません。

  1. ボーイスカウトでは、自ら地図を読み、歩くことを大切にしていることもあり、スマートフォン片手にGoogle Mapで調べながらいくことはしていません。 

  2. 去年までは格安AndroidスマホにSORACOMのSIMを入れてAndroidアプリを用いた仕組みを作っていましたが、ハイキング開始時にAndroidアプリを立ち上げておいたり、電池の減りが早かったりしてバッテリーを追加装着するなど運用しにくい代物でした。GPS定期的に送信してくれるGPS類似アプリはあるものの、子供たちのスマホに入れたりするのは保護者の承諾など色々面倒なこともあり、運営側で準備する方が楽なこともあります。 

  3. SORACOM Funk を利用して AWS Lambdaと連携する方法もあるようですが、うまくデータ送付できなくてハマりそうだったので SORACOM Beamを使い、 API Gateway経由としました。 

  4. 本当は、無料で使えるアプリケーション運用サービスである Heroku を使おうと思ったのですが、すぐにメモリ500MB(無料版の上限)を超えてしまい、OutOfMemoryエラーが連発して、使い物にならないので、代替のサービスを探すことに・・・トホホ。AWS Elastic Beanstalkを使うのが筋かもしれませんが、デプロイを試みたらエラーとなり、諦めてSpring Bootを簡単にデプロイできそうな Azure App Serviceへ。 

  5. テスト用に1台買って、残り9台を発注したので送料は2回分 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ローカルで開発したアプリケーションをS3、EC2、RDSを使ってとりあえず動かしてみる

はじめに

こちらの記事で作成したアプリをAWSのサービスを使ってとりあえず動くようにしてみる記事です。

おおまかな流れ

今回はAWSのS3、EC2、RDSのサービスを使ってとりあえずrailsサーバーを立ち上げてアプリケーションが閲覧できる状態に持っていきたいと思います。

  1. S3のバケット作成、EC2インスタンス立ち上げ、RDSデータベース作成
  2. EC2にアプリのソースをクローン、環境構築
  3. EC2上でrailsサーバー立ち上げて実際に見れることを確認

今回の作業用のIAMユーザー作成

AWSのルートユーザーしかもっていなかったので、今回の作業用にIAMユーザーを作りました。(IAMユーザー自体は作らなくてもルートユーザーの操作から作業を進めることはできますが、作業に必要な権限のみを持ったIAMユーザーを作るのがセオリーです。)
Screenshot from 2020-12-07 11-45-28.png
RDS、EC2、S3それぞれにフルアクセスできるグループを作成して、グループ内にユーザーを追加しました。

S3バケット作成

ローカルでpublic/images配下に置いていた全画像をS3バケットにアップロードします。
今回はS3上に置いた画像のパスを直接指定してアプリ上で表示させたかったので、アップロードの際のアクセスコントロールリストのアクセス許可をパブリックにしています。
Screenshot from 2020-12-07 11-58-04.png
これでS3上の画像パスを直接指定することで画像を表示できます。

public/images/配下を指定していたview側の画像パスもS3のパスを指定するように変更しておきます。
今回はS3アドレスドメインをヘルパ関数で定義しておいて、viewの画像パス指定箇所に関数を呼び出す形で対応しました。

# application_helper.rb
def aws_s3_path
  'https://rails-persona5.s3-ap-northeast-1.amazonaws.com'
end

EC2インスタンス起動

無料枠で作成できるt2.microでEC2インスタンスを作成しました。
Screenshot from 2020-12-07 12-37-41.png

EC2インスタンス作成は特に必要な詳細設定等はありませんでしたが、パブリックDNSを指定してブラウザで開けるようにしたかったので、インスタンスが属するセキュリティグループのインバウンドルールを編集する必要がありました。
Screenshot from 2020-12-07 13-02-54.png
デフォルトで設定されているSSHルールに加えて、railsサーバーを起動するポート3000に対して0.0.0.0/0からのアクセスを許可します。(0.0.0.0は任意のIPv4アドレス)
Screenshot from 2020-12-07 12-53-22.png
これでパブリックDNSのポート3000を指定してEC2にブラウザでアクセスできます。

RDSデータベースの作成

無料利用枠でRDSデータベースを作成しました。エンジンはローカル開発環境に合わせてmysqlを設定しています。
EC2インスタンスからRDSにアクセスできるようにするために、RDSデータベースが属しているセキュリティグループに関してもインバウンドルールを設定する必要がありました。

  • データベースが属するセキュリティグループのインバウンドルール編集画面 Screenshot from 2020-12-07 13-19-51.png

MYSQL/Auroraルールを追加して、ソースにはEC2が属するセキュリティグループ(今回はlaunch-wizard-2)を指定します。EC2が属するセキュリティグループに、データベースが属するセキュリティグループへのアクセスを許可することになります。
これでEC2からRDSにアクセスできます。

  • ターミナル上でEC2経由でRDSにアクセスできることを確認 Screenshot from 2020-12-07 13-33-00.png

EC2上でrailsサーバー起動

EC2インスタンスにソースをクローン、database.ymlにRDSデータベースのホスト名、ユーザー名、パスワードを設定して、マイグレーションを実行後、ローカルのデータベース上のデータをダンプしてRDSデータベースにリストアしてデータの準備をします。
またrailsサーバー起動時にBlocked hostエラーが出たので対応しました。

(実はこの他にもgemのバージョンやyarnのインストールなどいろいろつまづきました。このあたりの環境構築はdockerを利用すれば簡単になるらしいので勉強していきたいと思います。)

全ての準備が整ったら以下のコマンドでrailsサーバー起動です。

bundle exec rails s -b 0.0.0.0

railsサーバーを起動したらEC2のパブリックDNSの3000ポート指定してブラウザからアクセスしてみます。

Screenshot from 2020-12-07 14-00-21.png
トップ画面が表示できました!

画像についても問題なく表示できているようです。

Screenshot from 2020-12-07 14-09-16.png

さいごに

今回の作業はここまでです。AWSのサービスを実際に触ることで理解を深めることができました。特にセキュリティグループの考え方については作業前の理解がほぼほぼ0だったので勉強になりました。
EC2上で実際にrailsサーバーを立ち上げる時に環境構築でいろいろつまづいたので、dockerのコンテナを利用したソース管理を勉強していきたいですね。また今回作った環境はローカルの開発環境をAWSサービス上にコピーしただけなので、railsサーバー起動時にも開発(development)環境として立ち上げているのみです。実際にwebアプリケーションを運用していく際には本番(production)環境を構築しなければならないので、そちらの構築方法に関しても勉強していきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LaravelでAWSのSESからメール送信する方法

環境

Laravel:7.3
AWS SESリージョン:Tokyo(ap-northeast-1)

前提

・メールアドレスがある

やること

①composerでAmazon AWS SDK forPHPをインストールする
②SESにメールアドレスを登録する
③サンドボックスの解除
④SESのAPI使えるようにAIMでユーザー作る
⑤Laravelの設定(.env)
⑥試しにメール送信

①composerでAmazon AWS SDK forPHPをインストールする


composer.jsonに以下を記載しcomposer update する。

"aws/aws-sdk-php": "~3.0"

②SESにメールアドレスを登録する

Mails > Verify a New Email Addressの順でクリック

メールアドレスを入力してVerify This Email Addressをクリック

入力したメールアドレスに認証用のURLが記載されたメールが届きますので、URLを開いて認証を完了します。

③サンドボックスの解除

https://laraweb.net/knowledge/7083/

これ参考にしてくれぇ

④SESのAPI使えるようにAIMでユーザー作る

サービス(左上) > IAM > ユーザー > ユーザーの追加 > 任意の名前入力 + プログラムによるアクセスにチェック入れる > 次のステップ(右下の青いやつ) > [既存のポリシーを直接アタッチ]をクリック > [AmazonSESFullAccess] をアタッチ > 後はなんもいじらないで青いボタン押す。

重要↓

最後まで進むと、「アクセスキー」と「シークレットアクセスキー」が表示されますので、CSVでダウンロードするかメモしておきます。

⑤Laravelの設定(.env)

MAIL_MAILER=ses  //他の記事だと MAIL_DRIVER=ses ※config/mail.phpで確認してください
MAIL_FROM_ADDRESS=上で登録したメアド
MAIL_FROM_NAME="メールの送り主の名前になる"(何も書かないとExampleで送られる)
SES_KEY=上でメモした[アクセスキー]
SES_SECRET=上でメモした「シークレットアクセスキー」
SES_REGION=ap-northeast-1 //これ東京リージョンです

⑥試しにメール送信

 sudo php artisan tinker

>>> Mail::raw('mail 本文', function($message) { $message->to('送りたいメールアドレス')->subject('subjectだよーん'); });
=> null  ←これ成功!メール確認して届いてたら成功!

※上で成功したのに仮にお問い合わせフォームとかでエラー出たら自分で書いたソースを疑った方がいいかもです。。。知らんけど。

終了

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

yet-another-cloudwatch-exporterでCloudWatchのコストを削減する

PrometheusでCloudWatchメトリックを取得したときのコスト問題

久しぶりの投稿です。

CloudWatch用のPrometheus Exporterとして Officialなcloudwatch_exportergolang実装のcloudwatch_exporter がありますが、それとは別の yet-another-cloudwatch-exporter について書こうかと思います。

弊社でも当初はgolang版でRDSやAuroraのメトリックを取得していたらCloudWatchの料金が高額になってしまいました。。。

Amazon CloudWatch の料金
CloudWatch APIの課金体系はAPIリクエスト数によるものなので、AWS RDSのメトリック全部(25個ぐらい)を5分おきに取得した場合、1つのInstanceで月額2USD程度なので微々たるものです。
が、台数が増えたりCloudFrontなども取得するようにした場合にメトリックの数だけ課金が増えていきます(この時点で気づけばよかった・・・)。
案の定、翌月に請求額でビビった次第です。

CloudWatchでメトリックを取得するAPIには GetMetricStatisticsGetMetricData があり、GetMetricStatisticsは1度に1つのメトリックしか取得できませんが、GetMetricDataは1度に最大500コのメトリックを取得できます。
で、前述の cloudwatch_exporter は GetMetricStatistics を使用しているため、取得するメトリック・対象Instanceが増えるとCloudWatch API数も増えていきます(当然ですが・・・)。

yes-another-cloudwatch-exporterについて

前述が長くなりましたが、そこで弊社では yet-another-cloudwatch-exporter(後述YACE) へ置き換えることでコストを削減させています。

ivx/yet-another-cloudwatch-exporter
Improving the Prometheus exporter for Amazon CloudWatch

YACEの良い点

  • GetMetricData で複数のメトリックを1度のAPIコールで取得してくれる

  • exporter内でDimensionを取得してGetMetricDataを実行してくれるので設定ファイルがシンプル

discovery:
  exportedTagsOnMetrics:
    ec2:
      - Name
    ebs:
      - VolumeId
  jobs:
  - type: es
    regions:
      - eu-west-1
    searchTags:
      - Key: type
        Value: ^(easteregg|k8s)$
    metrics:
      - name: FreeStorageSpace
        statistics:
        - Sum
        period: 600
        length: 60
  • Prometheusからのscrapeによりメトリックを取得するのではなくProxyとして動作するため、Prometheus側で30秒おきとしても直近に取得(デフォルトでは5分おきにYACEがメトリックを取得する)したものを返す

  • クロスアカウントに対応しており、1つのNamespaceに対して複数のアカウント分の設定ができる

  jobs:
    - type: ecs-svc
      regions:
        - eu-north-1
      roleArns:
        - "arn:aws:iam::111111111111:role/prometheus" # newspaper
        - "arn:aws:iam:2222222222222:role/prometheus" # radio
        - "arn:aws:iam:3333333333333:role/prometheus" # television
      metrics:
        - name: MemoryReservation
          statistics:
            - Average
            - Minimum
            - Maximum
          period: 600
          length: 600

YACEのおかげでAPI数が大幅に減らすことができ、コストも半分以下にまで下げることができました。

おわりに

現時点ではNamespaceに制限があるため、すべてのサービスのメトリックが取得できないようです。
また、GetMetricDataを使用しているのは Auto-discovery のJobだけで Static のJobではGetMetricStatisticsが使用されるので注意が必要です。
CloudWatchのコスト増・APIスロットリングでお悩みの場合に選択肢の1つとしてアリかと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda 関数のログをLambda Extensionsを利用してS3に出してみた

はじめに

AWS Lambda関数を実装して動かすとログはCloudWatch Logsに出力されます。長期的で安価な保存や、ログデータを利用してAmazon Athena を利用した解析やAmazon Quick Sightを利用した可視化に使いたいと考えられる方もいるかもしれません。その場合、最終的にAmaonz Simple Storage Service(Amazon S3)に格納することが第一歩になるかと思います。その後、S3のライフサイクルポリシーの活用や、各種サービスへの連携が可能となります。CloudWatch Logsに出力されたログデータはSubscription Filterを利用してAWS Lambda経由で別の場所にExportし保存することができますが、当記事では2020年に登場したLambda ExtensionsのExternal extensions の機能を利用してAmazon S3にログを出力する方法について試した結果を共有したいと思います。なお、今回はGitHubのAWS Samplesを動かしてみた結果をもとに書いた記事となります。

Internal extensionsについては、こちらで解説がされていますのでご確認ください。

サマリ

  • AWS Lambda Logs APIを利用することでLambda 関数のログを制御可能
  • ログの種類は、Platform/Function/Extensionsの三種類があり任意のログを処理可能
  • Lambda Extensionsのサンプルを利用すると容易にS3にログ出力が可能(Production用ではない)

AWS Lambdaのログ

AWS Lambda が出力するログ

AWS Lambda関数を実行したときに出力されるログについて整理します。

種類 概要 出力のための実装
開始 Lambda 関数の実行開始を示すログ 不要。自動出力
終了 Lambda 関数の実行完了を示すログ 不要。自動出力
レポート Lambda 関数の実行完了後に使用した時間、リソース等を出力するログ 不要。自動出力
アプリケーション 関数内で標準出力・標準エラーに出力されたログ コーディングが必要

サンプルコード

app.py
import json

def lambda_handler(event, context):
    # TODO implement
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

CloudWatch Logsに出力されたログ

以下の画像にある通り、AWS Lambdaでは、アプリケーション開発者がログを出力するコーディングを一切しなくても1リクエストごとに3つのログが出力されます。
3つのログに加えて、アプリケーション内で出力する記載をするとログがさらに出力されます。

AWS Lambda関数から出力される標準出力のログは全てCloudWatch Logsに出力されます。
CloudWatch Logsに出力するためには、AWS Lambda 関数が CloudWatch Logsに出力する権限が必要となります。CloudWatch Logsに出力する権限を持っていないと以下のログは一切出力されません。

image.png

Lambda Extensionsを利用したログの出力先制御

Lambda Extensions

Lambda Extensionsは、既に利用中のモニタリング、オブザーバビリティ、セキュリティ、ガバナンス用ツールとLambdaとの統合を簡単にする新しい方法で、Lambda Extensionsの動作については、こちらのブログをご確認ください。なお、最大10個のExtensionsを登録可能です。

Extension API

Extension APIを使うことで、開発者はAWS Lambda 関数の実行環境のライフサイクル(Initフェーズ, Invokeフェーズ, Shutdownフェーズ)に合わせて処理を実行することが可能となります。
Extension APiは、AWS Lambda Serviceから呼び出されるAPIの一つで、それ以外には、Runtime APIとLogs APIがあります。当記事ではLogs APIについても触れます。

Extension APIには、二種類の動作モードがあり、詳細はAWS ドキュメントで解説されていますが、簡単にまとめてみました。

Internal extensions /External extensions

種類 Internal extensions External extensions
実行時のプロセス ランタイププロセスの一部として動く。つまり、Lambda 関数と同一プロセス内で実行 Lambda 関数のランタイムとは別個のプロセスを実行
使用するリソース 関数と同一実行環境を利用するためCPU/Memory等のリソースはLambda関数と共有する。 同左
初期化タイミング Lambda 関数と同じタイミングで初期化。Invoke時に呼び出す関数を登録。Shutdownはランタイムの中で自動的に呼ばれるため登録不要 Lambda 関数の初期化よりも前に初期化の呼び出し。Invoke/Shutdown時に呼び出す関数を登録することに加え、ランタイムの初期化が動く前に実施したい処理を組み込み可能(例:常駐プロセスを起動する等の処理など)
起動タイミング 実装された関数のInvokeよりも前に起動 ランタイムや実装された関数よりも前に起動
実装方法 利用するライブラリや他の依存関係を内包したZIPアーカイブである Lambda レイヤーとしてデプロイ。 同左

AWS Lambda 関数の実行環境のライフサイクル

前述した通り、三段階の実行フェーズがあります。

Init

Initは、Lambda ServiceからExtensions APIを通して呼び出され、External extensionsの初期化が最初に動き、次にRuntime APIが呼び出されの初期化が行われ、その中で、Internal Extensionsの初期化がされます。その後、関数の初期化がされます。Lambda は実行環境の /opt ディレクトリextensionsを含むLambda Layerを抽出します。Lambda は /opt/extensions/ ディレクトリ内のextensionsを探し、各ファイルをextensionsを起動するための実行可能なブートストラップとして解釈します。つまり、extensionsフォルダ内の実行ファイルをLambda Layerとして準備しておくことで、自動的に起動されます。

Invoke

InvokeはLambda関数の呼び出しとともに、Internal/ External のそれぞれのExtensionsがInitで登録した機能が呼び出されます。ExternalはRuntimeとは別のプロセスで動き、並行して動作します。Internal extensionsはRuntime、つまり関数と同じプロセスで動きます。

Shutdown

Lambda 関数が終了する際には、まず、Runtimeの終了処理が呼び出され、その後、Extensionsの終了処理が呼び出されます。なお、終了処理の実行時間には制限があります。AWSドキュメントをご確認ください。

AWS Lambda Logs API

Lambda extensionsでは、AWS Lambda Logs APIを利用して関数の実行環境のログストリームをサブスクライブしてログを処理(別のリソースへのログの転送、ログの変換、その他の処理)をすることが可能です。Kinesis Firehose経由でS3に出力することや他のAWSサービスや外部サービスにも転送可能です。つまり、任意の宛先にログを送りたい場合は、AWS Lambda extensionsを利用し、そこで、AWS Lambda Logs APIを利用することで、当記事ので実現したいことが可能となります。

ログの種類

Logs APIを利用することで以下の3種類のログのログストリームをサブスクライブすることができます。なお、サブスクライブはInitフェーズで実施する必要があります。初期化が完了した後では、サブスクライブ処理は受け付けられません。また、1つのExtensionsからのサブスクライブリクエストは冪等です。つまり、1つのExtensioからは重複した複数のサブスクライブはできない仕様のようです。

  • 標準出力・標準エラーのログ
  • START, END, REPORT ログ。Lambda のプラットフォームログ
  • Lambda Extensionsログ

Logs APIの利用時に必要なこと

Logs APIの詳細はAWS Lambda Logs APIに記載されています。必要なこととしては、Logs APIを利用してログを受け取るプロトコル(HTTP/TCP)やログをバッファリングするために利用するメモリの最適化のためのバッファリング設定などとなります。

利用したLambda Extenstionsのサンプル

今回は、GitHubのAWS Samplesにあるs3-logs-extension-demo-zip-archiveを利用して動作確認しました。サンプル構成は以下の通りです。サンプルを利用するだけのため構成手順は簡易ですが、私が検証したときには、READMEから漏れている手順があり、chmod +x extensions/logs_api_http_extension.py を実行せずデプロイすると以下のエラーログが出ます。

image.png

構築手順
git clone https://github.com/aws-samples/aws-lambda-extensions
cd s3-logs-extension-demo/
chmod +x extensions/logs_api_http_extension.py
sam build
sam deploy --stack-name s3-logs-extension-demo --guided

なお、画像のログは、Platformのログ(START,End)とExtensionのログが出ています。
image.png

権限エラーを解消することで、以下のログがでます。なお、sample sample sampleは私が print 文でログを出したものです。

lambda_function.py
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
# SPDX-License-Identifier: MIT-0

import json
import os

def lambda_handler(event, context):
    print(f"Function: Logging something which logging extension will send to S3")
    print(f"sample sample sample ")
    return {
        'statusCode': 200,
        'body': json.dumps('Hello from Lambda!')
    }

image.png

実装概説

まず、このサンプルは本番環境用で利用する場合は十分にテストし、必要な改善を実施する必要があります。(本番環境用ではないと書かれてます)

今回のサンプルでは、extensionssrc以下がLambda Layerとしてデプロイされるように構成されており、直下にlogs_api_http_extension.pyがあります。
前述した通り、Init時にLambdaはこのプログラムを実行します。

Initフェーズで実行する以下の内容を実装しています。

  1. Extension APIを利用してInvoke時とShutdown時のイベントを受け取るように登録する
  2. Logs APIを利用したLog Streamのサブスクリプション(受け取るポート、受け取るログの種類(Platform/Function)、バッファリング構成)
  3. HTTPサーバを起動し、Logs APIのサブスクリプションを受け取れる準備をする(今回は4243ポートをListen)
  4. Extensionsとして、Invoke/Shutdownへ登録(実際には、単にnextを呼び出しているだけでInvoke/Shutdown時に特に処理は何もしていない)
  5. 無限ループでキューに格納されたログをS3に出力する処理 #### Invoke/Shutdown 特に何もしていません。今回は、Logs APIをSubscribeした際に登録したポート(HTTP ServerでListenしているポート)にログデータが送信されてきたとき、キューにデータを格納し、順次ログを出力するように実装しています。Invoke/Shutdownで独自処理をしているわけではないため何も実装・実行されないです。

まとめ

AWS Lambdaのログは今までCloudWatch Logsに出すことが標準でしたが、これからは、Lambda Extensionsを利用しLogs APIを利用することで任意の場所に"も"ログを出力できるようになります。
独立したプロセスとして並行で動かす必要がないケース、例えば、Lamnbda関数に対して関数実行前後に処理を組み込みたい場合はExternal extensionsだけではなく、Internal Extensionsを利用することも可能です。
ぜひ、サンプルを利用して検証してみてください。
なお、その分、実行時間、リソース消費は発生しますのでご注意ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

aws lambdaで画像版のweb魚拓作成

これは何?

  • 例えばサイトの画像をクロールしたときに、画像にアクセスする期限がある場合、クロールできてもいずれアクセスできなくなります。このような場合、awsのs3などに保存してそちらを参照したいところです。このソリューションとしてawsのlambdaを選び、簡単かつ汎用的に実現してみました。
  • Web魚拓というサービスがありますが、その画像版のような挙動を目標にします。すなわち元画像が存在する場合はそちらを表示しつつ、s3にキャッシュ作成。画像がない場合でs3にキャッシュがある場合はキャッシュ表示。元画像が存在せず、キャッシュもない場合は404という感じです。ソースはこれ https://github.com/takundao71/lambda_fish_print_image/blob/master/lambda_function.rb
  • 悪意ある人に外部からたくさんアクセスされて、s3に大量に画像が作成されないようにするため、簡単な認証をつけてあります。認証つけなくても参照はできるけど、保存は認証が必要という感じにしました

手順

以下コマンドでupload.zipを作ってください。ソースコードは見ればわかるので見てくださいw 説明しませんw 上で書いてある通りです。なおzipはコマンドにある通り、lambda_function.rb vendorが対象です。cloneしたフォルダ自体をzipすると階層おかしくなって、lambda動かなくなります。これで地味にハマりました。。このあたりはaws、クソわかりにくい。。

rbenv local 2.7.1(不要かも)
bundle install --path vendor/bundle
zip -r upload.zip lambda_function.rb vendor
  • upload.zipをうp
    Screenshot 2020-12-07 11.43.37.png

  • 続いて環境変数の設定
    Screenshot 2020-12-07 11.46.19.png

  • トリガーでAPI GATEWAY追加します。レスポンスで画像返すので、バイナリーメディアタイプを/にしてください
    Screenshot 2020-12-07 11.48.58.png

なんとこれで完了なので、テストする

  • railsコンソールなどで対象の画像urlをurlエンコード
CGI.escape "https://upload.wikimedia.org/wikipedia/commons/c/c9/Moon.jpg"
  • 認証のためのdigest作成
digest_head_key = "head_key_dayo"
data = "#{digest_head_key}:#{Time.now.utc.strftime("%Y%m%d")}"
digest_hex = Digest::SHA1.hexdigest(data)
  • エンドポイントにくくりつけてアクセス!(さっき作成したAPI GATEWAYのエンドポイントです)
{APIのエンドポイント}/default/fish_print_image?image_url=https%3A%2F%2Fupload.wikimedia.org%2Fwikipedia%2Fcommons%2Fc%2Fc9%2FMoon.jpg&d={上の認証digest}
  • 画像表示されて、s3に保存されてたら完了。これで画像が消えても、このurlを参照すれば、表示され続けるということです、やったねw Screenshot 2020-12-07 11.51.48.png
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS CDKでAWS SAMのテンプレートを出力してデプロイする

はじめに

これまではSAMのテンプレートをYMLでがりがり書いていましたけど、定義したいリソースが増えてくるとYMLファイルが長くなり、作業効率がよくありません。作業効率を向上させるために、AWS CDKを使ってテンプレートを書こうと思い、CDKからSAMテンプレートを出力し、デプロイできることを確認してみました。

環境について

本記事は下記環境で実行しています。
- AWS SAM CLI version 0.39.0
- AWS CDK 1.76.0 (build c207717)
- typescript Version 4.1.2
- VSCode バージョン 1.51.1
- Docker Desktop 2.1.0.5
- macOS Catalina バージョン 10.15.7

手順

それでは、CDKからSAMテンプレートを作成する手順です

CDK

CDKのプロジェクトを作成します

% mkdir adventcalander2020; cd adventcalander2020
% cdk init --language=typescript
  • cdk initを実行する時にディレクトリが空じゃないと怒られます

CDKのバージョンが古い時は最新版にしておきます

これはやってもやらなくてもよいですが、CDKは頻繁にバージョンがアップしますので、新しいプロジェクトの時は最新を使ったほうがよいと思います。最新にする時にはnpm-check-updatesを使っています。

% ncu -u
(出力は割愛)
% npm install
(出力は割愛)
% which cdk
./node_modules/.bin/cdk  <-- ローカルの.binが先になるようにパスをきっています
% cdk --version
1.76.0 (build c207717)  <-- この記事を書いている時の最新版

Lambda 関数を用意します

今回はAPIGatewayからのリクエストに対してレスポンスを返すLambda関数を用意しました。なので、超シンプルです

app.py
import json

def lambda_handler(event, context):
    return {
        'statusCode': 200,
        'isBase64Encoded': False,
        'headers': {
            'Content-Type': 'application/json'
        },
        'body': json.dumps({
            'message': 'Hello World.',
        }),
    }

CDKを使ってSAMのテンプレートを記述します

コーディング中に必要となるモジュールはインストールします

npm install -s @aws-cdk/aws-sam

CDKのstackはこちらです。CDKのコードを書き終えたらcdk lsで認識されていることを確認して、cdk synthで出力します.

lib/adventcalander2020-stack.ts
import * as cdk from '@aws-cdk/core';
import * as sam from '@aws-cdk/aws-sam';
import { Role, ServicePrincipal, ManagedPolicy } from '@aws-cdk/aws-iam';

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

    const environment = this.node.tryGetContext('env')

    // The code that defines your stack goes here
    const lambdaRole = new Role(
      this,
      'lambdaRole',{
        assumedBy: new ServicePrincipal("lambda.amazonaws.com"),
        managedPolicies: [
          ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSLambdaBasicExecutionRole')
        ],
        path: "/lambda/",
      }
    );
    const lambdaFunctionHelloWorld = new sam.CfnFunction(
      this,
      'lambdaFunctionHelloWorld',{
        codeUri: "lambda/HelloWorld/src",
        functionName: `${environment}-HelloWorld`,
        handler: 'app.lambda_handler',
        runtime: 'python3.8',
        role: lambdaRole.roleArn,
        autoPublishAlias:`${environment}`,
        events:{
          GetMethod:{
            type: "Api",
            properties:{
              path: "/helloworld",
              method: "GET",
            }
          }
        }
      }
    );

  }
}

% cdk ls
Adventcalander2020Stack
% cdk synth > template.yml 
(出力は省略)

これでSAMテンプレートが出力されます。

SAM

テンプレートが用意できたらsamの出番です。
Lambdaから応答があるかどうかの確認をします。事前にDockerを起動しておきます。

% sam local start-api
Mounting lambdaFunctionHelloWorld at http://127.0.0.1:3000/helloworld [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-12-05 06:15:03  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

これで起動できたので、curlPostmanでAPIをコールします。

% curl http://127.0.0.1:3000/helloworld 

{"message": "Hello World."}%               

Lambdaが動作していることが確認できたのでdeployします。

sam deploy --guided

初回のdeployは--guidedを付けて実行すると設定がsamconfig.tomlというファイルに保存されるのでよいと思います。
無事にdeployできたらAWSコンソールでURLを調べて、呼び出してみます。

curl https://******.execute-api.ap-northeast-1.amazonaws.com/Prod/helloworld
{"message": "Hello World."}%

はい、無事に呼び出せませたね。

パラメータはCDKとSAMのどちらで吸収するのがいいのか?

実運用すると、環境は異なるけど1つのテンプレートで運用することが多いと思います。その場合、環境別のパラメーターをどうするのか?という問題が出てくると思います。

CDKの場合

いろんな方法があると思いますが、私の場合はcdk.jsonに環境別のパラメータを記述し、それを読み込む方法をよく使います。

cdk.json
{
  "app": "npx ts-node bin/cdk.ts",
  "context": {
   "stg":{
     "BucketName":"stgbucket"
    },
    "prod":{
     "BucketName":"prodbucket"
    }
  }
}

bin/adventcalander2020.ts
const app = new cdk.App();
const environment: string = app.node.tryGetContext('env')
const params = app.node.tryGetContext(environment)

このようにparamsで読み込んで、コンストラクターに渡してあげて、下のように使います。

lib/adventcalander2020.ts
const contentBucket = new s3.Bucket(this, "ContentBucket", {
     bucketName: `${props.BucketName}`,
})

cdkのコマンドを実行する時には、cdk synth -c env=prodとすれば、cdk.jsonの内容を読み込んでくれます。

SAMの場合

SAMでパラメータを渡したい場合は、CDKではパラメータは読み込まず、CloudFormationの組み込み関数である、Fn::RefFn::Subのままテンプレートを出力できるようにします。
CloudFormationパラメータをCDKで以下のように宣言します。

lib/adventcalander2020.ts
    const environment = new cdk.CfnParameter(this, 'environment',{
      type: 'String',
    });

宣言をされたパラメータは、以下のように使います。

lib/adventcalander2020.ts
        functionName: cdk.Fn.sub(`\${environment}-HelloWorld`),
        handler: 'app.lambda_handler',
        runtime: 'python3.8',
        role: lambdaRole.roleArn,
        autoPublishAlias: cdk.Fn.ref('environment'),

これで出力してみると、CloudFormationの組み込む関数のままテンプレートを出力できています。

template.yml
Parameters:
  environment:
    Type: String
(省略)
      AutoPublishAlias:
        Ref: environment
      FunctionName:
        Fn::Sub: ${environment}-HelloWorld

どっちがいいの?

どっちでも結果は同じなので、どっちを選択してもよいですが、実際の開発になると複数人で作業することになるし、CI/CDをどうするのかという話も絡んでくるので、チームの中でやりやすい方法でよいかと思います。

最後に

SAMのテンプレートもCDKで出力できることを確認できました。
SAMテンプレートの中で、Lambda関数を複数定義したい時には、TypeScriptのループ処理、関数やクラスを使えばとんでもなく長くなるCloudFormationのYAMLから開放されます。また、VSCodeのコード補完が使えるメリットがあります。
CDKのSAMには、High-level Constructsは無く、Low-level Constructsしかありませんが、メリットだけでデメリットは無いと思いますので、がんがんCDKを使ってSAMテンプレートを出力していきたいと思います。

参考にしたほうがよいサイト

CDKやSAMを使ったことがない場合は、以下のサイトを参考にするといいと思います。

  • AWS サービス別資料
    • このページにある、AWS Serverless Application ModelAWS Cloud Development Kit (CDK)はまず最初に読みましょう。
  • CDK workshop
    • はじめてCDKを触るときにはこのworkshopをやるとよいと思います。
  • aws-samples/aws-cdk-examples
    • CDKのサンプルがたくさんありますので参考になります。

:christmas_tree: FORK Advent Calendar 2020
PREV :arrow_backward: canvasとJSで、目に優しいオリジナルテトリス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambda Internal Extensions で Lambdaのチューニングに役立つ情報を取得する

Lambda Extensionsについて

AWSのblogで丁寧な説明がありますので、Extensionsそのものの詳細な説明は本稿からは割愛します。それでも、話の流れで必要な部分は少しずつ紹介しながら進めます。

AWS blog
仕組み

AWS Lambda Extensions (プレビュー) のご紹介でも書かれている通り、Extensionsはinternalとexternal二つのモードで実行することができます。

Internal extensionsは、利用者のコードと同じランタイムプロセスの一部として実行されます。ランタイムプロセスの起動を言語固有の環境変数とラッパースクリプトを用いて変更できます。Internal extensionsを用いて、コードの自動計測などのユースケースを実現することができます。

External extensionsは、対象のLambda関数のランタイムプロセスと同じ実行環境内の、別のプロセスで動作させることができます。External extensionsは、呼び出し前にシークレットを取得したり、関数呼び出しの外にいるカスタムな送信先へと測定情報を送信したりするようなユースケースで使用できます。こういったExtensionsはLambda関数に付随するプロセスとして実行されます。

そして、AWSの各種ブログではExternal extensionsの説明に 多くの項を割いていて、Internal Extensionsはほんの少ししか登場しない。

有名なこの図もExternal Extensionsのものになります。

extensions

External Extensionsはもちろん自作もできますが、AWSのMonitoringやSecurity系パートナーが提供するLambda Layerを組み込むだけですぐにLambdaを拡張することが可能になっています。(blogの中で、ローンチ時に利用可能なAWS Lambda Readyパートナーextensionとして紹介がありますのでご参考ください)

非常に便利ですね。

しかし本稿では、このExternal Extensionsではなく、あえてInternal Extensionsに光を当てて、その使い道を探っていきます。とくにAWS Lambdaのチューニングについてこんな使い方が出来ますよというご紹介にしていきたいと思います。

Internal Extensions

External Extensionsがサイドカー的に、Lambda実行環境にプロセスをアドオンしていたのに対し、Internal ExtensionsはHandlerが動くプロセスと同じプロセスで実行されるため、主としてRuntimeの拡張用途で使われることが多いです。

※AWSの公式Docにもランタイム環境の変更の方法が詳しく書かれているので合わせてそちらも是非お読みください。

言語固有の環境変数

Lambdaは、以下のように言語固有の環境変数を通じて、関数の初期化中に変化を加えることができるような設定をサポートしています。

言語 環境変数 説明
Java JAVA_TOOL_OPTIONS Java 11 および Java 8 (java8.al2) では、Lambda はこの環境変数をサポートし、Lambda で追加のコマンドライン変数を設定します。この環境変数では、ツールの初期化を指定できます。具体的には、agentlib または javaagent オプションを使用して、ネイティブまたは Java プログラミング言語エージェントの起動を指定できます。
Node.js NODE_OPTIONS Node.js 10x 以降では、Lambda はこの環境変数をサポートします。
.NET DOTNET_STARTUP_HOOKS .NET Core 3.1 以降では、Lambda はこの環境変数によって提供されるアセンブリ (dll) へのパスを使用できます。

Lambdaのチューニング

AWS Lambda 関数を使用するためのベストプラクティスにも詳しく書かれていますが、
Lambdaのチューニングにはそのスコープごとに3つの視点でみて行く必要があります。

  1. 他のAWSサービスとの統合方法やシステム要件などの俯瞰したアーキテクチャ的な視点
    • SQSを用いた非同期実装
    • EventBridgeを用いたイベントドリブン実装
    • StepFunctionsを用いたワークフロー構築
    • 継続的改善のための可観測性向上、X-Ray, CloudWatch Metrics(Logs), External Extensions
    • フロントキャッシュ
    • etc.
  2. AWS Lambdaサービス機能としてのチューニング
    • Provisioned Concurrencyによる暖機
    • Memory設定
    • VPC設定
    • Containerの利用
    • EFSの利用
    • etc.
  3. プログラミングコードとしてのチューニング
    • ランタイム言語ごとのプラクティス
    • Handler外のキャッシュ、再利用
    • 遅延が懸念される3rd Party APIの同期呼び出しを避ける
    • Lazy Loading (Pythonの場合、Lazy Importing)
    • etc.

このチューニングの中で、特に今回よく使われる技法として、PythonのLazy Importingについて着目し、Lambdaとの関わり方や、どのようにチューニングするかをみていきます。

Python での Lazy Importing

従来、Lazy Importingを行うには2つの方法がありました。

  1. ローカルインポートによる対処
  2. LazyLoaderによる対処

最初の方法で、まず思いつく方法は、グローバルインポートではなくローカルインポートを実行する方法です。(つまり、モジュールのグローバル部分ではなくHandler関数内で必要な場合にだけインポート)。これは、インポートするモジュールを実際に必要とするコードを実行するまでインポートを遅延するように機能します。しかし、同じインポートステートメントを何度も作成しなければならないという欠点があります。

また、個々のモジュール内でローカルインポートを行うと(あるいは開発者が複数の場合)、グローバールインポートを回避しようとしていたライブラリが何かを忘れて、不本意にグローバルにインポートしてしまうこともよくあります。

したがって、このアプローチは機能しますが、実装者に注意が必要です。(実装者の責務)

2つ目が、LazyLoaderによる対処方法です。

このLazyLoaderクラスを使うと、Importingテスト実施のオーバーヘッドを最小限に抑えることができます。ローカルインポートに対するLazyLoaderの利点は、モジュールが属性にアクセスするまで、モジュールのローディングを延期してくれます。(LazyLoaderの責務)

なるほど。これらの方法でLazy Loadingが実装できたので"よかったよかった"なのですが本当にLazy Loadingされているかを確認する方法ってどうしたら良いでしょうか。

課題感
  • Pythonコードの中でグローバルインポート/ローカルインポートしている部分をログから追跡/確認したい
  • 実際にimportされている部分のロード時間を計測したい

この課題に対処するアプローチがPythonのruntimeオプションである implementation-specific optionになります。

話しがここまでたどり着くのに長くなってしまったのですが、これまでのLambdaはこのRuntime Optionに触れることが難しかったのですが、Internal Extensionsを利用すると簡単に設定できるようになります。(本稿で言いたいのはこの部分でした。)

Python の implementation-specific option

さまざまな実装固有のオプションとして、以下のように定義されています。 (CPythonでは、次の可能な値を定義)

    -X faulthandler: enable faulthandler
         -X showrefcount: output the total reference count and number of used
             memory blocks when the program finishes or after each statement in the
             interactive interpreter. This only works on debug builds
         -X tracemalloc: start tracing Python memory allocations using the
             tracemalloc module. By default, only the most recent frame is stored in a
             traceback of a trace. Use -X tracemalloc=NFRAME to start tracing with a
             traceback limit of NFRAME frames
         -X showalloccount: output the total count of allocated objects for each
             type when the program finishes. This only works when Python was built with
             COUNT_ALLOCS defined
         -X importtime: show how long each import takes. It shows module name,
             cumulative time (including nested imports) and self time (excluding
             nested imports). Note that its output may be broken in multi-threaded
             application. Typical usage is python3 -X importtime -c 'import asyncio'
         -X dev: enable CPython’s “development mode”, introducing additional runtime
             checks which are too expensive to be enabled by default. Effect of the
             developer mode:
                * Add default warning filter, as -W default
                * Install debug hooks on memory allocators: see the PyMem_SetupDebugHooks() C function
                * Enable the faulthandler module to dump the Python traceback on a crash
                * Enable asyncio debug mode
                * Set the dev_mode attribute of sys.flags to True
                * io.IOBase destructor logs close() exceptions
         -X utf8: enable UTF-8 mode for operating system interfaces, overriding the default
             locale-aware mode. -X utf8=0 explicitly disables UTF-8 mode (even when it would
             otherwise activate automatically)
         -X pycache_prefix=PATH: enable writing .pyc files to a parallel tree rooted at the
             given directory instead of to the code tree

この中で今回利用するのは、-X importtime になります。

各インポートにかかる時間を示します。モジュール名が表示され、累積ロード時間(ネストされたインポートを含む)と自己ロード時間(ネストされたインポート時間は除外)。
※計測時間出力の精度はマルチスレッド計測なので、不正値も含まれることに注意してください。

Python 3.8 でのラッパースクリプトの作成と使用

Python 3.8 でのラッパースクリプトの作成と使用について、公式Docにのも掲載されていますので、合わせてご覧ください。

利用手順はいたってシンプルです。

  1. SAM templateを用意
  2. メインのLambda関数実装
  3. LayerとしてWrapper Scriptを用意
  4. Lambda関数のDeploy

今回、Layerの中にimport用のモジュールの定義しました。

実装の説明
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  python-wrapper

Globals:
  Function:
    Timeout: 20

Resources:
  PythonWrapperFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: src/
      Handler: app.lambda_handler
      Runtime: python3.8
      Layers:
        - !Ref PythonWrapperLayer
      Environment:
        Variables:
          AWS_LAMBDA_EXEC_WRAPPER: "/opt/importtime_wrapper"  #スクリプトのファイルシステムパス

  PythonWrapperLayer:
      Type: AWS::Serverless::LayerVersion
      Properties:
          LayerName: python-wrapper-layer
          Description: Dependencies for the Function
          ContentUri: layer/
          CompatibleRuntimes:
            - python3.8
          LicenseInfo: 'MIT'
          RetentionPolicy: Retain

基本的なSAMの構文説明は割愛しますが、Internal Extensionsスクリプトを指定するには、実行可能バイナリまたはスクリプトのファイルシステムパスとして AWS_LAMBDA_EXEC_WRAPPER 環境変数の値を設定します。

全体的な階層は以下の通り

├── layer
│   ├── importtime_wrapper
│   └── python
│       └── util
│           ├── __init__.py
│           ├── inside.py
│           └── outside.py
├── src
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
└── template.yaml

inside.pyとoutside.pyはそれぞれローカルとグローバルインポートする予定のモジュールです。(本来はInternal Extensionsと別のLayerで管理すべきですが簡単のために同居させています。)

LayerのそれぞれのPythonモジュールはechoするだけの機能しか持っていません。

注意点としてimporttime_wrapperにはchmod +xしておくのを忘れないようにしましょう。

importtime_wrapperの実装は以下のとおり。

  #!/bin/bash

  # the path to the interpreter and all of the originally intended arguments
  args=("$@")

  # the extra options to pass to the interpreter
  extra_args=("-X" "importtime")

  # insert the extra options
  args=("${args[@]:0:$#-1}" "${extra_args[@]}" "${args[@]: -1}")

  # start the runtime with the extra options
  exec "${args[@]}"

extra_args=("-X" "importtime") にて、implementation-specific optionを設定しています。
ここを他のさまざまなオプションに切り替えることも出来ますので、是非試してみてください。

Handler関数は、グローバルおよびローカルでimportを実装しています。

import json
from util import outside

outside.echo('this is loaded outside')

def lambda_handler(event, context):

    from util import inside
    inside.echo('this is loaded inside')

    return {
        "statusCode": 200,
        "body": json.dumps({
            "message": "hello world"
        }),
    }
実行してみる

デプロイしたLambdaをTest実行してみました。

START RequestId: e949850e-1435-473b-a852-a368dc23a358 Version: $LATEST
import time: self [us] | cumulative | imported package
import time:       749 |        749 | _frozen_importlib_external
import time:       293 |        293 |   time
import time:       342 |        635 | zipimport
import time:        82 |         82 |     _codecs
import time:      1121 |       1203 |   codecs
import time:       637 |        637 |   encodings.aliases
import time:      1464 |       3302 | encodings
import time:       297 |        297 | encodings.utf_8
import time:       191 |        191 | _signal
import time:       374 |        374 | encodings.latin_1
import time:        55 |         55 |     _abc
import time:       413 |        467 |   abc
import time:       577 |       1043 | io
import time:        79 |         79 |       _stat
import time:       621 |        699 |     stat
import time:      1420 |       1420 |     _collections_abc
import time:       246 |        246 |       genericpath
import time:       422 |        668 |     posixpath
import time:       913 |       3699 |   os
import time:       287 |        287 |   _sitebuiltins
import time:        52 |         52 |   pwd
import time:       307 |        307 |   sitecustomize
import time:       104 |        104 |   usercustomize
import time:      1146 |       5593 | site
import time:       388 |        388 |         types
import time:       868 |       1255 |       enum
import time:        69 |         69 |         _sre
import time:       368 |        368 |           sre_constants
import time:       635 |       1003 |         sre_parse
import time:       442 |       1513 |       sre_compile
import time:        75 |         75 |             _operator
import time:       422 |        497 |           operator
import time:       298 |        298 |           keyword
import time:       341 |        341 |             _heapq
import time:       315 |        655 |           heapq
import time:       220 |        220 |           itertools
import time:       309 |        309 |           reprlib
import time:       106 |        106 |           _collections
import time:      1991 |       4073 |         collections
import time:        76 |         76 |         _functools
import time:       924 |       5071 |       functools
import time:       225 |        225 |       _locale
import time:       275 |        275 |       copyreg
import time:       961 |       9298 |     re
import time:       415 |        415 |       _json
import time:       714 |       1128 |     json.scanner
import time:       628 |      11053 |   json.decoder
import time:       679 |        679 |   json.encoder
import time:       898 |      12629 | json
import time:       256 |        256 |         token
import time:      1217 |       1472 |       tokenize
import time:       347 |       1819 |     linecache
import time:       488 |       2307 |   traceback
import time:       462 |        462 |   warnings
import time:       353 |        353 |     _weakrefset
import time:       803 |       1156 |   weakref
import time:       292 |        292 |   collections.abc
import time:        49 |         49 |     _string
import time:      1005 |       1054 |   string
import time:       845 |        845 |   threading
import time:        49 |         49 |   atexit
import time:      3321 |       9483 | logging
import time:      1196 |       1196 |     http
import time:       438 |        438 |       email
import time:       569 |        569 |         email.errors
import time:       671 |        671 |             binascii
import time:       451 |        451 |             email.quoprimime
import time:       434 |        434 |                   _struct
import time:       260 |        693 |                 struct
import time:       880 |       1572 |               base64
import time:       329 |       1901 |             email.base64mime
import time:       233 |        233 |                 quopri
import time:       207 |        439 |               email.encoders
import time:       387 |        825 |             email.charset
import time:      1101 |       4947 |           email.header
import time:       453 |        453 |               math
import time:       283 |        283 |                 _bisect
import time:       297 |        580 |               bisect
import time:       414 |        414 |               _sha512
import time:       407 |        407 |               _random
import time:      1435 |       3287 |             random
import time:       652 |        652 |               _socket
import time:       422 |        422 |                 select
import time:       866 |       1287 |               selectors
import time:       118 |        118 |               errno
import time:      2574 |       4629 |             socket
import time:       595 |        595 |               _datetime
import time:      1333 |       1928 |             datetime
import time:       543 |        543 |               urllib
import time:      1276 |       1818 |             urllib.parse
import time:      1128 |       1128 |                 locale
import time:      1660 |       2788 |               calendar
import time:       465 |       3252 |             email._parseaddr
import time:       840 |      15752 |           email.utils
import time:       509 |      21207 |         email._policybase
import time:       879 |      22654 |       email.feedparser
import time:       445 |      23536 |     email.parser
import time:       324 |        324 |       uu
import time:       465 |        465 |       email._encoded_words
import time:       246 |        246 |       email.iterators
import time:      1005 |       2039 |     email.message
import time:     10632 |      10632 |       _ssl
import time:      4103 |      14735 |     ssl
import time:      1423 |      42928 |   http.client
import time:      3408 |       3408 |   rapid_client
import time:       746 |        746 |       lambda_internal
import time:       260 |        260 |       __future__
import time:       534 |        534 |           numbers
import time:      1405 |       1939 |         _decimal
import time:       315 |       2253 |       decimal
import time:       344 |        344 |       lambda_internal.simplejson.errors
import time:       225 |        225 |       lambda_internal.simplejson.raw_json
import time:       373 |        373 |           importlib
import time:       203 |        576 |         lambda_internal.simplejson.compat
import time:        89 |         89 |               simplejson
import time:       119 |        208 |             simplejson.raw_json
import time:       482 |        689 |           lambda_internal.simplejson._speedups
import time:       346 |       1034 |         lambda_internal.simplejson.scanner
import time:        71 |         71 |             simplejson
import time:        21 |         92 |           simplejson.raw_json
import time:       130 |        221 |         lambda_internal.simplejson._speedups
import time:       445 |       2275 |       lambda_internal.simplejson.decoder
import time:        74 |         74 |             simplejson
import time:        26 |         99 |           simplejson.raw_json
import time:       106 |        204 |         lambda_internal.simplejson._speedups
import time:       760 |        964 |       lambda_internal.simplejson.encoder
import time:       645 |       7708 |     lambda_internal.simplejson
import time:       464 |        464 |     lambda_runtime_exception
import time:       900 |       9071 |   lambda_runtime_marshaller
import time:      2398 |      57804 | lambda_runtime_client
import time:       258 |        258 |   importlib.machinery
import time:       629 |        629 |     importlib.abc
import time:       841 |        841 |     contextlib
import time:       494 |       1963 |   importlib.util
import time:       488 |       2708 | imp
import time:       266 |        266 | util
import time:       271 |        271 | util.outside
this is loaded outside
import time:       234 |        234 | util.inside
this is loaded inside
END RequestId: e949850e-1435-473b-a852-a368dc23a358
REPORT RequestId: e949850e-1435-473b-a852-a368dc23a358  Duration: 1.21 ms   Billed Duration: 2 ms   Memory Size: 128 MB Max Memory Used: 51 MB  Init Duration: 124.62 ms

ネストされたロード時間がマイクロ秒単位でロードモジュールごとに列挙されています。自身のロード時間(μs)そして累計のネストロード時間(μs)が表形式で出力されています。また、import packageのカラムには適切なインデントが施されているためネストが見やすくなっています。

最後の方の出力で

(snip)

import time:       266 |        266 | util
import time:       271 |        271 | util.outside
this is loaded outside
import time:       234 |        234 | util.inside
this is loaded inside
END RequestId: e949850e-1435-473b-a852-a368dc23a358

(snip)

this is loaded outside より上がグローバルにロードされたモジュールで、そこからthis is loaded insideまでがローカルに(Handler内で)ロードされたモジュールです。

モジュールごとのロード時間とネストの深さ、累計のロード時間がグローバルおよびローカルに分類されて計測されているためPythonコードのチューニングポイントがさらに分かりやすくなったのではないでしょうか?

Provisioned Concurrency と Lazy Loading の関係について

いったんここまででInternal Extensionsの話は終わりですが、ちょっと補足としてProvisioned ConcurrencyとLazy Loadingの関係について説明しておきます。

Provisioned ConcurrencyはLambda関数インスタンスのColdStart対策など(他にも効用はありますが)で用いられる暖機機能です。

※Provisioned Concurrency自体は別のblogに LambdaのProvisioned Concurrencyと1年付き合ってみて思ったことという内容で説明を書きましたので参照ください。

Provisioned Concurrencyは事前にLambda関数を暖機することができるので、あえてLazy Loadingにしなくても Provisioningフェーズでimport仕切ってしまえばよいという実装パターンがよく用いられます。

1st

こちらの資料でも、importはHandler外で実装することがプラクティスとして挙げられています。このようにProvisioned ConcurrencyのCapacityを超えない(spill overしない)リクエスト量であったり、もしくはApplication Auto ScalingでLambdaへのリクエスト増加に応じてProvisioned Concurrencyが適切にスケールするような設計をしている場合は、あえてLazy Loadingしないという考え方も取れるということに注意ください。

まとめ

本稿では、あまり脚光を浴びていなかった Lambda Internal Extensions をピックアップしその利用価値について説明しました。
Layerで導入できることから、チューニングフェーズのみ取り込んでみて、あとから外しておくこともできます。とても便利な機能ですのでぜひ皆さんのサービス開発にご活用ください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSサンプルプロジェクトから学ぶサーバレスアーキテクチャ

構築してみるAWSサーバレス

PARONYM Advent Calendar 2020 - Qiita の7日目です。

はじめに

昨今注目を浴びているサーバレスアーキテクチャ。うまく使えばサーバの構築・運用・保守の手間が省けエンジニアはサービスの実装に注力することができます。
しかし便利な反面新たに学ばなくてはならないことが沢山あり、学びはじめのエンジニアは何からはじめたらいいか悩ましいところです。
本記事では、AWS公式で作成されたサンプルプロジェクトを使用してサーバレスアーキテクチャを構築し、サーバレスの学習のきっかけになることを目的としています。

注意

サンプルプロジェクトをAWSにデプロイすると料金が発生する場合があります。デプロイは自己責任でお願いします。

本記事で使用するサンプルプロジェクト

AWSには公式から提供される便利なサンプルプロジェクトがあります。今回はその一つを使用して実際にクラウド上に一つのサービスを展開してみます。

aws-samples/lambda-refarch-webapp

使用するサービス

このサンプルプロジェクトは上記らのサービスを使用して簡単なTODOアプリを構築していきます。
デプロイには以下のツールをします。

デプロイの方法は、README.mdにも記載されています。

フロント

ビルドで生成される全ての静的リソース(HTML、JS、CSSなど)はAWS Amplify Consoleを用いてデプロイされます。

バックエンド

バックエンドはAmazon API GatewayAWS Lambdaを用いたREST APIを提供します。
データの保存場所としては、Amazon DynamoDBを使用しています。

認証

Amazon Cognitoのユーザプールを利用して認証機能を提供している。

前提条件

  • Githubアカウントを所持していること
  • dockerがインストールされていること
  • AWSアカウントを所持していること
  • AWS CLIがインストールされていること
  • SAMがインストールされていること

準備

aws-samples/lambda-refarch-webapp
上記リポジトリを自身のGithubアカウントへForkします。
これは、Amplifyコンソール用にGithubでパーソナルアクセストークンを作成し、デプロイでGitHubリポジトリのURLを指定する必要があるためです。

パーソナルトークンは以下のページを参照して作成してください。
個人アクセストークンを使用する - GitHub Docs

トークンは後で使うため、保存しておいてください。

デプロイ

まず環境変数を設定します。

$ export AWS_DEFAULT_REGION=<リージョン 例:ap-northeast-1>
$ export STACK_NAME=<CloudFormation stack名 ※ユニークである必要あり>

デプロイ実行

$ ./deploy.sh

実行中に入力を求められるが、AWS RegionParameter RepositoryParameter OauthToken以外はデフォルトの値で問題ありません。

  • AWS Region : リージョン
  • Parameter Repository : GithubのリポジトリURL
  • Parameter OauthToken : 準備で作成したパーソナルアクセストークン
〜〜省略〜〜
Configuring SAM deploy
======================

    Looking for config file [samconfig.toml] :  Found
    Reading default arguments  :  Success

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [serverless]:
    AWS Region [ap-northeast-1]: ap-northeast-1
    Parameter VersionParam [v1]:
    Parameter StageNameParam [prod]:
    Parameter CognitoDomainName [mytodoappdemo]:
    Parameter Repository []: <リポジトリURL>
    Parameter Branch [master]:
    Parameter OauthToken: <パーソナルアクセストークン>
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [y/N]:
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]:
    Save arguments to configuration file [Y/n]:
    SAM configuration file [samconfig.toml]:
    SAM configuration environment [default]:
〜〜省略〜〜

デプロイが完了したら、AWSコンソールにログインしてAmplifyからフロントのソースコードをデプロイします。

スクリーンショット 2020-12-07 1.23.53.png

ビルドが完了したら、URLにアクセスし動作確認してみます。

スクリーンショット 2020-12-07 1.28.21.png

以下の画像がデプロイされたTODOアプリの画面です。

スクリーンショット 2020-12-07 1.28.29.png

削除

使用しなくなったアプリは削除することが鉄則です。
AWS上に作成されたリソースを削除するにはcloudformationのコマンドを使用して削除します。

aws cloudformation delete-stack \
--stack-name $STACK_NAME

忘れがちがCloudWatchのログ・グループも削除しておきます。

for log_group in $(aws logs describe-log-groups --log-group-name-prefix '/aws/lambda/'$STACK_NAME --query "logGroups[*].logGroupName" --output text); do
  echo "Removing log group ${log_group}..."
  aws logs delete-log-group --log-group-name ${log_group}
  echo
done

まとめ

一口にサーバレスといっても、複数のクラウドサービスを併用しないと一つのアプリケーションを構築することはできません。
AWSのサーバレスの代表サービスといえばLambdaですが、今回のサンプルプロジェクトの様に一つのサービスを構築するためには各サービスを併用して処理を分散し構築していく必要があります。

今後プロダクトでサーバレスを利用していくのであればこれらサービスのベストプラクティスを学習し、適切に運用していく必要があります。
本記事で作成したサンプルプロジェクトは削除しても再度同じコマンドを実行すればデプロイされるため、是非何度でも構築・削除を繰り返し、動いているコードを学び、自身が携わっているプロダクトの役に立ててください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gitaly ClusterでGitLabのGitalyを冗長化させる

はじめに

GitLabは複数のコンポーネントから構成されていますが、その中でもGitalyはGitリポジトリへのアクセス処理を担っています。GitLab 13からはGitaly Clusterが提供されるようになり、Gitリポジトリの冗長化が可能となりました。

それ以前もNFSをストレージとして使用して可用性を上げることはできていましたが、公式ページによるとGitLab 13からNFSは非推奨となり、GitLab 14からはサポート外となるようです。

今回はGitaly ClusterをKubernetes上に構築する手順を説明します。

Gitaly Clusterについて

Gitaly ClusterはGitalyのクラスタ化を実現させたもので、PraefectというGitalyへのルーティングとトランザクション処理を行うコンポーネントを利用します。また、PraefectにはロードバランサとDBをデプロイする必要があります。Kubernetesの場合、ロードバランサはServiceになります。

image.png

Gitaly Clusterは以下の機能があります。

  • レプリカごとの高い整合性
  • 自動フェイルオーバー
  • データロス検知後のリポジトリへのready only設定

構築

環境はEKS 1.16を使用し、Helm 3.4.0でデプロイします。GitLab Helm Chart 4.5.3、ALB Ingress Controller Helm Chart 0.1.11を利用しています。

GitLabのデプロイ

GitLab Helm Chartのvaluesは以下のようにします。RDSのPostgreSQL DBを事前にPraefectのために作成しておきglobal.praefect.psql.hostに設定します。バージョンは11以上でないといけないことに注意しましょう。

my-values.yaml
global:
  edition: ce
  hosts:
    https: false
    gitlab:
      name: XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com
      https: false
  ingress:
    configureCertmanager: false
    enabled: false
    tls:
      enabled: false
  praefect:
    enabled: true
    gitalyReplicas: 3
    psql:
      host: YYYYYYYYYY.ap-northeast-1.rds.amazonaws.com
      port: 5432
      user: praefect
      dbName: praefect
      sslMode: "disable"
upgradeCheck:
  enabled: false
certmanager:
  install: false
nginx-ingress:
  enabled: false
prometheus:
  install: false
registry:
  enabled: false
gitlab-runner:
  install: false
gitlab:
  gitlab-exporter:
    enabled: false
  gitlab-shell:
    enabled: false
  praefect:
    replicas: 3
  sidekiq:
    registry:
      enabled: false
  task-runner:
    enabled: false
    registry:
      enabled: false
  webservice:
    registry:
      enabled: false
    service:
      type: NodePort
    ingress:
      enabled: false
$ helm install --name test-gitlab \
  -f my-values.yaml \
  gitlab/gitlab --version 4.5.3

global.hosts.gitlab.nameにはALBのエンドポイントを設定しています。ALB Ingress Controllerをデプロイしておき、以下のIngressでALBを自動作成します。gitlab.webservice.service.typeNodePortになっているのもそのためです。

ingress.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  annotations:
    alb.ingress.kubernetes.io/scheme: internet-facing
    kubernetes.io/ingress.class: alb
  name: alb-ingress
spec:
  rules:
  - http:
      paths:
       - backend:
           serviceName: test-gitlab-webservice
           servicePort: 8181
       - backend:
           serviceName: test-gitlab-webservice 
           servicePort: 8080
         path: /admin/sidekiq
$ kubectl apply -f ingress.yaml

以上を行うだけではPraefect PodはRunningになりません。Praefect DBの設定を手動で行う必要があります。

Praefect DBの設定

HelmでGitLabをデプロイした際、Praefect DBのパスワードがSecretとして作成されるので以下のコマンドで確認します。

$ kubectl get secret test-gitlab-praefect-dbsecret \
  -o jsonpath='{.data.secret}' \
  | base64 --decode

確認したパスワードを用いて、事前に作成したRDSのPostgreSQL DBに以下の設定を入れます。

$ psql -h YYYYYYYYYY.ap-northeast-1.rds.amazonaws.com \
  -p 5432 \
  -U postgres \
  -d template1
> CREATE ROLE praefect WITH LOGIN;
> \password praefect
Enter new password:
Enter it again:
> GRANT praefect TO postgres;
> CREATE DATABASE praefect WITH OWNER praefect;

Podの確認

実際にPodを確認します。PraefectとGitalyが複数存在していることが分かります。

$ kubectl get pod
NAME                                               READY   STATUS      RESTARTS   AGE
test-gitlab-gitaly-0                               1/1     Running     0          3h24m
test-gitlab-gitaly-1                               1/1     Running     0          3h24m
test-gitlab-gitaly-2                               1/1     Running     0          3h24m
test-gitlab-migrations-2-kn8sj                     0/1     Completed   0          3h18m
test-gitlab-minio-666d6596c-kpfzx                  1/1     Running     0          3h24m
test-gitlab-minio-create-buckets-2-psqds           0/1     Completed   0          3h18m
test-gitlab-postgresql-0                           2/2     Running     0          3h24m
test-gitlab-praefect-0                             1/1     Running     0          3h20m
test-gitlab-praefect-1                             1/1     Running     0          3h20m
test-gitlab-praefect-2                             1/1     Running     0          3h20m
test-gitlab-redis-master-0                         2/2     Running     0          3h24m
test-gitlab-sidekiq-all-in-1-v1-76489f4f45-4ql2m   1/1     Running     0          3h18m
test-gitlab-webservice-6db7f4bcd8-jgh57            2/2     Running     0          3h18m
test-gitlab-webservice-6db7f4bcd8-qrcrr            2/2     Running     0          3h18m

動作確認

実際にアクセスして動作を確認していきます。

image.png

rootでログインしてプロジェクトを作成してみます。

screencapture-d7f12bb1-updatetest-albing-6622-1463059363-ap-northeast-1-elb-amazonaws-2020-12-06-19_09_33.png

testという名前でプロジェクトを作成することにします。

screencapture-d7f12bb1-updatetest-albing-6622-1463059363-ap-northeast-1-elb-amazonaws-projects-new-2020-12-06-19_13_56.png

実際に作成されました。これでGitaly Clusterの動作確認は完了です。

image.png

まとめ

GitLabのGitリポジトリへのアクセス処理を行うGitalyを冗長化させるためのGitaly ClusterをEKS上に構築する方法を説明しました。

今回はGitalyにフォーカスしていたため別コンポーネントには特に触れていませんが、可用性をさらに上げる場合にはminioをS3、アプリ用のpostgresql DBをRDS、redisをElastiCache等で代替すると良いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンテナイメージを使用してAWS Lambda関数を作成する

この記事は ユニークビジョン株式会社 Advent Calendar 2020 の7日目の記事です。

1. はじめに

この記事ではAWS re:Invent 2020で発表された AWS Lambda の新機能 – コンテナイメージのサポート についてまとめます。

従来、AWS Lambdaの関数を作成するためには関数の実行に必要なものをZIP形式でパッケージ化する必要がありました。
個人的にはローカルで動作確認をする構成とZIP形式のパッケージを作る手順が存在しているのは好みではありませんでしたが、この新機能によって開発時・本番運用時の両方とも同じコンテナイメージを使用することが出来るようになりました。

また、従来のZIP形式ではデプロイパッケージのサイズが250MBに制限されており、例えばTensorFlowなどを使用した推論処理のためにAWS Lambdaを使用するのは困難でした。それがこの新機能では最大10GBのコンテナイメージに対応するということでAWS Lambdaの用途が広がったと感じています。

そこで、本記事では

  1. 最小限のコンテナイメージを使用した関数
  2. TensorFlowを使用して画像分類を行う関数

を作成しながら利用方法を確認します。

2. Hello, AWS Lambda with Conrtainer

まずは最小限の構成で動くLambda関数を作成します。
この章では

  1. 作成する関数を構成する要素の紹介
  2. ローカルでの動作確認
  3. デプロイ

について取り扱います。

2.1. 構成

ディレクトリ構成は以下の通りです。

(プロジェクトルートディレクトリ)/
├── Dockerfile
└── app.py

この章で作成する関数はDockerfileが肝になります。
Create an image from an AWS base image for Lambda」の手順に従って、Lambda用のベースイメージに app.py をコピーするだけの簡単な内容になっています。

Dockerfile
FROM public.ecr.aws/lambda/python:3.8

COPY app.py ${LAMBDA_TASK_ROOT}

CMD [ "app.handler" ]

app.py が実際に実行される関数になります。
今回は冒頭のAWS Lambda の新機能 – コンテナイメージのサポートに登場する、Pythonのバージョンを返すだけの処理としています。

app.py
import sys
def handler(event, context): 
    return 'Hello from AWS Lambda using Python' + sys.version + '!'

2.2. ローカルでの動作確認

ローカルでの動作確認は上で作成したDockerfileをビルドして実行するだけで良いです。
プロジェクトのルートディレクトリにて

$ docker build -t {イメージ名} .

を実行してイメージを作成し、

$ docker run --rm -p 9000:8080 {イメージ名}

で実行します。ポート番号は任意のもので良いです。
この時点で、/2015-03-31/functions/function/invocationsというパスで関数を実行することが出来るようになっているので

$ curl -d '{}' http://localhost:9000/2015-03-31/functions/function/invocations
"Hello from AWS Lambda using Python3.8.6 (default, Nov 26 2020, 14:33:53) \n[GCC 7.3.1 20180712 (Red Hat 7.3.1-9)]!"

のようにすることで動作確認を行うことが出来ます。
意図した通り、Pythonのバージョンが返っていることが分かります。

また、コンテナのログには以下の通り、本当にLambdaを実行した際のログに近い内容が出力されています。

START RequestId: 81e030ca-0657-40e5-a80f-c8eb23f522cd Version: $LATEST
END RequestId: 81e030ca-0657-40e5-a80f-c8eb23f522cd
REPORT RequestId: 81e030ca-0657-40e5-a80f-c8eb23f522cd  Duration: 1.20 ms       Billed Duration: 100 ms    Memory Size: 3008 MB    Max Memory Used: 3008 MB

2.3. デプロイ

デプロイの大まかな流れは以下の通りです。

  1. ECRに先ほど作成したイメージをプッシュ
  2. 関数を作成

2.3.1 イメージのプッシュ

本記事ではECRへのプッシュは主題ではないので手順を割愛します。以下のリンクの通りに進めれば困ることはないかと思います。
https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-push-ecr-image.html

2.3.2. 関数を作成

AWSマネジメントコンソールにて関数を作成すると以下の画面が表示されます。

関数の作成オプションで「コンテナイメージ」を選択し、関数名とコンテナイメージURIを入力すると関数が出来ます。

image.png

コンテナイメージURIの入力は「画像を参照」ボタンを押下するとこでECRからイメージを選択することが出来ます。

「Browse images」の日本語訳がバグっていますがこれはご愛敬ですね。

image.png

この手順で作成した関数は無事、動作することが確認できました。

image.png

3. TensorFlowを使用して画像分類をする関数を作成する

この章では、実用的な関数の例としてS3に画像をアップロードしたことをトリガーに画像分類する関数を作成します。
従来のZIP形式でのデプロイでは「AWS LambdaでTensorFlow 2.0を使った画像分類」という記事で扱われているように多くのことを考慮して作成する必要がありますが、コンテナイメージを使用すると前章とほぼ同じ手順で関数を作成することが出来ます。

3.1. 構成

本章の関数のディレクトリ構成は以下の通りです。

├── Dockerfile
└── app
    ├── app.py
    ├── model.h5
    └── requirements.txt

まず、画像分類を行うため、TensorFlowの学習済みモデルが追加されています。
今回は以下のプログラムで、学習済みモデルをダウンロードしています。

from tensorflow.keras.applications.mobilenet_v2 import MobileNetV2

model = MobileNetV2(weights='imagenet')
model.save('model.h5', include_optimizer=False)

もう一点、TensorFlowなどのパッケージをインストールする必要があるため、 requirements.txtを追加しています。
今回はTensorFlowでの推論とS3へのアクセスを行うため、それに必要なものを記述しています。

requirements.txt
boto3
tensorflow
pillow

Dockerfileには pip installを追加しています。
一応、ビルド時にキャッシュが利用できるように requirements.txtだけを先にコピーしています。

Dockerfile
FROM public.ecr.aws/lambda/python:3.8

COPY app/requirements.txt ${LAMBDA_TASK_ROOT}
RUN pip install -r requirements.txt

COPY app ${LAMBDA_TASK_ROOT}

CMD [ "app.handler" ]

app.pyは当然大きく内容が変わっています。処理内容は簡単に、

  1. handlerの引数、eventからアップロードされたファイルの情報を取得
  2. S3から上記ファイルをダウンロード
  3. 画像を読み込んで推論の前処理を実施
  4. 推論

という感じです。
本体は4. の後に結果を保存したりすると思いますが、今回は標準出力に出すだけとしています。

app.py
import boto3
import tensorflow as tf
from tensorflow.keras.applications.mobilenet_v2 import (decode_predictions,
                                                        preprocess_input)
from tensorflow.keras.preprocessing.image import img_to_array, load_img

s3_client = boto3.client('s3')
model = tf.keras.models.load_model('./model.h5', compile=False)


def handler(event, context):
    try:
        for record in event['Records']:
            # アップロードされた画像をダウンロード
            bucket = record['s3']['bucket']['name']
            key = record['s3']['object']['key']
            download_path = '/tmp/target_image'
            s3_client.download_file(bucket, key, download_path)

            # 画像を読み込んで前処理
            img = load_img(download_path, target_size=(224, 224))
            img = img_to_array(img)
            img = img[tf.newaxis, ...]
            img = preprocess_input(img)

            # 推論
            predict = model.predict(img)
            result = decode_predictions(predict, top=5)
            print(result)

        return 'Success'

    except Exception as e:
        print(e)
        return 'Failure'

3.2. ローカルでの動作確認

この関数はS3へのアクセスが必要となるため、以下の通り環境変数を渡してDockerを起動します。

docker run --rm \
  -p 9000:8080 \
  -e AWS_ACCESS_KEY_ID={アクセスキー} \
  -e AWS_SECRET_ACCESS_KEY={シークレットキー} \
  -e AWS_DEFAULT_REGION={リージョン} \
  {イメージ名}

関数の実行ではS3へのPUTを模したデータを送信します。

curl -d '{"Records": [{"s3": {"bucket": {"name": "バケット名"}, "object": {"key": "ファイル名"}}}]}' \
http://localhost:9000/2015-03-31/functions/function/invocations

3.3. デプロイ

この関数のデプロイ方法は本記事の主題からは離れるため割愛しますが、

  1. 前章の通りイメージをプッシュ
  2. 関数を作成
  3. S3の当該バケットへのアクセス権限を設定
  4. 関数のトリガーを設定

のようにすることでS3に画像をアップロードすると推論を実施する関数が作成できます。

4. まとめ

本記事では2つの関数を作成しながらAWS Lambdaの新機能を確認しました。
個人的には従来通りの使用感で用途が広がっており、ローカルでの動作確認もしやすいので良い機能だと感じています。

今回は手動でデプロイを行いましたが、SAMやCDKなどを使用してデプロイが行えればすぐにでも実用可能な機能かなと思います。
次はデプロイ用のツールも含めて技術検証が行えたらと考えています。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む