ソフトウェアの「手の内化」を推し進めるサステナブルな学習環境を内製化してみた
Author : 濵﨑 智宏 (株式会社デンソー)
こんにちは、株式会社デンソーの濵﨑です。
私はこれまで AWS が主催する、AWS GameDay や AWS Jam に参加し、研鑽を積んできました。初めて、参加したのは 2019 年 6 月に AWS Loft Tokyo で開催された AWS GameDay です。結果はさて置き。AWS Loft Tokyo から新幹線で名古屋に戻り、里帰り中の妻から陣痛が来たと連絡を受け、翌朝、始発で山口に帰りましたが、出産に間に合わなかったことは今でも鮮明に覚えています。その後、2020 年にロシアで開催された技能五輪国際大会にて、クラウドコンピューティング職種の日本代表選手として参加し、銅メダルを獲得することができました。
このような経験を社内の他のメンバーにも共有したいとの思いから、社内で有志を集め、DX 人材強化策の一環として、社内コンテストを企画し、AWS Jam を参考にしたコンテストシステムを開発し、2022 年 2 月 19 日に開催された社内イベントで社員 108 名に提供しました。この記事では、このコンテストシステムについて紹介します。
「手の内化」とは
「手の内化」という言葉をご存じでしょうか ? トヨタグループで使われている言葉です。
「先ずは自らやってみて、原理原則を理解すること、そして現場で改善を繰り返し、競争力を高めること」と紹介されています。私は「手の内化」はトヨタグループの基本姿勢であると理解し、ソフトウェアにおいても、他社に任せっきりになることなく、手の内化を進めるべきと考え、コンテストシステムの内製化に挑戦しました。

コンテストシステム「Minoru」
このシステムは「Minoru」と名付けました。
その由来は初めて自分で作ったシステムであり、今後も育てていく、という思いから息子の「実」の名前を取りました。また、「Minoru」を使って学習した人の努力が「実る」とも掛けています
「Minoru」の今後の展望としては、利用者の方が任意のタイミングで自分用の環境を構築し、公開されている課題にチャレンジすることができ、また、利用者自身でも課題を開発し、それを共有できるようにすることで、自然とバリエーションが増えることを期待しています。これによって「サステナブルな」学習環境を構築していくことができると考えています。
AWS GameDay / AWS Jam とは
皆さん、AWS GameDay や AWS Jam には参加したことがありますか ? それぞれ、AWS のイベントなどで開催されるコンテキスト形式の学習コンテンツです。
コロナ禍の現在、オンラインでの開催になっていますが、私が参加した当時は、AWS Loft などに集まって、初対面の方数人とチームを組んで参加する形でした。リアルタイムに更新されるスコアを見ながら、協力してプレイすることは、エキサイティングで楽しかったです。それぞれ下記のようなコンテンツになっています。
AWS GameDay
与えられたシステムと手順書を元に実際のサービスを AWS 上で運用します。途中で様々な障害などが発生し、チームでそれらに対処し、システムを安定的に高いパフォーマンスで運用できると高得点が取れるようになっています。お題に沿って自ら考えてシステムの構築、障害対応、パフォーマンス改善、自動化などをする必要があり、それらを通して、AWS のベストプラクティスを学ぶことができます。
AWS Jam
様々なユースケースをお題として、小さな課題が複数用意されています。課題を解決することで得点を得ることができ、その得点を競います。使うと獲得できる点数は減ってしまいますが、Hint も用意されています。普段触ることのない AWS サービスに触ったり新しいスキルを身に着けることができます。
私は、AWS GameDay や AWS Jam は AWS やシステム運用のスキルを深めるために非常に有効な学習コンテンツだと思います。しかし、これらは常時開催されているものではなく、経験できるチャンスは年に数回しかありません。この点についても内製化に踏み切る動機になりました。
1. アーキテクチャ概要
「Minoru」の構築に利用するオペレーション環境には AWS Cloud9 を利用しました。
「Minoru」の大半は、AWS Serverless Application Model (AWS SAM) を利用して構築することができます。Cloud9 にソースコードを展開後、sam packageと sam deploy を実行することで環境構築が完了するようにしています
次に AWS CLI を利用して Amazon S3 へ課題のドキュメントで参照する画像をアップロードし、AWS SDK for Python (Boto3) を利用して Amazon DynamoDB へ課題のドキュメントや得点などのデータを挿入します。
私は、シンプルな操作は AWS CLI を、複雑な操作は AWS SDK を利用するようにしています。 AWS CLI は、aws s3 help と打つことで手元でヘルプを見ることができ、さっと目的を達成したいときに向いています。しかし、複雑な処理を行いたい時や失敗する可能性もあるときには、AWS SDK を使うと便利です。AWS SDK は API 呼び出しのリトライ機構が組み込まれており、言語ごとの例外処理機構を使ったエラーハンドリングも実装しやすくなっています。

2. 使用している技術
- 開発運用環境 : AWS Cloud9、シェルスクリプト、Python スクリプト、Docker、AWS Serverless Application Model (AWS SAM)
- バックエンド
- Webアプリケーション : Flask + Jinja2 + AWS SDK for Python (Boto3)
- REST API : boto3 (Python)
- フロントエンド : JavaScript、Bootstrap
フロントエンドの開発に経験がなくかなり苦戦し時間をかけてしまいましたが、最終的には、他の方に助けてもらいかっこよくしてもらいました。
3. 工夫したポイント
ここからは、「Minoru」の開発にあたり、工夫したポイントを 3 つ紹介します。
- 複数アカウントでのデプロイ管理
- 同一 AWS アカウント内に複数の課題を共存させ、権限により分離する
- クリーンアップ
3-1. 複数アカウントでのデプロイ管理
AWS Jam は様々な難易度設定がされた小さな課題が複数用意されています。「Minoru」でも同様の構成をとっています。各課題は AWS CloudFormation を利用して初期構成を行いました。
今回は 40 の AWS アカウントにそれぞれ 20 個の課題を用意していたので合計 800 の CloudFormation スタックを作成する必要がありました。1 つのスタックから 800 個のスタックを順番に作っていると日が暮れてしまいます。そこで、Docker を利用した並列デプロイの仕組みを作成しました。1 つの課題用スタックをデプロイするコンテナはメモリーが 70 MB 程度必要でしたが、t3.2xlarge を利用すると 400 個以上のコンテナを同時に実行できるので 800 スタックをデプロイするのに 30 分もかかりませんでした。

AWS Serverless Application Model (AWS SAM)と Docker を利用し、コンテナごとに AWS 認証情報を渡して AWS SAM CLI を実行することで、各課題に利用するソースの可搬性を保ちながら並列デプロイを実現することができました。
テスト時には、EC2 インスタンスの空きメモリーに関係なくコンテナを起動したため、Cloud9 の接続は切れ、コンテナは潰れ、大失敗しました。そこで、メモリーを監視しながらコンテナを管理するようにスクリプトを改善しました。一部抜粋して下記にサンプルコードを記載します。
cmd="""
docker run -v `pwd`/Challenges:/Challenges \
--env RootUserId={} \
--env AWS_ACCESS_KEY_ID=`echo {} | cut -d , -f 3` \
--env AWS_SECRET_ACCESS_KEY=`echo {} | cut -d , -f 4` \
--env AWS_AccountId=`echo {} | cut -d , -f 5` \
--env RandomString=`echo {} | cut -d , -f 6 | tr -d '\r'` \
--env ChallengeTitle=`echo {} | cut -d , -f 1` \
--env LabId=`echo {} | cut -d , -f 2` \
--env AWS_DEFAULT_REGION=`echo {} | cut -d , -f 3 | tr -d '\r'` \
createstack &
"""
while True:
proc = subprocess.Popen("free -m | grep Mem | awk '{print $4}'", stdout=subprocess.PIPE, shell=True)
(out, err) = proc.communicate()
if int(out) > 100:
break
else:
time.sleep(5)
print("Free Mem:", int(out), "MB")
cmd=(cmd.format(
RootUserId,
CredentialsItems_list[CredentialsItemIndex_int]['Access key ID'],
CredentialsItems_list[CredentialsItemIndex_int]['Secret access key'],
CredentialsItems_list[CredentialsItemIndex_int]['AWS Account Id'],
CredentialsItems_list[CredentialsItemIndex_int]['RandomString'],
ChallengesItems_list[ChallengesItemIndex_int]['Title'],
ChallengesItems_list[ChallengesItemIndex_int]['ChallengeId'],
ChallengesItems_list[ChallengesItemIndex_int]['Region']
))
os.system(cmd)
また、今後は、AWS Step Functions と AWS Lambda を活用して改善するとスタック作成をより迅速にかつコストを削減できそうと考えています。
3-2. AWSアカウント内に複数の課題を共存させ、権限により分離する
AWS Jam のような AWS 上での課題を解決する問題を出すときには、問題ごとにクリーンな環境を用意したくなります。しかし、コンテスト参加者に対して、課題ごとに別の AWS アカウントを用意すると AWS アカウントの数が膨大になってしまいます。
そこで今回は、1 リージョンにつき 1 課題とし、1 つの AWS アカウントに複数の課題が共存できるようにしました。権限を制限する例として今回利用した課題から抜粋し例を示します。
LabUserPermissionsBoundary:
Type: AWS::IAM::Policy
Properties:
PolicyName: LabUserPermissionsBoundary-000ff9fd-b3ddb-s
Roles:
- !Ref LabUserRole
PolicyDocument:
Version: '2012-10-17'
Statement:
# グローバルサービス以外のStackを作成したリージョン以外のアクセスを禁止する
- Effect: Deny
NotAction:
- a4b:*
- acm:*
- aws-marketplace-management:*
- aws-marketplace:*
- aws-portal:*
- budgets:*
- ce:*
- chime:*
- cloudfront:*
- config:*
- cur:*
- directconnect:*
- ec2:DescribeRegions
- ec2:DescribeTransitGateways
- ec2:DescribeVpnGateways
- fms:*
- globalaccelerator:*
- health:*
- iam:*
- importexport:*
- kms:*
- mobileanalytics:*
- networkmanager:*
- organizations:*
- pricing:*
- route53:*
- route53domains:*
- s3:GetAccountPublic
- s3:ListAllMyBuckets
- s3:PutAccountPublic
- shield:*
- sts:*
- support:*
- trustedadvisor:*
- waf-regional:*
- waf:*
- wafv2:*
- wellarchitected:*
Resource: '*'
Condition:
StringNotEquals:
'aws:RequestedRegion':
- !Sub '${AWS::Region}'
# LabUserPermissionsBoundary に対するアクセスを禁止する
- Effect: Deny
Action:
- iam:RemoveUserFromGroup
- iam:DeleteGroup
- iam:UpdateGroup
- iam:DeletePolicy
- iam:DetachGroupPolicy
- iam:DeleteUserPermissionsBoundary
- iam:DeleteGroupPolicy
Resource:
- !Sub 'arn:aws:iam::${AWS::AccountId}:policy/*LabUserPermissionsBoundary*'
LabUserRole:
Type: AWS::IAM::Role
Properties:
MaxSessionDuration: 43200
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
AWS: !Sub 'arn:aws:iam::${RootUserId}:root'
Action: sts:AssumeRole
Path: /
Policies:
# 課題を進めるために必要な許可する
- PolicyName: labuserpolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Action:
- s3:ListAllMyBuckets
Resource: '*'
- Effect: Allow
Action:
- s3:*
Resource:
- !Sub 'arn:aws:s3:::${ProductBucket}/*'
- !Sub 'arn:aws:s3:::${ProductBucket}'
# 課題を壊すアクセスを禁止する
- PolicyName: labbasepolicy
PolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Deny
Action:
- lambda:*
Resource:
!GetAtt 'CopyFilesFunction.Arn'
- Effect: Deny
Action:
- logs:*
Resource:
- !Sub 'arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/lambda/*:*'
# このStackに対するアクセスを禁止する
- Effect: Deny
Action:
- cloudformation:DescribeStackEvents
- cloudformation:GetTemplate
- cloudformation:DescribeStackResource
- cloudformation:ListStackResources
Resource:
- !Sub '${AWS::StackId}'
PolicySimulatorへのアクセス許可
- Effect: Allow
Action:
- iam:GetRole
- iam:GetRolePolicy
- iam:ListRolePolicies
- iam:ListAttachedRolePolicies
Resource:
- !Sub 'arn:aws:iam::${AWS::AccountId}:role/*LabUserRole*'
- Effect: Allow
Action:
- iam:ListRoles
- iam:GetPolicy
- iam:GetPolicyVersion
Resource: '*'
3-3. クリーンアップ
コンテスト後に無駄な課金が発生しないためにも、クリーアップが必要でした。誰もが一度は、リソース全削除ボタンを切望したのではないでしょうか。しかし、現実にはそんな機能はありません。また、課題の初期構成を行った CloudFormation のスタックを削除するだけでは、参加者が競技中に追加で作成したリソースの削除はできませんし、S3 にファイルを追加した場合など、操作によってはスタックの削除自体が失敗します。そこで 2 Step に分けて削除する方法をとりました。
- Step 1 : AWS CLI から AWS CloudFormation スタックを削除
- Step 2 : aws-nuke の実行
Step 1 を必要とした理由は、Step 2 だけを実行した場合に比べると削除所要時間の短縮と成功率の向上が図れると想定したためです。Step 1 は「3-1. 複数アカウントでのデプロイ管理」で実行した方法と同じ手法を用いています。ここからは、Step 2 の aws-nuke についてご紹介します。
3-3-1. aws-nuke について
aws-nuke は、AWS アカウントのリソースを全て削除する OSS のツールです。今回は、Docker イメージのビルド時に GitHub からダウンロードしてバイナリを使用しています。
aws-nuke は非常に強力なツールですので試す際は気を付けてください。コンテナ作成に利用した、Dockerfile と実行スクリプト、aws-nuke の設定ファイルのサンプルを記載します。
aws-nukeDockerfile : Dockerfile
FROM amazonlinux:2
ENV LANG=en_US.SJIS
ENV PYTHONIOENCODING=utf-8
WORKDIR /
# aws cli install
RUN curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" \
&& yum install -y unzip \
&& unzip awscliv2.zip \
&& ./aws/install \
&& amazon-linux-extras install python3.8 -y \
&& python3.8 -m pip install pillow==8.3.2 \
&& yum install -y less
RUN yum install -y wget tar gzip expect
# aws-nuke のダウンロード
RUN wget https://github.com/rebuy-de/aws-nuke/releases/download/v2.16.0/aws-nuke-v2.16.0-linux-amd64.tar.gz \
&& tar -zxvf aws-nuke-v2.16.0-linux-amd64.tar.gz
# nuke-config.yaml と実行スクリプトをコンテナに配置する
COPY nuke-config.yaml /
COPY Run-nuke.sh /
RUN chmod +x Run-nuke.sh
CMD ["/bin/sh", "Run-nuke.sh"]
AllDelete.sh :
AWS セキュリティ認証情報を登録したファイル (conf.ini, Credentials.csv) を読み込み、空きメモリーを監視しながら aws-nuke イメージを実行する
#!/bin/bash -ex
INI_FILE=conf.ini
INI_SECTION=Organizer_Account
# ini parser
eval `sed -e 's/[[:space:]]*\=[[:space:]]*/=/g' \
-e 's/;.*$//' \
-e 's/[[:space:]]*$//' \
-e 's/^[[:space:]]*//' \
-e "s/^\(.*\)=\([^\"']*\)$/\1=\"\2\"/" \
< $INI_FILE \
| sed -n -e "/^\[$INI_SECTION\]/,/^\s*\[/{/^[^;].*\=.*/p;}"`
OrganizationAccountId=${AccountId}
CREDENTIALS_FILE_NAME='Credentials.csv'
WORK_CREDENTIALS_FILE_NAME='./w_'$CREDENTIALS_FILE_NAME
cp $CREDENTIALS_FILE_NAME $WORK_CREDENTIALS_FILE_NAME
HEADER=`head -n 1 $WORK_CREDENTIALS_FILE_NAME`
sed -i -e '1d' $WORK_CREDENTIALS_FILE_NAME
while read credential; do
echo "AWS_AccountId:" `echo ${credential} | cut -d , -f 5`
while true; do
FreeMemMB=`free -m | grep Mem | awk '{print $4}'`
if [ $FreeMemMB -gt 100 ]; then
break
else
echo "FreeMemMB" $FreeMemMB
sleep 5
fi
done
docker run -v `pwd`/AllDelete:/AllDelete \
--env RootUserId=${OrganizationAccountId} \
--env AWS_ACCESS_KEY_ID=`echo ${credential} | cut -d , -f 3` \
--env AWS_SECRET_ACCESS_KEY=`echo ${credential} | cut -d , -f 4` \
--env AWS_AccountId=`echo ${credential} | cut -d , -f 5` \
nuke &
done < $WORK_CREDENTIALS_FILE_NAME
nuke-config.yaml : aws-nuke の設定ファイルのテンプレート
全てのリージョンを対象に、IAM 以外のリソースを削除する。
regions:
- us-west-1
- us-west-2
- us-east-2
- ap-south-1
- ap-northeast-1
- ap-northeast-2
- ap-northeast-3
- ap-southeast-1
- ap-southeast-2
- ca-central-1
- eu-central-1
- eu-north-1
- eu-west-1
- eu-west-2
- eu-west-3
- sa-east-1
- me-south-1
- ap-southeast-3
- af-south-1
- ap-east-1
- global
account-blocklist:
- "BlockID" # production
resource-types:
excludes:
- IAMRole
- IAMRolePolicy
- IAMRolePolicyAttachment
- IAMUser
- IAMUserAccessKey
- IAMUserGroupAttachment
- IAMLoginProfile
- IAMGroup
- IAMGroupPolicyAttachment
accounts:
"TargetID": {} # aws-nuke-example
Run-nuke.sh : nuke-config.yaml を更新して対話型の aws-nuke を自動実行する。
#!/bin/bash -eu
mkdir -p AllDelete/${AWS_AccountId}
# 運営用アカウントを削除しないよう、nuke-config.yaml にaccount-blocklist を設定する
echo ${RootUserId}
sed -i -e "s/BlockID/${RootUserId}/" nuke-config.yaml
sed -i -e "s/TargetID/${AWS_AccountId}/" nuke-config.yaml
aws iam create-account-alias --account-alias ${AWS_AccountId}-test
script="""
set timeout -1
spawn bash -c \"./aws-nuke-v2.16.0-linux-amd64 -c ./nuke-config.yaml --access-key-id ${AWS_ACCESS_KEY_ID} --secret-access-key ${AWS_SECRET_ACCESS_KEY} --no-dry-run\"
expect \"Do you want to continue? Enter account alias to continue.\"
send -- \"${AWS_AccountId}-test\r\"
expect \"Do you want to continue? Enter account alias to continue.\"
send -- \"${AWS_AccountId}-test\r\"
expect \"Nuke complete\"
exit 0
"""
expect -c "$script" 2>&1 | tee /AllDelete/${AWS_AccountId}-log.txt
イベント実行後、コストが無事に 0 になっていることが確認できて一安心しました。
ちなみにコスト取得 API を開発し、日ごとのコストを取得できるようにしていました。

4. コストについて
今回のイベントのコストについて、108 名で 3 時間半実施してどのくらいの費用が掛かったと思いますか ? ずばり、723.85 USD です。1 チーム当たり 17.42 USD、初めてということで前日に準備しましたので、時間あたりに換算すると 0.67 USD / 時になりました。
運営に利用した「Minoru」のコストは、26.906 USD でした。この金額をどう考えますか?弊社としては、コンテストの予算として、数百万規模を想定していましたので、破格でした。特に EC2 インスタンス以外サービスのコストの安さには驚きました。

5. パフォーマンスと監視について
ここからは、サービスの提供にあたってユーザー体験に大きな影響を与えるパフォーマンスについて開発中に得た学びを 2 つご紹介したいと思います。
- オブザーバビリティを活用したボトルネックの発見
- Amazon API Gateway のレスポンスレイテンシーの改善について
5-1. オブザーバビリティを活用したボトルネックの発見
コンテスト本番中のエラーを把握するために Amazon CloudWatch と AWS X-Ray を使って、各サービスの連携を可視化してみたところ、アプリケーションの設計の見直しに繋がった気づきがありました。
まず、下図の Amazon CloudWatch のメトリクスから Lambda 関数 Properties-Function の実行時間が 2.5 秒を超えていて、他の関数より明らかに時間がかかっていることが分かりました。

そのため AWS X-Ray のトレースを確認してみたところ、AWS CloudFormation へのリクエストに時間が掛かっていることが分かりました。ソースコードを確認したところ、この AWS CloudFormation へのアクセスは AWS アカウント、リージョンを跨ぐもので、そのため、大きく遅延が発生しているものと推測しました。
また AWS CloudFormation のスタックから取得する出力セクションのデータはリアルタイム性を必要としないため、データベースに保存するなどの改善策を考えることができました。
5-2. Amazon API Gateway のレスポンスレイテンシーの改善について
次に、AWS X-Ray のサービスマップとトレースビューを利用してのパフォーマンス改善について紹介します。
こちらの図は Lambda 関数 Load-Function のサービスマップです。これをみると処理に 1 秒も掛かっていることが分かり、ユーザー体験に影響があったため、改善に取り組みました。
案 1. Lambda 関数のメモリーをスケールアップする
ログを確認すると下記のようにそもそもメモリを使いきれていなかったので効果が薄いのではないかと思いましたが、Lambda は割り当てるメモリーに比例して割り当てられる CPU も強化されます。
今回は、512 MB から 5120 MB にスケールアップして評価しました。しかし、メモリ割り当てを増やすとコストも上がってしまいます。この関数の場合は、処理時間 (Duration) が 1.0S から 737ms に改善されました。改善される処理時間と単位時間あたりのコスト増加は比較してコスト効率を検討する必要があります。
REPORT RequestId: c518157a-0c9c-4f77-a087-f9f0593bbbea Duration: 913.43 ms Billed Duration: 914 ms Memory Size: 512 MB Max Memory Used: 71 MB
XRAY TraceId: 1-61b2acad-5422ef6d7eed5deb52e3d981 SegmentId: 6ccf50315b9740a5 Sampled: true
参考: AWS Lambda よくある質問「Q: コンピューティングリソースはどのように AWS Lambda 関数に割り当てられるのですか?」 の項目
案 2. Amazon API Gateway を REST API から HTTP API へ変更する
下記のブログでは「HTTP API はレイテンシーを最大 60 % 削減します。」とありますが、現在、HTTP API は X-Ray との連携がサポートされていないので今回は選択肢から外しました。
案 3. リージョン選定の見直し
開発中は us-east-1 米国東部 (バージニア北部) を利用していました。
なぜ us-east-1 米国東部 (バージニア北部) を利用していたかというと、安い、早い、うまいが 3 拍子揃っているからです。
- 安いは、t2.micro のコストを比較すると
us-east-1 米国東部 (バージニア北部) : 0.0116 USD / 時間
ap-northeast-1 アジアパシフィック (東京) : 0.0152 USD / 時間 - 早いは、対応サービス数
us-east-1 米国東部 (バージニア北部) : 190
ap-northeast-1 アジアパシフィック (東京) : 168 - うまいは、ちょっと無理やりですが、us-east-1 だと、IAM Polilcy の制限をリージョン指定でかけてもグローバルサービスに影響しない権限の設計がしやすいです。
しかし、今回のケースでは「早い」は、レスポンスのレイテンシーで考えるべきです。
サービス設置先のリージョン選定のポイントとして物理的な距離は重要なポイントです。
参考までに計測した東京リージョンとのレスポンスタイムの比較は、
- ap-northeast-2 アジアパシフィック (ソウル) : 39.32 ms
- us-east-1 米国東部 (バージニア北部) : 160.83 ms
でした。
そして、東京リージョンに「Minoru」を移すと処理全体のレイテンシーが 613 ms にまで改善されました。そのため、今回はリージョンを東京リージョンに移動することで、レイテンシーを改善しました。
効果としてはクライアントから見たレスポンスタイムが 47 % に改善されました。
まとめ
今回、初めて設計から開発、検証、運用を行い、改めてこれまで学んできた知識が腹落ちしました。私の場合は、技能五輪の参加を目的として AWS の学習をはじめましたが、Web アプリケーションを自分自身で作り切るというのは貴重な経験と沢山の学びを得ることができ、これを作ること自体が「手の内化」につながる活動となったと考えています。まだまだ未熟ですが、これからも慢心せず、精進します。
この記事が「新しい挑戦をしてみよう !」と思うきっかけになっていただければ幸いです。
筆者プロフィール

濵﨑 智宏
株式会社デンソー コアスキル開発部
WorldSkills Kazan 2019 に出場後、後進育成をしています。有志活動として社内のクラウドエンジニア育成にも取り組んでします。
AWS を無料でお試しいただけます