AWS CDKで構築するサーバレスMarkItDown Webアプリ ~ExcelやWordを手軽にマークダウンに変換~

記事タイトルとURLをコピーする

MarkItDownというMicrosoftが開発しているPythonライブラリがあります。

https://github.com/microsoft/markitdown

これはExcelやWord, PowerPoint, PDF, 画像, 音声, HTMLなどをマークダウンに変換することができるツールで、Pythonの実行環境さえあれば非常に簡単に利用することができます。

生成AIによるRAG(Retrieval-Augmented Generation)の実装において、ドキュメントの変換や整形に非常に役立つツールです。

今回はAWS CDKを利用したIaCでフロントエンド、バックエンドを構築する方法の学習も兼ねて、このMarkItDownをWebサービスとして誰でも簡単に利用いただける仕組みを作成してみましたのでご紹介いたします。

Pythonの環境のご用意が難しいお客様にMarkItDownを試していただいたり、スマートフォンや様々なデバイスからさくっとMarkItDownを利用することが可能となります。

背景

  • AWSが提供するIaCの仕組みAWS CDKではAWS LambdaのソースやS3への資材設置など、CloudFormationでは実現できない便利な点があることを知り、その利点を活かしたサーバレスなWebサービスを構築してみたい
  • MarkItDownをWebサービスとして誰でも簡単に利用できるようにすると便利そう

といったきっかけから、MarkItDownを利用するWebサービスをAWS CDKで構築してみました。

注意事項

  • Webサービスそのもののご紹介ではなく、ご自身のAWS環境にサーバレスなMarkItDownのWebサービスを構築する方法のご紹介となります。AWSアカウントのご用意やAWS CDKの実行環境のご用意が必要となります。
  • Lambdaのリクエストサイズの上限により利用可能なファイルサイズは約5MB程度までとなります。
  • MarkItDownでは生成AIと組み合わせることで画像や動画をより正確に理解させて変換することができますが当仕組みでは実装していません

対象読者

  • AWS CDKを利用したことがある方、これから学習される方
  • MarkItDownをWebサービスとして試してみたい方
  • 生成AI/RAGシステムの構築を検討している方

GitHubリポジトリ

https://github.com/knziiy/markitdown-web

機能

以下のようなシンプルな機能を持つWebサービスが構築されます。

  • ドラッグ&ドロップまたはクリックでファイルアップロード
  • 対応形式: Word (.docx, .doc), Excel (.xlsx, .xls), PowerPoint (.pptx, .ppt), PDF (.pdf), テキストファイル (.txt, .md)
  • Markdownへの変換とブラウザ表示
  • クリップボードへのコピー機能
  • アップロードされたファイルは破棄され、サーバ側に保持されることはない(そのように実装していますが利用される際には機密情報を含むファイルは避けるなどご注意ください)
  • [オプション]IPアドレス制限(AWS WAFを使用, IPv4のみ対応しています)

画面イメージ

以下のようなWeb画面を持つサービスがAWS上にサーバレスの構成で構築されます。

例として、東京都府中市のオープンデータにて、人口や高齢化率などのデータが公開されていますので、 高齢化率(令和7年4月1日現在)(Excel:12KB) をドラッグ&ドロップしてみます。

Excelは以下のようなシンプルな表が含まれるオープンデータです。

アップロード後、Markdownに変換されてブラウザ上に表示されます。

お手持ちのWordやExcelなどがMarkitdownによってどんな感じで変換できるのかをサクッと確認することが可能です。

デプロイ方法

AWS CDKで実装されており、npm run deployを実行することでAWSのサーバレス環境の構築とアプリケーションのビルド、デプロイが行われます。

git clone https://github.com/knziiy/markitdown-web.git
cd markitdown-web
npm install
npm run deploy

成功すると最後にAmazon CloudFrontのURLが表示されます(MarkitdownWebStack.WebsiteURL)。 ブラウザアクセスすることでWebアプリを利用可能です。

出力例:

Outputs:
MarkitdownWebStack.ApiEndpoint = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/
MarkitdownWebStack.BucketName = markitdown-website-000000000000-ap-northeast-1
MarkitdownWebStack.WebsiteURL = https://xxxxxxxxxxxxxx.cloudfront.net

削除方法

npm run destroy

構成

以下のようなシンプルな構成です。

設定

cdk.jsonでIPアドレスを指定することで、WAFのIPアドレス制限を有効にすることができます。デフォルトではIPアドレス制限は無効となっています。

{
  "context": {
    "allowedIpAddresses": [
      "x.x.x.x/32",
      "y.y.y.y/29"
    ]
  },
}

構成上の補足など

deploy-time-buildを利用したシングルショットでのデプロイ

Amazon API Gatewayを構築しないとAPIのEndpointが発行されないため、先にcdk deployしないとフロントエンド(React)に必要なAPIの情報が得られません。
そのため従来は "CDKでAPI Gatewayをデプロイ" → "発行されたAPIエンドポイント情報を利用してフロントエンドをビルド" の2段階でのデプロイが必要です。

deploy-time-build を利用することで cdk deploy 実行時にNodejsのビルドを実行できるため、API Endpointを環境変数で引き渡してビルドすることができます。

これは、Generative AI Use Cases (GenU) での実装を参考にさせていただいております。

deploy-time-build, Generative AI Use Cases (GenU) は共にAWS Japanのエンジニアの方が開発、提供いただいているものです。

AWS CDKを利用することのメリット

LambdaやDockerfileをリポジトリに含め、同時にビルド、デプロイが可能です。(他にもたくさんあると思いますが今回はこれだけです)

これにより cdk deploy のコマンド一回のみで全ての構築が完結します。

今回の仕組みでは利用していませんが、S3への資材設置も aws-cdk-lib/aws-s3-deployment を利用することでIaCに含めることが可能です。

CloudFormationの場合はLambda関数やソースコードの資材は別途管理しAmazon S3に配置しておくなど事前準備が必要です。

当仕組みの構成では、以下のようにfrontend(React)のソースコードやLambda関数のソースコードをリポジトリに含めており、CDKのデプロイ時に自動的にビルド、デプロイが行われます。

.
├── bin
│   └── markitdown-web-service.ts
├── cdk.json
├── frontend
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   ├── src
│   └── tsconfig.json
├── lambda
│   ├── Dockerfile
│   └── lambda_function.py
├── lib
│   ├── markitdown-web-stack.ts
│   └── waf-stack.ts
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
└── tsconfig.json

Lambda関数のビルド

Lambda関数はDockerfileを利用してcdk deploy時にビルドされ、Amazon ECRにコンテナイメージがアップロードされ、そのイメージを利用した関数として構築されます。

以下のように fromAssetImage でDockerfileのパスを指定するだけで済んでしまいます。

lib/markitdown-web-stack.ts

    const markitdownFunction = new lambda.Function(this, 'MarkitdownFunction', {
      runtime: lambda.Runtime.FROM_IMAGE,
      handler: lambda.Handler.FROM_IMAGE,
      code: lambda.Code.fromAssetImage(path.join(__dirname, '../lambda'), {
        file: 'Dockerfile',
      }),
      timeout: cdk.Duration.seconds(30),
      memorySize: 1024,
      architecture: lambda.Architecture.X86_64,
    });

フロントエンドのビルド

フロントエンドのReactアプリケーションは、cdk deploy時にビルドされ、S3にアップロードされます。これは前述の deploy-time-build が提供してくれているL3 Constructor NodejsBuild を利用して実現しています。

lib/markitdown-web-stack.ts

    const build = new NodejsBuild(this, 'BuildWeb', {
      assets: [
        {
          path: path.join(__dirname, '../frontend'),
          exclude: [
            'node_modules/**/*',
            '.git/**/*',
            'build/**/*',
            'dist/**/*',
          ],
        },
      ],
      destinationBucket: websiteBucket,
      distribution: distribution,
      outputSourceDirectory: 'build',
      buildCommands: ['npm ci', 'npm run build'],
      buildEnvironment: {
        VITE_REACT_APP_API_ENDPOINT: api.url,
      },
    });

この設定により、../frontendにあるソースがCDKのアセット用S3バケットにアップロードされ、CloudFormationのカスタムリソースによって、AWS CodeBuildで指定したbuildCommandsが実行され、ビルドされた成果物がdestinationBucketで指定しているS3にアップロードされます。

buildEnvironmentとして環境変数でAPI GatewayのURLを渡すことで、フロントエンドのビルド時にAPIのエンドポイントをReactアプリケーションに設定しています。

料金

月1,000回の利用を想定した場合のAWSの概算参考料金です。
サーバレスのため特にWAF(IP制限)を利用しない場合はほとんど料金は発生しないと思います。

※us-east-1の参考料金です

サービス IP制限なし IP制限あり
AWS Lambda $0.20 $0.20
Amazon API Gateway $0.004 $0.004
Amazon S3 $0.25 $0.25
Amazon CloudFront $0.10 $0.10
AWS WAF $0.00 $12.01
合計 約$0.55/月 約$12.56/月

Lambda関数

  • 実行時間: 30秒タイムアウト、1024MBメモリ
  • 想定利用: 月1,000回実行、平均10秒実行時間
  • 料金: 約$0.20/月(無料枠内の可能性が高い)

API Gateway

  • 構成: REST API、/convertエンドポイント
  • 想定利用: 月1,000リクエスト
  • 料金: 約$0.004/月($3.50 per million requests)

S3

  • 用途: 静的ウェブサイトホスティング
  • 想定: 1GB保存、月10,000リクエスト
  • 料金: 約$0.25/月

CloudFront

  • 構成: S3とAPI Gatewayの両方をオリジンとする配信
  • 想定: 月1GBデータ転送、10,000リクエスト
  • リクエスト料金: 10,000リクエスト × $0.0075 per 10,000 requests = $0.0075/月
  • データ転送: 想定1GB転送で約$0.085/月
  • 合計: 約$0.10/月

WAF(IP制限有効時)

CloudFront用WAF(us-east-1)

  • Web ACL: $5.00/月
  • Rule: $1.00/月(AllowSpecificIPsCloudFrontルール1つ)
  • Request: 10,000リクエスト × $0.60 per million = $0.006/月

API Gateway用WAF(リージョナル)

  • Web ACL: $5.00/月
  • Rule: $1.00/月(AllowSpecificIPsルール1つ)
  • Request: 月1,000リクエストで約$0.0006/月

APIとしての利用

API Gatewayのエンドポイント(cdkのOutputでMarkitdownWebStack.ApiEndpointとして出力)を利用することで、LambdaをAPIとして利用することも可能です。
fileDataをBase64で渡していただければ、Markdownに変換して返します。

% curl https://xxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/api/convert -X POST -H "Content-Type: application/json" -d '{"fileData": "SGVsbG8gV29ybGQ=", "fileName": "test.txt"}'
{"success": true, "markdown": "Hello World", "fileName": "test.txt"}

※API Gatewayもデフォルトで認証はありませんので、必要な場合はIP制限を設定ください

おわりに

単一機能だけのSPA、API一つだけのという非常にミニマムな構成ですが、AWS CDKを利用したサーバレスなWebサービスの構築を体験することができました。

cdkのコードもconstructの定義もなく1ファイルに小さく収まっていますので基本的な構成を把握するにも適していると思います。

ご自身のAWS環境にMarkItDownをWebサービスとして構築してみたい方はお試しください。

"生成AIのチャットは利用可能だが、ExcelやWordなどの添付には対応していない"、といった環境でMarkItDownを利用して手元のファイルをMarkdownに変換してからチャットに貼り付けるといった使い方もできるかと思います。

久保 賢二(執筆記事の一覧)

猫とAWSが好きです。