AWS CLIを使用したPython Dashアプリのデプロイ
AWS CLIによるデプロイの概要
AWS CLIを使用したデプロイは、最も柔軟性が高く、完全なカスタマイズが可能なアプローチです。このアプローチでは、AWS CLIコマンドを直接使用して、Lambda関数の作成、設定、デプロイを行います。
このアプローチの主な特徴は以下の通りです:
- 完全なカスタマイズ制御
- AWS サービスへの直接アクセス
- 自動化スクリプトとの統合が容易
- 詳細な設定オプション
前提条件
- AWS CLIがインストールされ、設定済みであること
- Python 3.6以上がインストールされていること
- AWS IAMユーザーに必要な権限が付与されていること
AWS CLIのインストールと設定
# AWS CLIのインストール
pip install awscli
# AWS CLIの設定
aws configure
設定時に以下の情報を入力します:
- AWS Access Key ID
- AWS Secret Access Key
- Default region name(例:ap-northeast-1)
- Default output format(例:json)
IAMロールの作成
まず、Lambda関数が使用するIAMロールを作成します。このロールには、Lambda関数の実行に必要な権限を付与します。
1. 信頼ポリシーの作成
trust-policy.json
というファイルを作成し、以下の内容を記述します:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
2. IAMロールの作成
aws iam create-role \
--role-name DashLambdaRole \
--assume-role-policy-document file://trust-policy.json
このコマンドの出力には、作成されたロールのARNが含まれています。このARNは後で使用するため、メモしておきます。
3. ポリシーのアタッチ
aws iam attach-role-policy \
--role-name DashLambdaRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
必要に応じて、追加のポリシーをアタッチすることもできます。例えば、DynamoDBにアクセスする場合は、以下のコマンドを実行します:
aws iam attach-role-policy \
--role-name DashLambdaRole \
--policy-arn arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess
Dashアプリケーションの作成
1. プロジェクトディレクトリの作成
mkdir -p dash-lambda-app
2. Dashアプリケーションの作成
dash-lambda-app/app.py
というファイルを作成し、以下の内容を記述します:
import plotly.express as px
import dash
from dash import html, dcc
from dash.dependencies import Input, Output
import json
import base64
# サンプルデータを読み込む
df = px.data.gapminder()
# アプリケーションを作成する
app = dash.Dash(__name__)
# グラフを作成する関数
def create_graph(data, x_col, y_col):
fig = px.scatter(data, x=x_col, y=y_col, color='continent',
log_x=True, size_max=60, title=f"{y_col} vs {x_col}")
return fig
# レイアウトを定義する
app.layout = html.Div([
html.H1("Dash App on AWS Lambda"),
html.Div([
dcc.Graph(id='graph-1')
]),
html.Div([
dcc.Dropdown(
id='x-column',
options=[{'label': i, 'value': i} for i in df.columns],
value='gdpPercap'
)
], style={'width': '25%', 'display': 'inline-block'}),
html.Div([
dcc.Dropdown(
id='y-column',
options=[{'label': i, 'value': i} for i in df.columns],
value='lifeExp'
)
], style={'width': '25%', 'display': 'inline-block'}),
])
# コールバックを定義する
@app.callback(
Output('graph-1', 'figure'),
[Input('x-column', 'value'),
Input('y-column', 'value')])
def update_graph(x_col, y_col):
fig = create_graph(df, x_col, y_col)
return fig
# Lambda関数のハンドラー
def lambda_handler(event, context):
# API Gatewayからのイベントを処理
path = event.get('path', '/')
http_method = event.get('httpMethod', 'GET')
headers = event.get('headers', {})
query_string_parameters = event.get('queryStringParameters', {})
# WSGIアプリケーションを実行
from werkzeug.wrappers import Request
from werkzeug.wrappers import Response
from werkzeug.serving import run_simple
# リクエストの作成
environ = {
'PATH_INFO': path,
'REQUEST_METHOD': http_method,
'QUERY_STRING': '',
'SERVER_NAME': 'localhost',
'SERVER_PORT': '80',
'wsgi.url_scheme': 'http',
'wsgi.input': '',
'wsgi.errors': '',
'wsgi.multithread': False,
'wsgi.multiprocess': False,
'wsgi.run_once': False,
}
# クエリパラメータの追加
if query_string_parameters:
query_string = '&'.join([f"{k}={v}" for k, v in query_string_parameters.items()])
environ['QUERY_STRING'] = query_string
# ヘッダーの追加
for key, value in headers.items():
key = key.upper().replace('-', '_')
if key not in ('CONTENT_TYPE', 'CONTENT_LENGTH'):
key = 'HTTP_' + key
environ[key] = value
# リクエストボディの追加
if 'body' in event and event['body']:
body = event['body']
if event.get('isBase64Encoded', False):
body = base64.b64decode(body)
environ['wsgi.input'] = body
environ['CONTENT_LENGTH'] = str(len(body))
# レスポンスの取得
response_data = {}
def start_response(status, response_headers, exc_info=None):
status_code = int(status.split(' ')[0])
response_data['statusCode'] = status_code
response_data['headers'] = dict(response_headers)
return lambda x: None
# アプリケーションの実行
response_body = b''.join(app.server(environ, start_response))
# レスポンスの作成
response_data['body'] = response_body.decode('utf-8')
response_data['isBase64Encoded'] = False
return response_data
# ローカルテスト用
if __name__ == '__main__':
app.run_server(debug=True, host="0.0.0.0")
重要なポイント:
lambda_handler
関数は、API GatewayからのイベントをWSGIアプリケーションに変換し、レスポンスを返します。- このコードは簡略化されており、実際の環境では
aws-wsgi
などのライブラリを使用することをお勧めします。
3. 依存関係ファイルの作成
dash-lambda-app/requirements.txt
というファイルを作成し、以下の内容を記述します:
dash==2.14.0
plotly==5.18.0
pandas==2.1.1
werkzeug==2.3.7
flask==2.2.5
デプロイメントパッケージの作成
1. 仮想環境の作成
cd dash-lambda-app
python3 -m venv venv
source venv/bin/activate
2. 依存関係のインストール
pip install -r requirements.txt
3. パッケージディレクトリの作成
mkdir -p package
4. 依存関係のコピー
pip install -t package -r requirements.txt
5. アプリケーションコードのコピー
cp app.py package/
6. ZIPファイルの作成
cd package
zip -r ../dash-lambda-package.zip .
cd ..
S3バケットへのアップロード
1. S3バケットの作成
aws s3 mb s3://dash-lambda-deployment-bucket
バケット名はグローバルに一意である必要があります。適切な名前に変更してください。
2. デプロイメントパッケージのアップロード
aws s3 cp dash-lambda-package.zip s3://dash-lambda-deployment-bucket/
Lambda関数の作成
aws lambda create-function \
--function-name dash-lambda-app \
--runtime python3.9 \
--role arn:aws:iam::123456789012:role/DashLambdaRole \
--handler app.lambda_handler \
--timeout 30 \
--memory-size 512 \
--code S3Bucket=dash-lambda-deployment-bucket,S3Key=dash-lambda-package.zip
以下のパラメータを適切に設定してください:
--role
: 先ほど作成したIAMロールのARN--runtime
: 使用するPythonのバージョン--handler
: Lambda関数のエントリーポイント--timeout
: 関数のタイムアウト時間(秒)--memory-size
: 関数のメモリサイズ(MB)--code
: デプロイメントパッケージのS3バケットとキー
API Gatewayの設定
1. REST APIの作成
aws apigateway create-rest-api \
--name "Dash Lambda API" \
--description "API for Dash Lambda application"
このコマンドの出力には、作成されたAPIのIDが含まれています。このIDは後で使用するため、メモしておきます。
2. ルートリソースIDの取得
aws apigateway get-resources \
--rest-api-id abcdef1234
このコマンドの出力には、ルートリソースのIDが含まれています。このIDは後で使用するため、メモしておきます。
3. プロキシリソースの作成
aws apigateway create-resource \
--rest-api-id abcdef1234 \
--parent-id rootResourceId \
--path-part "{proxy+}"
このコマンドの出力には、作成されたリソースのIDが含まれています。このIDは後で使用するため、メモしておきます。
4. ANY メソッドの作成(ルートリソース)
aws apigateway put-method \
--rest-api-id abcdef1234 \
--resource-id rootResourceId \
--http-method ANY \
--authorization-type NONE
5. Lambda統合の設定(ルートリソース)
aws apigateway put-integration \
--rest-api-id abcdef1234 \
--resource-id rootResourceId \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:region:lambda:path/2015-03-31/functions/arn:aws:lambda:region:123456789012:function:dash-lambda-app/invocations
6. ANY メソッドの作成(プロキシリソース)
aws apigateway put-method \
--rest-api-id abcdef1234 \
--resource-id proxyResourceId \
--http-method ANY \
--authorization-type NONE
7. Lambda統合の設定(プロキシリソース)
aws apigateway put-integration \
--rest-api-id abcdef1234 \
--resource-id proxyResourceId \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:region:lambda:path/2015-03-31/functions/arn:aws:lambda:region:123456789012:function:dash-lambda-app/invocations
8. Lambda関数の権限設定
aws lambda add-permission \
--function-name dash-lambda-app \
--statement-id apigateway-root \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:region:123456789012:abcdef1234/*/ANY/"
aws lambda add-permission \
--function-name dash-lambda-app \
--statement-id apigateway-proxy \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:region:123456789012:abcdef1234/*/ANY/{proxy+}"
9. APIのデプロイ
aws apigateway create-deployment \
--rest-api-id abcdef1234 \
--stage-name prod
APIがデプロイされると、以下のURLでアクセスできます:
https://abcdef1234.execute-api.region.amazonaws.com/prod/
Lambda関数の更新
アプリケーションを更新する場合は、以下の手順を実行します:
1. コードの変更
必要に応じてアプリケーションコードを変更します。
2. 新しいデプロイメントパッケージの作成
cd package
rm -rf *
pip install -t . -r ../requirements.txt
cp ../app.py .
zip -r ../dash-lambda-package-v2.zip .
cd ..
3. S3バケットへのアップロード
aws s3 cp dash-lambda-package-v2.zip s3://dash-lambda-deployment-bucket/
4. Lambda関数の更新
aws lambda update-function-code \
--function-name dash-lambda-app \
--s3-bucket dash-lambda-deployment-bucket \
--s3-key dash-lambda-package-v2.zip
リソースの削除
不要になったリソースを削除する場合は、以下の手順を実行します:
1. API Gatewayの削除
aws apigateway delete-rest-api \
--rest-api-id abcdef1234
2. Lambda関数の削除
aws lambda delete-function \
--function-name dash-lambda-app
3. IAMロールの削除
aws iam detach-role-policy \
--role-name DashLambdaRole \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
aws iam delete-role \
--role-name DashLambdaRole
4. S3バケットの削除
aws s3 rm s3://dash-lambda-deployment-bucket --recursive
aws s3 rb s3://dash-lambda-deployment-bucket
デプロイの自動化
上記の手順をシェルスクリプトにまとめることで、デプロイを自動化することができます。以下は、基本的なデプロイスクリプトの例です:
#!/bin/bash
# 変数の設定
FUNCTION_NAME="dash-lambda-app"
BUCKET_NAME="dash-lambda-deployment-bucket"
ROLE_NAME="DashLambdaRole"
API_NAME="Dash Lambda API"
STAGE_NAME="prod"
REGION="ap-northeast-1"
ACCOUNT_ID=$(aws sts get-caller-identity --query "Account" --output text)
# S3バケットの作成(存在しない場合)
aws s3 mb s3://$BUCKET_NAME --region $REGION || true
# デプロイメントパッケージの作成
mkdir -p package
pip install -t package -r requirements.txt
cp app.py package/
cd package
zip -r ../dash-lambda-package.zip .
cd ..
# S3バケットへのアップロード
aws s3 cp dash-lambda-package.zip s3://$BUCKET_NAME/
# IAMロールの作成(存在しない場合)
ROLE_ARN=$(aws iam get-role --role-name $ROLE_NAME --query "Role.Arn" --output text 2>/dev/null || echo "")
if [ -z "$ROLE_ARN" ]; then
echo "Creating IAM role..."
cat > trust-policy.json << EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "lambda.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
ROLE_ARN=$(aws iam create-role \
--role-name $ROLE_NAME \
--assume-role-policy-document file://trust-policy.json \
--query "Role.Arn" \
--output text)
aws iam attach-role-policy \
--role-name $ROLE_NAME \
--policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
# IAMロールが有効になるまで待機
echo "Waiting for IAM role to be ready..."
sleep 10
fi
# Lambda関数の作成または更新
FUNCTION_EXISTS=$(aws lambda get-function --function-name $FUNCTION_NAME 2>/dev/null || echo "")
if [ -z "$FUNCTION_EXISTS" ]; then
echo "Creating Lambda function..."
aws lambda create-function \
--function-name $FUNCTION_NAME \
--runtime python3.9 \
--role $ROLE_ARN \
--handler app.lambda_handler \
--timeout 30 \
--memory-size 512 \
--code S3Bucket=$BUCKET_NAME,S3Key=dash-lambda-package.zip
else
echo "Updating Lambda function..."
aws lambda update-function-code \
--function-name $FUNCTION_NAME \
--s3-bucket $BUCKET_NAME \
--s3-key dash-lambda-package.zip
fi
# API Gatewayの作成
API_ID=$(aws apigateway get-rest-apis --query "items[?name=='$API_NAME'].id" --output text)
if [ -z "$API_ID" ]; then
echo "Creating API Gateway..."
API_ID=$(aws apigateway create-rest-api \
--name "$API_NAME" \
--description "API for Dash Lambda application" \
--query "id" \
--output text)
# ルートリソースIDの取得
ROOT_RESOURCE_ID=$(aws apigateway get-resources \
--rest-api-id $API_ID \
--query "items[?path=='/'].id" \
--output text)
# プロキシリソースの作成
PROXY_RESOURCE_ID=$(aws apigateway create-resource \
--rest-api-id $API_ID \
--parent-id $ROOT_RESOURCE_ID \
--path-part "{proxy+}" \
--query "id" \
--output text)
# ANY メソッドの作成(ルートリソース)
aws apigateway put-method \
--rest-api-id $API_ID \
--resource-id $ROOT_RESOURCE_ID \
--http-method ANY \
--authorization-type NONE
# Lambda統合の設定(ルートリソース)
aws apigateway put-integration \
--rest-api-id $API_ID \
--resource-id $ROOT_RESOURCE_ID \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$REGION:$ACCOUNT_ID:function:$FUNCTION_NAME/invocations
# ANY メソッドの作成(プロキシリソース)
aws apigateway put-method \
--rest-api-id $API_ID \
--resource-id $PROXY_RESOURCE_ID \
--http-method ANY \
--authorization-type NONE
# Lambda統合の設定(プロキシリソース)
aws apigateway put-integration \
--rest-api-id $API_ID \
--resource-id $PROXY_RESOURCE_ID \
--http-method ANY \
--type AWS_PROXY \
--integration-http-method POST \
--uri arn:aws:apigateway:$REGION:lambda:path/2015-03-31/functions/arn:aws:lambda:$REGION:$ACCOUNT_ID:function:$FUNCTION_NAME/invocations
# Lambda関数の権限設定
aws lambda add-permission \
--function-name $FUNCTION_NAME \
--statement-id apigateway-root \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$REGION:$ACCOUNT_ID:$API_ID/*/ANY/"
aws lambda add-permission \
--function-name $FUNCTION_NAME \
--statement-id apigateway-proxy \
--action lambda:InvokeFunction \
--principal apigateway.amazonaws.com \
--source-arn "arn:aws:execute-api:$REGION:$ACCOUNT_ID:$API_ID/*/ANY/{proxy+}"
fi
# APIのデプロイ
aws apigateway create-deployment \
--rest-api-id $API_ID \
--stage-name $STAGE_NAME
# デプロイされたURLの表示
echo "Deployment completed. Your API is available at:"
echo "https://$API_ID.execute-api.$REGION.amazonaws.com/$STAGE_NAME/"
このスクリプトをdeploy.sh
として保存し、実行権限を付与します:
chmod +x deploy.sh
デプロイを実行するには、以下のコマンドを実行します:
./deploy.sh