カミナシ エンジニアブログ

株式会社カミナシのエンジニアが色々書くブログです

社内で AWS Workshop を開催しました!

社内で AWS Workshop を開催しました!

こんにちは。 カミナシでソフトウェアエンジニアをやっている Taku です。 先日、社内で AWS の Workshop を開催してみたところ良い反応をいただいたのでその共有となります。

Workshop 開催の目的

今回 Workshop を開催した主な目的はAWS の自己学習を推進するためです。

カミナシには学習・実験・検証を目的とした「AWS アカウント(検証用個人 AWS アカウント)」を発行して利用できる制度があります。

もっとこの良い制度を活用していきたいという思いと、特に新しく入社した人にはあまり知られていない状態をカイゼンしようと思い、 Workshop を開催することで気軽に AWS を触っていただけるようにしたいと考えました。

Workshop でやったこと

Workshop の題材としては、昨年末に参加した AWS re:Invent で体験した以下を利用することとしました。

Serverless Security Workshop

catalog.us-east-1.prod.workshops.aws

このワークショップでは、AWS Lambda、Amazon API Gateway、RDS Aurora で構築されたサーバーレス アプリケーションを保護するテクニックを学びます。次の 5 つのドメインでサーバーレス アプリケーションのセキュリティを向上させるために活用できる AWS のサービスと機能について説明します。

1. ID とアクセスの管理
2. コード 
3. データ 
4. インフラストラクチャー 
5. ロギングとモニタリング

こちらを採用した理由としては、カミナシは組織としてセキュリティの意識を高めていこうとしているため、AWS におけるセキュリティの対応方法を学ぶ良い機会になると考えたためです。 ぜひ以下も参考にご覧ください。

kaminashi-developer.hatenablog.jp

AWS re:Invent に参加した際の様子は以下ブログに記載しておりますので、よろしければご覧いただけると嬉しいです。

kaminashi-developer.hatenablog.jp kaminashi-developer.hatenablog.jp

re:Invent でも様々な Workshop が開催されてましたが、他にも様々な Workshop や ハンズオンが公開されているため、興味があるサービスがあれば探してやってみることをオススメします。

aws-samples.github.io

事前準備

事前準備として実施したことは以下の2つです。

  1. 検証用個人 AWS アカウントの申請依頼
  2. Workshop 用の環境構築手順の作成

1. 検証用個人 AWS アカウントの申請依頼

当日の Workshop で利用するために、社内 Slack にて制度の周知と申請の依頼を行いました。

SlackでのAWS検証アカウントの作成依頼

カミナシでは検証用個人 AWS アカウントの作成をGithub の issue で依頼し、その申請を元に AWS Control Tower を利用してアカウントを作成しています。

AWS検証アカウントの申請

これにて今度 AWS の Workshop をやるよ〜という周知と、これを機会に社内の制度をもっと使ってもらえるようにしました。

2. Workshop 用の環境構築手順の作成

今回 AWS の workshop studio にて公開されている Workshop を利用しましたが、AWS が主催するイベントではないため環境は自前で用意する必要がありました。 今回の Workshop では環境構築用の CloudFormation のテンプレートも公開されていたのですが、情報が古くなっているのかそのままでの利用は出来ませんでした。

CloudFormation のテンプレート以外にも、Workshop で利用している Cloud9 の設定変更が必要などいくつか修正する必要がありましたので環境構築の部分は新たに手順書を作り直しました。

環境構築手順

※公開されている Workshop から修正が必要だった点について知りたい方は、最後のおまけをご覧ください。

この準備している際に、有識者の協力を得ながら利用するサービスの特性を学ぶことが出来て個人的に勉強になりました。

当日

オフラインでエンジニアメンバー全員が集まるタイミングで、2時間の時間を貰い Workshop を開催しました。 Workshop の流れとしては、AWS に慣れていない方もいたため、以下のように環境構築まで(約1時間)は前で画面を見せながら一緒に実施し、完了した人から 3 の Workshop に取り組んでいただきました。

  1. Workshop の目的の共有
  2. 環境構築
  3. 各自 Workshop の実施
  4. 作成したリソースの削除

事前に環境構築の手順を用意していたことから環境構築は比較的スムーズに進めていただくことができ、開催者の私は一部エラーが発生した人のサポートに回ることができました。

今回は AWS を気軽に触ってもらえるようになることが主目的でしたので、この時間で全ての Workshop の内容を終わらせるようにはしませんでした。 ※もともと全部実施する場合3時間程度を要する Workshop で、複数あるカリキュラムから興味のある分野を自由に実施できる内容でした。

そのため後日安心して各自好きなタイミングで取り組んでいただけるように、最後にはリソースの削除の手順を案内するようにしました。

振り返り

実施後アンケートを取り、フィードバックを貰いましたので、私の所感を踏まえて振り返りました。

よかった点

  • 「検証用個人 AWS アカウント」という社内の制度を共有し、実際に使ってもらうことができた
  • Workshop 後も、新規に技術検証が必要なシーンで引き続き利用してくれている
  • 事前に環境構築手順を検証・用意しておいたので当日スムーズに進めることができた
  • リソースの削除まで Workshop に含めておいた点
  • AWS を触る障壁として、消し忘れて大量に課金されたらどうしようというのがある様子でした

メンバーからのフィードバック

  • 普段 AWS は全く触らないのでこういった機会があり、ありがたかったです。
  • 知らない技術、話題には上がるが普段は触らない、触ったことのない技術に触れる機会となって良かったです!
  • はまりポイントを先に踏んで修正してくれていたのがとても良かったです!
  • リソースの削除まで含まれていたことで、すごく安心感を持って参加することができました。

次の開催に向けて改善できそうな点

Workshop 開催の主な目的である AWS の自己学習を推進は達成できましたが、メンバーからのフィードバックを踏まえ次回より良い Workshop 開催するために以下のようなものができたらと思いました。

  • AWS の環境構築は事前にやって貰っておく
    • 事前に手順を検証して資料を作って貰っておけばあまり問題なく進めることができるはず
  • Workshop 環境で利用するサービスやコマンドの説明も行う
    • 環境構築に時間を要したため、初心者の方にはサービスの内容の説明をちゃんとできずただコマンドを打つだけの時間があった。
    • 補足として、サービスやコマンドの説明も構築手順に盛り込んでおきたい。
  • 一人でもくもく作業をさせるのではなく、ペアワークをしてもらう
    • AWS 初心者と熟練者を組み合わせると知識の共有ができて良さそう。

メンバーからのフィードバック

  • 環境構築に時間が掛かって Module-1 が完了できなかったので、環境構築の時間をスキップできたらなお良かったかなと思いました!
  • AWS 初心者もいるので、「何をやっているのか?」の説明が都度あっても良かったかも〜と思いました
  • ペアワークあると良かったかも知れません。隣同士に座っていたので会話していたのですが、もくもくするよりは良いかも?

総合的に Workshop を実施することで主催者・参加者ともに AWS について学ぶ良い機会になりましたので、また機会があれば実施したいと考えております。

おまけ(環境構築にあたり修正した部分)

CloudFormation のテンプレート修正

エラーになる箇所を以下のとおり修正

修正箇所

  1. PublicSubnet2 が PublicSubnet3になっている箇所があったため修正
  2. Aurora の Engine 指定法が古かったため修正
AWSTemplateFormatVersion: '2010-09-09'
Description: Initial resource setup for serverless security workshop
​
Parameters:
 DbPassword:
   Type: String
   NoEcho: true
​
Resources:
 PubPrivateVPC:
   Type: 'AWS::EC2::VPC'
   Properties:
     CidrBlock: 10.0.0.0/16
     Tags:
       - Key: Name
         Value: Secure-Serverless
​
 PublicSubnet1:
   Type: 'AWS::EC2::Subnet'
   Properties:
     VpcId: !Ref PubPrivateVPC
     AvailabilityZone: !Select [0, !GetAZs '']
     CidrBlock: 10.0.1.0/24
     MapPublicIpOnLaunch: true
     Tags:
       - Key: Name
         Value: pub-subnet-1-Secure-Serverless
​
 PublicSubnet2:
   Type: 'AWS::EC2::Subnet'
   Properties:
     VpcId: !Ref PubPrivateVPC
     AvailabilityZone: !Select [1, !GetAZs '']
     CidrBlock: 10.0.2.0/24
     MapPublicIpOnLaunch: true
     Tags:
       - Key: Name
         Value: pub-subnet-2-Secure-Serverless
​
 PrivateSubnet1:
   Type: 'AWS::EC2::Subnet'
   Properties:
     VpcId: !Ref PubPrivateVPC
     AvailabilityZone: !Select [0, !GetAZs '']
     CidrBlock: 10.0.3.0/24
     MapPublicIpOnLaunch: false
     Tags:
       - Key: Name
         Value: priv-subnet-1-Secure-Serverless
​
 PrivateSubnet2:
   Type: 'AWS::EC2::Subnet'
   Properties:
     VpcId: !Ref PubPrivateVPC
     AvailabilityZone: !Select [1, !GetAZs '']
     CidrBlock: 10.0.4.0/24
     MapPublicIpOnLaunch: false
     Tags:
       - Key: Name
         Value: priv-subnet-2-Secure-Serverless
​
 InternetGateway:
   Type: 'AWS::EC2::InternetGateway'
​
 GatewayToInternet:
   Type: 'AWS::EC2::VPCGatewayAttachment'
   Properties:
     VpcId: !Ref PubPrivateVPC
     InternetGatewayId: !Ref InternetGateway
​
 PublicRouteTable:
   Type: 'AWS::EC2::RouteTable'
   Properties:
     VpcId: !Ref PubPrivateVPC
​
 PublicRoute:
   Type: 'AWS::EC2::Route'
   DependsOn: GatewayToInternet
   Properties:
     RouteTableId: !Ref PublicRouteTable
     DestinationCidrBlock: 0.0.0.0/0
     GatewayId: !Ref InternetGateway
​
 PublicSubnet1RouteTableAssociation:
   Type: 'AWS::EC2::SubnetRouteTableAssociation'
   Properties:
     SubnetId: !Ref PublicSubnet1
     RouteTableId: !Ref PublicRouteTable
​
 PublicSubnet2RouteTableAssociation:
   Type: 'AWS::EC2::SubnetRouteTableAssociation'
   Properties:
     SubnetId: !Ref PublicSubnet2
     RouteTableId: !Ref PublicRouteTable
​
 NatGateway:
   Type: 'AWS::EC2::NatGateway'
   DependsOn: NatPublicIP
   Properties:
     AllocationId: !GetAtt NatPublicIP.AllocationId
     SubnetId: !Ref PublicSubnet1
​
 NatPublicIP:
   Type: 'AWS::EC2::EIP'
   DependsOn: PubPrivateVPC
   Properties:
     Domain: vpc
​
 PrivateRouteTable:
   Type: 'AWS::EC2::RouteTable'
   Properties:
     VpcId: !Ref PubPrivateVPC
​
 PrivateRoute:
   Type: 'AWS::EC2::Route'
   Properties:
     RouteTableId: !Ref PrivateRouteTable
     DestinationCidrBlock: 0.0.0.0/0
     NatGatewayId: !Ref NatGateway
​
 PrivateSubnet1RouteTableAssociation:
   Type: 'AWS::EC2::SubnetRouteTableAssociation'
   Properties:
     SubnetId: !Ref PrivateSubnet1
     RouteTableId: !Ref PrivateRouteTable
​
 PrivateSubnet2RouteTableAssociation:
   Type: 'AWS::EC2::SubnetRouteTableAssociation'
   Properties:
     SubnetId: !Ref PrivateSubnet2
     RouteTableId: !Ref PrivateRouteTable
​
 Cloud9Environment:
   Type: AWS::Cloud9::EnvironmentEC2
   Properties:
     Description: Use Cloud 9 as the default environment to launch your operations.
     InstanceType: t2.micro
     Name: Secure-Serverless-Cloud9
     SubnetId: !Ref PublicSubnet1
​
 DeploymentsS3Bucket:
   Type: AWS::S3::Bucket
​
 AuroraSubnetGroup:
   Type: 'AWS::RDS::DBSubnetGroup'
   Properties:
     DBSubnetGroupDescription: Subnet for Serverless Aurora
     DBSubnetGroupName: secure-serverless-aurora
     SubnetIds:
       - !Ref PrivateSubnet1
       - !Ref PrivateSubnet2
​
 AuroraSecurityGroup:
   Type: AWS::EC2::SecurityGroup
   Properties:
     GroupDescription: Serverless Aurora Access trhough the VPC
     VpcId:
       Ref: PubPrivateVPC
     SecurityGroupIngress:
       - IpProtocol: tcp
         FromPort: 3306
         ToPort: 3306
         CidrIp: 10.0.0.0/16
​
 # should we start with a broad SG and narrow it down as part of workshop?
 # move this to the sam template instead?
 LambdaSecurityGroup:
   Type: AWS::EC2::SecurityGroup
   Properties:
     GroupDescription: SecurityGroup for lambda function
     VpcId:
       Ref: PubPrivateVPC
     SecurityGroupEgress:
       - Description: Access to Aurora MYSQL
         FromPort: 3306
         IpProtocol: tcp
         DestinationSecurityGroupId: !Ref AuroraSecurityGroup
         ToPort: 3306
       - Description: Access to Secrets Manager
         FromPort: 80
         IpProtocol: tcp
         CidrIp: 0.0.0.0/0
         ToPort: 80
       - Description: Access to Secrets Manager SSL
         FromPort: 443
         IpProtocol: tcp
         CidrIp: 0.0.0.0/0
         ToPort: 443
       - Description: Access to Secrets Manager SSL
         FromPort: 53
         IpProtocol: udp
         CidrIp: 0.0.0.0/0
         ToPort: 53
​
 AuroraDBInstance:
   Type: AWS::RDS::DBInstance
   Properties:
     DBInstanceClass: db.t2.small
     Engine: aurora-mysql
     DBClusterIdentifier: !Ref AuroraDBCluster
​
 AuroraDBCluster:
   Type: AWS::RDS::DBCluster
   DependsOn: AuroraSubnetGroup
   DeletionPolicy: Delete
   Properties:
     MasterUsername: admin
     MasterUserPassword: !Ref DbPassword
     Engine: aurora-mysql
     DBSubnetGroupName: !Ref AuroraSubnetGroup
     VpcSecurityGroupIds:
       - !Ref AuroraSecurityGroup
​
Outputs:
 AuroraEndpoint:
   Description: Aurora endpoint for aurora database
   Value: !GetAtt AuroraDBCluster.Endpoint.Address
​
 DeploymentS3Bucket:
   Description: S3 Bucket to place your SAM deployments
   Value: !Ref DeploymentsS3Bucket
​
 LambdaSecurityGroup:
   Description: SecurityGroup for lambda function
   Value: !Ref LambdaSecurityGroup
   Export:
     Name: !Sub ${AWS::StackName}-LambdaSecurityGroup
 PublicSubnet1:
   Description: PublicSubnet1
   Value: !Ref PublicSubnet1
   Export:
     Name: !Sub ${AWS::StackName}-PublicSubnet1
 PublicSubnet2:
   Description: PublicSubnet2
   Value: !Ref PublicSubnet2
   Export:
     Name: !Sub ${AWS::StackName}-PublicSubnet2
 PrivateSubnet1:
   Description: PrivateSubnet1
   Value: !Ref PrivateSubnet1
   Export:
     Name: !Sub ${AWS::StackName}-PrivateSubnet1
 PrivateSubnet2:
   Description: PrivateSubnet2
   Value: !Ref PrivateSubnet2
   Export:
     Name: !Sub ${AWS::StackName}-PrivateSubnet2

Cloud9 の設定変更

ワークショップの手順には含まれていなかったのですが、デフォルトの AWS managed temporary credentials(Cloud9 サービスが用意した仮の AWS クレデンシャル)では IAM に関する操作ができなかったため、これをオフにした上でCloud9 の EC2 インスタンスに独自に作成した Administrator 権限を持つ IAM ロールをアタッチすることで対応しました。

Cloud9設定変更手順


最後に宣伝です!カミナシでは絶賛採用中です!📣 一緒に強いチームを作っていく仲間を募集しています📣