RDS DBインスタンスを暗号化する – Step Functionsバージョン
暗号化していないRDS DBインスタンスを暗号化する方法について考えます。
AWS公式では、以下のように説明されています。
Amazon RDS DB インスタンスは、DB インスタンスの作成時にのみ暗号化できます。作成後には暗号化できません。
ただし、暗号化されていないスナップショットのコピーは暗号化できるので、暗号化されていない DB インスタンスに効果的に暗号化を追加できます。つまり、DB インスタンスのスナップショットを作成し、そのスナップショットの暗号化済みコピーを作成します。この暗号化されたスナップショットから DB インスタンスを復元することで、元の DB インスタンスの暗号化されたコピーを作成できます。
Amazon RDS の暗号化された DB インスタンスの制限事項
今回はStep Functionsを使用して、上記の手続きを実現します。
構築する環境
以下を順番に実施するStep Functionsステートマシンを作成します。
- 暗号化していないDBインスタンスのスナップショットを作成する。
- 作成したスナップショットのステータスを確認し、「available」であることを確認する。
- スナップショットのコピーを作成する。コピー作成時に、暗号化オプションを有効化する。
- 作成したスナップショットのステータスを確認し、「available」であることを確認する。
- 暗号化済みのスナップショットから新しいDBインスタンスを作成する。
手順の中で、2回スナップショットのステータスを確認します。
これは作成直後のスナップショットのステータスは「creating」であるためです。
このステータスでは、スナップショットのコピーや、DBインスタンスのリストアは不可です。
上記に対応するために、ChoiceステートおよびWaitステートを使用します。
ステータスが「available」の場合は、次のステートに進めます。
ステータスが「available」ではない場合は、Waitステートに移動し、待機します。
待機後は、再びステータスをチェックします。
ステートマシンを構成するLambda関数はPython3.8を使用します。
EC2インスタンスを作成します。
元々のDBインスタンスや、暗号化済みのDBインスタンスにアクセスするために使用します。
このインスタンスのOSは、最新のAmazon Linux 2とします。
CloudFormationテンプレートファイル
上記の構成をCloudFormationで構築します。
以下のURLにCloudFormationテンプレートを配置しています。
https://github.com/awstut-an-r/awstut-soa/tree/main/04/005
テンプレートファイルのポイント解説
Step Functionsステートマシン
Resources:
StateMachine:
Type: AWS::StepFunctions::StateMachine
Properties:
Definition:
Comment: !Sub "${Prefix}-StateMachine"
StartAt: CreateSnapshotState
States:
CreateSnapshotState:
Type: Task
Resource: !Ref FunctionArn1
Next: DescribeSnapshotState1
DescribeSnapshotState1:
Type: Task
Resource: !Ref FunctionArn2
Parameters:
snapshot_id.$: $.no_encrypted_snapshot_id
ResultPath: $.no_encrypted_snapshot_status
Next: ChoiceState1
ChoiceState1:
Type: Choice
Choices:
- Not:
Variable: $.no_encrypted_snapshot_status
StringEquals: !Ref SnapshotAvailableStatus
Next: WaitState1
- Variable: $.no_encrypted_snapshot_status
StringEquals: !Ref SnapshotAvailableStatus
Next: CopySnapshotState
WaitState1:
Type: Wait
Seconds: !Ref WaitSeconds
Next: DescribeSnapshotState1
CopySnapshotState:
Type: Task
Resource: !Ref FunctionArn3
Parameters:
instance_id.$: $.instance_id
snapshot_id.$: $.no_encrypted_snapshot_id
ResultPath: $.encrypted_snapshot_id
Next: DescribeSnapshotState2
DescribeSnapshotState2:
Type: Task
Resource: !Ref FunctionArn2
Parameters:
snapshot_id.$: $.encrypted_snapshot_id
ResultPath: $.encrypted_snapshot_status
Next: ChoiceState2
ChoiceState2:
Type: Choice
Choices:
- Not:
Variable: $.encrypted_snapshot_status
StringEquals: !Ref SnapshotAvailableStatus
Next: WaitState2
- Variable: $.encrypted_snapshot_status
StringEquals: !Ref SnapshotAvailableStatus
Next: RestoreDbInstanceState
WaitState2:
Type: Wait
Seconds: !Ref WaitSeconds
Next: DescribeSnapshotState2
RestoreDbInstanceState:
Type: Task
Resource: !Ref FunctionArn4
Parameters:
availability_zone.$: $.availability_zone
db_subnet_group_name.$: $.db_subnet_group_name
instance_id.$: $.instance_id
security_group_id.$: $.security_group_id
snapshot_id.$: $.encrypted_snapshot_id
ResultPath: $.encrypted_instance_id
End: true
LoggingConfiguration:
Destinations:
- CloudWatchLogsLogGroup:
LogGroupArn: !GetAtt LogGroup.Arn
IncludeExecutionData: true
Level: ALL
RoleArn: !GetAtt StateMachineRole.Arn
StateMachineName: !Ref Prefix
StateMachineType: STANDARD
LogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub "${Prefix}-StateMachineLogGroup"
StateMachineRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action: sts:AssumeRole
Principal:
Service:
- states.amazonaws.com
Policies:
- PolicyName: !Sub "${Prefix}-InvokeTaskFunctions"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- lambda:InvokeFunction
Resource:
- !Ref FunctionArn1
- !Ref FunctionArn2
- !Ref FunctionArn3
- !Ref FunctionArn4
- PolicyName: !Sub "${Prefix}-DeliverToCloudWatchLogPolicy"
PolicyDocument:
Version: 2012-10-17
Statement:
- Effect: Allow
Action:
- logs:CreateLogDelivery
- logs:GetLogDelivery
- logs:UpdateLogDelivery
- logs:DeleteLogDelivery
- logs:ListLogDeliveries
- logs:PutLogEvents
- logs:PutResourcePolicy
- logs:DescribeResourcePolicies
- logs:DescribeLogGroups
Resource: "*"
Code language: YAML (yaml)
Step Functionsステートマシンの基本的な事項については、以下のページをご確認ください。
1つ目のステート(CreateSnapshotState)
このステートはDBインスタンスのスナップショットを作成します。
Resourcesプロパティで実行するLambda関数を設定します。
本ステートでは、以下のLambda関数1を指定します。
Resources:
Function1:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
REGION: !Ref AWS::Region
Code:
ZipFile: |
import boto3
import os
region = os.environ['REGION']
client = boto3.client('rds', region_name=region)
def lambda_handler(event, context):
instance_id = event['instance_id']
response1 = client.describe_db_instances(
DBInstanceIdentifier=instance_id
)
response2 = client.create_db_snapshot(
DBSnapshotIdentifier='{instance}-no-encrypted'.format(instance=instance_id),
DBInstanceIdentifier=instance_id
)
return {
'instance_id': instance_id,
'availability_zone': response1['DBInstances'][0]['AvailabilityZone'],
'db_subnet_group_name': response1['DBInstances'][0]['DBSubnetGroup']['DBSubnetGroupName'],
'security_group_id': response1['DBInstances'][0]['VpcSecurityGroups'][0]['VpcSecurityGroupId'],
'no_encrypted_snapshot_id': response2['DBSnapshot']['DBSnapshotIdentifier'],
}
FunctionName: !Sub "${Prefix}-function1"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
本関数は引数として、instance_idを取ります。
これは暗号化されていないDBインスタンスのIDです。
describe_db_instancesおよびcreate_db_snapshotメソッドを実行します。
前者は既存DBインスタンスに関する情報を取得します。
具体的には、DBインスタンスに設定されているセキュリティグループ、DBサブネットグループ、インスタンスが配置されているAZです。
後者はDBインスタンスのスナップショットを作成します。
冒頭の引用の通り、まだこのスナップショットは暗号化されていません。
DBインスタンスの情報と作成したスナップショットのIDを、辞書にまとめて戻り値とします。
これらの値をステートマシンは保持します。
例えば以下のようなデータを保持することになります。
{
"availability_zone": "ap-northeast-1d",
"instance_id": "soa-04-005",
"no_encrypted_snapshot_id": "soa-04-005-no-encrypted",
"db_subnet_group_name": "dbsubnetgroup",
"security_group_id": "sg-09dfb82d21e4846c4"
}
Code language: JSON / JSON with Comments (json)
2つ目のステート(DescribeSnapshotState1)
このステートは前ステートで作成したスナップショットのステータスを確認します。
Lambda関数2を実行します。
Parameterプロパティで、関数に渡すパラメータを指定できます。
例えば以下のようなデータを渡します。
{
"snapshot_id": "soa-04-005-no-encrypted"
}
Code language: JSON / JSON with Comments (json)
渡すデータの値は、前ステートで保持したものを参照しています。
以下の関数を実行します。
Resources:
Function2:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
REGION: !Ref AWS::Region
Code:
ZipFile: |
import boto3
import os
region = os.environ['REGION']
client = boto3.client('rds', region_name=region)
def lambda_handler(event, context):
snapshot_id = event['snapshot_id']
response = client.describe_db_snapshots(
DBSnapshotIdentifier=snapshot_id
)
return response['DBSnapshots'][0]['Status']
FunctionName: !Sub "${Prefix}-function2"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
describe_db_snapshotsメソッドを実行して、スナップショットのステータスを確認します。
先ほど確認したように、引数としてスナップショットIDを渡して、同メソッドを呼び出します。
取得したステータス情報を返します。
ステートマシンのResultPathプロパティで、関数から渡されたデータの受け取り方を設定できます。
今回はno_encrypted_snapshot_statusというキーに、ステータス情報を格納します。
これを受けて、ステートマシンが保持するデータが更新されます。
以下がその具体例です。
{
"availability_zone": "ap-northeast-1d",
"instance_id": "soa-04-005",
"no_encrypted_snapshot_id": "soa-04-005-no-encrypted",
"db_subnet_group_name": "dbsubnetgroup",
"security_group_id": "sg-09dfb82d21e4846c4",
"no_encrypted_snapshot_status": "creating"
}
Code language: JSON / JSON with Comments (json)
3つ目・4つ目のステート(ChoiceState1, WaitState1)
これらのステートは、前ステートと連携して動作します。
現在のスナップショットのステータスが「creating」等の状態であれば、しばらく待機します。
改めてステータスをチェックし、「available」であれば、次のステートに進みます。
上記の挙動を、ChoiceステートのStringEqualsおよびNotプロパティ、Waitステートで実現します。
Choice・Waitステートを組み合わせて、特定の条件を満たすまでループ処理する方法については、以下のページをご確認ください。
今回は作成したスナップショットのステータスに応じて条件分岐します。
ですから先ほどステートマシンに保持させたno_encrypted_snapshot_statusを参照します。
5つ目のステート(CopySnapshotState)
このステートは作成したスナップショットを暗号化を有効化した上でコピーします。
Lambda関数3を実行します。
Parameterプロパティで、例えば以下のようなデータを渡します。
{
"instance_id": "soa-04-005",
"snapshot_id": "soa-04-005-no-encrypted"
}
Code language: JSON / JSON with Comments (json)
先述のインスタンスのIDと、作成が完了したスナップショットのIDです。
以下の関数を実行します。
Resources:
Function3:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
REGION: !Ref AWS::Region
KMS_KEY_ID: !Sub "arn:aws:kms:${AWS::Region}:${AWS::AccountId}:alias/aws/rds"
Code:
ZipFile: |
import boto3
import os
region = os.environ['REGION']
kms_key_id = os.environ['KMS_KEY_ID']
client = boto3.client('rds', region_name=region)
def lambda_handler(event, context):
instance_id = event['instance_id']
snapshot_id = event['snapshot_id']
response = client.copy_db_snapshot(
SourceDBSnapshotIdentifier=snapshot_id,
TargetDBSnapshotIdentifier='{instance}-encrypted'.format(instance=instance_id),
KmsKeyId=kms_key_id,
)
return response['DBSnapshot']['DBSnapshotIdentifier']
FunctionName: !Sub "${Prefix}-function3"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
copy_db_snapshotメソッドを使用して、スナップショットをコピーします。
ポイントはこのメソッドを呼び出す際の引数KmsKeyIdです。
これを指定することで、暗号化が有効化になります。
このパラメータには、暗号化に使用するKMSキーを指定します。
今回はRDS用のAWSマネージドキーを使用します。
コピーして新たに作成されるスナップショットのIDを戻り値にします。
ステートマシンにおいて、encrypted_snapshot_idというキーに、スナップショットIDを格納します。
これを受けて、ステートマシンが保持するデータが更新されます。
以下がその具体例です。
{
"availability_zone": "ap-northeast-1d",
"instance_id": "soa-04-005",
"no_encrypted_snapshot_id": "soa-04-005-no-encrypted",
"db_subnet_group_name": "dbsubnetgroup",
"security_group_id": "sg-09dfb82d21e4846c4",
"no_encrypted_snapshot_status": "creating",
"encrypted_snapshot_id": "soa-04-005-encrypted"
}
Code language: JSON / JSON with Comments (json)
6〜8つ目のステート(DescribeSnapshotState2, ChoiceState2, WaitState2)
先ほどと同じ流れです。
暗号化を有効化したスナップショットに対してステータスチェックを行い、「creating」の場合は、一時停止します。
停止後、改めてステータスチェックを実施し、「available」の場合は次のステートに進みます。
9つ目のステート(RestoreDbInstanceState)
このステートは暗号化済みのスナップショットから、DBインスタンスを作成します。
Lambda関数4を実行します。
Parameterプロパティで、例えば以下のようなデータを渡します。
{
"availability_zone": "ap-northeast-1d",
"instance_id": "soa-04-005",
"db_subnet_group_name": "dbsubnetgroup",
"security_group_id": "sg-09dfb82d21e4846c4",
"snapshot_id": "soa-04-005-encrypted"
}
Code language: JSON / JSON with Comments (json)
ポイントはsnapsho_idの値です。
こちらに暗号化済みのスナップショットIDを指定することで、これからDBインスタンスが作成されます。
以下の関数を実行します。
Resources:
Function4:
Type: AWS::Lambda::Function
Properties:
Environment:
Variables:
REGION: !Ref AWS::Region
Code:
ZipFile: |
import boto3
import os
region = os.environ['REGION']
client = boto3.client('rds', region_name=region)
def lambda_handler(event, context):
instance_id = event['instance_id']
snapshot_id = event['snapshot_id']
availability_zone = event['availability_zone']
db_subnet_group_name = event['db_subnet_group_name']
security_group_id = event['security_group_id']
response = client.restore_db_instance_from_db_snapshot(
DBInstanceIdentifier='{instance}-encrypted'.format(instance=instance_id),
DBSnapshotIdentifier=snapshot_id,
AvailabilityZone=availability_zone,
DBSubnetGroupName=db_subnet_group_name,
VpcSecurityGroupIds=[
security_group_id,
]
)
return response['DBInstance']['DBInstanceIdentifier']
FunctionName: !Sub "${Prefix}-function4"
Handler: !Ref Handler
Runtime: !Ref Runtime
Role: !GetAtt FunctionRole.Arn
Timeout: !Ref Timeout
Code language: YAML (yaml)
restore_db_instance_from_db_snapshotメソッドを実行して、スナップショットからDBインスタンスを作成します。
戻り値には、作成されたDBインスタンスのIDを指定します。
ステートマシンにおいて、encrypted_instance_idというキーに、DBインスタンスのIDを格納します。
これを受けて、ステートマシンが保持するデータが更新されます。
以下がその具体例です。
{
"availability_zone": "ap-northeast-1d",
"instance_id": "soa-04-005",
"no_encrypted_snapshot_id": "soa-04-005-no-encrypted",
"db_subnet_group_name": "dbsubnetgroup",
"security_group_id": "sg-09dfb82d21e4846c4",
"no_encrypted_snapshot_status": "creating",
"encrypted_snapshot_id": "soa-04-005-encrypted",
"encrypted_instance_id": "soa-04-005-encrypted"
}
Code language: JSON / JSON with Comments (json)
(参考) RDS
Resources:
DBInstance:
Type: AWS::RDS::DBInstance
DeletionPolicy: Delete
Properties:
AllocatedStorage: !Ref DBAllocatedStorage
AvailabilityZone: !Sub "${AWS::Region}${AvailabilityZone}"
DBInstanceClass: !Ref DBInstanceClass
DBInstanceIdentifier: !Ref Prefix
DBSubnetGroupName: !Ref DBSubnetGroup
Engine: !Ref DBEngine
EngineVersion: !Ref DBEngineVersion
MasterUsername: !Ref DBMasterUsername
MasterUserPassword: !Ref DBMasterUserPassword
StorageEncrypted: false
VPCSecurityGroups:
- !Ref DBSecurityGroup
DBSubnetGroup:
Type: AWS::RDS::DBSubnetGroup
Properties:
DBSubnetGroupName: dbsubnetgroup
DBSubnetGroupDescription: testgroup.
SubnetIds:
- !Ref DBSubnet1
- !Ref DBSubnet2
Code language: YAML (yaml)
特別な設定は行いません。
DBインスタンスを作成します。
(参考) EC2
Resources:
Instance:
Type: AWS::EC2::Instance
Properties:
IamInstanceProfile: !Ref InstanceProfile
ImageId: !Ref ImageId
InstanceType: !Ref InstanceType
NetworkInterfaces:
- DeviceIndex: 0
SubnetId: !Ref InstanceSubnet
GroupSet:
- !Ref InstanceSecurityGroup
UserData: !Base64 |
#!/bin/bash -xe
yum update -y
yum install -y mariadb
Code language: YAML (yaml)
ユーザデータで、初期化処理を定義します。
ユーザデータに関する詳細については、以下のページをご確認ください。
MariaDBをインストールすることで、DB接続用のクライアントを準備します。
詳細につきましては、以下のページをご確認ください。
環境構築
CloudFormationを使用して、本環境を構築し、実際の挙動を確認します。
CloudFormationスタックを作成し、スタック内のリソースを確認する
CloudFormationスタックを作成します。
スタックの作成および各スタックの確認方法については、以下のページをご確認ください。
各スタックのリソースを確認した結果、今回作成された主要リソースの情報は以下の通りです。
- EC2インスタンス:i-0c948c4d3fa569462
- DBインスタンス:soa-04-005
- DBインスタンスのエンドポイント:soa-04-005.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com
- Lambda関数1:soa-04-005-function1
- Lambda関数2:soa-04-005-function2
- Lambda関数3:soa-04-005-function3
- Lambda関数4:soa-04-005-function4
- Step Functionsステートマシン:soa-04-005
AWS Management ConsoleからDBインスタンスを確認します。
Storageを見ると、Encryptionが「Not enabled」となります。
つまり暗号化されていません。
Step Functionsステートマシンを確認します。
確かにステートマシンが作成されています。
ステートマシンの流れがグラフとして表現されています。
動作確認
暗号化していないDBインスタンスに接続
準備が整いましたので、EC2インスタンスにアクセスします。
EC2インスタンスへのアクセスはSSM Session Managerを使用します。
% aws ssm start-session --target i-0c948c4d3fa569462
...
sh-4.2$
Code language: Bash (bash)
SSM Session Managerの詳細につきましては、以下のページをご確認ください。
MySQLクライアントのインストール状況を確認します。
sh-4.2$ mysql -V
mysql Ver 15.1 Distrib 5.5.68-MariaDB, for Linux (aarch64) using readline 5.1
Code language: Bash (bash)
確かにユーザデータによって、MySQLクライアントがインストールされています。
このクライアントパッケージを使用して、DBインスタンスに接続します。
sh-4.2$ mysql -h soa-04-005.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com -P 3306 -u testuser -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 18
Server version: 8.0.28 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
Code language: Bash (bash)
接続できました。
試しにテスト用データベースおよびテーブルを作成し、テストデータを保存します。
MySQL [(none)]> CREATE database test;
Query OK, 1 row affected (0.01 sec)
MySQL [(none)]> use test;
Database changed
MySQL [test]> CREATE TABLE planet (id INT UNSIGNED AUTO_INCREMENT, name VARCHAR(30), PRIMARY KEY(id));
Query OK, 0 rows affected (0.02 sec)
MySQL [test]> INSERT INTO planet (name) VALUES ("Mercury");
Query OK, 1 row affected (0.00 sec)
MySQL [test]> select * from planet;
+----+---------+
| id | name |
+----+---------+
| 1 | Mercury |
+----+---------+
1 row in set (0.00 sec)
Code language: Bash (bash)
テストデータを書き込むことができました。
Step Functionsステートマシン実行
ステートマシンを実行します。
Inputに引数を設定します。
引数には暗号化されていないDBインスタンスのIDを指定します。
Start executionを押下します。
ステートマシンの実行が開始されました。
これから各ステートが順番に実行されます。
WaitState1で一時停止しました。
Inputを見ると、no_encypted_snapshot_statusの値が「creating」とあります。
つまりDBインスタンスから作成されたスナップショットのステータスが「available」に変更することを待っている状態です。
このスナップショットの詳細を確認します。
Statusを見ると、確かに「creating」とあります。
なおKMS key IDが設定されていないため、暗号化されていません。
しばらく待機すると、処理が次のステートに移行します。
WaitState2で一時停止しました。
Inputを見ると、encypted_snapshot_statusの値が「creating」とあります。
このスナップショットは暗号化オプションを有効化した上で、先ほどのスナップショットをコピーして作成したものです。
これのステータスが「available」に変更することを待っている状態です。
このスナップショットの詳細を確認します。
Statusを見ると、確かに「creating」とあります。
なおKMS key IDが設定されているため、暗号化されています。
しばらく待機すると、全てのステートの処理が完了します。
暗号化済みのスナップショットから新しいDBインスタンスが作成されました。
DBインスタンスのIDは「soa-04-005-encrypted」です。
暗号化済みDBインスタンス
新たに作成されたDBインスタンスを確認します。
Encryptionを見ると「enabled」とあります。
このことから確かに暗号化されていることがわかります。
最後にこのDBインスタンスにアクセスします。
sh-4.2$ mysql -h soa-04-005-encrypted.cl50iikpthxs.ap-northeast-1.rds.amazonaws.com -P 3306 -u testuser -p
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MySQL connection id is 9
Server version: 8.0.28 Source distribution
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MySQL [(none)]>
Code language: Bash (bash)
正常にログインできました。
保存されているデータを確認します。
MySQL [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| sys |
| test |
+--------------------+
5 rows in set (0.00 sec)
MySQL [(none)]> use test;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
MySQL [test]>
MySQL [test]> show tables;
+----------------+
| Tables_in_test |
+----------------+
| planet |
+----------------+
1 row in set (0.01 sec)
MySQL [test]> select * from planet;
+----+---------+
| id | name |
+----+---------+
| 1 | Mercury |
+----+---------+
1 row in set (0.00 sec)
Code language: Bash (bash)
データが保存されていました。
これは暗号化前に保存していたものです。
以上より、暗号化していないDBインスタンスと同じ内容を持つ、暗号化されたDBインスタンスを作成できました。
まとめ
Step Functionsを使用して、暗号化していないRDS DBインスタンスを暗号化する方法をご紹介しました。