20200728のAWSに関する記事は18件です。

AWS本番環境でよく使うコマンド一覧

自分のメモ用です。

環境

Rails 5.2.4
ruby 2.5.1
capistrano 3.13.0
AWS(EC2)
Web Server Nginx
Application Server Unicorn

Capistranoを導入後の自動デプロイの手順

ターミナルからEC2userへログイン

MacBook-Pro ~ % $ cd .ssh/

# ログイン
MacBook-Pro .ssh % ssh -i app名.pem ec2-user@18.000.000.00(IPアドレス)

# リポジトリに移動
[ec2-user@ip-111-11-11-111 ~]$ cd /var/www/app名 

Nginxの再起動

ターミナル(EC2サーバ)[ec2-user@ip-111-11-11-111 ~]
$ sudo service nginx reload
$ sudo service nginx restart

MySQL起動

ターミナル(EC2サーバ)[ec2-user@ip-111-11-11-111 ~]
$ sudo service mysqld start

unicornのプロセスを停止(killコマンド)

ターミナル(EC2サーバ)[ec2-user@ip-111-11-11-111 ~]
$ pgrep -f unicorn
123456
678910
$ kill -9 123456

自動デプロイ前に念のため、git pull origin master

ターミナル(EC2サーバ)[ec2-user@ip-111-11-11-111 ~]
$ git pull origin master

git pull origin masterとは、

ローカルリポジトリの状態を追跡ブランチであるorigin/masterにコピー(git fetch)、
masterブランチの追跡ブランチ(origin/master)をローカルのmasterブランチにマージする

ローカルリポジトリがリモートについて

Capistranoによる自動デプロイ

ターミナル(ローカルPC) アプリのディレクトリで下記を実行
$ bundle exec cap production deploy

credentials.yml 編集方法

ターミナル(ローカルPC) アプリのディレクトリで下記を実行
$ EDITOR='code --wait' rails credentials:edit

閉じれば、保存される。編集中はTerminal使用不可。

mysqlをターミナルで操作

$ mysql -u root -p
Passward :  123456
「ユーザ名:root」で接続します、パスワードは後で聞いて という意味

#ログアウト
mysql> exit

#データベース一覧を表示
mysql> show databases;

#使用するデータベースを選択
mysql> use app名_production 

#テーブル一覧を表示
mysql> show tables;

#カラム一覧を表示
mysql> show full columns FROM [テーブル名]; 

#特定のテーブル
mysql> show index FROM [テーブル名]; 

マイグレーションファイルを変更した場合の本番環境でのコマンド

本番環境のcurrentディレクトリで
[ec2-user@ip-111-11-11-11 current] $ git pull origin master

[ec2-user@ip-111-11-11-11 app]$

#rails db:drop
$ RAILS_ENV=production DISABLE_DATABASE_ENVIRONMENT_CHECK=1 bundle exec rake db:drop

#rails db:create
$ rake db:create RAILS_ENV=production

#rails db:migrate
$ rake db:migrate RAILS_ENV=production

#rails db:seed
$ rake db:seed RAILS_ENV=production

本番環境のエラー解決

・currentディレクトリでproduction.logの確認

[ec2-user@ip-111-11-11-111 ~]$ ec2サーバーにログイン
$ cd /var/www/app名/current/
$ cd log/
$ tailf production.log

#ログアウト
$ exit

本番環境で写真が反映されない写真保存先の確認

$cd app/assets/
$cd images/

環境変数を定義

・本番環境やローカル環境は ~/.bash_profileへ記述
$ vim ~/.bash_profile


・Capistranoを使用し自動デプロイを行う場合、環境変数は/.etc/environmentmに記述
$ vim ~/.etc/environment
$ sudo vi /etc/environment

viコマンド(vimコマンド)リファレンス

本番環境のエラー表示

config/environments/production.rb

config.consider_all_requests_local = false
↓
config.consider_all_requests_local = true
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2インスタンス作成が2回目以降の場合の手順

無料使用期間について

アカウント作成から12ヶ月間、以下のアプリケーションは無料で利用できます。12ヶ月間の無料期間を過ぎた後、課金が開始されます。
また、12ヶ月以内でも使用量が無料利用枠を超えた場合、料金がかかるので注意が必要です。

カリキュラム内で使用したサービスの無料枠は以下の通りです。
○Amazon EC2
 ・750時間/月 (t2.microインスタンスの使用もこれに含む)まで
○Amazon S3:
 ・5GBの標準ストレージ、20,000件のGETリクエスト、2,000件のPUTリクエスト
○Elastic IPアドレス
 ・実行中のインスタンスに関連づけられたElastic IPアドレスを1つだけ
 ・インスタンスに紐付いていないElastic IPアドレスは全て課金対象
○IAM
 ・IAMユーザーの一時的なセキュリティ認証情報を使用して他のAWSサービスにアクセスするときのみ料金が発生

2つ目以降のインスタンスを立ち上げる際に踏む手順

1 Elastic IPアドレスを解放する
2 (すでに作成済みで、これ以上必要の無い場合)紐付いているS3バケットを削除する
3 インスタンスを削除する
4 新たなインスタンス立ち上げを行う

1.Elastic IPアドレスを解放しよう

Elastic IPアドレスは、停止しているインスタンスに関連づけられている場合に時間ごとに料金が発生します。そのため、インスタンスを停止するときにElastic IPアドレスを解放する必要があります。

  1. https://console.aws.amazon.com/ec2/) にある Amazon EC2 コンソールを開きます。


  2. ナビゲーションペインで [Elastic IP] を選択します。


  3. Elastic IP アドレスを選択し、[Actions]、[Release addresses] の順に選択します。プロンプトが表示されたら、[Release] を選択します。

参考URL:Elastic IPアドレスを解放する方法

2.S3バケットを削除しよう

今後、使用予定のない不要なバケットである場合、以下の手順で削除することができます。(ただし、同じバケット名を別のサービス等で使いたい場合は、バケットの中身を空にするようにしてください。)

  1. AWS マネジメントコンソール にサインインし、Amazon S3 コンソール (https://console.aws.amazon.com/s3/) を開きます。


  2. [バケット名] リストで、削除するバケットの名前の横にあるバケットアイコンを選択し、[バケットを削除する] を選択します。

  3. [バケットを削除する] ダイアログボックスで、削除するバケットの名前を確認のために入力し、[確認] を選択します。

参考URL: S3バケットを削除する方法

3.EC2インスタンスを停止させましょう

インスタンスを停止するとEIastic IPアドレスは関連づけられなくなるので、あらかじめEIastic IPアドレスを解放し課金されないように気をつけましょう。

  1. ナビゲーションペインで [Instances] を選択し、インスタンスを選択します。


  2. [Actions] を選択して [Instance State] を選択し、[Stop] を選択します。
    [Stop] が無効になっている場合は、インスタンスが既に停止しているか、またはルートボリュームがインスタンスストアボリュームです。(インスタンスを停止すると、インスタンスストアボリューム上のデータは消去されます。インスタンスストアボリュームのデータを保持するには、このデータを永続的ストレージに必ずバックアップしてください。)


  3. 確認ダイアログボックスで [Yes, Stop] を選択します。
    ※インスタンスが停止するまで、数分かかる場合があります。

参考URL: インスタンスの停止と起動

最後に

費用はこまめに確認する

発生している料金については、AWSの請求ダッシュボードからいつでも確認することができます。気になる場合は、こまめに確認するようにしましょう。

インスタンスを複数作成して課金されたとしたらいくら位になるのか

もしインスタンスの停止やElastic IPアドレスの解放を失念した状態で複数のインスタンスを作成した場合、状況によりますが500~3000円程度の費用が発生します。もちろん状況によっては数万円程度に達することもありますが、多くの場合法外な費用が発生するわけではないので、落ち着いて対応しましょう。

法外な課金がされる場合

awsのアクセスキーをgithubにpushした際などに、不正に利用されて数十万円の課金がされることがあります。基本的にTECH::EXPERTのカリキュラムのみに沿って進めて頂いた場合はその可能性はありません。一方、Webで検索して見つけた記事などを参考にし、デプロイ作業等を進める場合は注意してください。

不明な課金がされている場合

その他にも数十円の料金が発生していたり、不明な料金が発生していることがあったら以下のような対応を取りましょう。
 ・AWSのカスタマーサポートに問い合わせてみる
 ・解決しない場合は、AWSのアカウントごと消去する

高度な設定

費用発生を最小限に留めるために、EC2を自動でシャットダウンされるように設定したり、無料利用枠を超えないようにS3でオブジェクトの有効期限を設定できたりします。以下のリンクを参考にしてください。


無駄なコストを省こう!AWSで消し忘れを防止するためにチェックすべき7つのポイント

AWSのようなクラウドコンピューティングサービスはこれからも広く使われて行くものです。エンジニアになった後も、頻繁に使用することでしょう。正しく使えば適正な費用しか発生しませんので、ぜひしっかりと調査を行いつつ、様々なチャレンジをしてみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ECR のイメージを使った ECS (Fargate) の構築を VPC の 中で完結させる

解決したいエラー

VPC 内で ECR のイメージを使って ECS Cluster を起動しようとすると、CannotPullContainerError のようなエラーが頻出する。

原因

ECR からイメージを取得するためには、ECR, S3 に接続する必要があるが、private subnet を使用している場合、これらのリソースにアクセスできず、取得に失敗している。

解決方法

  • ECR のイメージ取得をインターネット接続を経由して行えるようにする
  • VPC 内に各リソースを繋ぐトンネルを構築し、そこ経由で ECR のイメージ等が取得できるようにする(VPC Endpoint)

Ref: https://aws.amazon.com/premiumsupport/knowledge-center/ecs-pull-container-api-error-ecr/?nc1=h_ls

2番目の方が綺麗だけれど、Cloud Formation を書くのがめんどくさい。
一度作成したので、使いまわせるようにここに置いておく。

Cloud Formation

VPC

AWSTemplateFormatVersion: "2010-09-09"
Description:
  VPC and Subnet Create

Metadata:
  "AWS::CloudFormation::Interface":
    ParameterGroups:
      - Label:
          default: "Project Name Prefix"
        Parameters:
          - PJPrefix
      - Label:
          default: "Network Configuration"
        Parameters:
          - VPCCIDR
          - PublicSubnetACIDR
          - PublicSubnetCCIDR
          - PrivateSubnetACIDR
          - PrivateSubnetCCIDR
    ParameterLabels:
      VPCCIDR:
        default: "VPC CIDR"
      PublicSubnetACIDR:
        default: "PublicSubnetA CIDR"
      PublicSubnetCCIDR:
        default: "PublicSubnetC CIDR"
      PrivateSubnetACIDR:
        default: "PrivateSubnetA CIDR"
      PrivateSubnetCCIDR:
        default: "PrivateSubnetC CIDR"

# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
Parameters:
  PJPrefix:
    Type: String

  VPCCIDR:
    Type: String
    Default: "10.1.0.0/16"

  PublicSubnetACIDR:
    Type: String
    Default: "10.1.10.0/24"

  PublicSubnetCCIDR:
    Type: String
    Default: "10.1.20.0/24"

  PrivateSubnetACIDR:
    Type: String
    Default: "10.1.100.0/24"

  PrivateSubnetCCIDR:
    Type: String
    Default: "10.1.200.0/24"

Resources:
# ------------------------------------------------------------#
#  VPC
# ------------------------------------------------------------#
# VPC Create
  VPC:
    Type: "AWS::EC2::VPC"
    Properties:
      CidrBlock: !Ref VPCCIDR
      EnableDnsSupport: "true"
      EnableDnsHostnames: "true"
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-vpc"

# InternetGateway Create
  InternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-igw"

# IGW Attach
  InternetGatewayAttachment:
    Type: "AWS::EC2::VPCGatewayAttachment"
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

# ------------------------------------------------------------#
#  Subnet
# ------------------------------------------------------------#
# Public SubnetA Create
  PublicSubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PublicSubnetACIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-a"

# Public SubnetC Create
  PublicSubnetC:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PublicSubnetCCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-subnet-c"

# Private SubnetA Create
  PrivateSubnetA:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1a"
      CidrBlock: !Ref PrivateSubnetACIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-a"

# Private SubnetC Create
  PrivateSubnetC:
    Type: "AWS::EC2::Subnet"
    Properties:
      AvailabilityZone: "ap-northeast-1c"
      CidrBlock: !Ref PrivateSubnetCCIDR
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-private-subnet-c"

# ------------------------------------------------------------#
#  RouteTable
# ------------------------------------------------------------#
# Public RouteTableA Create
  PublicRouteTableA:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-a"

# Public RouteTableC Create
  PublicRouteTableC:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-public-route-c"

# Private RouteTableA Create
  PrivateRouteTableA:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-a"

# Private RouteTableC Create
  PrivateRouteTableC:
    Type: "AWS::EC2::RouteTable"
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-private-route-c"

# ------------------------------------------------------------#
# Routing
# ------------------------------------------------------------#
# PublicRouteA Create
  PublicRouteA:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableA
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

# PublicRouteC Create
  PublicRouteC:
    Type: "AWS::EC2::Route"
    Properties:
      RouteTableId: !Ref PublicRouteTableC
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: !Ref InternetGateway

# ------------------------------------------------------------#
# RouteTable Associate
# ------------------------------------------------------------#
# PublicRouteTable Associate SubnetA
  PublicSubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PublicSubnetA
      RouteTableId: !Ref PublicRouteTableA

# PublicRouteTable Associate SubnetC
  PublicSubnetCRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PublicSubnetC
      RouteTableId: !Ref PublicRouteTableC

# PrivateRouteTable Associate SubnetA
  PrivateSubnetARouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PrivateSubnetA
      RouteTableId: !Ref PrivateRouteTableA

# PrivateRouteTable Associate SubnetC
  PrivateSubnetCRouteTableAssociation:
    Type: "AWS::EC2::SubnetRouteTableAssociation"
    Properties:
      SubnetId: !Ref PrivateSubnetC
      RouteTableId: !Ref PrivateRouteTableC

  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: !Ref VPC
      GroupName: !Sub "${PJPrefix}-lambda-sg"
      GroupDescription: "for biomass sam"

# ------------------------------------------------------------#
# Output Parameters
# ------------------------------------------------------------#
Outputs:
# VPC
  VPC:
    Value: !Ref VPC
    Export:
      Name: !Sub "${PJPrefix}-vpc"

  VPCCIDR:
    Value: !Ref VPCCIDR
    Export:
      Name: !Sub "${PJPrefix}-vpc-cidr"

# Subnet
  PublicSubnetA:
    Value: !Ref PublicSubnetA
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a"

  PublicSubnetACIDR:
    Value: !Ref PublicSubnetACIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-a-cidr"

  PublicSubnetC:
    Value: !Ref PublicSubnetC
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c"

  PublicSubnetCCIDR:
    Value: !Ref PublicSubnetCCIDR
    Export:
      Name: !Sub "${PJPrefix}-public-subnet-c-cidr"

  PrivateSubnetA:
    Value: !Ref PrivateSubnetA
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-a"

  PrivateSubnetACIDR:
    Value: !Ref PrivateSubnetACIDR
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-a-cidr"

  PrivateSubnetC:
    Value: !Ref PrivateSubnetC
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-c"

  PrivateSubnetCCIDR:
    Value: !Ref PrivateSubnetCCIDR
    Export:
      Name: !Sub "${PJPrefix}-private-subnet-c-cidr"

# Route
  PublicRouteTableA:
    Value: !Ref PublicRouteTableA
    Export:
      Name: !Sub "${PJPrefix}-public-route-a"

  PublicRouteTableC:
    Value: !Ref PublicRouteTableC
    Export:
      Name: !Sub "${PJPrefix}-public-route-c"

  PrivateRouteTableA:
    Value: !Ref PrivateRouteTableA
    Export:
      Name: !Sub "${PJPrefix}-private-route-a"

  PrivateRouteTableC:
    Value: !Ref PrivateRouteTableC
    Export:
      Name: !Sub "${PJPrefix}-private-route-c"

  SecurityGroup:
    Value: !Ref SecurityGroup
    Export:
      Name: !Sub "${PJPrefix}-lambda-sg"

ECR

AWSTemplateFormatVersion: '2010-09-09'
Description: ECR repository

Parameters:
  PJPrefix:
    Type: String
  ENVPrefix:
    Type: String

Resources:
  ECR:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Sub ${PJPrefix}-${ENVPrefix}-migration-ecr

Outputs:
  ECR:
    Value: !Ref ECR
    Export:
      Name: !Sub ${PJPrefix}-migration-ECR

ECS Cluster

AWSTemplateFormatVersion: '2010-09-09'
Description: Create ECS Cluster

Parameters:
  PJPrefix:
    Type: String

Resources:
  ECSCluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: !Sub ${PJPrefix}
  MigrationSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      GroupName: !Sub "${PJPrefix}-migration-task-sg"
      GroupDescription: !Sub "${PJPrefix}-migration-task-sg"

Outputs:
  ECSCluster:
    Value: !Ref ECSCluster
    Export:
      Name: !Sub ${PJPrefix}-Cluster
  MigrationSecurityGroup:
    Value: !Ref MigrationSecurityGroup
    Export:
      Name: !Sub ${PJPrefix}-migration-sg

Task Definition

AWSTemplateFormatVersion: '2010-09-09'
Description: Create for RDS migration

Parameters:
  PJPrefix:
    Type: String
  ENVPrefix:
    Type: String
  ImageTag:
    Type: String
  AwsDefaultRegion:
    Type: String
  AwsAccessKeyId:
    Type: String
  AwsSecretAccessKey:
    Type: String

Mappings:
  NodeEnvMap:
    dev:
      VALUE: development
    stg:
      VALUE: production
    prd:
      VALUE: production

Resources:
  LogGroup:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub '/ecs/logs/${PJPrefix}-${ENVPrefix}-migration-groups'

  EcsTaskExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: "Allow"
            Principal:
              Service:
                - "ecs-tasks.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy
        - arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess
        - arn:aws:iam::aws:policy/SecretsManagerReadWrite

  ECSTask:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ContainerDefinitions:
        - Name: !Sub ${PJPrefix}-ECR
          Image: !Sub '${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${PJPrefix}-${ENVPrefix}-migration-ecr:${ImageTag}'
          LogConfiguration:
            LogDriver: awslogs
            Options:
              awslogs-group: !Ref LogGroup
              awslogs-region: ap-northeast-1
              awslogs-stream-prefix: ecs
          MemoryReservation: 1024
          Cpu: 256
          Environment:
            - Name: DB_HOST
              Value: !Sub  "{{resolve:secretsmanager:${PJPrefix}-rds-${ENVPrefix}:SecretString:host}}"
            - Name: DB_PORT
              Value: !Sub  "{{resolve:secretsmanager:${PJPrefix}-rds-${ENVPrefix}:SecretString:port}}"
            - Name: DB_USER
              Value: !Sub  "{{resolve:secretsmanager:${PJPrefix}-rds-${ENVPrefix}:SecretString:username}}"
            - Name: DB_PASSWORD
              Value: !Sub  "{{resolve:secretsmanager:${PJPrefix}-rds-${ENVPrefix}:SecretString:password}}"
            - Name: DB_DATABASE_NAME
              Value: !Sub  "{{resolve:secretsmanager:${PJPrefix}-rds-${ENVPrefix}:SecretString:dbname}}"
            - Name: DYNAMO_ENDPOINT
              Value: !Sub "http://dynamodb.${AwsDefaultRegion}.amazonaws.com"
            - Name: NODE_ENV
              Value: !FindInMap [NodeEnvMap, !Ref "ENVPrefix", VALUE]
            - Name: AWS_ACCESS_KEY_ID
              Value: !Sub "${AwsAccessKeyId}"
            - Name: AWS_SECRET_ACCESS_KEY
              Value: !Sub "${AwsSecretAccessKey}"
            - Name: AWS_DEFAULT_REGION
              Value: !Sub "${AwsDefaultRegion}"
      Cpu: '256'
      ExecutionRoleArn: !Ref EcsTaskExecutionRole
      Family: !Sub ${PJPrefix}-migration-task
      Memory: 1024
      NetworkMode: awsvpc
      RequiresCompatibilities:
        - FARGATE

Outputs:
  LogGroup:
    Value: !Ref LogGroup
    Export:
      Name: !Sub ${PJPrefix}-${ENVPrefix}-LogGroup
  ECSTask:
    Value: !Ref ECSTask
    Export:
      Name: !Sub ${PJPrefix}-${ENVPrefix}-ECSTask
  EcsTaskExecutionRole:
    Value: !Ref EcsTaskExecutionRole
    Export:
      Name: !Sub ${PJPrefix}-${ENVPrefix}-EcsTaskExecutionRole

VPC Endpoint

これで、パブリックネットワークを経由せず各リソース間の連携ができるようになる。

AWSTemplateFormatVersion: "2010-09-09"
Description: Create for RDS migration

Parameters:
  PJPrefix:
    Type: String

Resources:
  MigrationEndpointSecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties:
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      GroupName: !Sub "${PJPrefix}-migration-vpce-sg"
      GroupDescription: !Sub "${PJPrefix}-migration-vpce-sg"
      SecurityGroupIngress:
        - IpProtocol: tcp
          FromPort: "443"
          ToPort: "443"
          SourceSecurityGroupId: { "Fn::ImportValue": !Sub "${PJPrefix}-migration-sg" }
      Tags:
        - Key: Name
          Value: !Sub "${PJPrefix}-migration-vpce-sg"
  ECRApiEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.api"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SubnetIds:
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-a" }
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-c" }
      SecurityGroupIds:
        - !Ref MigrationEndpointSecurityGroup
  ECRDkrEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.ecr.dkr"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SubnetIds:
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-a" }
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-c" }
      SecurityGroupIds:
        - !Ref MigrationEndpointSecurityGroup
  S3Endpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.s3"
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      RouteTableIds:
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-route-a" }
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-route-c" }
  LogsEndpoint:
    Type: AWS::EC2::VPCEndpoint
    Properties:
      ServiceName: !Sub "com.amazonaws.${AWS::Region}.logs"
      VpcEndpointType: Interface
      PrivateDnsEnabled: true
      VpcId: { "Fn::ImportValue": !Sub "${PJPrefix}-vpc" }
      SubnetIds:
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-a" }
        - { "Fn::ImportValue": !Sub "${PJPrefix}-private-subnet-c" }
      SecurityGroupIds:
        - !Ref MigrationEndpointSecurityGroup
        - { "Fn::ImportValue": !Sub "${PJPrefix}-lambda-sg" }

Outputs:
  MigrationEndpointSecurityGroup:
    Value: !Ref MigrationEndpointSecurityGroup
    Export:
      Name: !Sub "${PJPrefix}-MigrationEndpointSecurityGroup"
  ECRApiEndpoint:
    Value: !Ref ECRApiEndpoint
    Export:
      Name: !Sub "${PJPrefix}-ECRApiEndpoint"
  ECRDkrEndpoint:
    Value: !Ref ECRDkrEndpoint
    Export:
      Name: !Sub "${PJPrefix}-ECRDkrEndpoint"
  S3Endpoint:
    Value: !Ref S3Endpoint
    Export:
      Name: !Sub "${PJPrefix}-S3Endpoint"
  LogsEndpoint:
    Value: !Ref LogsEndpoint
    Export:
      Name: !Sub "${PJPrefix}-LogsEndpoint"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

逆引き Serverless Framework events for AWS

最近、既存の lambda を全て Serverless Framework を使って置き換えました。
Lambda は色んな event をトリガーにできるのですが記述方法を毎度ど忘れしてしまい、調べ直すことがあったのでメモを残します。もちろんこれで網羅しているわけではないので気づいたことあればご指摘ください。

serverless.yml

ざっくりと基本構成について。

以下のように外部ファイルに値を定義している方法についてはこちらを参照
${file(../variables-${self:provider.stage}.yml), file(../variables-dev.yml)}
Serverless Framework + Kotlin + Gradle で Lambda をデプロイする - Qiita

serverless.yml
service: my-service # サービス名

provider: # 各functionの共通設定
  name: aws
  runtime: java8
  stage: ${opt:stage, self:custom.defaultStage}
  region: ap-northeast-1
  role: ${self:custom.settings.role}
  vpc:
    securityGroupIds:
      - ${self:custom.settings.vpc.securitygroup}
    subnetIds:
      - ${self:custom.settings.vpc.subnet}
  memorySize: 512

custom: # カスタム値を定義する場合はこちら
  defaultStage: dev
  settings: ${file(../variables-${self:provider.stage}.yml), file(../variables-dev.yml)}

package: # deployするjarを指定
  artifact: ../../build/libs/my-service.jar

functions: # lambda関数を定義する
  TestHandler:
    timeout: 20
    handler: jp.dev.edamame.test.TestHandler
    name: TestHandler
    # 今回はこの lambda関数 をトリガーする events について
    events: ${file(./job_enqueue_handler.yml):events}

※ コールドスタートする java runtime をlambdaで使用するのはあまり効率がいいとは言えませんが、考えあってのことです。

S3 event

  • eventsに s3 を指定する
  • S3でObjectが作成されたタイミングでLambdaを実行したいときに
serverless.yml
    events:
      - s3: # s3 event
          bucket: jp.co.valus.test
          event: s3:ObjectCreated:*
          rules:
            - prefix: hoge/import
            - suffix: .csv
          existing: true
KEY DESCRIPTION
bucket イベントを受け取りたいbacket名を指定
event 対象のイベント(作成/更新/削除など)を指定
rurles prefix : ファイル名の前方一致。特定のディレクトリなどを指定したいときに使ってた
sufix : ファイル名の後方一致。拡張子を指定したいときに使ってた
existing 既に作成済みのbucketを使うときはtrueにする

s3に指定できるEventについて

  • オブジェクト作成イベント
serverless.yml
   - s3:
       event: s3:ObjectCreated:*
EVENT DESCRIPTION
s3:ObjectCreated:* 全てのオブジェクト作成イベント。更新でもトリガーされる
s3:ObjectCreated:Put 主に新規作成イベント。更新はトリガーされない
s3:ObjectCreated:Post 不明
s3:ObjectCreated:Copy ファイルの複製を作成した時
s3:ObjectCreated:CompleteMultipartUpload マルチパートアップロード完了時
アップロードするファイルサイズが大きいときはこちらのイベント
  • オブジェクト削除イベント
serverless.yml
   - s3:
       event: s3:ObjectRemoved:*
EVENT DESCRIPTION
s3: ObjectRemoved:* 全てのオブジェクト削除イベント
s3:ObjectRemoved:Delete オブジェクトが削除されたとき、またはバージョニングが有効なオブジェクトが完全に削除されたとき
s3:ObjectRemoved:DeleteMarkerCreated バージョニングが有効なオブジェクトに対して削除マーカーが作成されたとき

以下はあまり使ったこと無いが…

  • オブジェクト復元イベント
EVENT DESCRIPTION
s3:ObjectRestore:* S3 Glacier ストレージクラスにアーカイブされたオブジェクトの復元イベント全て
s3:ObjectRestore:Completed オブジェクトの復元完了
s3:ObjectRestore:Post オブジェクト復元開始

Cloudwatch Event (schedule)

  • eventsに schedule を指定する
  • 〜分おきに起動させたり cron 指定でスケジューリングできる
serverless.yml
    events:
    - schedule:
        name: job-hoge-cron-event
        description: 'hello world'
        rate: cron(0 16 * * ? *)
        input: '{"jobName":"hoge","accountId":10}'
        enabled: true
    - schedule:
        name: job-hoge-check-event
        description: 'hogefuga'
        rate: rate(1 minute)
        input: '{"jobName":"check","accountId":100}'
        enabled: true
KEY DESCRIPTION
name スケジュール名(cloudwatch eventにこの名前で登録されます)
description eventの説明
rate cron(0 16 * * ? *) : cron表記
rate(1 minute) : 〜分毎に起動する
rate(2 hours) : 〜時間毎に起動する
input 起動時に lambda関数 に渡す引数
key1: value1 : KEY : VALUEの形でも定義できる

rate() の設定の注意点

OK: rate(1 minute)
NG: rate(5 minute)

OK: rate(5 minutes)

1分以上のときは minutes としなければいけない(hourのときはhours)

input

以下の形式をサポートしています。
※ 公式のサンプルから抜粋

serverless.yml
      - schedule:
          rate: rate(10 minutes)
          enabled: false
          input:
            key1: value1
            key2: value2
            stageParams:
              stage: dev
      - schedule:
          rate: cron(0 12 * * ? *)
          enabled: false
          inputPath: '$.stageVariables'
      - schedule:
          rate: rate(2 hours)
          enabled: true
          inputTransformer:
            inputPathsMap:
              eventTime: '$.time'
            inputTemplate: '{"time": <eventTime>, "key1": "value1"}'

SQS Queues Event

  • eventsに sqs を指定する
  • SQSキューにメッセージがある場合に関数がトリガーされる
serverless.yml
    events:
      - sqs:
          arn:aws:sqs:region:XXXXXX:MyFirstQueue
          batchSize: 10
KEY DESCRIPTION
arn イベントをトリガーしたいsqsのARN
batchSize 一度に送信されるメッセージの数
default:10
max: 10

※ 色んな記述方法があるが上記のフォーマット以外試したことない

SNS Event

  • eventsに sns を指定する
  • メッセージが対象のSNSトピックに送信されるたびに呼び出される
serverless.yml
events:
  - sns: dispatch
  - sns:
      arn: arn:xxx
      displayName: display name
        filterPolicy:
        pet:
          - dog
          - cat
        redrivePolicy:
            deadLetterTargetArn: arn:aws:sqs:us-east-1:11111111111:myDLQ

KEY DESCRIPTION
arn 既存のトピックを追加する場合はarnを指定する
displayName AWSコンソール上でのトピック表示名
filterPolicy フィルターを定義する。
サンプルは key が pet の value が dog/cat のメッセージ以外は除外する
redrivePolicy 関連付けられたLambdaの処理が失敗したときにメッセージを送るデッドレターキューを指定する
  • 新規作成して追加する場合
KEY DESCRIPTION
- sns: snsName 新しいSNSトピックを作成して追加
  • sns 定義 サンプル
serverless.yml
resources:
  Resources:
    myDLQ:
      Type: AWS::SQS::Queue
      Properties:
        QueueName: myDLQ
        VisibilityTimeout: 90 # メッセージ配信後に90秒間は利用できないようにする

    SuperTopic:
      Type: AWS::SNS::Topic
      Properties:
        TopicName: MyCustomTopic

その他

ほかにも色々あるが、とりあえずは使っているもののみ。
また何か試したら追記します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWS] Serverless Application Model (SAM) でAPI Gateway + Lambda + DynamoDBなサンプルを作成してみる

前提

環境

今回は、Macの環境で行いますが、セットアップ方法以外の実行に際しては、Windows / Linuxでも大きな差異はないので、ご参考になるかと思います。

構成

  • API Gateway
  • Lambda (Python 3.7)
  • DynamoDB

boto3によりDynamoDBにレコードを作成するサンプルを作成してみたいと思います。

その他必要なもの

  • AWSアカウント

セットアップ

まずは、「[AWS] Serverless Application Model (SAM) の基本まとめ」にあわせてセットアップをしていきたいと思います。

  • Docker Desktopをインストールする
  • Dockerを起動する
Version確認
$ docker -v
Docker version 19.03.12, build 48a66213fe
  • Homebrewをインストールする
Version確認
$ brew -v
Homebrew 2.4.8
Homebrew/homebrew-core (git revision 820df; last commit 2020-07-27)
Homebrew/homebrew-cask (git revision 35ad13; last commit 2020-07-28)
  • SAM CLIをインストールする
Install-tap
$ brew tap aws/tap
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
No changes to formulae.

==> Tapping aws/tap
Cloning into '/usr/local/Homebrew/Library/Taps/aws/homebrew-tap'...
remote: Enumerating objects: 20, done.
remote: Counting objects: 100% (20/20), done.
remote: Compressing objects: 100% (16/16), done.
remote: Total 654 (delta 8), reused 10 (delta 4), pack-reused 634
Receiving objects: 100% (654/654), 134.26 KiB | 384.00 KiB/s, done.
Resolving deltas: 100% (297/297), done.
Tapped 5 formulae (42 files, 208.2KB).
Install-sam-cli
$ brew install aws-sam-cli
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/core).
==> Updated Formulae
pnpm                                     xxhash

==> Installing aws-sam-cli from aws/tap
==> Downloading https://github.com/awslabs/aws-sam-cli/releases/download/v1.0.0/
Already downloaded: /Users/hirtsuru/Library/Caches/Homebrew/downloads/e6dd54932e5f527780cb97f2a3637d334e4e40bf26c83783ab6b9e7740cd568e--aws-sam-cli-1.0.0.sierra.bottle.tar.gz
==> Pouring aws-sam-cli-1.0.0.sierra.bottle.tar.gz
?  /usr/local/Cellar/aws-sam-cli/1.0.0: 4,109 files, 83MB

Python 3.8xがインストール済みの場合、失敗することがあります。

Version確認
$ sam --version
SAM CLI, version 1.0.0

あと、AWS CLIも必要になるため、インストールされていない場合は、

install-aws-cli
$ brew install awscli

でインストールしてください。

Version確認
$ aws --version
aws-cli/2.0.34 Python/3.8.5 Darwin/19.6.0

また、今回はboto3を使用するので、

install-boto3
$ pip install boto3

でboto3のインストールもお願いします。

SAMでアプリケーション作成

まずは HelloWorld から

プロジェクトの作成

まずは、作業用のディレクトリを用意します。

$ mkdir samapp
$ cd samapp

Pythonの環境も仮想環境にしておきましょうか。

$ python -m venv venv
$ . ./venv/bin/activate

では、さっそく雛形を作成していきましょう。

init
$ sam init --runtime python3.7
Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1

Project name [sam-app]:

Cloning app templates from https://github.com/awslabs/aws-sam-cli-app-templates.git

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
    5 - Elastic File System Sample App
Template selection: 1

-----------------------
Generating application:
-----------------------
Name: sam-app
Runtime: python3.7
Dependency Manager: pip
Application Template: hello-world
Output Directory: .

Next steps can be found in the README file at ./sam-app/README.md

途中で入力が必要な箇所がありますが、まずはオーソドックスに、

  • Template source: AWS Quick Start Templates
  • Project Name : sam-app
  • Template : Hello World Example

としておきます。

ビルド

sam initで、プロジェクト名のディレクトリが作成されます。今回はデフォルトのままsam-appを指定しましたので、プロジェクトディレクトリに移動してから、ビルドを実行します。

$ cd sam-app/
$ sam build
Building function 'HelloWorldFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

実行

ローカルで動作させるためには、Dockerが動作している必要があります。
もし、Dockerのエンジンが起動されていない場合は、起動しておいてください。

start-api
$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-07-28 11:54:52  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

これで起動している状態なので、ブラウザでhttp://127.0.0.1:3000/helloにアクセスしてみましょう。
初回は、準備等が実行されるので、少し時間がかかりますが、少し待つと、ブラウザに

{"message": "hello world"}

と表示されるかと思います。これで、ローカル環境のDocker上の仮装的なLambda環境が動作していることが確認できたと思います。
この状態で、コンソールで「Ctrl+C」でdockerコンテナが停止しましょう。

リファクタリング

コードディレクトリ名の変更

では、現在「HelloWorld」用に構成されているアプリケーションを少しいじってみましょう。
まずは。プロジェクト配下にあるディレクトリ名を変更したいと思います。
現在「hello_world」というディレクトリができているので、これを「sam_ddb」に変更してみることにしましょう。

$ mv hello_world sam_ddb

template.ymlの変更

SAMの定義でもあるテンプレートを修正します。
ここでは、「hello_world」

template.yml変更前
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
template.yml変更後
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 30

Resources:
  SamDdbFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      CodeUri: sam_ddb/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        SamDdb:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /ddb
            Method: get

Outputs:
  # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function
  # Find out more about other implicit resources you can reference within SAM
  # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
  SamDdbApi:
    Description: "API Gateway endpoint URL for Prod stage for SAM DDB function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ddb/"
  SamDdbFunction:
    Description: "SAM DDB Lambda Function ARN"
    Value: !GetAtt SamDdbFunction.Arn
  SamDdbFunctionIamRole:
    Description: "Implicit IAM Role created for SAM DDB function"
    Value: !GetAtt SamDdbFunctionRole.Arn

テストコード

今回は使用しませんが、ついでにテスト用のコードも修正してしまいましょう。
5行目を以下のように修正します。

tests/unit/test_handler.py修正前
from hello_world import app
tests/unit/test_handler.py修正後
from sam_ddb import app

ビルド

この段階で、一度ビルドしてみましょう。

build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

Building functionが変更されていることが確認できると思います。

実行

では、先ほど同様、ローカルで実行してみましょう。

start-api
$ sam local start-api
Mounting SamDdbFunction at http://127.0.0.1:3000/ddb [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-07-28 13:29:59  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

こちらも成功し、URLがhttp://127.0.0.1:3000/ddbに変わっていることが確認できるかと思います。
そして、http://127.0.0.1:3000/ddbにブラウザでアクセスして、先ほどと同じ結果になることを確認しましょう。

ローカルにDynamoDBの環境を構築してみる

次は、DynamoDBへのアクセスですが、AWS上にプロビジョニングする前に、ローカルでLambda関数のテストを行うために、ローカルにDynamoDBの環境を作る必要があります。

テーブル定義

まずは、テーブルの構造をJsonファイルで定義します。
下記内容を格納するディレクトリとして、プロジェクトのディレクトリ直下に「ddb」というディレクトリを作成してください。
その中に「ddb.json」というファイル名で、下記内容のファイルを作成してみましょう。

ddb.json
{
  "TableName": "Demo",
  "KeySchema": [
    {
      "AttributeName": "Key",
      "KeyType": "HASH"
    },
    {
      "AttributeName": "CreateDate",
      "KeyType": "RANGE"
    }
  ],
  "AttributeDefinitions": [
    {
      "AttributeName": "Key",
      "AttributeType": "S"
    },
    {
      "AttributeName": "CreateDate",
      "AttributeType": "S"
    }
  ],
  "ProvisionedThroughput": {
    "ReadCapacityUnits": 5,
    "WriteCapacityUnits": 5
  }
}

Lambdaの修正

では、現在「hello world」というJsonデータを返すだけのLambdaで、DynamoDBにレコードを登録するような実装に変更してみましょう。
今回は、POSTリクエストに渡された「key」に指定された内容をDynamoDBに登録するサンプルにしたいと思います。
CreateDateは、現在日時を取得して格納します。
以下が、書き換え後のサンプルコードです。

app.py
import json
import boto3
import os
from datetime import datetime

def lambda_handler(event, context):
    try:
        event_body = json.loads(event["body"])
        if "local" in event_body and event_body["local"] == True:
            dynamodb = boto3.resource("dynamodb", endpoint_url="http://dynamodb:8000")
        else:
            dynamodb = boto3.resource("dynamodb")

        table = dynamodb.Table("Demo")
        table.put_item(
            Item={
                "Key": event_body["key"],
                "CreateDate": datetime.utcnow().isoformat()
            }
        )

        return {
            "statusCode": 200,
            "body": json.dumps({
                "message": "succeeded",
            }),
        }
    except Exception as e:
        return {
            "statusCode": 500,
            "body": json.dumps({
                "message": e.args
            }),
    }

定義の修正

まず、Pythonでboto3を使用するためrequirements.txtに「boto3」を追記し、以下のような内容に修正します。

requirements.txt
requests
boto3

そして、SAMのテンプレートも、少しだけ修正を加えます。

25行目

template.yml変更前
            Method: get
template.yml変更後
            Method: post

DynamoDBを起動する

ローカルでのDynamoDBはDockerコンテナで動作させることができます。
以下での手順でDockerコンテナを起動するところまで実行してみましょう。

$ docker pull amazon/dynamodb-local
Using default tag: latest
latest: Pulling from amazon/dynamodb-local
638b75f800bf: Pull complete
a455ee0c5c58: Pull complete
c7e215ba3460: Pull complete
Digest: sha256:c8aeb014edfff8b93d7f14054cb9d2a1be214d62b7c5b61eb7fb40893a8404bb
Status: Downloaded newer image for amazon/dynamodb-local:latest
docker.io/amazon/dynamodb-local:latest
$ docker network create ddb-network
5c81f9e8f32413cfcf9aec4d4165d6f53cee67d167ad87d2e99813b90219c422
$ docker run --network ddb-network --name dynamodb -p 8000:8000 amazon/dynamodb-local -jar DynamoDBLocal.jar -sharedDb
Initializing DynamoDB Local with the following configuration:
Port:   8000
InMemory:   false
DbPath: null
SharedDb:   true
shouldDelayTransientStatuses:   false
CorsParams: *

この状態で一度「Ctrl+C」でコンテナを停止し、

$ docker start dynamodb

を実行してください。バックグランドでDynamoDBが起動されます。

ローカルDynamoDBにテーブル定義を追加する

現在起動しているローカルのDynamoDBにはテーブルの定義がありませんので、先ほど「ddb/ddb.json」で定義した内容でテーブルを作成したいと思います。

$ aws dynamodb create-table --cli-input-json file://./ddb/ddb.json --endpoint-url http://localhost:8000

これで、ローカルのDynamoDBに、テーブルが作成されました。

ビルド

では、例によって、ビルドを実行したいと思います。

build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

実行

では、さっそく実行してみます。
今回は、ローカルのDynamoDBのコンテナと通信するために、--docker-netorkオプションが必要になるので注意してください。

start-api
$ sam local start-api --docker-network ddb-network
Mounting SamDdbFunction at http://127.0.0.1:3000/ddb [POST]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2020-07-28 15:20:35  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

起動できましたね。
今度は、前回と違って「POST」でアクセスする必要があります。
したがって、今回はブラウザではなく、curlでアクセスしてみたいと思います。

$ curl -X POST -H "Content-Type: application/json" -d '{"key": "demo-data", "local": true}' http://127.0.0.1:3000/ddb

この実行の結果

{"message": "succeeded"}

が表示されると、成功です。

DB確認

では、実際にデータがDynamoDBに登録できたかを確認してみましょう。

$ aws dynamodb scan --table-name Demo --endpoint-url http://localhost:8000
{
    "Items": [
        {
            "CreateDate": {
                "S": "2020-07-28T06:23:33.150156"
            },
            "Key": {
                "S": "demo-data"
            }
        }
    ],
    "Count": 1,
    "ScannedCount": 1,
    "ConsumedCapacity": null
}

これでLambda関数でのDynamoDBへのデータ登録ができることがローカルで確認することができました。

後始末

Lambda環境は「Ctrl+C」で終了できます。
あと、DynamoDBについては、以下のコマンドで終了させてください。

$ docker stop dynamodb
$ docker rm dynamodb

AWSへのデプロイ

SAMテンプレート修正

ここまできたら、デプロイのための定義をテンプレートに記述してデプロイするだけです。
テンプレートの修正は

  • Resourceの中にDynamoDBのテーブルの定義追加
  • Lambda関数のIAMロールの定義追加
  • Lambda関数へのIAMロール適用

が主な変更点です。

template.yml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  sam-app

  Sample SAM Template for sam-app

Globals:
  Function:
    Timeout: 30

Resources:
  DynamoTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: Demo
      AttributeDefinitions:
        - AttributeName: Key
          AttributeType: S
        - AttributeName: CreateDate
          AttributeType: S
      KeySchema:
        - AttributeName: Key
          KeyType: HASH
        - AttributeName: CreateDate
          KeyType: RANGE
      ProvisionedThroughput: 
        ReadCapacityUnits: 5
        WriteCapacityUnits: 5

  SamDdbFunction:
    Type: AWS::Serverless::Function
    Properties:
      Role: !GetAtt SamDdbFunctionIamRole.Arn
      CodeUri: sam_ddb/
      Handler: app.lambda_handler
      Runtime: python3.7
      Events:
        SamDdb:
          Type: Api
          Properties:
            Path: /ddb
            Method: post

  SamDdbFunctionIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - 'lambda.amazonaws.com'
            Action:
              - 'sts:AssumeRole'
      ManagedPolicyArns:
        - 'arn:aws:iam::aws:policy/CloudWatchLogsFullAccess'
      Policies:
        - PolicyName: 'SamDdbPolicy'
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - dynamodb:PutItem
                Resource: !GetAtt DynamoTable.Arn

Outputs:
  SamDdbApi:
    Description: "API Gateway endpoint URL for Prod stage for SAM DDB function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/ddb/"

AWSクレデンシャルの設定

AWSにDeployするために、クレデンシャルを設定します。
aws configで設定してもよいですが、今回は、環境変数に設定するやり方にしたいと思います。
環境変数に設定する内容は、マネジメントコンソールのIAMより、認証情報からアクセスキーを生成します。
アクセスキーの管理(コンソール)

$ export AWS_ACCESS_KEY_ID="アクセスキーID"
$ export AWS_SECRET_ACCESS_KEY="シークレットアクセスキー"

で環境変数をコンソールに設定します。
ここまで準備ができたら、あとはビルドしてデプロイを実行するだけです。

build
$ sam build
Building function 'SamDdbFunction'
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

続いてデプロイですが、今回は対話形式で進めたいと思います。

deploy
$ sam deploy --guided

Configuring SAM deploy
======================

    Looking for samconfig.toml :  Found
    Reading default arguments  :  Success

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]:
    AWS Region [ap-northeast-1]:
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [Y/n]: y
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]: y
    SamDdbFunction may not have authorization defined, Is this okay? [y/N]: y
    Save arguments to samconfig.toml [Y/n]: y

    Looking for resources needed for deployment: Found!

        Managed S3 bucket: aws-sam-cli-managed-default-samclisourcebucket-pqa3p7mrbwhc
        A different default S3 bucket can be set in samconfig.toml

    Deploying with following values
    ===============================
    Stack name                 : sam-app
    Region                     : ap-northeast-1
    Confirm changeset          : True
    Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-pqa3p7mrbwhc
    Capabilities               : ["CAPABILITY_IAM"]
    Parameter overrides        : {}

Initiating deployment
=====================

    Saved arguments to config file
    Running 'sam deploy' for future deployments will use the parameters saved above.
    The above parameters can be changed by modifying samconfig.toml
    Learn more about samconfig.toml syntax at
    https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

    Deploying with following values
    ===============================
    Stack name                 : sam-app
    Region                     : ap-northeast-1
    Confirm changeset          : True
    Deployment s3 bucket       : aws-sam-cli-managed-default-samclisourcebucket-pqa3p7mrbwhc
    Capabilities               : ["CAPABILITY_IAM"]
    Parameter overrides        : {}

Initiating deployment
=====================
SamDdbFunction may not have authorization defined.
Uploading to sam-app/c72e0b0797684ec987c82c67edd96573.template  1949 / 1949.0  (100.00%)

Waiting for changeset to be created..

CloudFormation stack changeset
------------------------------------------------------------------------------------------------
Operation                        LogicalResourceId                ResourceType
------------------------------------------------------------------------------------------------
+ Add                            DynamoTable                      AWS::DynamoDB::Table
+ Add                            SamDdbFunctionIamRole            AWS::IAM::Role
+ Add                            SamDdbFunctionSamDdbPermission   AWS::Lambda::Permission
                                 Prod
+ Add                            SamDdbFunction                   AWS::Lambda::Function
+ Add                            ServerlessRestApiDeploymentd34   AWS::ApiGateway::Deployment
                                 ff828b5
+ Add                            ServerlessRestApiProdStage       AWS::ApiGateway::Stage
+ Add                            ServerlessRestApi                AWS::ApiGateway::RestApi
------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:703609122166:changeSet/samcli-deploy1595922824/c5c5c271-c3f7-4ffe-916f-d02a10b034d4


Previewing CloudFormation changeset before deployment
======================================================
Deploy this changeset? [y/N]: y

2020-07-28 16:53:59 - Waiting for stack create/update to complete

CloudFormation events from changeset
-------------------------------------------------------------------------------------------------
ResourceStatus           ResourceType             LogicalResourceId        ResourceStatusReason
-------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS       AWS::DynamoDB::Table     DynamoTable              Resource creation
                                                                           Initiated
CREATE_IN_PROGRESS       AWS::DynamoDB::Table     DynamoTable              -
CREATE_COMPLETE          AWS::DynamoDB::Table     DynamoTable              -
CREATE_IN_PROGRESS       AWS::IAM::Role           SamDdbFunctionIamRole    -
CREATE_IN_PROGRESS       AWS::IAM::Role           SamDdbFunctionIamRole    Resource creation
                                                                           Initiated
CREATE_COMPLETE          AWS::IAM::Role           SamDdbFunctionIamRole    -
CREATE_IN_PROGRESS       AWS::Lambda::Function    SamDdbFunction           -
CREATE_IN_PROGRESS       AWS::Lambda::Function    SamDdbFunction           Resource creation
                                                                           Initiated
CREATE_COMPLETE          AWS::Lambda::Function    SamDdbFunction           -
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        Resource creation
                         pi                                                Initiated
CREATE_IN_PROGRESS       AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_COMPLETE          AWS::ApiGateway::RestA   ServerlessRestApi        -
                         pi
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   -
                         n                        rmissionProd
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    ymentd34ff828b5
CREATE_IN_PROGRESS       AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   Resource creation
                         yment                    ymentd34ff828b5          Initiated
CREATE_IN_PROGRESS       AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   Resource creation
                         n                        rmissionProd             Initiated
CREATE_COMPLETE          AWS::ApiGateway::Deplo   ServerlessRestApiDeplo   -
                         yment                    ymentd34ff828b5
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_IN_PROGRESS       AWS::ApiGateway::Stage   ServerlessRestApiProdS   Resource creation
                                                  tage                     Initiated
CREATE_COMPLETE          AWS::ApiGateway::Stage   ServerlessRestApiProdS   -
                                                  tage
CREATE_COMPLETE          AWS::Lambda::Permissio   SamDdbFunctionSamDdbPe   -
                         n                        rmissionProd
CREATE_COMPLETE          AWS::CloudFormation::S   sam-app                  -
                         tack
-------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
-------------------------------------------------------------------------------------------------
Outputs
-------------------------------------------------------------------------------------------------
Key                 SamDdbApi
Description         API Gateway endpoint URL for Prod stage for SAM DDB function
Value               https://l6uressyl8.execute-api.ap-northeast-1.amazonaws.com/Prod/ddb/
-------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-app in ap-northeast-1

AWS Regionは、東京リージョンを指定するようにしてください。それ以外の「y/n」の質問はすべて「y」で問題ありません。
また、CloudFormationのスタック名も、他と重複しないような名前であれば特になんでもよいので、今回はデフォルトのアプリケーション名のままとしました。

実行

sam deployの最後に、API GatewayのURLが表示されているので、このURLに対してリクエストを発行するようにします。

$ curl -X POST -H "Content-Type: application/json" -d '{"key": "demo-data"}' https://l6uressyl8.execute-api.ap-northeast-1.amazonaws.com/Prod/ddb/

はい、成功のメッセージが返ってきました。

{"message": "succeeded"}

では、マネジメントコンソールで、DynamoDBの中身をみてみましょう。

DynamoDB_·_AWS_Console.png

Demoテーブルに、きちんとデータが登録できていることが確認できました。

まとめ

今回は手順を踏んで、HelloWorldのテンプレートから少しずつアプリケーションの形に変えていく形で作成してみましたが、慣れてくれば直接テンプレートを作成する方法でもできないことはないと思います。
あと、今回はテストコードについては放置しましたが、できればテストコードもきちんと対応しましょう。
SAMのテンプレートでは、複数のLambda関数を定義することもできますし、DynamoDB以外のリソース定義を行うこともできるので、Serverlessなアプリケーションを作る際には、十分実用価値があるのでは、と思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EC2 インスタンス名の一部からインスタンス情報を瞬時に出す

背景

あ、あの情報が必要なんだけど、なんだっけな。。
という時に、さくっと情報を取り出せたらスマートですよね。

業務上、EC2 インスタンスID 知りたくなることがあります。
SSH over Session Manager で接続したいので、インスタンスID が必要だったり、 aws cli で AMI作りたいので、インスタンスID が必要だったり。

ブラウザからAWSコンソールにアクセスして調べたり aws cli で describe-instances を使って調べることももちろん可能です。

ただただ、スマートではありません。
調べようとしているうちに、他のエンジニアから質問を受けて、
あれ、何を調べようとしてたんだっけと、コンテキストスイッチが発生して、時間が余計にかかってしまいます。
(※ 質問するのがダメとか言いたいわけではありませんw)

前提

  • aws cli がインストール済みである
  • EC2 を業務でよくさわる

対応

簡単です

$ cat <<'FUNCTION' >> ~/.$(basename $SHELL)rc
function guess-ec2() {
    function help() {
         cat <<EOT >&2
guess-ec2 <guess> <profile>

    <guess> ec2 インスタンスの tag:Name の文字列の一部を指定します
    <profile> aws-cli で指定するプロファイルがある場合は指定します (任意)

ex.
    1. guess-ec2 web prod
    2. guess-ec2 batch
EOT
    }

    if [ $# -eq 0 ]; then
        echo 引数は1つ以上指定してください >&2
        help
        return 1;
    fi

    GUESS=$1
    PROFILE=default
    if [ $# -ge 2 ]; then
        PROFILE="$2"
    fi
    aws --profile $PROFILE \
        ec2 describe-instances \
        --filter "Name=tag:Name,Values=*$GUESS*" \
        --query 'Reservations[].Instances[].{id:InstanceId,name:Tags[?Key==`Name`].Value|[0],ip:PrivateIpAddress}'
}
FUNCTION

// shell をリロード
$ $SHELL -l

これでOK
guess-ec2 ${tag:Name の一部} と打つと欲しい情報が瞬時にとれます
情報が取れたあと、他のエンジニアから質問されても
そうだった、インスタンスID がほしかったんだと、仕事もスムーズです。

$ guess-ec2 web
[
    {
        "id": "i-0bdafc0a9",
        "name": "production-web",
        "ip": "10.0.16.94"
    },
    {
        "id": "i-ef7ff14d",
        "name": "production-web",
        "ip": "10.0.23.84"
    }
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

東京大学計数工学科講義「AWSによるクラウド入門」をやってみた

東京大学計数工学科で開講されている講義資料「AWSによるクラウド入門」が公開されており、AWSの基礎だけでなく、機械学習やサーバレスなどもHands-Onを通じて学べるということで、早速実施してみました。

クラウド入門者向けに簡潔かつ平易な言葉で書かれており、大変勉強になりました。飽き性の私でも最後まで楽しんでできました。

かかった時間と費用

・一通り解説を流し読み、Hands-Onも全て実行して、約4時間
・費用は、0.49ドル

cost.png

学べること

大きく以下3点です。気になるキーワードがあれば、そこだけ掻い摘むのも良いのではないかと思います。

クラウドの基礎

・クラウドの基礎となる概念・知識
・セキュリティやネットワークなど,クラウドを利用する上で最低限おさえなければいけないポイント理解
・ハンズオン:仮想サーバーをAWSに立ち上げる
・キーワード:AWS CLI、AWS CDK、CloudFormation、VPC、EC2、Security Group

クラウドで行う機械学習

・クラウド上で機械学習を走らせるための入門となる知識・技術
・Docker に自分のプログラムをパッケージングする手順
・ハンズオン1:AWSでJupyter notebookを使って簡単な機械学習の計算を走らせる
・ハンズオン2:ディープラーニングを用いた自然言語処理により,質問に自動で回答を生成するボットを作成
・キーワード:GPU、DLAMI、Jupyter notebook、PyTorch、MNIST、Docker、Elastic Container Service (ECS)、Fargate、Transformer

Serverless Architecture

・Serverless architectureと呼ばれる最新のクラウドのアーキテクチャを解説
・ハンズオン1:簡単なデータベースをクラウド上に作成
・ハンズオン2:俳句を投稿するSNS "Bashoutter" 作成
・キーワード:REST API、Lamba、S3、DynamoDB、API Gateway、Simple Notification Service、Step Functions

おわりに

1点だけ、詰まったというか、記載通りにならなかった箇所がありましたので、備忘録的に残しておきます。

「6.3. スタックのデプロイ」で、GPU搭載の仮想マシンを立ち上げようとすると、以下のようなエラーとなりました。

| CREATE_FAILED        | AWS::EC2::Instance                    | Ec2ForDlInstanceF0415522
You have requested more vCPU capacity than your current vCPU limit of 0 allows for the instance buck
et that the specified instance type belongs to. Please visit http://aws.amazon.com/contact-us/ec2-re

対処法は、エラーメッセージにもありますが、立ち上げられるvCPUの制限が初期値だと0なので、その上限を上げてもらう必要があります。

以下のように、制限緩和のリクエストを投げれば、10分ほどで作業完了の連絡がありました。その後、再度デプロイすれば、うまく行きます。

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker: AWSから完全にuninstall

1 dockerが作成したnetworkを削除

docker network prune

2 pre-definedのnetworkも削除

service docker stop
vim /etc/docker/daemon.json

daemon.json
{
  "bridge": "none"
}

vim service start docker

3 uninstall

sudo yum remove docker docker-common docker-selinux docker-engine

4 設定ファイルの削除

rm -rf /etc/docker

5 imageの削除

rm -rf /var/lib/docker

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

git-remote-codecommit(GRC)を使ってAWS CodeCommitに繋いでみたら楽になった

こんにちは。AWS歴3年生の人です。最近、インフラもコードで管理するInfrastructure as Code(IaC)に関連して、Gitを使い始めたのですが、2020年春にAWSからgit-remote-codecommitという新しいGit認証ツールがリリースされたので勉強がてら試してみました。

AWS CodeCommit が新しい Git 認証情報ヘルパーの git-remote-codecommit を導入

注意

  • 本記事は2020年7月17日時点でのバージョンで確認しました。

  • 記載のコードにつきましては参考となりますので、利用時の不具合について一切の責任を負いません。

結論。こんな方におすすめです。

  • AWSがきっかけでGitを使い始める人。
    • AWSが提供しているGitリポジトリへ接続するための認証補助ツールなので。
  • Gitの認証設定がうまくいかない人や、.gitconfigファイルの設定が不慣れな人。
    • AWSCLIのCredential設定ができれば、簡単にCodeCommitに接続できるので。

普段からCodeCommit以外のリポジトリや、複数のGit認証情報を使いこなしている方にはこのツールは不要かもしれません。

準備

前提条件である、PythonやAWSCLI、Gitクライアントのインストール、CodeCommitやAWSCLIのCredentialとprofile設定は済んでいるものとします。

参考

ステップ 0: git-remote-codecommit の前提条件をインストールする
ステップ 1: CodeCommit の初期設定

インストール

一行 pip install git-remote-codecommitを実行するだけです

$ pip install git-remote-codecommit
Collecting git-remote-codecommit
  Downloading https://files.pythonhosted.org/packages/bb/f7/1180a1f2319a9120c94f33bba61e1738db8dea31b622f8aaf382f219aaeb/git-remote-codecommit-1.13.tar.gz
Collecting botocore>=1.10.4 (from git-remote-codecommit)
(snip)
Successfully built git-remote-codecommit

依存関係のあるパッケージもあわせて一緒に更新されますが、最後に Successfully built git-remote-codecommit が表示されれば成功です。

参考

ステップ 2: git-remote-codecommit をインストールする

使い方

例えば、AWSのprofile名を project-aとして、リージョン ap-northeast-1 にあるAWS CodeCommitリポジトリ demoappをローカルにクローンしたい場合は以下のようにします。

$ git clone codecommit::ap-northeast-1://project-a@demoapp
Cloning into 'demoapp'...
remote: Counting objects: 40, done.
Unpacking objects: 100% (40/40), done.

以降は、いつものように使うだけでOKです。

毎回profile名を指定する必要はありません。また、インストールさえできれば、GUIでもクローン時に上記のように指定することで同じく使うことができました。

例:pushの例

$ git push
Counting objects: 4, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (4/4), 363 bytes | 363.00 KiB/s, done.
Total 4 (delta 1), reused 0 (delta 0)
To codecommit::ap-northeast-1://project-a@demoapp
   9d88cfd..2247110  master -> master

参考

ステップ 3: CodeCommit コンソールに接続し、リポジトリのクローンを作成する

深掘りしてみる

クローン時に指定した情報がどこに設定されているか探してみたところローカルリポジトリ/.git/configというファイルに記載されていました。

[remote "origin"]
        url = codecommit::ap-northeast-1://project-a@demoapp
        fetch = +refs/heads/*:refs/remotes/origin/*

いままでの認証情報ヘルパーと比べて

いままでは下記のAWSユーザサイトを参考にGitコマンドでGit認証情報ヘルパーを設定していました。

ステップ 3: 認証情報ヘルパーを設定する

個人的には、Git初心者にとってはなかなか難しく

  • GitのCredential設定もしないとならない(AWSCLIのCredential設定だけで完結しない)
  • Gitクライアントによってはインストール時、もしくは.gitconfigファイルでCredential Manager設定を無効化しないと接続できない場合がある
  • 異なるCodeCommitリポジトリの認証を追加したいときに、globalの.gitconfigファイルを変更していた
    • 最近、git configコマンドに--localオプションがあることを知りました

という感じだったので、今回のツールはGit初心者でもGit接続環境を導入しやすくなりました。

まとめ

git-remote-codecommitを使って今までより比較的簡単にAWS CodeCommitに接続することができました。また、新しいツールの調査の過程でGitについても少し理解を深めることができました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ドメイン駆動設計の概念を古代哲学で整理する

今回の記事では、ドメイン駆動設計のドメイン層のアイディアを紀元前300年頃に発生した古代哲学の概念を利用して整理します。

古代の世界では、理論と実践が一致していたため、理論家は実践経験を踏まえた上で概念やアイディアを作成していました。
そのため、古代の世界では哲学者とは理論化であり実践者でした。
哲学者は実践経験において感じ取った概念をパターン化、抽象化し、人間の認識領域で共通した知識を共有するために哲学を作成しました。
古代の哲学は、現実の世界を捉えるためのテンプレートを提供し、人間の認識領域を拡大します。

今回の記事では、古代の哲学の概念を拝借し、ドメイン層の力の概念、形態、構造を明らかにし、ドメイン層をより深く理解するアイディアを提供します。

ドメイン層の概念整理に利用する古代哲学

アリストテレスの四原因説

四原因説とはアリストテレスが自著『自然学』の中で論じた、自然学の現象に関する4種類の原因。以下の4つの因子で構成されている。
目的因・・・物事の終り、すなわち物事がそれのためにでもあるそれ(目的)をも原因と言う。
形相因・・・形相または原型。
作用因・・・新たな結果・成果を産出する意味での作用因。力のスタート地点を表す。
質料因・・・存在するものの物質的な原因。銅像においては青銅が、銀盃においては銀が該当

陰陽論

陰陽論とは
原初は混沌(カオス)の状態と考え、この混沌の中から光に満ちた明るい澄んだ気、すなわち陽の気が上昇して天となり、重く濁った暗黒の気、すなわち陰の気が下降して地となった。この二気の働きによって万物の事象を理解し、また将来までも予測しようというのが陰陽思想です。
陰陽論とは、世界を陰と陽に分類し、世界のダイナミックな動きを捉えようとする現実主義に基づいた認知アプローチのことです。
陰と陽は互いに相互浸透し合っている。陰と陽は互いに変化する循環的な関係にある。陽が発生した後に陰となり、陰が発生した後は陽となる。
陰と陽はどちらかが欠けてしまえば、存在しない。陰と陽は二つで一つのシステムを形成しています。
このシステムのことを中国人は矛盾と呼びます。

使用するアーキテクチャ

アーキテクチャは以下の5層モデルを使用します。
・コントロール層・・・RestApiなどでアプリへの接続窓口を担当
・ユースケース層・・・ミッション層を呼び出し、実現したいユースケースを作成
・ドメイン層・・・共通処理を記述する層。ユースケース層で呼び出す
・インフラ層・・・メール、FTP接続、など外部ライブラリに依存した技術的なロジックを担当。インフラ層以外の層から呼び出される。
ミッション層・・・アプリのビジネスロジックを担当。ドメイン層のメソッドをここに集約する
image.png

5層モデルに関する詳しい説明は以下のURL参照
https://qiita.com/aLtrh3IpQEnXKN7/items/b7fe2014ccefcbb9e458

ドメイン駆動設計のドメイン層を古代哲学のアイディアを使用して整理する

整理対象となるドメイン層の概念

Repositories・・・DBから特定のデータを取得する際の処理を記載します。DI(依存性注入)の機能を利用し、DIP(依存関係逆転の原則)によって実装される。
Aggregates・・・オブジェクトのライフサイクルを設計するために、ライフサイクルの単位となるオブジェクトのまとまりを考る。集約の中から1つエンティティを選んで、それを集約のルート(root)とする
Value Objects・・・DBから取得したデータを格納する構造体。状態を変更できないもの(immutable)として扱う。
Factories・・・オブジェクトや集約の生成処理は複雑になるため、ファクトリを導入して生成処理をカプセル化を行う。
Entities・・・オブジェクトの同一性を担保するための属性。Value Objectsから特定のデータを取得する際に使用します。
Specification・・・ビジネスルールや入力値チェックなど、ビジネスのルールや運用方法を記載します。
Services・・・メール送信、フォルダの作成、FTP通信の実行など外部ライブラリを使用した処理を実行。

古代哲学を利用してドメイン層の概念を整理

目的因
ミッション層では、ドメイン層のメソッドを集約します。ミッション層はドメイン層の処理の開始地点であり、終了地点といえる。
ミッション層は目的因に該当します。

質料因
プログラミングにおいて、質料因とは情報のことです。そのため、情報を取得する以下のドメイン層の処理は質料因に該当します。
Repositories

形相因
形相因は建築方法、形、構造体を表す。そのため、オブジェクトを組み立て、オブジェクトにデータを設定する以下のドメイン層の処理は形相因に該当します。
Value Objects
Factories
Entities

作用因
作用因は力のスタート地点、運動、状態の変更、力の出力を表します。
そのため、データの変更、データの送信などを扱う以下の以下のドメイン層の処理は作用因に該当します。
また、ビジネスルールなどは陰陽論の解釈によって、データ変更、データ送信に対するカウンターの概念として扱うため作用因に該当します。
Aggregates
Specification
Services

まとめ

古代哲学を利用して、ドメイン層を概念を整理することができました。
四原因説に基づいてドメインモデルの作成、パッケージ名を作成すればドメイン層のメソッドをより深く理解することができるようになります。

今後の課題

ミッション層の整理が、今後の課題です。
ミッション層のメソッドは目的、時間、場所、手段の組み合わせによって決定されます。
ミッション層の概念を発展させるには、目的、時間、場所、手段を分析する手法を取り入れる必要があります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SageMakerの学習インスタンスのディレクトリ構成

概要

AWS SageMakerで学習用インスタンスを立ち上げ、学習させる際に
依存ライブラリや独自のソースコードを使うための方法をまとめる。

ディレクトリ構成

- notebook.ipynb
- src
 - train.py
 - requirements.txt
 - my_source/
- lib
 - library/

Notebook

今回はMXNetの例を用いる。
notebookで学習インスタンスを立ち上げるパラメータを以下のように設定する。
パラメータsource_dirdependenciesが依存するディレクトリの設定である。

notebook.py
from sagemaker.mxnet import MXNet

gluon_bert = MXNet("train.py", 
                  role=sagemaker.get_execution_role(), 
                  source_dir = "src",
                  train_instance_count=1, 
                  train_instance_type="ml.m4.xlarge",
                  framework_version="1.6.0",
                  dependencies=['lib'],
                  distributions={'parameter_server': {'enabled': True}},
                  py_version = "py3",
                  hyperparameters={'batch-size': 16, 
                                   'epochs': 1, 
                                   'log-interval': 1})

src

このディレクトリには、

  • 学習用コードtrain.py
  • pipでインストールするリストrequirements.txt
  • train.pyで依存する独自コード(※my_source/などディレクトリに分けてもよい)

等を格納する。
requirements.txtは学習時にインストールされる。

lib

srcとの違いはlibにはnotebook上でインストールしたライブラリを入れる点である。
例えばgithubからcloneし、元コードを書き換える必要がある場合に使用する。
注意する点は、notenookインスタンスのOSはamazon linuxであり、学習インスタンスのOSはubuntsである点である。
そのため、OSに依存するパッケージや特に書き換えが必要ない場合はrequirements.txtに書く方ことをオススメする。

学習用コードの書き換え

libのディレクトリを反映するためtrain.py内でpathを通す。

import sys
sys.path.append('./lib/')

参考文献

Amazon SageMaker コンテナライブラリを使用した Docker コンテナの作成

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SSH通信について まとめたこと

SSH通信とは

SSH通信とは、Secure Shell の略で、言わばセキュリティの強固な通信方法のこと。
SSH通信を用いて送られている情報は全て暗号化されており、
仮に通信を覗き見をされていたとしても、キーを持っていなければ解読することが出来ないようになっている。

SSHの仕組み(訂正)

*当初公開鍵認証では乱数を公開鍵で暗号化しそれを複合するという説明を参照としましたがこれらの説明は間違いであったようです。(@angel_p_57さんご指摘ありがとうございます。下記にリンク貼られているページは非常に詳しくまとまっているのでおすすめです。)

SSHで「公開鍵」と言ってるのは、大抵のケースで「認証鍵」(それ以外に「ホスト鍵」もある)
認証鍵は、サーバにログインするユーザを識別するための手続き(ユーザ認証)で使うデータ
認証鍵の実体は、公開鍵暗号の1種である電子署名における、秘密鍵と公開鍵の鍵ペア
ユーザは秘密鍵・公開鍵(両方)を使ってログインを試み、サーバは公開鍵を使ってユーザを識別する
(何らかの方法でサーバに公開鍵を予め登録する必要がある)

ユーザが自身の秘密鍵を使って署名を生成し、その署名をサーバ側が公開鍵を使って検証できれば、その署名は確かに正当なユーザが作成したものだからと判断し、認証成功ということになる。
これと同時にユーザと公開鍵との対応が適切かどうかも照合が行われる。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】EC2 インスタンス SSH接続エラーを解消(ssh: connect to host xxx.xxx.xxx.xxx port 22: Operation timed out)

はじめに

Rails アプリをAWSにてデプロイしようと試みていた過程でEC2サーバにローカル環境からアクセス(SSH接続)しようとした際に、突然アクセスできなくなる、というトラブルに遭遇したので解決方法とともに残します。
(かなり初歩的な部分かと思います。)

原因は場所を移動して作業したこと(接続場所によりIPアドレスが相違していたこと)にありました。
以下で詳細について書きます。

※筆者はMacを使っており、接続方法はMacの仕様で行なっております。

どのようなエラーが起きたのか?

ローカル環境(MacBookAir)から、該当のECSインスタンスにSSHキーを使って、SSH接続を試みる。

# ターミナルにて

$ ssh -i .ssh/ファイル名.pem ec2-user@xxx.xxx.xxx.xxx(該当EC2インスタンスのパブリックIP)

ssh: connect to host xxx.xxx.xxx.xxx port 22: Operation timed out

すると、「接続操作がタイムアウトした」という内容のエラーメッセージが出てきて接続できない。

さっき(1時間程前)まで接続できていたのになぜ?!!

となった。

エラー原因

元作業していた場所から、カフェに移動して作業を開始し、その際に起きたエラー。
該当EC2インスタンスのセキュリティグループ、SSH接続設定と自分の接続元(IPアドレス)に問題があった。

もう少し具体的にいうと、
元いた場所にて最初の設定で、該当EC2インスタンスのセキュリティグループのインバウンドルールのSSH接続のソース(接続元)を「マイIP」に限定していた。これにより、その作業場所のIPアドレスからでないとSSH接続ができない設定がされる。
自分が元作業していた場所を移動して、そこのwifiで作業を開始しSSH接続をしようとし、ソース(接続元のIP)が変わってしまったため、接続元が許可されておらずエラーが起きた。

解決方法

その接続元の許可設定を移動した場所のIPアドレスに合わせて設定し直した。

①AWS該当EC2インスタンスのセキュリティグループから、「インバウンドルールの編集」をクリック
②タイプ:SSH の ソースで再度「マイIP」を選び直す。
③ルールを保存をクリック

スクリーンショット 2020-07-28 10.27.30.png

こうすることで、作業中の場所のIPアドレスからSSH接続を許可したことになるため、接続可能になった。

$ ssh -i .ssh/ファイル名.pem ec2-user@xxx.xxx.xxx.xxx(該当EC2インスタンスのパブリックIP)

[ec2-user@ip-xx-xx-xx-xx ~]$

最後に

SSHキーを所有しているため、セキュリティグループのインバウンドルールを「任意の場所」にすることでもまず不正に利用されるようなことはないと思いますが、「マイIP」に限定しておいた方が無難だと思います(万が一、キーが流出した際の危険性なども考慮して)。
ただ、接続元(場所、wifiなど)を変更した際にこの作業を毎回しなければならないので少し面倒です。

このエラーに遭遇したことでまた少し、ネットワークについても勉強になりました。

誤り、ご指摘等ありましたら気軽にコメント等いただけると幸いです。
ありがとうございました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWSを利用した会員サイトをサーバーレスで実装しました

AWSを利用した会員サイトをサーバーレスで実装しました。
始めはCognitoとS3でなんとかなるだろうと思っていましたが、
いざやってみるとどうにも思い通りにならず、四苦八苦しました。

調べてみると、どうやらCognitoとS3だけでは、S3に対してユーザー単位のコンテンツにしかアクセスできず、ユーザー共有のコンテンツにはアクセスできないみたいです。(私の調査不足/理解不足かもしれませんが...)

というわけで、以下のAWSの機能を利用して実装してみました。

  • S3
  • CloudFront
  • Cognito
  • API Gateway
  • Lambda

処理手順

  1. S3に公開フォルダと会員限定フォルダを作成する。
  2. S3に対してはCloudFrontを通してアクセスする。
  3. S3の会員限定フォルダへは、CloudFrontの閲覧者のアクセスを制限する機能のCookieを使用する。
  4. Cognitoで認証が成功した場合、認証情報のJWTトークンをAPI Gatewayのオーソライザーで検証する。
  5. API Gatewayのオーソライザーで検証が成功した場合、同APIをトリガーに実行されるLambda関数から、Cookieを取得する。
  6. 取得したCookieを使用して、会員限定ページへ遷移する。
  7. サインアウトするとき、Cookieを削除する。

つまり、会員ページへのアクセス制限にCloudFrontを使用し、
CognitoはCloudFrontで必要なCookieを取得するための認証ということになります。

1. S3バケットを作成する

一般公開するpublicフォルダと、ログインしたユーザーがアクセス可能なprivateフォルダを作成します。

\
├── private
│   ├── private-index.bundle.js
│   ├── private-index.bundle.js.map
│   └── private-index.html
└── public
    ├── public-index.bundle.js
    ├── public-index.bundle.js.map
    └── public-index.html

Static website hostingや、バケットポリシーを設定する必要はありません。

2. CroudFrontを設定する

手順1で作成したバケットにCloudFrontを介してアクセスするようにします。
privateフォルダにCookieを使用してアクセスするBehaviorを追加します。
これでprivateフォルダは、Cookieを指定しないとアクセスできないフォルダになります。

3. Cognitoを作成する

ユーザープールを作成する

作成について特筆すべき点はありませんが、以下の情報を控えておきます。

  • ユーザープールID
  • アプリクライアントID

IDプールを作成する

以下の点を留意してください。

  • 認証プロバイダーには、ユーザープールのユーザープールIDアプリクライアントIDを指定します。
  • IDプールのIDを控えておきます。

4. CloudFrontへアクセスするためのCookieを作成する

CloudFrontのキーペアを作成する

AWSアカウントにrootでログインし、CloudFront のキーペアを作成します。

  1. AWSアカウントにrootログインします。
  2. ルートアクセスキーの削除を選択します。
  3. セキュリティ認証情報の管理を選択します。
  4. CloudFront のキーペアを選択します。
  5. 新しいキーペアの作成を選択し、プライベートキーファイル(pk-XXXXXXXX.pem)とパブリックキーファイル(rsa-XXXXXXXX.pem)をダウンロードします。
    • 画面に表示されるアクセスキーIDを控えておいてください。

CloudFrontへアクセスするためのCookieを生成する

CloudFrontへアクセスするためには、3つのCookieが必要です。

  • key-pair
  • policy
  • signature
key-pair

CloudFront のキーペアアクセスキーIDです。

policy

Jsonから改行とインデントを取り除いたものになります。

  • ResourceはアクセスするURLを設定します。(CloudFrontのURL)
  • 最低限DateLessThan(URLの有効期限切れ日時)を設定しておく必要があります。
{
   "Statement":[
      {
         "Resource":"https://xxxxxxxx.cloudfront.net/*",
         "Condition":{
            "DateLessThan":{
               "AWS:EpochTime":2595364179
            }
         }
      }
   ]
}

インデントと改行を取り除いたものをファイルとして保存します。(ここではpolicy.jsonとします)

{"Statement":[{"Resource":"https://xxxxxxxx.cloudfront.net/*","Condition":{"DateLessThan":{"AWS:EpochTime":2595364179}}}]}

以下のコマンドの出力結果がpolicyの値となります。

$ cat ./policy.json | openssl base64 | tr '+=/' '-_~'
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
signature

以下のコマンドの出力結果がsignatureの値となります。

$ cat policy.json | openssl sha1 -sign <プライベートキーファイル> | openssl base64 | tr '+=/' '-_~'
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXX

5. Lambda関数を作成する

CloudFrontへアクセスするためのCookieの値を返す関数を作成します。

  • 手順4で作成した文字列を設定します。
  • CORSに対応したレスポンスになるようにヘッダーを設定します。
exports.handler = async () => {
  const keyPair = 'XXXXXXXX';
  const policy = 'XXXXXXXX';
  const signature = 'XXXXXXXX';

  const json = {
    policy: policy,
    signature: signature,
    keyPair: keyPair
  };

  const response = {
    "statusCode": 200,
    "headers": {
        "Content-Type": 'application/json',
        "Access-Control-Allow-Headers": 'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token',
        "Access-Control-Allow-Methods": "GET",
        "Access-Control-Allow-Origin": "*"
    },
    "body": JSON.stringify(json)
  };

  return response;
};

6. API Gatewayを作成する

API GatewayでCognitoで作成したユーザーのJWT Tokenを検証するためにオーソライザーを作成します。

API Gatewayのオーソライザーを作成する

  1. オーソライザーの名前を入力します。
  2. タイプでは、Cognitoを選択します。
  3. Cognitoユーザープールでは、今回使用する作成済みのユーザープール指定します。
  4. トークンのソースには、Authorizationと入力します。 保存したら、テストしてみましょう。 認証トークンに、認証できたユーザーの情報から取得できるdata.signInUserSession.idToken.jwtTokenを入力してテストします。 ユーザー情報が出力されたらテストは成功です。

API Gatewayを作成する

  • GETメソッドの認可に先ほど作成したオーソライザーを指定します。
  • CROSを有効化します。
  • 手順5で作成したLambda関数と紐付けます。

おまけ

クライアント側のソースコードです。

cognitoの情報を記述したaws-export.js

const AwsConfig = {
  Auth: {
    // IDプールのID
    identityPoolId: 'us-east-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
    // リージョン
    region: 'us-east-1',
    // プールID
    userPoolId: 'us-east-1_xxxxxxxx',
    // アプリクライアントID
    userPoolWebClientId: 'xxxxxxxxxxxxxxxxxxxxxxxxx',
    mandatorySignIn: true,
  },
  Storage: {
    // 使用するS3バケット名
    bucket: 'xxxxxxxx',
    // リージョン
    region: 'us-east-1'
  }
}

export default AwsConfig;

public-index.js

import Amplify, { Auth } from 'aws-amplify';
import AwsConfig from './aws-exports.js';
Amplify.configure(AwsConfig);

const getEmail = () => 
  document.getElementById('email').value;

const getPassword = () => 
  document.getElementById('password').value;

const signUp = () => {
  const email = getEmail();
  const password = getPassword();

  Auth.signUp(email, password)
    .then(data => console.log(data))
    .catch(err => console.log(err));
}

const signIn = () => {
  const email = getEmail();
  const password = getPassword();

  Auth.signIn(email, password)
    .then(data => {
      const url = 'https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/dev';
      const token = data.signInUserSession.idToken.jwtToken;

      fetch(url, {
        method: 'GET',
        headers: {
          'Authorization': token
        }
      })
      .then(res => res.json())
      .then(json => {
        document.cookie = `CloudFront-Key-Pair-Id=${json.keyPair}; path=/;`;
        document.cookie = `CloudFront-Policy=${json.policy}; path=/;`;
        document.cookie = `CloudFront-Signature=${json.signature}; path=/;`;
        document.cookie = `Email=${email}; path=/;`;
        location.href = '../private/private-index.html';
      })
      .catch(err => console.log(err));

    })
    .catch(err => console.log(err));
}

window.addEventListener('DOMContentLoaded', () => {
  const signUpButton = document.getElementById("signUpButton");
  signUpButton.addEventListener("click", () => {
    signUp();
  });

  const signInButton = document.getElementById("signInButton");
  signInButton.addEventListener("click", () => {
    signIn();
  });
});

private-index.js

import Amplify, { Auth } from 'aws-amplify';
import AwsConfig from './aws-exports.js';
Amplify.configure(AwsConfig);

const signOut = () => {
  console.log('Sign out Start.')

  // Cookieを削除します。
  document.cookie = "CloudFront-Key-Pair-Id=; path=/; max-age=0";
  document.cookie = "CloudFront-Policy=; path=/; max-age=0";
  document.cookie = "CloudFront-Signature=; path=/; max-age=0";
  document.cookie = "Email=; path=/; max-age=0";

  const url = '../public/public-index.html';

  Auth.signOut()
    .then(data => {
      console.log(data);
      location.href = url;
    })
    .catch(err => {
      console.log(err)
      location.href = url;
    });

}

window.addEventListener('DOMContentLoaded', () => {
  const signOutButton = document.getElementById("signOutButton");
  signOutButton.addEventListener("click", () => {
    signOut();
  });

  Auth.currentUserInfo()
    .then(user => console.log(user))
    .catch(err => console.log(err))
});

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS 認定クラウドプラクティショナー(CLF)を、これまでの振り返りを兼ねて取得したやったことまとめ

はじめに

AWS 認定クラウドプラクティショナー(CLF)について、これまでの振り返りを兼ねて受験してみました。

クラウドプラクティショナー認定については、Qiita記事が多く投稿されていますので、少し視点を変えて、クラウドプラクティショナー認定に特化した内容は何だったのか感じた内容をお伝えできればと思います。
変則的とはなりますが、クラウドプラクティショナーに関わるAWS関連サービスのイメージを掴んでいただければ幸いです。

本記事の主な対象者

  • クラウドプラクティショナーの受験を検討している方
  • 取得に向けて有効な学習方法などの情報収集したい方

筆者のAWS認定履歴

AWS認定 取得日
ソリューションアーキテクト - アソシエイト 2018-05-13
デベロッパー - アソシエイト 2018-06-03
SysOpsアドミニストレーター - アソシエイト 2018-06-10
ソリューションアーキテクト - プロフェッショナル 2018-07-29
DevOpsエンジニア - プロフェッショナル 2018-08-26
ビッグデータ専門知識 2019-08-25
セキュリティ専門知識 2019-12-08
機械学習専門知識 2020-02-23
高度なネットワーキング専門知識 2020-06-20
データベース専門知識 2020-07-04
Alexaスキルビルダー専門知識 2020-07-24
クラウドプラクティショナー 2020-07-27

今回のスコア(2020-07-27受験)

総合スコア: 949/1000 (ボーダー700)

AWS 認定クラウドプラクティショナー(CLF)について

ここからが本題となります。
まずは、以下の公式ページから試験概要の把握を行いました。

実際に認定試験を受けてみて感じたこと

  • その他の認定区分とシナジー効果があったのは、「セキュリティ専門知識」「SysOpsアドミニストレーター」かなと感じました。これは、これは試験ガイドにも記載があります通り、「セキュリティ」と「請求と料金」 が、クラウドプラクティショナーの試験内容となっているためです。

  • テクニカルな部分以外の問題も散見されました。例えば、サポートプラン(ベーシック、開発者、ビジネス、エンタープライズ)については、それぞれ何ができて何ができないのかを整理しておくことが必要かなと思います。あとは、不意打ち的なところですと、APNパートナーなど、AWSで定義される人物ロールがいくつかの設問に登場してきました。

おわりに

足掛け2年ほど。AWS認定12区分を取り終えることができました。
振り返ると、認定の取得自体は一種の筋トレで、基礎体力を獲得するようなものでした。AWSのサービスは日々アップグレードしていますので、引き続き継続した学習を続けたいと思います。
今後受験を検討される方の一助になれば幸いです。

著者関連リンク

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pulumiでAWSにWardPressの環境を構築する

はじめに

前回、pulumiでwebアプリケーションを動かすための基本構成(ec2+rdsの環境)をAWSに構築しました前回の記事

今回は、ALB+ec2の動的webサイトの構成をpulumiで構築します。
また、pulumiで構築したAWS環境にWordPress(docker container)を動作させます。

構成図

スクリーンショット 2020-07-28 0.38.26.png

実際のコード

コード全体

今回作成した、pulumiのコードになります

__main__.py
import pulumi
import pulumi_aws as aws

# VPCの作成
vpc = aws.ec2.Vpc(
    "pulumi-vpc",
    cidr_block="10.0.0.0/16",
    tags={
        "Name": "pulumi-vpc",
    })

# Subnetの作成
public_subnet_a = aws.ec2.Subnet(
    "pulumi-public-subnet-a",
    cidr_block="10.0.1.0/24",
    availability_zone="ap-northeast-1a",
    tags={
        "Name": "pulumi-public-subnet-a",
    },
    vpc_id=vpc.id)

public_subnet_c = aws.ec2.Subnet(
    "pulumi-public-subnet-c",
    cidr_block="10.0.2.0/24",
    availability_zone="ap-northeast-1c",
    tags={
        "Name": "pulumi-public-subnet-c",
    },
    vpc_id=vpc.id)

private_subnet_a = aws.ec2.Subnet(
    "pulumi-private-subnet-a",
    cidr_block="10.0.3.0/24",
    availability_zone="ap-northeast-1a",
    tags={
        "Name": "pulumi-private-subnet-a",
    },
    vpc_id=vpc.id)

private_subnet_c = aws.ec2.Subnet(
    "pulumi-private-subnet-c",
    cidr_block="10.0.4.0/24",
    availability_zone="ap-northeast-1c",
    tags={
        "Name": "pulumi-private-subnet-c",
    },
    vpc_id=vpc.id)

# InternetGatewayの作成
igw = aws.ec2.InternetGateway(
    "pulumi-igw",
    tags={
        "Name": "pulumi-igw",
    },
    vpc_id=vpc.id)

# EIPの作成
ngw_eip_a = aws.ec2.Eip("pulumi-ngw-eip-a")

ngw_eip_c = aws.ec2.Eip("pulumi-ngw-eip-c")

# NatGatewayの作成
ngw_a = aws.ec2.NatGateway(
    "pulumi-ngw-a",
    allocation_id=ngw_eip_a.id,
    subnet_id=public_subnet_a.id)

# NatGatewayの作成
ngw_c = aws.ec2.NatGateway(
    "pulumi-ngw-c",
    allocation_id=ngw_eip_c.id,
    subnet_id=public_subnet_c.id)

# RouteTableの作成
public_route_table_a = aws.ec2.RouteTable(
    "pulumi-public-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "gateway_id": igw.id,
        },
    ],
    tags={
        "Name": "pulumi-public-route-table-a",
    },
    vpc_id=vpc.id)

public_route_table_c = aws.ec2.RouteTable(
    "pulumi-public-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "gateway_id": igw.id,
        },
    ],
    tags={
        "Name": "pulumi-public-route-table-c",
    },
    vpc_id=vpc.id)

private_route_table_a = aws.ec2.RouteTable(
    "pulumi-private-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_a.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-a",
    },
    vpc_id=vpc.id)

private_route_table_c = aws.ec2.RouteTable(
    "pulumi-private-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_c.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-c",
    },
    vpc_id=vpc.id)

# RouteTableAssociationの作成
route_table_association_public_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-public-a",
    subnet_id=public_subnet_a.id,
    route_table_id=public_route_table_a.id)

route_table_association_public_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-public-c",
    subnet_id=public_subnet_c.id,
    route_table_id=public_route_table_c.id)

route_table_association_private_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-a",
    subnet_id=private_subnet_a.id,
    route_table_id=private_route_table_a.id)

route_table_association_private_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-c",
    subnet_id=private_subnet_c.id,
    route_table_id=private_route_table_c.id)

# SecurityGroupの作成
alb_sg = aws.ec2.SecurityGroup(
    "pulumi-alb-sg",
    ingress=[
        {
            "from_port": 80,
            "protocol": "TCP",
            "to_port": 80,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-alb-sg",
    },
    vpc_id=vpc.id)

ec2_sg = aws.ec2.SecurityGroup(
    "pulumi-ec2-sg",
    ingress=[
        {
            "from_port": 80,
            "protocol": "TCP",
            "to_port": 80,
            "security_groups": [alb_sg.id]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-ec2-sg",
    },
    vpc_id=vpc.id)

rds_sg = aws.ec2.SecurityGroup(
    "pulumi-rds-sg",
    ingress=[
        {
            "from_port": 3306,
            "protocol": "TCP",
            "to_port": 3306,
            "security_groups": [ec2_sg.id]
        },
    ],
    egress=[
        {
            "from_port": 0,
            "protocol": "TCP",
            "to_port": 65535,
            "cidr_blocks": ["0.0.0.0/0"]
        },
    ],
    tags={
        "Name": "pulumi-rds-sg",
    },
    vpc_id=vpc.id)

# KeyPairの作成
key_pair = aws.ec2.KeyPair(
    "pulumi-keypair",
    public_key="ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD3F6tyPEFEzV0LX3X8BsXdMsQz1x2cEikKDEY0aIj41qgxMCP/iteneqXSIFZBp5vizPvaoIR3Um9xK7PGoW8giupGn+EPuxIA4cDM4vzOqOkiMPhz5XK0whEjkVzTo4+S0puvDZuwIsdiW9mxhJc7tgBNL0cYlWSYVkz4G/fslNfRPW5mYAM49f4fhtxPb5ok4Q2Lg9dPKVHO/Bgeu5woMc7RY0p1ej6D4CKFE6lymSDJpW0YHX/wqE9+cfEauh7xZcG0q9t2ta6F6fmX0agvpFyZo8aFbXeUBr7osSCJNgvavWbM/06niWrOvYX2xwWdhXmXSrbX8ZbabVohBK41 email@example.com",
    tags={
        "Name": "pulumi-keypair",
    })

#  TargetGroupの作成
target_group = aws.alb.TargetGroup(
    "pulumi-target-group",
    health_check={
        "healthyThreshold": 5,
        "interval": 30,
        "matcher": "200,302",
        "path": "/",
        "protocol": "HTTP",
        "timeout": 5,
        "unhealthyThreshold": 2
    },
    name="pulumi-target-group",
    port=80,
    protocol="HTTP",
    tags={
        "Name": "pulumi-target-group",
    },
    target_type="instance",
    vpc_id=vpc.id)

# ユーザデータの読み込み
file = open("./user-data")
user_data = file.read()

# LaunchConfigurationの作成
launch_conf = aws.ec2.LaunchConfiguration(
    "pulumi-launch-conf",
    image_id="ami-0ee1410f0644c1cac",
    instance_type="t2.micro",
    associate_public_ip_address=True,
    key_name=key_pair.key_name,
    security_groups=[ec2_sg.id],
    user_data=user_data)

file.close()

# AutoScalingGroupの作成
autoscaling_group = aws.autoscaling.Group(
    "pulumi-autoscaling-group",
    availability_zones=["ap-northeast-1a", "ap-northeast-1c"],
    health_check_type="ELB",
    desired_capacity=1,
    launch_configuration=launch_conf.name,
    max_size=1,
    min_size=1,
    target_group_arns=[target_group.arn],
    vpc_zone_identifiers=[
        private_subnet_a.id,
        private_subnet_c.id
    ])

# LoadBalancerの作成
alb = aws.alb.LoadBalancer(
    "pulumi-alb",
    load_balancer_type="application",
    name="pulumi-alb",
    security_groups=[alb_sg.id],
    subnets=[
        public_subnet_a.id,
        public_subnet_c.id
    ],
    tags={
        "Name": "pulumi-alb",
    })

# Listenerの作成
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="80",
    protocol="HTTP")

# RDS SubnetGroupの作成
rds_subnet = aws.rds.SubnetGroup(
    "pulumi-rds-subnet",
    subnet_ids=[
        private_subnet_a.id,
        private_subnet_c.id,
    ],
    tags={
        "Name": "pulumi-rds-subnet",
    })

#RDSインスタンスの作成
rds = aws.rds.Instance(
    "pulumi-rds",
    allocated_storage=20,
    db_subnet_group_name=rds_subnet.name,
    engine="mysql",
    engine_version="5.7",
    identifier="pulumi-rds",
    instance_class="db.t2.micro",
    name="pulumi",
    parameter_group_name="default.mysql5.7",
    password="password",
    skip_final_snapshot=True,
    storage_type="gp2",
    tags={
        "Name": "pulumi-rds",
    },
    username="admin",
    vpc_security_group_ids=[rds_sg.id])
user-data
#!/bin/bash

yum install docker -y

service docker start

docker pull wordpress

docker run -p 80:80 -d wordpress

NAT Gateway、Route Table(private subnet用)

今回は、アプリケーションをホストするEC2をprivate subnetで起動する
EC2がインターネットに抜けられるようにNAT Gatewayとそれに向けたRoute Tableを作成した

# NAT Gatewayに紐付けるEIPの作成
ngw_eip_a = aws.ec2.Eip("pulumi-ngw-eip-a")

ngw_eip_c = aws.ec2.Eip("pulumi-ngw-eip-c")

# NatGatewayの作成
ngw_a = aws.ec2.NatGateway(
    "pulumi-ngw-a",
    allocation_id=ngw_eip_a.id,
    subnet_id=public_subnet_a.id)

# NatGatewayの作成
ngw_c = aws.ec2.NatGateway(
    "pulumi-ngw-c",
    allocation_id=ngw_eip_c.id,
    subnet_id=public_subnet_c.id)

# private subnet用のRouteTableの作成
# NAT Gateway向けのrouteを追加している
private_route_table_a = aws.ec2.RouteTable(
    "pulumi-private-route-table-a",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_a.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-a",
    },
    vpc_id=vpc.id)

private_route_table_c = aws.ec2.RouteTable(
    "pulumi-private-route-table-c",
    routes=[
        {
            "cidr_block": "0.0.0.0/0",
            "nat_gateway_id": ngw_c.id,
        },
    ],
    tags={
        "Name": "pulumi-private-route-table-c",
    },
    vpc_id=vpc.id)

# RouteTableとsubnetの紐付け
route_table_association_private_a = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-a",
    subnet_id=private_subnet_a.id,
    route_table_id=private_route_table_a.id)

route_table_association_private_c = aws.ec2.RouteTableAssociation(
    "pulumi-route-table-association-private-c",
    subnet_id=private_subnet_c.id,
    route_table_id=private_route_table_c.id)

LaunchConfiguration、AutoScalingGroup、TargetGroup

LaunchConfigurationとAutoScalingGroupを作成する
また、ALBとAutoScalingGroupを紐づけるためのTargetGroupを作成する

#  TargetGroupの作成
target_group = aws.alb.TargetGroup(
    "pulumi-target-group",
    health_check={
        "healthyThreshold": 5,
        "interval": 30,
        "matcher": "200,302",
        "path": "/",
        "protocol": "HTTP",
        "timeout": 5,
        "unhealthyThreshold": 2
    },
    name="pulumi-target-group",
    port=80,
    protocol="HTTP",
    tags={
        "Name": "pulumi-target-group",
    },
    target_type="instance",
    vpc_id=vpc.id)

# ユーザデータの読み込み
file = open("./user-data")
user_data = file.read()

# LaunchConfigurationの作成
launch_conf = aws.ec2.LaunchConfiguration(
    "pulumi-launch-conf",
    image_id="ami-0ee1410f0644c1cac",
    instance_type="t2.micro",
    associate_public_ip_address=True,
    key_name=key_pair.key_name,
    security_groups=[ec2_sg.id],
    user_data=user_data)

file.close()

# AutoScalingGroupの作成
autoscaling_group = aws.autoscaling.Group(
    "pulumi-autoscaling-group",
    availability_zones=["ap-northeast-1a", "ap-northeast-1c"],
    health_check_type="ELB",
    desired_capacity=1,
    launch_configuration=launch_conf.name,
    max_size=1,
    min_size=1,
    target_group_arns=[target_group.arn],
    vpc_zone_identifiers=[
        private_subnet_a.id,
        private_subnet_c.id
    ])

TargetGroupのhealth_checkの設定について、
WordPressの/にアクセスした際にredirectされるため、matcherの設定値にredirectのステータスコード(302)も追加している

LaunchConfigurationのuser_dataについては、別ファイルから読み込むようにしている
user_dataの中身については後述する

ALB

ALBの作成とListenerを作成する。

# LoadBalancerの作成
alb = aws.alb.LoadBalancer(
    "pulumi-alb",
    load_balancer_type="application",
    name="pulumi-alb",
    security_groups=[alb_sg.id],
    subnets=[
        public_subnet_a.id,
        public_subnet_c.id
    ],
    tags={
        "Name": "pulumi-alb",
    })

# Listenerの作成(HTTP)
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="80",
    protocol="HTTP")

今回は、HTTPでListenerを作成したが、HTTPSのListenerを作成する場合は以下のように証明書などの設定も必要となる

# Listenerの作成(HTTPS)
alb_listener = aws.alb.Listener(
    "pulumi-alb-listener",
    certificate_arn="arn:aws:iam::187416307283:server-certificate/test_cert_rab3wuqwgja25ct3n4jdj2tzu4",
    default_actions=[{
        "target_group_arn": target_group.arn,
        "type": "forward",
    }],
    load_balancer_arn=alb.arn,
    port="443",
    protocol="HTTPS",
    ssl_policy="ELBSecurityPolicy-2016-08")

ユーザーデータ

今回は、Docker hubに登録されているWordPressのimageを起動させる
ユーザデータで、dockerのインストールと起動、WordPressコンテナの起動を行う

user-data
#!/bin/bash

# dockerのインストール
yum install docker -y

# dockerデーモンの起動
service docker start

# WordPressのimageをpull
docker pull wordpress

# WordPressのコンテナを起動
docker run -p 80:80 -d wordpress

まとめ

今回は、動的なWebサイトを動作させるための代表的なAWS構成をpulumiで作成し、WordPressのdockerコンテナを起動してみました。

前回の記事で作成したAWSリソースについては今回は詳しく説明していないので、よろしければ前回の記事も参考にしてみてください。

おまけ

実際にアクセスしてみる

言語の設定
スクリーンショット 2020-07-28 1.46.46.png

データベース情報の設定
ここでは、先ほど作成したRDSの設定を入力してください。
スクリーンショット 2020-07-28 1.47.05.png

ユーザー情報の設定
スクリーンショット 2020-07-28 1.48.27.png

WordPressログイン後の画面
スクリーンショット 2020-07-28 1.49.46.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go Gin Docker AWSで完璧なTodoアプリを作る

こんにちは。
今回は最近流行りのGoの勉強ついでにQiitaを書いてみようと思います。

普段はReactでのフロント開発やRailsでAPI開発などをしています。
社内でもRailsからGoへの書き換えが始まっている中で、Goってどうやって書くの?インフラわからないんだけどデプロイしてみたい!って軽い気持ちでサンプルアプリを作ってみたいと思います。
意外とローカルで動くものを作って終わりな記事が多いので今回はデプロイまで行うことをゴールとして開発していきます!!

使用技術

  • go
  • gin
  • go modules
  • mysql
  • docker
  • gorm
  • AWS

参考文献

Go / Gin で超簡単なWebアプリを作る
DockerでGoの開発環境を構築する

早速やっていこーう(環境構築)

(ここではgoのインストールは省略しまーす。)
まずやることはディレクトリの作成!

mkdir perfect_todo
cd perfect_todo

そして今回はgoのライブラリーを管理するためにgo modというものを使っていきます。これはどのライブラリーを入れたかを管理してくれるので、docker化した時や共同開発の時にとても便利らしい。。。

go mod init perfect_todo

これで go.modgo.sum というファイルが生成されたかと思います。

そしたら次にgoのフレームワークであるginをインストールしていきたいと思います。

go get github.com/gin-gonic/gin

go.mod をみてみてください。これで github.com/gin-gonic/gin が追加されていたらインストールが完了しています!

それが成功したら

touch main.go

でmain.goのファイルを作ります。このファイルが実行されて、ここでインポートされているファイルが読み込まれていくので、アプリケーションの肝となるファイルです。

main.go
package main

import (
    "github.cogom/gin-gonic/gin"
)

func main() {
    router := gin.Default()
    router.LoadHTMLGlob("templates/*.html")

    router.GET("/", func(ctx *gin.Context){
        ctx.HTML(200, "index.html", gin.H{})
    })

    router.Run()
}

ここではginを使って、ルーティングを設定しています。/にアクセスが来たらindex.htmlを表示するといった具合に設定をしています。

そしてそのindex.htmlがなくては表示させるファイルがないのでindex.htmlを作成します!

templatesディレクトリーを作ってその下にindex.htmlを作成します

templates/index.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Sample App</title>
</head>
<body>
    <h1>Hello World!!!</h1>
</body>
</html>

これでお待ちかねのHello Worldの準備が完了です!

go run main.go

これを実行して localhost:8080 にいくと。。。!?!?

image.png

ででーーーーーん!!
Hell World!

何回みてもこの感動は良いですよねえ。

Docker化

ここではDockerの説明は省きますが、Dockerにしておくと環境の違いで苦しんだりすることがないのでDocker化しておきます。

touch Dockerfile

Dockerfile を作成します。

FROM golang:latest
RUN mkdir /go/src/app
WORKDIR /go/src/app
ADD . /go/src/app
VOLUME /go/src/app
RUN go mod download
EXPOSE 8080
CMD "go" "run" "main.go"

途中の go mod download でさっき作成した go.mod から自動でライブラリーを読み込んでくれると!便利ですね。
あとは今までやってきたことをDockerにやってもらっているだけです!

では実行していきましょう!

docker build -t perfect_todo .
docker run -it -p 8080:8080 perfect_todo

これでさっき実行した時と同じ挙動になったら成功です!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者】Amazon Interactive Video Service (Amazon IVS) を使ってみる

目的

  • 以前、AWSの動画配信サービス(AWS Elemental MediaLive + MediaService) を使ってみたが、今回それよりも簡単に設定、利用開始できるサービスがリリースされたと聞き、使用感を確認することとした。

Amazon Interactive Video Service (Amazon IVS) とは(自分の理解)

  • 簡単に動画配信を行えるサービス。OBS Studio等のソフトウェアからの動画の入力を受付し、クライアント(ブラウザやアプリ)に配信する。

やったこと

  • Amazon IVSの設定(チャネルの作成)を行い、OBS Studio から動画を配信し、ブラウザで受信する。
  • OBS Studio(配信) -> Amazon IVS -> ブラウザ(受信)までの遅延時間を計測する。

構成図

  • 既にサービスのロゴがあるようだが、2020/7時点でアイコンリストにはまだ掲載されていないため使用できず残念。
  • 2020/7時点で、提供リージョンはバージニア北部、オレゴン、アイルランドのみ。今回はオレゴンを利用。

構成図.png

作業手順

IVSの設定

  • マネージメントコンソールのAmazon IVS - チャネル から、「チャネルの作成」を選択し、チャネル名を入力してチャネルを作成する。今回はデフォルト設定のままとする。

ivs01a.png

ivs02a.png

  • 以下の3つの値をメモする。
    • 取り込みサーバー(OBS Studio等の動画配信元からの入力を受け付けるエンドポイントURL)
    • ストリームキー(パスワードのようなもの)
    • 再生URL(クライアント側からアクセスされる時に用いる出力用のURL)

ivs03a.png

OBS Studioの設定

ivs04a.png

クライアント用htmlの作成

<script src="https://player.live-video.net/VERSION_NUMBER/amazon-ivs-player.min.js"></script>
<video id="video-player" playsinline></video>
<script>
  if (IVSPlayer.isPlayerSupported) {
    const player = IVSPlayer.create();
    player.attachHTMLVideoElement(document.getElementById('video-player'));
    player.load(PLAYBACK_URL);
    player.play();
  }
</script>
  • 「VERSION_NUMBER」のところは、公式リリースノート:「Amazon Interactive Video Service Release Notes」を参照し、「1.0.0」とする。
  • 「PLAYBACK_URL」のところに「再生URL」を指定する。
  • 上記の2つの値を設定し、一応htmlの形に整える。
sample.html
<!DOCTYPE html>
<html>
<head>
<title>Amazon IVS sample.html</title>
</head>
<body>
<script src="https://player.live-video.net/1.0.0/amazon-ivs-player.min.js"></script>
<video id="video-player" playsinline></video>
<script>
  if (IVSPlayer.isPlayerSupported) {
    const player = IVSPlayer.create();
    player.attachHTMLVideoElement(document.getElementById('video-player'));
    player.load("https://xxxxxxxxxxxx.us-west-2.playback.live-video.net/api/video/v1/us-west-2.xxxxxxxxxxxx.channel.xxxxxxxxxxxx.m3u8");
    player.play();
  }
</script>
</body>
</html>


動作確認

配信動作と遅延の確認

  • 左:配信画面(OBS Studio)と右:受信画面(Edge)
  • 今回は OBS Studio(関東) -- Amazon IVS(オレゴン) -- ブラウザ(関東)の構成のため、ネットワークでの遅延要素も大きいかもしれない。今回の環境では6秒程度の遅延だった。

ivs05a.png

所感

  • 配信設定だけであれば数分で完了する、非常に簡単なサービスであると感じた。
  • 単純な動画配信だけでなく、受信者へのメッセージ配信等も機能として存在する様子のため、そちらも触ってみたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む