本記事は、Ragate 株式会社の AWS 開発業務で使用される AWS の設計ガイドラインです。
本記事はAWSのWell-ArchitectedのServerless Applications Lensに則り定義されており、当社の AWS 開発業務に関わる各位が閲覧する事を前提とした開発ドキュメントとなります。
※ 但しお客様がすでに同様のガイドラインを所有している場合はそちらに従います
※ お客様の所有するガイドラインに懸念がある場合はプロジェクト初期段階に設計ガイドラインを慎重にすり合わせます(当社の AWS 開発プロジェクトは開発内製化を前提としているため)
コンピュートレイヤーには極力、従来の AWS EC2 のようなホットスタンバイする製品ではなく、サーバーレス製品の提案を目指します。ビジネス要件を要件定義フェーズで慎重にヒアリングし、サーバーレス製品が要件にフィットすると判断した場合は、サーバーレス製品による各メリット(従量課金制によるコスト最適化、保守運用の簡素化等々)の説明と提案に努めてください。
ただし、注意したいのはサーバーレスの製品は銀の弾丸ではないということです。例えば以下のような、サーバーレス製品の弱みを理解しお客様への説明に努め、適切に提案することが重要です。
要件がサーバーレス製品にフィットしそうなら、コンピュートレイヤーの設計は以下戦略をベースとしてお客様へ提案を行います。
AWS コンピュート製品 | 製品説明 | 戦略 (主なユースケース) |
Lambda | Lambdaの概要 | – NodeJS の実行 ( TypeScript のデプロイ) – APIGateway との統合 – AppSync との統合 – DynamoDB Stream との統合 – その他 AWS 上でのイベント駆動実行 |
API Gateway | API Gatewayとは? | – サーバーレスな RestAPI サーバ―の構築 |
AppSync | AppSyncの概要 | – サーバーレスな Graphql サーバ―の構築 |
Step Functions | Step Functionsとは? | – ビジネスクリティカルな処理の実行 – 複雑なワークロードの管理 |
サーバーレスアーキテクチャーにメリットはいずれのビジネスでも重宝されるものばかりですが、デメリットも理解の上、提案・導入するように努めてください。
データレイヤーはシステム内の永続ストレージを扱い、ビジネスロジックが必要とする状態を保存するための安全なメカニズムの提供を目指します。尚、データ変更に応じたイベントトリガーのメカニズムも含め提供します。
データレイヤーの設計者は、設計の原則として以下を確認してください。
以下は主に提案するデータレイヤーの製品です。
AWS データレイヤー製品 | 製品説明 | 戦略 (主なユースケース) |
DynamoDB | DynamoDB の概要 | – メインの DBMS – KVS によるデータ格納 – 検索要件に合わせたインデックス設計 – 低コストで高速な可用性高い DBMS – スタートアップから大規模まで対応可能 |
OpenSearch Serverless | OpenSearch の概要 | – 全文検索(形態素解析付き) – Nグラム検索 – コンシューマー向けの複雑な検索要件 – DynamoDB で対応できない検索ニーズに応える – DynamoDBStream との組み合わせ – ログの検索 |
DocumentDB | DocumentDB の概要 | – MongoDB の要件は DocumentDB で満たす – ドキュメント型のデータ管理 |
Neptune Serverless | Neptune Serverless の概要 | – グラフ型のワークロードを実行 – 縦軸の大規模な演算 |
Aurora Serverless | Aurora Serverless の概要 | – RDBMS 型の保持 |
S3 | S3の概要 | – バイナリー型データの保持 – 非常に高い可用性でバイナリーデータを管理 |
ここで大きな注意点としては、Aurora Serverless のコールドスタート(レイテンシー)問題です。事前に PoC 検証の段階を挟むなどしてワークロードに確実にフィットするかを慎重に検証してください。もし許容出来ないレイテンシーが発生するなら、通常のホットスタンバイするデータストアー(RDS)を使用するのも悪い選択肢ではありません。
メッセージングレイヤーは、コンポーネント間の通信を扱います。ストリーミングレイヤーは、ストリーミングデータのリアルタイム分析と処理を扱います。
AWS データレイヤー製品 | 製品説明 | 戦略 (主なユースケース) |
SNS | SNS の概要 | – マイクロサービスの非同期イベント通知 – サーバーレスアプリケーションの非同期イベント通知 – モバイルプッシュ通知 – pub/sub パターンのためのフルマネージドメッセージングサービス |
Kinesis ファミリー | Kinesis の概要 | – リアルタイムのストリーミング・データの収集 – リアルタイムのストリーミング・データの処理・分析 |
特に Kinesis はサーバーレス製品でありながら、 秒あたりにギガバイト単位のデータをストリーミング可能です。大量のインサイトの収集要件が上がれば、ぜひ積極的に提案しましょう。
ユーザー管理 / IDレイヤーは、ワークロードにおける外部・内部顧客の両方に ID、認証、および認可を定義します。
要件にフィットするなら、基本的には CognitoUserPool を提案・導入しましょう。
Amazon Cognito を使用すると、サーバーレスアプリケーションにユーザーサインアップ、サインイン、データ同期を簡単に追加できます。Amazon Cognito User Pools は、組み込みのサインイン画面と Facebook、Google、Amazon、および Security Assertion Markup Language (SAML) とのフェデレーションを提供します。更に、Amazon CognitoFederated Identities を使用すると、他の AWS リソースへのスコープ付きアクセスを安全に提供できます。
補足として、高い確率でお客様が独自に認証サーバー(IDP)を保有しているケースが多数見られますので、もし既存のIDPとの認証が必要なら、まずは Cognito と連携可能かを調査するのも一つです。Cognito と既存の IDP が連携できると、セッションタグを使用した安全な AWS リソースへのアクセスが可能となります。
またよくあるユースケースとして、ユーザー自身がプロフィール画像のアップロードをS3へ行う際に、ユーザーに対して、ユーザーIDを Prefix とした IAM ポリシーを動的に生成しユーザーにS3へのアップロードを許可するケースがあります。これらの一連の処理をフロントエンドで完結できるようにすると、API の実装コストが削減できます。
以下は、上記ユースケースの IAM ロールの一例です。
AuthenticatedRole:
Type: AWS::IAM::Role
Properties:
RoleName: 'authenticated-role'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Federated: cognito-identity.amazonaws.com
Action:
- sts:AssumeRoleWithWebIdentity
- sts:TagSession
Condition:
StringEquals:
cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
ForAnyValue:StringLike:
cognito-identity.amazonaws.com:amr: authenticated
Path: '/'
Policies:
- PolicyName: authenticated-role-policy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- 's3:ListBucket'
Resource:
- !GetAtt UserAssetBucket.Arn
Condition:
StringLike:
s3:prefix:
- !Join
- ''
- - '$'
- '{aws:PrincipalTag/username}'
- '/*' # 要件に合わせて権限を絞る
- Effect: Allow
Action:
- 's3:PutObject'
Resource:
- !Join
- ''
- - !GetAtt UserAssetBucket.Arn
- '/'
- '$'
- '{aws:PrincipalTag/username}'
- '/*' # 要件に合わせて権限を絞る
エッジレイヤーは、プレゼンテーションレイヤーと外部顧客との接続を管理します。エッジレイヤーは、地理的に異なる場所にいる外部顧客に効率的な配信方法を定義します。
特段、お客様から希望がなければCloudFront導入を提案してください。
ただし、Edge ロケーションでの高度なコンピュート処理(画像の圧縮等々、通信情報変換等々)が求められる要件が存在する場合は、CloudFlare 等々の外部サービスとの連携も視野に入れてください。結果として運用コスト及び開発工数が小さく抑えられる方法をきちんと検討・提案した上で、最終的なアーキテクチャーを決定してください。
システム監視レイヤーは、メトリクスを通じてシステムの可視性を管理し、ワークロードが時間とともにどのように動作し、どのように振る舞うかについて定義します。
AWS 開発におけるシステム監視は、お客様の構想されている運用体制と、ビジネス的にどのような運用監視が必要化を慎重にヒアリングした上で設計を行います。
AWS システム監視製品 | 製品説明 | 戦略 (主なユースケース) |
Amazon CloudWatch | Amazon CloudWatch とは | – 使用中の全 AWS サービスのメトリクスにアクセス – システムとアプリケーションレベルのログを統合 – 特定のニーズに合わせてカスタムメトリクスを定義 – ビジネス要件に合わせた KPI 作成 – ダッシュボードによる運用中の監視 – アラートを設定しプラットフォーム上で自動化されたアクションをトリガー |
AWS X-Ray | AWS X-Ray とは | – リクエストのエンドツーエンドを可視化 – パフォーマンスのボトルネックを特定 – サーバーレスアプリケーションの分析とデバッグ |
尚、上記のような監視ツールでの監視が行えない要件の場合は、独自に運用監視用のワークロードを Step Functionsや EventBridge を用いて構築することも視野に入れてください。
アプリケーション用の AWS アカウントとは別に、ログ収集用の AWS アカウントの構築及び設定を検討してください。
AWS ContorolTowerでは、ランディングゾーンセットアップすると、共有アカウントの 1 つとしてログアーカイブアカウントが作成されます。これは、すべてのログ (他のすべてのアカウントのログを含む) を一元的に収集する専用のアカウントです。これらのログファイルを参照することで、管理者と監査人は発生したアクションとイベントを確認できます。
AWS アカウントの初期設定段階では、AWS ContorolTowerの利用を検討してください。
基本的な運用要件は AWS の製品で満たすことが可能ですが、ダッシュボード GUI の洗練さを求める場合や、お客様の運用チームの持つ運用に対する経験・知見次第では、データドッグ等のツールの使用も検討してください。
尚、サードパーティ製品については、エンタープライズ割引等も受けれることもがありますので、お客様と共に価格交渉を極力行ってください。
当社は ServerlessFramework で開発を行うことが非常に多いです。そのため、CICD については ServerlessFramework社の製品でもある Serverless CI.CD の導入を検討してみてください。以下は CodePipeLine との比較です。
製品 | メリット | デメリット |
AWS Code シリーズ | – AWS の内部に閉じてデプロイできるのでセキュア – AWS の通知系サービスと統合可能 – AWS CloudFormation で管理可能 | – ServerlessFramework のデプロイを独自設定必要 – CodeBuild のビルドプロセス等でログを確認しなければ行けない – リポジトリ変更検知が少し遅い印象 |
Serverless CI.CD | – ServerlessFramework のデプロイ設定が基本的に不要 – ServerlessFramework のデプロイ進捗を見やすく可視化 – ServerlessFramework 社のサポートを一部受けることが可能 – デプロイ失敗の原因をダッシュボードで可視化 – 高速なリポジトリの変更検知 | – AWS の外部に強い IAM 権限を渡さなければいけない – 外資系サービスの為コンプライアンスと相談必須 – ServerlessFramework で開発していない場合は使用するメリットは無い |
マイクロサービスアーキテクチャにおけるデプロイのベストプラクティスは、変更がコンシューマのサービス契約を壊さないようにすることです。もし API オーナーがサービス契約を壊すような変更を行い、コンシューマーがそれに備えていない場合、障害が発生する可能性があります。
どのコンシューマが API を使用しているかを把握することは、デプロイメントを安全に行うための第一歩です。コンシューマとその使用状況に関するメタデータを収集することで、変更の影響についてデータ駆動型の意思決定を行うことができます。データの収集方法については、前述のシステム監視レイヤーをご覧ください。
デプロイ戦略については、特段お客様の方で案がない場合、サーバーレス構成なら以下のような戦略の提案してください。
環境 | デプロイ方法 | 詳細 |
開発環境 | オール・アット・ワンス・デプロイメント | オール・アット・ワンスのデプロイメントでは、既存の構成の上に一括で変更を加えます。このタイプのデプロイメントスタイルは開発者の労力が少なく、デプロイの疎通が容易に行なえます。 しかし、ロールバックに関してはリスクが増し、通常はダウンタイムが発生します。このデプロイメント・モデルは、開発などの非クリティカルな環境で、顧客への影響がリスクにならない場合に使用するようにしてください。 |
ステージング環境 | オール・アット・ワンス・デプロイメント | 同上 |
本番環境 | ブルー/グリーンの配備 | API を別のサブドメインにクローンし、そしてRoute53 等々でルーティングすることで、既存のコンシューマーに影響が及ばないようにします。フェイルオーバールーティング等が検討できます。 このアプローチは、ブルーグリーンのデプロイメントをシンプルにそして容易にしますが、二重の API(及びそれに伴うバックエンドインフラストラクチャ)を維持するためのオーバーヘッドが追加で必要になります。 コストの試算を行った上で、適切なデプロイ戦略を検討してください。 |
サーバーレスアーキテクチャーでは、OS・システムのパッチ適用やバイナリ更新といったインフラ管理タスクを AWS が行うため、従来の多くのセキュリティリスクに自動で対処してくれます。しかし、 非サーバーレスアーキテクチャと比較していくつかの攻撃から自動で守ってくれるものの、OWASP を始めとしたアプリケーション層のセキュリティ対策のベストプラクティスは、依然として開発者に求められます。
本セクションを確認し、サーバーレスアーキテクチャーで意識すべきセキュリティ事項を把握してください。
本記事では APIGateway を題材として API のアクセス制御について解説します。まず、APIGateway には現在5つの認証タイプがあります。
上記を必要に応じて API サーバーへ設定してください。
IAM 認証は、事前に API の利用者が確定している場合に有効です。(リクエスターがより厳格にきまっている場合は後述の Resource policies のユースケースセクションもご覧ください)
例えばお客様のパートナー企業が APIGateway へリクエストするケースを想定した場合、アクセスの都度CognitoUserPool による認証を実行してもらうのではなく、必要な権限をセットした IAM ロールを AssumeRole してもらい、トークンを使用し API Gateway へリクエストしてもらうようなアプローチが検討できます。IAM ロールの Pricipal と IAM ポリシーは常に最小範囲であることが求められますので注意してください。Principal の設定方法は、こちらをご覧ください。
コンシューマーを始めとした利用者が認証要求を行うユースケースに最適です。ユーザーは CognitoUserPool で認証後に、認証トークンを Auhtorization ヘッダーに設定することで、APIGateway へリクエストすることが可能です。尚、APIGateway 以外の AWS リソースへのアクセスは、ユーザーに CognitoUserPool 認証で得た認証トークンをCognitoID プールへリクエストしSTSトークンを取得させることで可能となります。
補足として、CognitoID プールで実現可能な認可(動的な IAM ポリシー生成)は、セッションタグまでとなります。より高度な認可設定を行う場合は、別途 API を用意し、セッションポリシーを生成し、ユーザーへ認可トークンを提供してください。セッションポリシーのユースケースとして、例えばユーザーが複数の組織に属しており、複数組織に対応する認可トークンを生成する必要があるなどが挙げられます。(1回のセッションポリシー生成処理におけるJSON オブジェクトのサイズには制限があるため注意してください)
お客様が独自に認証サーバーを運用している場合に有効な方法です。LambdaAuthorizer 上で、お客様の認証サーバーと連携しトークンの検証などを行う事が可能です。尚、LambdaAuthorizer の認可はデフォルトで300秒のキャッシュが有効化されているため、1回目のリクエストが疎通しても、実装方法次第では2回目のリクエストが疎通しなくなります。詳しくは以下の LambdaAuthorizer のソースコードを参考にしてください。
import middy from 'utils/middy';
import { APIGatewayAuthorizerResult, APIGatewayTokenAuthorizerEvent } from 'aws-lambda';
import logger from 'utils/logger';
export const handler = middy.handler(async (event: APIGatewayTokenAuthorizerEvent): Promise<APIGatewayAuthorizerResult> => {
const region = process.env.REGION as string;
const accountId = process.env.ACCOUNT_ID as string;
const apiId = process.env.API_GATEWAY_ID as string;
const stage = process.env.STAGE as string;
const methodArn = `arn:aws:execute-api:${region}:${accountId}:${apiId}/${stage}/*`;
// 以下のように実装するとキャッシュの有効期間(TTL)中は、同一APIにしかリクエストできなくなります
// const methodArn = event.methodArn;
try {
return generatePolicy('user', 'Allow', methodArn);
} catch (error) {
logger.error({
...(error as UnauthorizedError),
});
return generatePolicy('user', 'Deny', methodArn);
}
});
export const generatePolicy = (principalId: string, effect: string, resource: string): APIGatewayAuthorizerResult => {
const authResponse: APIGatewayAuthorizerResult = {
principalId: principalId,
policyDocument: {
Version: '2012-10-17',
Statement: [
{
Action: 'execute-api:Invoke',
Effect: effect,
Resource: resource,
},
],
},
};
return authResponse;
};
リクエスターが事前に特定されている場合に有効な方法です。リクエストクライアントの IP アドレスなどをもとに、アクセスを制限することが可能です。
リクエスターは特定の企業だけど、不特定多数が使用する可能性がある…場合は IAM 認証の使用を検討してください。
IoT デバイスやアプリケーション間認証のようなケースでは、相互 TLS(mTLS)認証を設定することができます。API Gateway のエンドポイントにアクセスする際に、クライアントは証明書を提示して身元を確認する必要があります。また、mTLS を Lambda オーソライザーと組み合わせることで、よりきめ細かな認証メカニズムを実現することも可能です。証明書を事前に発行し提供できるユースケースにおいては有効です。
Lambda 関数では、最小特権アクセスに従い、与えられた操作の実行に必要なアクセスのみを許可するようにしてください。必要以上の権限を持つロールを付けると、システムが悪用される可能性があります。
ただし、開発運用が大変になり工数に大きく影響のでる場合は、例えば AppSync のデータソースアクセスに対して以下のような IAM ロールを提供します。
※ ${self:custom.awsResourcePrefix}
には AWS リソースの Prefix が設定されます
# データソース=DynamoDB
AppSyncDynamoDBServiceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: '${self:custom.awsResourcePrefix}AppSyncDynamoRole'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service:
- 'appsync.amazonaws.com'
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: '${self:custom.awsResourcePrefix}AppSyncDynamoPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'dynamodb:PutItem'
- 'dynamodb:ConditionCheckItem'
Resource: !Sub 'arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${self:custom.awsResourcePrefix}*'
# データソース=Lambda
AppSyncLambdaServiceRole:
Type: 'AWS::IAM::Role'
Properties:
RoleName: '${self:custom.awsResourcePrefix}AppSyncLambdaRole'
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Principal:
Service:
- 'appsync.amazonaws.com'
Action:
- 'sts:AssumeRole'
Policies:
- PolicyName: '${self:custom.awsResourcePrefix}AppSyncLambdaPolicy'
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: 'Allow'
Action:
- 'lambda:invokeFunction'
Resource: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${env:AWS_RESOURCE_PRIFIX}*'
万が一オープンソースのモジュールに対し悪意のあるスクリプトが含まれていた場合に情報漏洩やクラッキングの恐れがあります。VPC 内の Lambda はセンシティブなデータと通信する可能性が高いため、常に最小の設定になるように努めてください。また、Lambda は基本的には PrivateSubnet に配置し、インターネットや AWS リソースとの通信は、NAT または VPC エンドポイントを使用し行ってください。(VPC エンドポイントの方が NAT よりもコストが安い為、要件にフィットするなら VPC エンドポイントを推奨)
サードパーティの API_KEY や SECRET_KEY 等々は、クラッキングは勿論の事、開発者自身にも見えないような工夫が必要です。プレーンテキストを SecretManager に暗号化して保持するか、あるいは KMS を使用して暗号化することを検討してください。
RDS Proxy の SecretManager との連携機能を使用して、RDS への問い合わせを行うように設計します。アンチパターンは、RDS のマスターパスワードをプレーンテキストで例えば Lambda の環境変数等々に保持することです。RDS Proxy は Lambda と RDS で直接通信した場合のコネクションプールリミットを防止してくれますので、Lambda×RDS環境で使わない手はありません。
尚、RDS のパスワードは確実にローテーションさせてください。データベースのパスワードの漏洩は死活問題と心得てください。
AWS の Well-Architected フレームワークに則り、以下の事項を意識の上設計を行ってください。
ファンクションは簡潔で、短く・単一目的に実装し、リクエストのライフサイクルに沿うものである。一つのLambda 関数に全ての処理をデプロイする行為は、マイクロアーキテクチャ―の原則からも外れ推奨されません。
また、トランザクションは効率的にコストを考慮するため、より速い開始が推奨されます。
サーバーレス構成はスロットリングのリスクを抱えます。そのため、サーバーレス・アプリケーションは同時実行モデルを活用し、設計段階で確実に同時実行数を試算してください。
実行環境と基盤となる Lambda 等のコンピュートリソースは短命であるため、一時ストレージなどのローカルリソース(例えば Lambda の/tmp 領域)は保証されません。
基盤となるインフラは AWS が運用しています。そのため、例えば Lambda の OS などは更新される可能性があります。ハードウェアに依存しないコードを実装してください。
アプリケーションのワークフローをオーケストレーションするために、コード内で Lambda 実行を Invoke すると、モノリシックで密結合なアプリケーションになってしまいます。 その代わりに、StepFunctions 等を活用し、ステートマシンを使ってトランザクションと通信フローをオーケストレーションしてください。
Amazon S3 へのオブジェクト書き込みや DynamoDB のアイテム変更イベントは、ビジネス機能に応じてトランザクションな実行を可能にします。この非同期イベントによる設計アプローチは、多くの場合コンシューマに依存せず、無駄のないサービス設計を実現できます。
リクエストやイベントからトリガーされるオペレーションは、失敗する可能性があり、与えられたリクエストやイベントが複数回配信される可能性があるため、冪等でなければなりません。
例えば SQS からメッセージをポーリングする Lambda 関数は、非同期実行においては冪等性を考慮した実装を行わなければいけません。
コンピュートリソースに不要なアクセス権限を与えないでください。常に必要最低限の権限を付与してください。
本記事では、AWS の設計〜セキュリティ考慮に関する実践的な内容をまとめました。AWS の開発プロジェクトに関わるヒトは以下の点も意識し日々業務に取り組んでみてください!
スモールスタート開発支援、サーバーレス・NoSQLのことなら
ラーゲイトまでご相談ください
低コスト、サーバーレスの
モダナイズ開発をご検討なら
下請け対応可能
Sler企業様からの依頼も歓迎