はじめに
Amazon APIGatewayは複数のLambda関数や別のALB配下のアプリケーションとかを簡単に統合できて便利。
でも、色々なデプロイ方法が競合して死ぬことってないの?SAMとCloudFormation両方で更新したらどうなってしまうの?というのがよく分からないので、実験してどのように運用すれば良いのかを考えてみる。
サンプルプログラムは、以下の仕様で動作する。
- 初回デプロイ
-
/name
のパスに対するPUTメソッドでクエリパラメータとしてid
とname
を受け取り、DynamoDBにid, name, version(1.0
)を書き込む -
/names
のパスに対するGETメソッドでクエリパラメータとしてid
を受け取り、DynamoDBからidで指定されたキーを検索してnameを応答する
-
- 2回目のデプロイ
-
/name
のパスに対するPUTメソッドのDynamoDBに書き込むレコードをversion(2.0
)に変更する -
/names
のパスに対するGETメソッドの応答にversionを追加する
-
DynamoDBについては、SAM上でAWS::DynamoDB::Table
を定義するという方法もあるが、基本/共通リソースとサービスリソースを分割した方が良かろうということで、先にDynamoDBテーブルを作成する構成にしている。
あと、Lambda関数中でDynamoDBを扱うので、サービスロールにはDynamoDBを触れるようなIAMポリシーをアタッチしておく。
DynamoDBの準備
何でも良いが、↓こんな感じで用意しておく。
01_createDynamlDB.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
Create DynamoDB template for ApigwTest2
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2"
TableNameSuffix:
Description: "DynamoDB Table name suffix"
Type: "String"
Default: "-Table"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "DynamoDB configuration"
Parameters:
- TableNameSuffix
Resources:
# ------------------------------------------------------------#
# DynamoDB
# ------------------------------------------------------------#
DynamoDBTable:
Type: AWS::DynamoDB::Table
Properties:
TableName: !Sub ${Prefix}${TableNameSuffix}
KeySchema:
- AttributeName: id
KeyType: HASH
AttributeDefinitions:
- AttributeName: id
AttributeType: S
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
Tags:
- Key: Name
Value: !Sub ${Prefix}
イベントソースで対応する
SAMテンプレート中のAWS::Serverless::Function
の中でEvents
指定するのが一番お手軽なので試してみる。
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2-SetName"
LambdaFunctionNameSuffix:
Description: "Lambda function name suffix"
Type: "String"
Default: "-LambdaFunction"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "Lambda Configuration"
Parameters:
- LambdaFunctionNameSuffix
Globals:
Function:
Timeout: 60
Resources:
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
Handler: index.lambda_handler
Runtime: python3.7
MemorySize: 128
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
Events:
ApigwTest2:
Type: Api
Properties:
Method: put
Path: /name
InlineCode: |
import json
import pprint
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ApigwTest2-Table')
try:
response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name']})
except ClientError as e:
if e.response['Error']['Message'] == 'The provided key element does not match the schema':
response_statuscode = 404
else:
pprint.pprint(e.response['Error']['Message'])
response_statuscode = 500
else:
response_statuscode = 200
return {
'statusCode': response_statuscode,
'isBase64Encoded': 'false'
}
ポイントは以下の部分。
Events:
ApigwTest2:
Type: Api
Properties:
Method: put
Path: /name
これでAPIGatewayをイベントソースとして、以下の様に取り込むことができる。
だがしかし、この方法だと、APIGatewayの名前が勝手に決まってしまう。
※しかも、割と良い感じの名前になっているが、どうやって抽出しているのだろうか……
これでは、複数のLambdaを1つのAPIGatewayに統合できないので、別の手段が必要だ。
もう少し細かい話は以下のサイトに書いてある。
結局、公式に「変更はできない」と書いてある。ついでに、ステージはProdが適用されるからね!とも書いてあるように見える。
CloudFormation Resources Generated By SAM
たしかに、このSAMテンプレートをCloudFormationに流し込むと、見覚えのないリソースを勝手に作っていた。なるほどこういう仕組みだったのか。
ちゃんとRESTAPIのリソースを定義する
自動作成されるリソースなんぞには任せていられん!
ということで、以下の様にSAMテンプレートを修正する。
リソースを手動作成する場合、作ったAPIGatewayに対するLambdaのパーミッションを設定しなければいけない。
あとは、API GatewayとLambdaの紐付けをSwaggerで定義してあげないといけないようだ。
うーむ、なかなか面倒だ……。
SwaggerはAWS拡張形式で書く必要があるx-amazon-apigateway-integration
については、ちゃんと公式のドキュメントを読もう。PUTだとかGETを扱うからといって、プロキシのメソッドまで変えてしまうと動かなくなる。
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2-GetName"
LambdaFunctionNameSuffix:
Description: "Lambda function name suffix"
Type: "String"
Default: "-LambdaFunction"
APIGatewayName:
Description: "Lambda function name suffix"
Type: "String"
Default: "ApigwTest2"
APIGatewayStageName:
Description: "Lambda function name suffix"
Type: "String"
Default: "default"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "Lambda Configuration"
Parameters:
- LambdaFunctionNameSuffix
- Label:
default: "API Gateway Configuration"
Parameters:
- APIGatewayName
- APIGatewayStageName
Globals:
Function:
Timeout: 60
Resources:
# ------------------------------------------------------------#
# API Gateway
# ------------------------------------------------------------#
APIGateway:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${APIGatewayName}
StageName: !Sub ${APIGatewayStageName}
DefinitionBody:
swagger: "2.0"
info:
description: "Created by SAM template"
version: "1.0.0"
title: "ApiGatewayTest2"
basePath: "/default"
schemes:
- "https"
paths:
/names:
get:
produces:
- "application/json"
responses:
"200":
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
uri:
!Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
definitions:
Empty:
type: "object"
title: "Empty Schema"
Tags:
Key: Name
Value: !Sub ${APIGatewayName}
# ------------------------------------------------------------#
# Lambda Function
# ------------------------------------------------------------#
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
Handler: index.lambda_handler
Runtime: python3.7
MemorySize: 128
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
Events:
ApigwTest2:
Type: Api
Properties:
Path: /names
Method: get
RestApiId: !Ref APIGateway
InlineCode: |
import json
import pprint
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ApigwTest2-Table')
try:
response = table.get_item(Key={'id': event['queryStringParameters']['id']})
except ClientError as e:
response_statuscode = 500
pprint.pprint(e.response['Error']['Message'])
else:
if 'Item' not in response:
response_statuscode = 404
response_body = json.dumps({'name': 'null'})
else:
response_statuscode = 200
response_body =json.dumps({'name': response['Item']['name']})
return {
'statusCode': response_statuscode,
'body': response_body,
'isBase64Encoded': 'false'
}
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunction
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
だが、これで任意の名前でAPI Gatewayを作成することができるようになった。
でも、なぜかこれ、指定したステージ名じゃなくて勝手にStage
とかいうのまで作ってしまうんだよなぁ……バグなのか??
で、ここからが本題。
別のLambda関数のSAMテンプレートから、上手くこのAPI Gatewayを更新することができるだろうか。
これまで作ってきたDynamoDBにレコードを挿入するAPIに対して、同じAPI Gatewayにレコードを参照するAPIをぶら下げてみよう。
GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2-GetName"
LambdaFunctionNameSuffix:
Description: "Lambda function name suffix"
Type: "String"
Default: "-LambdaFunction"
APIGatewayName:
Description: "Lambda function name suffix"
Type: "String"
Default: "ApigwTest2"
APIGatewayStageName:
Description: "Lambda function name suffix"
Type: "String"
Default: "default"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "Lambda Configuration"
Parameters:
- LambdaFunctionNameSuffix
- Label:
default: "API Gateway Configuration"
Parameters:
- APIGatewayName
- APIGatewayStageName
Globals:
Function:
Timeout: 60
Resources:
# ------------------------------------------------------------#
# API Gateway
# ------------------------------------------------------------#
APIGateway:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${APIGatewayName}
StageName: !Sub ${APIGatewayStageName}
DefinitionBody:
swagger: "2.0"
info:
description: "Created by SAM template"
version: "1.0.0"
title: "ApiGatewayTest2"
basePath: "/default"
schemes:
- "https"
paths:
/names:
get:
produces:
- "application/json"
responses:
"200":
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
uri:
!Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
definitions:
Empty:
type: "object"
title: "Empty Schema"
Tags:
Key: Name
Value: !Sub ${APIGatewayName}
# ------------------------------------------------------------#
# Lambda Function
# ------------------------------------------------------------#
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
Handler: index.lambda_handler
Runtime: python3.7
MemorySize: 128
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
Events:
ApigwTest2:
Type: Api
Properties:
Path: /names
Method: get
RestApiId: !Ref APIGateway
InlineCode: |
import json
import pprint
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ApigwTest2-Table')
try:
response = table.get_item(Key={'id': event['queryStringParameters']['id']})
except ClientError as e:
response_statuscode = 500
pprint.pprint(e.response['Error']['Message'])
else:
if 'Item' not in response:
response_statuscode = 404
response_body = json.dumps({'name': 'null'})
else:
response_statuscode = 200
response_body =json.dumps({'name': response['Item']['name']})
return {
'statusCode': response_statuscode,
'body': response_body,
'isBase64Encoded': 'false'
}
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunction
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
残念なことに、同じAPI名が二つ作られてしまった。ガーン……。
APIGatewayのリソースを分離したSAMテンプレートにする
結論としては、この方法でやりたいことを実現できた。
以下の構成とする。
- GetName.yml … Lambda関数①と、Lambda関数へのアクセス許可を定義
- SetName.yml … Lambda関数②と、Lambda関数へのアクセス許可を定義
- APIGateway.yml … Lambda関数①②にアクセスするAPI Gateway(REST API)の定義
たぶん、API Gateway.ymlはCloudFormationテンプレートでもTerraformのHCLでも作ることは可能だが、色々と面倒な手間を省くことを考えると、ここもSAMにしてしまった方が手っ取り早いと思う。
Lambda関数の定義
以下の様にSAMテンプレートを定義する。
テンプレート中のDeploymentPreference
プロパティで指定しているCodeDeploy.LambdaCanaryHalf3minutes
はデフォルトには無い。検証のために今回作成しているので、ここは適当なものを作っておく。
GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2-GetName"
LambdaFunctionNameSuffix:
Description: "Lambda function name suffix"
Type: "String"
Default: "-LambdaFunction"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "Lambda Configuration"
Parameters:
- LambdaFunctionNameSuffix
Globals:
Function:
Timeout: 60
Resources:
# ------------------------------------------------------------#
# Lambda Function
# ------------------------------------------------------------#
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
Description: Created by SAM template
Handler: index.lambda_handler
Runtime: python3.7
MemorySize: 128
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
AutoPublishAlias: Prod
DeploymentPreference:
Type: CodeDeploy.LambdaCanaryHalf3minutes
InlineCode: |
import json
import pprint
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ApigwTest2-Table')
try:
response = table.get_item(Key={'id': event['queryStringParameters']['id']})
except ClientError as e:
response_statuscode = 500
pprint.pprint(e.response['Error']['Message'])
else:
if 'Item' not in response:
response_statuscode = 404
response_body = json.dumps({'name': 'null'})
else:
response_statuscode = 200
response_body =json.dumps({'name': response['Item']['name']})
return {
'statusCode': response_statuscode,
'body': response_body,
'isBase64Encoded': 'false'
}
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunctionAliasProd
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
Outputs:
LambdaFunction:
Description: ApigwTest2 GetName Lambda function ARN
Value: !Ref LambdaFunctionAliasProd
Export:
Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2-SetName"
LambdaFunctionNameSuffix:
Description: "Lambda function name suffix"
Type: "String"
Default: "-LambdaFunction"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "Lambda Configuration"
Parameters:
- LambdaFunctionNameSuffix
Globals:
Function:
Timeout: 60
Resources:
# ------------------------------------------------------------#
# Lambda Function
# ------------------------------------------------------------#
LambdaFunction:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
Description: Created by SAM template
Handler: index.lambda_handler
Runtime: python3.7
MemorySize: 128
Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
AutoPublishAlias: Prod
DeploymentPreference:
Type: CodeDeploy.LambdaCanaryHalf3minutes
InlineCode: |
import json
import pprint
import boto3
from botocore.exceptions import ClientError
def lambda_handler(event, context):
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('ApigwTest2-Table')
try:
response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
except ClientError as e:
if e.response['Error']['Message'] == 'The provided key element does not match the schema':
response_statuscode = 404
else:
pprint.pprint(e.response['Error']['Message'])
response_statuscode = 500
else:
response_statuscode = 200
return {
'statusCode': response_statuscode,
'isBase64Encoded': 'false'
}
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunctionAliasProd
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
Outputs:
LambdaFunction:
Description: ApigwTest2 SetName Lambda function ARN
Value: !Ref LambdaFunctionAliasProd
Export:
Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
ポイントは
LambdaInvokePermission:
Type: AWS::Lambda::Permission
Properties:
FunctionName: !Ref LambdaFunctionAliasProd
Action: 'lambda:InvokeFunction'
Principal: apigateway.amazonaws.com
の部分。SAMテンプレートでは、AWS::Serverless::Function
のEvent
プロパティについて、中で良い感じにAPI Gatewayのリソースを作ってアクセス許可も与えるように動いてくれる(というのがTransform: AWS::Serverless-2016-10-31
の正体。こういう便利なことをやってくれる反面、本質的な動作が分かりにくくなる……)が、今回はAPI Gatewayのリソースを切り出しているので、自力でアクセス許可を与える必要がある。
参照しているLambdaFunctionAliasProd
というリソースは自分では定義していないが、これもSAMテンプレートの変換の中で良い感じに動いてくれる部分。詳細は以前この記事に書いた。
また、API Gateway側で作ったLambda関数のARNが必要になるため、
Outputs:
LambdaFunction:
Description: ApigwTest2 GetName Lambda function ARN
Value: !Ref LambdaFunctionAliasProd
Export:
Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn
で参照可能な状態にしておく。
実行は、
$ aws cloudformation deploy --template-file GetName.yml --stack-name ApigwTest2-GetName --capabilities CAPABILITY_IAM
$ aws cloudformation deploy --template-file SetName.yml --stack-name ApigwTest2-SetName --capabilities CAPABILITY_IAM
な感じで行う。アクセス許可を変更するために、--capabilities CAPABILITY_IAM
が必要。
API Gateway(REST API)の定義
たいして難しいわけではない。
x-amazon-apigateway-integration
で、前節のLambda関数のARNを表現するのが少し面倒なくらい。
APIGateway.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description:
Create APIGateway template for ApigwTest2
Parameters:
Prefix:
Description: "Project name prefix"
Type: "String"
Default: "ApigwTest2"
RESTApiNameSuffix:
Description: "REST API name suffix"
Type: "String"
Default: "-RESTAPI"
RESTApiStageName:
Description: "REST API stage name suffix"
Type: "String"
Default: "default"
GetLambdaFunctionNameSuffix:
Description: "Lambda Function name suffix"
Type: "String"
Default: "-GetName-LambdaFunction"
SetLambdaFunctionNameSuffix:
Description: "Lambda Function name suffix"
Type: "String"
Default: "-SetName-LambdaFunction"
Metadata:
AWS::CloudFormation::Interface:
ParameterGroups:
- Label:
default: "Project name prefix"
Parameters:
- Prefix
- Label:
default: "API Gateway configuration"
Parameters:
- RESTApiNameSuffix
- RESTApiStageName
- GetLambdaFunctionNameSuffix
- SetLambdaFunctionNameSuffix
Resources:
# ------------------------------------------------------------#
# API Gateway
# ------------------------------------------------------------#
APIGATEWAY:
Type: AWS::Serverless::Api
Properties:
Name: !Sub ${Prefix}${RESTApiNameSuffix}
StageName: !Sub ${RESTApiStageName}
DefinitionBody:
swagger: "2.0"
info:
description: "Created by CloudFormation template"
version: "1.0.0"
title: "ApiGatewayTest2"
basePath: "/default"
schemes:
- "https"
paths:
/names:
get:
produces:
- "application/json"
responses:
"200":
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetLambdaFunctionArn}/invocations
- GetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${GetLambdaFunctionNameSuffix}-Arn'}
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
/name:
put:
produces:
- "application/json"
responses:
"200":
description: "200 response"
schema:
$ref: "#/definitions/Empty"
x-amazon-apigateway-integration:
uri: !Sub
- arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SetLambdaFunctionArn}/invocations
- SetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${SetLambdaFunctionNameSuffix}-Arn'}
passthroughBehavior: when_no_templates
httpMethod: POST
type: aws_proxy
definitions:
Empty:
type: "object"
title: "Empty Schema"
EndpointConfiguration: REGIONAL
Tags:
Key: Name
Value: !Sub ${Prefix}
これで
$ aws cloudformation deploy --stack-name ApigwTest2-APIGateway --template-file APIGateway.yml
すると、以下のように複数のパス・メソッドを定義したAPI Gatewayを作れる。
アプリケーションのデプロイを行ってみる
例えば、以下のような感じでアプリケーションを修正する。
65c64
< response_body = json.dumps({'name': 'null'})
---
> response_body = json.dumps({'name': 'null', 'version': 'null'})
68c67
< response_body =json.dumps({'name': response['Item']['name']})
---
> response_body =json.dumps({'name': response['Item']['name'], 'version': response['Item']['version']})
58c58
< response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
---
> response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '2.0'})
それぞれ、もう一度 aws cloudformation deploy
で確認してみると、ちゃんとCanaryなリリースが行われているし、それぞれの機能やデプロイにも干渉しなかった。
SAMテンプレートで完結しないもやっと感はあるが、アプリケーションの大幅改修が無い限りは、API GatewayのREST API定義とのライフサイクルが異なる気がするので、これで特に大きな問題にはならないだろう。