api gateway + lambda(python) + laravel (3) フォームの送信値によって読みこむyamlを変更するとかいう編
この種類のシステムを構築するとき一人でやる場合pythonもphpもやらなくちゃいけないので、混乱気味になりがちだが、対処法は、無い。
lambda(python)側
とりあえず現状
import json
def lambda_handler(event, context):
# eventの 'body' からデータを取得し、JSONとして解析
body = json.loads(event.get('body', '{}'))
# keyとvalueのペアを格納する辞書
output_dict = {}
# body内のすべてのkey-valueペアをイテレーション
for key, value in body.items():
output_dict[key] = value # キーと値をoutput_dictに追加
# レスポンスを生成
response = {
'statusCode': 200,
'body': json.dumps(output_dict) # output_dictをJSON形式にしてbodyに設定
}
return response
まず、このコードはほぼ意味を無さないので、もうちょいまじめにやろう。configというディレクトリを作成し、その中にdefault.yamlとdev.yamlという2つのファイルを作成するとする。これはlambda側に用意する。
今は簡単なコードなのでlambdaのオンラインエディタで作成しちゃえばいいと思う
default.yamlの例
settings:
mode: 'default'
timeout: 30
retry: 3
logging: false
dev.yamlの例
settings:
mode: 'dev'
timeout: 60
retry: 10
logging: true
まあ、何というかこんなのをでっち上げて読み込むとしましょうか。
さてさて、環境ができたらflagを受けとろう
laravel編
の前に
api urlをベタ書きするのではなくconfigに追い出してみよう。ここではconfig/services.phpに追記する
config/services.php
// これをまるっと追加
'lambda' => [
'api_url' => env('LAMBDA_API_URL'),
],
この設定ファイルは.envのLAMBDA_API_URLを読みこむので.envにapiのurlを書けばokだ。
$url = config('services.lambda.api_url');
// POSTリクエストを送信
$response = Http::post($url, [
'flag' => $flag,
]);
// レスポンスを処理(例:JSONとして取得)
$data = $response->json();
dd($data);
最終的にこのようなファイルでチェックボックスの有無により返却されるファイル名が変更されていれば成功であーる
lambda側のロギング、テスト
さてさて、まあ今は何のファイルを読んでいるかreturnしているので明確なのだが、これを一応ログに取って見る方法も行ってみよう
import json
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# eventの 'body' からデータを取得し、JSONとして解析
body = json.loads(event.get('body', '{}'))
# キー「flag」を取得、存在しなければ0で埋める
flag = body.get('flag', 0)
if flag == 0:
config_path = 'config/default.yaml'
else:
config_path = 'config/dev.yaml'
logging.info(f"Selected config path: {config_path}")
# レスポンスを生成
response = {
'statusCode': 200,
'body': json.dumps({
'config': config_path
}) # 読み込んだconfig_dataをJSON形式にしてbodyに設定
}
return response
これでdeployする。これはimport loggingでロガーをimportし
logging.info(f"Selected config path: {config_path}")
でログ出力する。fっていうのは {}の中の変数展開するものと覚えといてもまあ問題ない
実行結果は全く変わらないはずだ。なお、最新のログストリームに以下のように主力されていれば成功である。
なお、eventのフルダンプをこのように取得してテストする方法もあるけど、まあ長くなるからええか…
yamlをロードしてみる
yamlのロードの方法はPyYAML を使う方法が一般的であるが、これは標準モジュールではないので取り込んでこないといけない。
モジュールの取り込み方
ここからはちょっとディープであるがライブラリーを取得してそれをzip転送するという方法がある、が、まずそもそもライブラリーを取得せねばなるまい。使っているpythonのバージョンを確認しよう。「関数」のところまで戻ると一覧できるだろう。
このようにlambdaのpytyon環境はいろいろとランタイムを分ける事ができるので、その環境に応じたライブラリーをひっこぬいてくる事がベストプラクティスである。簡単に行う場合はdockerを利用する事だ(ただしアーキテクチャーの違いは越えられないからね。armで動かしてるならarmのホストで行う事)。
% docker run -it --name temp-container python:3.11 bash
Unable to find image 'python:3.11' locally
3.11: Pulling from library/python
a014e5e7d08c: Already exists
715cea74ecbb: Already exists
003f1109a212: Already exists
a56ae3b61eb9: Already exists
c3668095a3a2: Already exists
7a7e9903522f: Pull complete
e17425f8448e: Pull complete
16fc1f695aa0: Pull complete
Digest: sha256:02808bfd640d6fd360c30abc4261ad91aacacd9494f9ba4e5dcb0b8650661cf5
Status: Downloaded newer image for python:3.11
root@66d820ed532e:/#
こうすると簡単にpythonのそれぞれのバージョンの環境が手に入る
root@66d820ed532e:/# mkdir /packages
root@66d820ed532e:/# pip install pyyaml -t /packages
Collecting pyyaml
Obtaining dependency information for pyyaml from https://files.pythonhosted.org/packages/5e/94/7d5ee059dfb92ca9e62f4057dcdec9ac08a9e42679644854dc01177f8145/PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.metadata
Downloading PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.metadata (2.1 kB)
Downloading PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (732 kB)
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 732.2/732.2 kB 14.8 MB/s eta 0:00:00
Installing collected packages: pyyaml
Successfully installed pyyaml-6.0.1
WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv
root@66d820ed532e:/# ls -l /packages/
total 12
drwxr-xr-x 2 root root 4096 Sep 5 01:32 PyYAML-6.0.1.dist-info
drwxr-xr-x 3 root root 4096 Sep 5 01:32 _yaml
drwxr-xr-x 3 root root 4096 Sep 5 01:32 yaml
rootでやるなよーと言われるけどまあコンテナなのでいいでしょう
で、コンテナの外に出て
% mkdir tmp
% cd tmp
% docker cp temp-container:/packages .
% ls
packages
などとすればフルコピーできる。コピーしたらコンテナはもう使わないので削除するのがいいだろう
% docker rm -f temp-container
temp-container
さて、ここでこんなディレクトリ構成を作成する
mkdir -p python/lib/python3.11/site-packages/
python3.11は御自身のランタイムのバージョンに合わせる必要があるが、この後
cp -r packages/* python/lib/python3.11/site-packages/
等して、コピーを行った後
zip -r layer.zip python/
でlayer.zipを作成する
なお、もう少し小さくもできる
find ./packages -type d -name '__pycache__' -exec rm -r {} +
find ./packages -name '*.pyc' -exec rm -f {} +
こうしておくとcacheが削除される
いずれにせよlayer.zipを手に入れたならとりあえずアップロードできる環境に転送しよう。aws cliが使えるよーって人はまあここみなくても出来るでしょう。
レイヤーのアップロードの前に失敗コードを書く
import json
import logging
import yaml # PYAMLをインポート
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# テスト用のYAMLデータ
sample_yaml = '''
name: John
age: 30
'''
# YAMLをJSONに変換(PYAMLが正常にインストールされているかのテスト)
try:
sample_json = yaml.safe_load(sample_yaml)
logger.info(f"Converted YAML to JSON: {sample_json}")
except Exception as e:
logger.error(f"Could not convert YAML to JSON: {e}")
# eventの 'body' からデータを取得し、JSONとして解析
body = json.loads(event.get('body', '{}'))
# キー「flag」を取得、存在しなければ0で埋める
flag = body.get('flag', 0)
if flag == 0:
config_path = 'config/default.yaml'
else:
config_path = 'config/dev.yaml'
logger.info(f"Selected config path: {config_path}")
# レスポンスを生成
response = {
'statusCode': 200,
'body': json.dumps({
'config': config_path
}) # 読み込んだconfig_dataをJSON形式にしてbodyに設定
}
return response
このようにyamlのロードを行うコードを書いておく。import yamlは非標準モジュールなので実行すると失敗するだろう。これはテストで行ってみるのが楽かもしれない。
レイヤーのアップロード
これは関数ごとに存在しているわけじゃなくて、lambdaのトップから行うんだね
レイヤーの作成から
こんな感じでアップロードしている
そうするとlayerが作成されARNが与えられるのでコピっておく
さて、当該のlambda関数に戻ってlayerを追加する。下の方にあるよ
arnを貼り付けてまあ一応検証するとこんな感じになる。「互換性のあるランタイム」に関してはpython3.10までしか出てこねえんじゃないかなあ?いずれにせよ追加する
でまあ先刻のテストを行うと成功した
ログをよくみりゃテストコードがログに出力されているよね
Converted YAML to JSON: {'name': 'John', 'age': 30}
最後の仕上げとして指定されたconfigを読んで、返す
今はダミーのyaml文字列を読んでるだけだが、configでファイルを指定しているので、これを読んでみよう。
import json
import logging
import yaml # PYAMLをインポート
logger = logging.getLogger()
logger.setLevel(logging.INFO)
def lambda_handler(event, context):
# eventの 'body' からデータを取得し、JSONとして解析
body = json.loads(event.get('body', '{}'))
# キー「flag」を取得、存在しなければ0で埋める
flag = body.get('flag', 0)
if flag == 0:
config_path = 'config/default.yaml'
else:
config_path = 'config/dev.yaml'
# 指定されたyamlファイルのロード
with open(config_path, 'r') as f:
config_data = yaml.safe_load(f)
logger.info(f"Selected config path: {config_path}")
logger.info(f"Loaded config data: {config_data}")
# レスポンスを生成
response = {
'statusCode': 200,
'body': json.dumps({
'config_path': config_path,
'config_data': config_data
}) # 読み込んだconfig_dataをJSON形式にしてbodyに設定
}
return response
phpでの出力は以下のようになるはずである
この記事が気に入ったらサポートをしてみませんか?