本記事は 夏休みクラウド自由研究 8/31付の記事です。 |
こんにちは、SCSK木澤です。
8/1から始まった夏休みクラウド自由研究も今回で最終回となりました。
今回の企画は幅広いクラウドやソリューションを担当する31名の執筆者にご協力いただき、無事完走することが出来ました。
TechHarmonyは今春に500記事を超え、その後も順調に記事数を増やしています。
今後も色々な企画を考えていきたいと思いますので、どうぞ引き続きご愛顧の程よろしくお願いします。
さて今回は自由研究らしく、普段とは違う話題としてAWS Lambdaのファイルシステムについて調査してみました。
きっかけ
私が本件に興味を持ったのは、5年前に携わった案件に遡ります。
管理しているEC2インスタンスに配布する設定ファイル一式についてユーザー毎/インスタンス毎にカスタマイズする必要があり、調査の上、Lambdaのファイルシステム上で処理してS3に出力することが適切と判断して実装しました。
- S3バケットからzipファイルをダウンロードしてzip解凍
- 設定ファイルを書き換え
- 再度zip圧縮して、S3バケットに書き込み
サーバレスコンピューティングとはいえ、実態としてはサーバーがあるんだなと興味深く当時は思った次第です。
そのあたりをもう少し詳細にお話していきます。
動作仕様
Lambdaの動作原理
理解を深めるためにも、Lambdaの動作原理についてから触れたいと思います。
とはいえJAWS DAYS 2022でのAWS(当時)の亀田さんの講演がとても解りやすく私の印象に強く残っているので、こちらから紹介します。
AWS Lambdaは2014年のre:Inventで発表されたものとなりますが、当時はユーザー毎に内部にEC2インスタンスを持つ構成でした。
その後Lambdaの実行に特化した構成としてNitroシステムとFirecrackerが導入され、より強固なアイソレーション(環境分離)と高速・多数の起動環境を実現しています。
実は私はAWSを担当する前に自社クラウドに携わっていたため強く思うのですが、AWS Lambdaのリリース当初はEC2ベース+コンテナの技術レベルであったにも関わらず、お客様(ディベロッパー)にとってあるべき姿を定義し、あるべき価格で(赤字でも?)提供する、そしてその後改善・解決していくという姿はThink BigでありCustomer Obesessionでもあるので感銘を受けたのを覚えています。
話が長くなったので戻しますが、現在AWS LambdaはNitroシステム+Firecrackerで提供されています。
つまりLambdaも小さい仮想サーバー(microVM)という訳です。
よって、当然ながらファイルシステムもあります。
クオータ(制限)についてはこちらをご覧下さい。
ユーザーがLambdaにアタッチできるローカルファイルシステムは、/tmp にマウントされ512MB~10240MBの間で設定可能です。
ウォームスタートとコールドスタート
Lambdaのローカルファイルシステムについて触れる際に、この話は避けて通れません。
Lambdaを起動した際にmicroVMを作成される訳ですが、どうしてもこの初期化作業には一定の時間が掛かります(コールドスタート)。そのため、もし再利用できるmicroVMがあれば再利用してスピードアップするよう動作します(ウォームスタート)
https://pages.awscloud.com/rs/112-TZM-766/images/20190402_AWSBlackbelt_AWSLambda%20Part1&2.pdf
この再利用(ウォームスタート)が発生した際は、前の実行時のファイルがユーザーファイルシステムに残ったままになっています。
ファイル処理を行った際にファイル重複でエラーになる可能性があるのでご注意ください。
実行開始時に一旦すべて削除するか、例外処理を入れる必要があるでしょう。
調査
さて、ファイルシステムの詳細を調べてみたいと思います。
容量・パーミッションの確認
デフォルトの設定でLambdaを作成し、以下のコードで実行してみます。
PythonのOSライブラリでOSコマンドを実行しています。
import json import os def lambda_handler(event, context): # 実行ユーザーを確認 print("--user") print(os.system("whoami")) # ディスク容量を確認 print("--disk capacity") print(os.system("df -h")) # マウント状態を確認 print("--mount") print(os.system("cat /etc/fstab")) # パーミッションの確認 print("--permisson /") print(os.system("ls -al /")) print("--permisson /tmp") print(os.system("ls -al /tmp")) # 書き込み print("--write /") print(os.system("touch /test1.txt")) print(os.system("ls -al /")) print("--write /tmp") print(os.system("touch /tmp/test2.txt")) print(os.system("ls -al /tmp")) return { 'statusCode': 200, 'body': '' }
実行結果を確認します
実行ユーザー
--user sbx_user1051
当然ながら一般ユーザー権限でユーザープロセスは実行されています
ディスク容量、マウント状態
--disk capacity Filesystem Size Used Avail Use% Mounted on /dev/vde 144G 143G 0 100% / /dev/vdb 1.5G 18M 1.4G 2% /etc/hosts tmpfs 64M 0 64M 0% /dev /dev/vdd 525M 8.0K 514M 1% /tmp /dev/root 9.7G 386M 9.3G 4% /etc/passwd /dev/vdc 128K 128K 0 100% /var/task --mount # LABEL=/ / auto defaults,noatime 1 1
/tmpはユーザー設定により容量が変わるので、別ディスクとしてマウントされています。
ルートディスクはリードオンリーではないですが、書き込み不可となっています。
パーミッション
--permisson / total 116 drwxr-xr-x 18 root root 4096 Jul 31 08:47 . drwxr-xr-x 18 root root 4096 Jul 31 08:47 .. -rw-r--r-- 1 root root 48222 Jul 31 22:38 THIRD-PARTY-LICENSES.txt lrwxrwxrwx 1 root root 7 Jan 30 2023 bin -> usr/bin dr-xr-xr-x 2 root root 4096 Jan 30 2023 boot drwxr-xr-x 2 root root 200 Aug 30 00:50 dev drwxr-xr-x 34 root root 4096 May 10 11:16 etc drwxr-xr-x 2 root root 4096 Jan 30 2023 home -rwxr-xr-x 1 root root 397 Jul 31 15:59 lambda-entrypoint.sh lrwxrwxrwx 1 root root 7 Jan 30 2023 lib -> usr/lib lrwxrwxrwx 1 root root 9 Jan 30 2023 lib64 -> usr/lib64 drwxr-xr-x 2 root root 4096 Apr 25 19:25 local drwxr-xr-x 2 root root 4096 Jan 30 2023 media drwxr-xr-x 2 root root 4096 Jan 30 2023 mnt drwxr-xr-x 2 root root 4096 Jan 30 2023 opt dr-xr-xr-x 123 root root 0 Aug 30 01:40 proc dr-xr-x--- 2 root root 4096 Jan 30 2023 root drwxr-xr-x 5 root root 4096 Apr 25 19:25 run lrwxrwxrwx 1 root root 8 Jan 30 2023 sbin -> usr/sbin drwxr-xr-x 2 root root 4096 Jan 30 2023 srv drwxr-xr-x 2 root root 4096 Apr 25 19:25 sys drwxrwxrwx 2 root root 4096 Aug 5 15:36 tmp drwxr-xr-x 12 root root 4096 Jul 31 08:47 usr drwxr-xr-x 24 root root 4096 May 9 21:46 var
/tmpは権限777のため、一般ユーザーでも書き込み可能です。
ファイル書き込みの確認
--write / touch: cannot touch '/test1.txt': Read-only file system
当然ながらルートディスクは書き込み不可です。
--write /tmp total 8 drwxrwxrwx 2 root root 4096 Aug 30 01:40 . drwxr-xr-x 18 root root 4096 Jul 31 08:47 .. -rw-r--r-- 1 sbx_user1051 990 0 Aug 30 01:40 test2.txt
/tmpには書き込み可能です。
ファイル入出力速度の調査
さて、ローカルファイルシステム(/tmp)には何も保存されていない訳ですし、データは永続化できません。
よって、S3バケットなどからダウンロードしてきてから処理を行う必要があります。
ファイルの入出力時間が気になるので、確認してみます。
S3バケットを作成して、CloudShellから1GBのファイルを作成して保存します。
$ dd if=/dev/zero of=1gb.dat bs=1024k count=1024 $ aws s3 cp 1gb.dat s3://S3バケット名/
以下のようなコードでLambda関数を作成します。
import json import boto3 import os bucketname = "S3バケット名" def lambda_handler(event, context): s3r = boto3.resource('s3') # /tmp削除 print("--delete") print(os.system("rm -rf /tmp/*")) # コピー data_bucket = s3r.Bucket(bucketname) print("--copy from S3 Bucket Start") data_bucket.download_file('1gb.dat','/tmp/1gb.dat') print("--copy from S3 Bucket End") return { 'statusCode': 200, 'body': '' }
S3にアクセスできるLambda用IAMロールを作成し、本関数に適用します。
ディスク容量は増やしておきます。
EC2インスタンスと同様、Lambdaにおいてもメモリ容量とCPUスペックは比例するように設計されています。
そこで、メモリ容量≒CPU割り当てとS3からの読み取り速度を調べたところ、以下の通りになりました。(3回平均、CloudWatch Logsに出力されたログのタイムスタンプから算定)
Lambdaメモリ割当量 | ダウンロード時間 | 速度 |
---|---|---|
128MB | 74.2秒 | 110Mbps |
256MB | 40.4秒 | 203Mbps |
512MB | 19.4秒 | 422Mbps |
1024MB | 10.7秒 | 768Mbps |
2048MB | 10.4秒 | 785Mbps |
メモリ割り当てに比例してネットワークI/O帯域も拡張されること、但し(S3バケットからのダウンロードにおいては)800Mbps弱で頭打ちになることを確認しました。
つまり、/tmpの最大容量10GBのダウンロードもしくはアップロードを行うには、最低100秒程度の時間が掛かることになります。
この辺りがローカルディスクを用いた処理の限界であるであろうことが解りました。
要件満たさない場合はEFS利用
10GBを超える大容量ファイルを扱いたい場合、あるいは永続ファイルをマウントしたい場合はAmazon EFSの利用が可能です。
なお、Amazon EFSはVPC上のサービスですので、LambdaもVPCアタッチが必要となります。
まとめ
実に地味な内容でしたが、Lambdaのファイルシステムについて解説・確認しました。
ネットワーク帯域にも変動があることなど、新たな発見があり楽しかったですね。
Lambdaのローカルファイルシステム(/tmp)は非永続ということもあり、ディスク容量は課金には影響しません。
色々使いこなすと楽しいので、皆様もご活用下さい!