前書き
仲間と一緒に、カツカツで起業活動を行っています。
AWSを使用しているため、最近の円安により頭を悩ませていました。
どうにかランニングコストを下げるため、行った一部の施策を記録します。
使用する技術は以下の通りです。。
言語 | Typescript | |||
---|---|---|---|---|
フレームワーク | tRPC | Express | Turborepo | Prisma |
インフラ・ミドルウェア | Docker | AWS | CDK | PostgreSQL |
既存構成
リポジト管理はモノレポ
形式を採用してます、モノレポの簡単な説明は下記に載せます。
モノレポとは、アプリケーションやマイクロサービスの全コードを単一のリポジトリに保存するパターン。
共用ライブラリはpackages
フォルダに集約し、各アプリケーションはapps
フォルダに配置しています。
例えば、ログ機能はpackages
フォルダに置き、各アプリケーションから利用できるようにしています。デプロイ時には、Turborepo の機能で効率よくコンテナ化され、最適な状態で展開されます。
インフラ構成は、以下の図に示すように、ベーシックな構成になっています。
サービスのアクセス数がまだ少ないため、サーバーレス化を決定しました。
ただし、サーバーレス化はあくまで暫定的な対処です。今後、ユーザー数が増加した場合には、コストを抑えるためにECSへ戻すことも考慮しています。
サーバーレス化
domain層のパッケージ化
正直、「domain層」という命名が正しいのか不安はありますが、
フロントエンドからパラメーターを受け取り、最初に処理を行う層として意識しています。
DB処理(infra層)のコードは最初からパッケージ化されているため、controller層が変更されてもアプリ全体への影響は少なく済みます。
この構造にすれば、今後移行作業が発生しても、人力コストをある程度削減できるはずです。
ALB + ECSからAPIGatway + Lambdaへ変更
変更前はExpress
を採用してましたが、よく調べてみたら、tRPC
はAWS APIGatway
をサポートしてるらしく、公式ドキュメント通りにtRPC
使って置き換えました。
ただし、通常の.zipファイルを使用したLambda
デプロイではサイズ制限に引っかかってしまったため、Express採用時のDockerfileを改造して運用しました。
.zipの場合、解凍後サイズ250MBが上限ですが、イメージの場合は10GBです。
使用するDockerfileの一部抜粋を以下に載せておきます。
Turborepo流儀の通常ステージビルド
...
FROM public.ecr.aws/lambda/nodejs:20
COPY --from=installer --chown=app:nodejs /app/pruned .
COPY --from=builder --chown=app:nodejs /app/apps/serverless-api/dist .
ENV NODE_ENV=production
CMD [ "index.handler" ]
サーバーレス化されたイメージのECRへのプッシュと別に、LambdaのデプロイにはCDKを採用しています。
使用したcdkのコードの抜粋も載せておきます。
import {
aws_iam as iam,
aws_lambda as lambda,
aws_apigateway as apigateway,
Stack,
StackProps,
} from "aws-cdk-lib";
import { Handler, Runtime } from 'aws-cdk-lib/aws-lambda';
import { Construct } from "constructs";
import * as path from "path";
export class FormationStack extends Stack {
constructor(scope: Construct, id: string, props?: StackProps) {
super(scope, id, props);
... ecr、vpc、rdsなど
const tRpcLambda = new lambda.Function(this, "TRpcLambda", {
runtime: Runtime.FROM_IMAGE,
handler: Handler.FROM_IMAGE,
code: lambda.Code.fromEcrImage(ecrRepo, {
tagOrDigest: 'latest',
}),
vpc,
role: lambdaRole,
environment: {
NODE_ENV: "production",
DATABASE_URL: "",
},
});
dbSecret.grantRead(tRpcLambda);
const api = new apigateway.RestApi(this, "TRpcApi", {
restApiName: "TRpcApi",
description: "TRpcApi",
endpointTypes: [apigateway.EndpointType.EDGE],
deployOptions: { stageName: "v1" },
});
const apiResource = api.root.addResource("api");
const anyResource = apiResource.addResource("{path+}");
anyResource.addMethod("any", new apigateway.LambdaIntegration(tRpcLambda))
}
}
コスト削減の効果
単純計算すると2.93 + 5.14 = 8.07%となります。
しかし、無料利用枠や従量課金制があるとはいえ、API Gateway と Lambda の構成も無料ではありません。
そのため、実際のコスト削減率はさらに少ないかもしれません
今後もコストに気を配りながら開発していきたいです。
終わりに
今は若干円高に戻りましたが、2024年7月10日前後には約161円になり、
サーバー代だけで倒産しそうになって冷や汗をかきました
コスト削減したことは無駄にはならないと思います、
コストを低く抑えられれば、サービスも長続きし、成功する可能性も上がると思います
欲を言えば、インフラをcloudflare
利用すればもう少し安価にできたのかもしれません、今度試してみたいです。