20190530のAWSに関する記事は17件です。

S3にアップロードした画像をAjaxで取得する

やりたいこと

デバッグ等で、S3にアップロードされているかどうかをチェックした後に、
URLを差し替えて画像を表示したいというニッチなニーズに遭遇。

普通に何も考えずに実装すると

has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

という感じに怒られる。
ちゃんとヘッダを設定して、クロスドメインを有効にしましょうという話。

S3のCORS設定

s3_cors_settings.png

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>*</AllowedOrigin>
    <AllowedMethod>GET</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>

AjaxでcrossDomainを有効にする

    _.each($('.s3-image-debugger'), function(img, _idx) {
      var $img = $(img);
      var src = $img.attr('src');

      if (src && src.match(/s3-ap-northeast-1.amazonaws.com/)) {
        $.get(src, {crossDomain: true}).fail(function() {
          $img.attr('src', src.replace('production', 'test'));
        });
      }
    });

かなり急いで作ったので雑ですが、意図した挙動になりました :innocent:

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

Lambda-backed カスタムリソースで最新Boto3 Lambda Layersを自動生成

背景

Python3でLambda Functionを書いて実行するとき、Boto3のドキュメントには使用方法が書かれているのに、いざ実行してみるとメソッドが存在していないと怒られることがあります。
例えば、RDSのstop_db_clusterは、LambdaのBoto3では実行できませんでした。(2019/5/30現在)

sample
def stop_aurora_cluster(cluster_identifier):
    rds = boto3.client('rds')
    response = rds.stop_db_cluster(DBClusterIdentifier=cluster_identifier)

上記関数をLambda Functionで実行すると、以下のような結果となり、失敗します。

'RDS' object has no attribute 'stop_db_cluster': AttributeError
(略)
AttributeError: 'RDS' object has no attribute 'stop_db_cluster'

しかしboto3のドキュメントには、stop_db_clusterが定義されています。
メソッドは存在するのに、Lambdaからは使えないという状況です。

何故このようなことが起こるのかというと、Lambdaに搭載されているBoto3のバージョンが、Boto3の最新バージョンに比べて古いからです。
ただ、Lambda Layersを使えば、最新のBoto3を利用できます。
詳しくは以下のリンク先が参考になりますが、Lambdaに標準搭載されているBoto3を、より新しいバージョンのBoto3のLambda Layersで上書きするという方法です。
(参考) Lambda Layers で最新の AWS SDK を使用する

このように、最新のBoto3を使いたい場合、都度、上記リンク先の方法でLambda Layersを作る必要がありますが、これは面倒な作業です。
zipを作ってマネジメントコンソールでアップロードするのも、zipをS3にアップロードしてCloudFormationのテンプレートを実行するのも、手作業が多くて非常に面倒です。

  • Lambda Functionをデプロイする度にLambda Layersをデプロイし直すのは嫌だ!
  • なんとか自動でやりたい!

ということで、

  • 最新のBoto3 Lambda Layersを自動で生成するLambda Function

を作ってしまいました。

このLambda FunctionをAWS CloudFormationのLambda-backed カスタムリソース (CloudFormationのテンプレート実行時に、テンプレート内でLambda Functionを実行できる機能) で実行すれば、毎回、自動で最新のBoto3 Lambda Layersを生成し、別のLambda Functionにアタッチすることができるようになります。

CloudFormationテンプレート(YAML)

S3 Bucketを作っておきます。
ぶっちゃけそのS3 BucketもCloudFormationで作れば良いのですが、今回は割愛しています。

IAM Role

Lambda Functionで実行するs3:PutObjectを許可します。

DeployNewestBoto3LambdaLayerRole:
    Type: AWS::IAM::Role
    Properties: 
        RoleName: "DeployNewestBoto3LambdaLayerRole"
        AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Principal:
                Service:
                - lambda.amazonaws.com
              Action:
              - sts:AssumeRole
        Path: "/"
        ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        Policies:
            - PolicyName: "DeployNewestBoto3LambdaLayerPolicy"
              PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                - Effect: Allow
                  Sid: "DeployNewestBoto3LambdaLayerPolicy"
                  Action:
                    - s3:PutObject
                  Resource: "*"

Lambda Function (Boto3 Lambda Layersを生成するLambda Function)

まず、Lambda-backed カスタムリソースのお作法として、cfnresponse.send()でシグナルを返すというものがあります。
Pythonのコードにcfnresponse.send()を書き忘れますと、CloudFormationのテンプレートを実行したとき、カスタムリソースの作成時にLambda Functionからシグナルが返らずに1時間待ちとなって、果てには作成に失敗します。
さらに、ロールバック時にもLambda Functionが実行され、そこでもシグナルが返らずに1時間待ちとなって、合計2時間待ちを食らいます。
image.png

次に、以下のコードでどのようにLambda Layersを生成しているかですが、まず、Lambda FunctionがLinux上で動いていることを利用し、Lambda Functionに書き込み権限がある/tmpに、subprocessとpipを駆使して、最新Boto3を書き込んでいます。
その後Boto3をzipに圧縮しますが、Lambda Functionが動くLinuxではzipコマンドが使えなかったため、Pythonのshutilを使って圧縮しています。
(ちなみにgzip等は使えるようです。何故標準zipが使えないかはよく分かりません。。。)

最後の処理で、出来上がったzipファイルをS3にアップロードします。

DeployNewestBoto3LambdaLayerLambdaFunction:
    Description: "To make the newest boto3 Lambda Layers and upload it to s3 bucket"
    Type: "AWS::Lambda::Function"
    Properties: 
      Code:
        ZipFile: !Sub |
            import boto3
            import subprocess
            import shutil
            import cfnresponse

            # Lambdaハンドラ
            def lambda_handler(event, context):
                try:
                    # 引数(event)チェック
                    if event['RequestType'] == 'Delete':
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                        return

                    # mkdir /tmp/python
                    mkdir_command = subprocess.run(["mkdir", "/tmp/python"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # pip install -t /tmp/python/ boto3
                    boto3_install_command = subprocess.run(["pip", "install", "-t", "/tmp/python", "boto3"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # ls -al /tmp/python | grep boto3- | grep .dist-info
                    ls_boto3_command = subprocess.Popen(["ls", "/tmp/python"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    grep_boto3_command = subprocess.Popen(["grep", "boto3-"], stdin = ls_boto3_command.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    dist_boto3_command = subprocess.Popen(["grep", ".dist-info"], stdin = grep_boto3_command.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    dist_boto3_command_stdout = dist_boto3_command.communicate()[0]
                    boto3_version_str = dist_boto3_command_stdout.decode('utf-8').replace('boto3-', '').replace('.dist-info', '').replace('\n', '')

                    ls_boto3_command.stdout.close()
                    grep_boto3_command.stdout.close()
                    dist_boto3_command.stdout.close()

                    # /tmp/pythonをzipに圧縮
                    boto3_zip_path = '/tmp/boto-'+boto3_version_str
                    shutil.make_archive(boto3_zip_path, 'zip', root_dir='/tmp/python')

                    # ls /tmp/
                    ls_boto3_command = subprocess.run(["ls", "/tmp/"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # boto3-x.x.xxx.zip をS3にアップロード
                    s3Resource = boto3.resource('s3')
                    s3FileUpload = s3Resource.meta.client.upload_file(Filename=boto3_zip_path+'.zip', Bucket=event['ResourceProperties']['S3Bucket'], Key='boto3-'+boto3_version_str+'.zip')

                    # 戻り値の生成
                    responseData = {
                        'S3Bucket': event['ResourceProperties']['S3Bucket'],
                        'S3Key': 'boto3-'+boto3_version_str+'.zip'
                    }
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

                except Exception:
                    # 戻り値の生成
                    cfnresponse.send(event, context, cfnresponse.FAILED, {})
      FunctionName: DeployNewestBoto3LambdaLayer
      Handler: index.lambda_handler
      MemorySize: 256
      ReservedConcurrentExecutions: 1
      Role: !GetAtt DeployNewestBoto3LambdaLayerRole.Arn
      Runtime: "python3.6"
      Timeout: 300
      Tags: 
      - Key: Name
        Value: 'DeployNewestBoto3LambdaLayer'
      - Key: CloudformationArn
        Value: !Ref 'AWS::StackId'

カスタムリソース (Boto3 Lambda Layersを生成するLambda Functionを呼び出す)

Lambda Functionが書ければ、カスタムリソースはこれだけです。
ServiceTokenでLambda Functionを指定して呼び出します。
S3Bucketは、Lambda Functionの内部で利用するための独自パラメータです。

NewestBoto3LambdaLayerCustomResource:
  Type: Custom::NewestBoto3LambdaLayerCustomResource
  Properties:
    ServiceToken: !GetAtt DeployNewestBoto3LambdaLayerLambdaFunction.Arn
    S3Bucket: 'hogehoge'

Lambda Layers

カスタムリソースの実行が正常に完了すると、Lambda LayersのzipファイルがアップロードされたS3 Bucket名と、zipファイルのパスを示すS3Keyが返ります。
これらの値をContentのパラメータに代入します。

NewestBoto3LambdaLayer:
    Type: "AWS::Lambda::LayerVersion"
    Properties:
      Description: !GetAtt NewestBoto3LambdaLayerCustomResource.S3Key
      CompatibleRuntimes: 
        - python3.6
        - python3.7
      Content: 
        S3Bucket: !GetAtt NewestBoto3LambdaLayerCustomResource.S3Bucket
        S3Key: !GetAtt NewestBoto3LambdaLayerCustomResource.S3Key
      LayerName: 'boto3-python-layer'

テンプレート実行結果

CloudFormationテンプレート内で、Lambda Layersを生成するLambda FunctionがLambda-backed カスタムリソースとして実行され、結果、最新のBoto3を含むLambda Layersがアップロードされました。
image.png

まとめ

Lambda Layersを生成するLambda FunctionをLambda-backed カスタムリソースで実行することで、Lambda Layersを手作業で作ることなく、自動生成できるようになりました。
手作業でのzip圧縮&アップロードがなくなり、AttributeErrorも回避でき、非常に快適となります。

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

Lambda-backed カスタムリソースでLambdaのBoto3を自動で最新化する

背景

AWSのLambda FunctionをPython3で書いて実行するとき、Boto3のドキュメントには使用方法が書かれているのに、いざ実行してみるとメソッドが存在していないと怒られることがあります。
例えば、RDSのstop_db_clusterは、LambdaのBoto3では実行できませんでした。(2019/5/30現在)

sample
def stop_aurora_cluster(cluster_identifier):
    rds = boto3.client('rds')
    response = rds.stop_db_cluster(DBClusterIdentifier=cluster_identifier)

上記関数をLambda Functionで実行すると、以下のような結果となり、失敗します。

'RDS' object has no attribute 'stop_db_cluster': AttributeError
(略)
AttributeError: 'RDS' object has no attribute 'stop_db_cluster'

しかしboto3のドキュメントには、stop_db_clusterが定義されています。
メソッドは存在するのに、Lambdaからは使えないという状況です。

何故このようなことが起こるのかというと、Lambdaに搭載されているBoto3のバージョンが、Boto3の最新バージョンに比べて古いからです。
ただ、Lambda Layersを使えば、最新のBoto3を利用できます。
詳しくは以下のリンク先が参考になりますが、Lambdaに標準搭載されているBoto3を、より新しいバージョンのBoto3のLambda Layersで上書きするという方法です。
(参考) Lambda Layers で最新の AWS SDK を使用する

このように、最新のBoto3を使いたい場合、都度、上記リンク先の方法でLambda Layersを作る必要がありますが、これは面倒な作業です。
zipを作ってマネジメントコンソールでアップロードするのも、zipをS3にアップロードしてCloudFormationのテンプレートを実行するのも、手作業が多くて非常に面倒です。

  • Lambda Functionをデプロイする度にLambda Layersを手動でデプロイし直すのは嫌だ!
  • なんとか自動でやりたい!

ということで、

  • 最新のBoto3 Lambda Layersを自動で生成するLambda Function

を作ってしまいました。

このLambda FunctionをAWS CloudFormationのLambda-backed カスタムリソース (CloudFormationのテンプレート実行時に、テンプレート内でLambda Functionを実行できる機能) で実行すれば、毎回、自動で最新のBoto3 Lambda Layersを生成し、別のLambda Functionにアタッチすることができるようになります。

CloudFormationテンプレート(YAML)

S3 Bucketを作っておきます。
ぶっちゃけそのS3 BucketもCloudFormationで作れば良いのですが、今回は割愛しています。

IAM Role

Lambda Functionで実行するs3:PutObjectを許可します。

DeployLatestBoto3LambdaLayerRole:
    Type: AWS::IAM::Role
    Properties: 
        RoleName: "DeployLatestBoto3LambdaLayerRole"
        AssumeRolePolicyDocument:
            Version: '2012-10-17'
            Statement:
            - Effect: Allow
              Principal:
                Service:
                - lambda.amazonaws.com
              Action:
              - sts:AssumeRole
        Path: "/"
        ManagedPolicyArns:
            - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
        Policies:
            - PolicyName: "DeployLatestBoto3LambdaLayerPolicy"
              PolicyDocument: 
                Version: "2012-10-17"
                Statement: 
                - Effect: Allow
                  Sid: "DeployLatestBoto3LambdaLayerPolicy"
                  Action:
                    - s3:PutObject
                  Resource: "*"

Lambda Function (Boto3 Lambda Layersを生成するLambda Function)

まず、Lambda-backed カスタムリソースのお作法として、cfnresponse.send()でシグナルを返すというものがありますので、必ず書くようにします。
Pythonのコードにcfnresponse.send()を書き忘れますと、CloudFormationのテンプレートを実行したとき、カスタムリソースの作成時にLambda Functionからシグナルが返らずに1時間待ちとなって、果てには作成に失敗します。
さらに、ロールバック時にもLambda Functionが実行され、そこでもシグナルが返らずに1時間待ちとなって、合計2時間待ちを食らいます。
image.png

次に、以下のコードでどのようにLambda Layersを生成しているかですが、まず、Lambda FunctionがLinux上で動いていることを利用し、Lambda Functionに書き込み権限がある/tmpに、subprocessとpipを駆使して、最新Boto3を書き込んでいます。
その後Boto3をzipに圧縮しますが、Lambda Functionが動くLinuxではzipコマンドが使えなかったため、Pythonのshutilを使って圧縮しています。
(ちなみにgzip等は使えるようです。何故標準zipが使えないかはよく分かりません。。。)

最後の処理で、出来上がったzipファイルをS3にアップロードします。

DeployLatestBoto3LambdaLayerLambdaFunction:
    Description: "To make the Latest boto3 Lambda Layers and upload it to s3 bucket"
    Type: "AWS::Lambda::Function"
    Properties: 
      Code:
        ZipFile: !Sub |
            import boto3
            import subprocess
            import shutil
            import cfnresponse

            # Lambdaハンドラ
            def lambda_handler(event, context):
                try:
                    # 引数(event)チェック
                    if event['RequestType'] == 'Delete':
                        cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
                        return

                    # mkdir /tmp/python
                    mkdir_command = subprocess.run(["mkdir", "/tmp/python"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # pip install -t /tmp/python/ boto3
                    boto3_install_command = subprocess.run(["pip", "install", "-t", "/tmp/python", "boto3"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # ls -al /tmp/python | grep boto3- | grep .dist-info
                    ls_boto3_command = subprocess.Popen(["ls", "/tmp/python"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    grep_boto3_command = subprocess.Popen(["grep", "boto3-"], stdin = ls_boto3_command.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    dist_boto3_command = subprocess.Popen(["grep", ".dist-info"], stdin = grep_boto3_command.stdout, stdout = subprocess.PIPE, stderr = subprocess.PIPE)
                    dist_boto3_command_stdout = dist_boto3_command.communicate()[0]
                    boto3_version_str = dist_boto3_command_stdout.decode('utf-8').replace('boto3-', '').replace('.dist-info', '').replace('\n', '')

                    ls_boto3_command.stdout.close()
                    grep_boto3_command.stdout.close()
                    dist_boto3_command.stdout.close()

                    # /tmp/pythonをzipに圧縮
                    boto3_zip_path = '/tmp/boto-'+boto3_version_str
                    shutil.make_archive(boto3_zip_path, 'zip', root_dir='/tmp/python')

                    # ls /tmp/
                    ls_boto3_command = subprocess.run(["ls", "/tmp/"], stdout = subprocess.PIPE, stderr = subprocess.PIPE)

                    # boto3-x.x.xxx.zip をS3にアップロード
                    s3Resource = boto3.resource('s3')
                    s3FileUpload = s3Resource.meta.client.upload_file(Filename=boto3_zip_path+'.zip', Bucket=event['ResourceProperties']['S3Bucket'], Key='boto3-'+boto3_version_str+'.zip')

                    # 戻り値の生成
                    responseData = {
                        'S3Bucket': event['ResourceProperties']['S3Bucket'],
                        'S3Key': 'boto3-'+boto3_version_str+'.zip'
                    }
                    cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)

                except Exception:
                    # 戻り値の生成
                    cfnresponse.send(event, context, cfnresponse.FAILED, {})
      FunctionName: DeployLatestBoto3LambdaLayer
      Handler: index.lambda_handler
      MemorySize: 256
      ReservedConcurrentExecutions: 1
      Role: !GetAtt DeployLatestBoto3LambdaLayerRole.Arn
      Runtime: "python3.6"
      Timeout: 300
      Tags: 
      - Key: Name
        Value: 'DeployLatestBoto3LambdaLayer'
      - Key: CloudformationArn
        Value: !Ref 'AWS::StackId'

カスタムリソース (Boto3 Lambda Layersを生成するLambda Functionを呼び出す)

Lambda Functionが書ければ、カスタムリソースはこれだけです。
ServiceTokenでLambda Functionを指定して呼び出します。
S3Bucketは、Lambda Functionの内部で利用するための独自パラメータです。

LatestBoto3LambdaLayerCustomResource:
  Type: Custom::LatestBoto3LambdaLayerCustomResource
  Properties:
    ServiceToken: !GetAtt DeployLatestBoto3LambdaLayerLambdaFunction.Arn
    S3Bucket: 'hogehoge'

Lambda Layers

カスタムリソースの実行が正常に完了すると、Lambda LayersのzipファイルがアップロードされたS3 Bucket名と、zipファイルのパスを示すS3Keyが返ります。
これらの値をContentのパラメータに代入します。

LatestBoto3LambdaLayer:
    Type: "AWS::Lambda::LayerVersion"
    Properties:
      Description: !GetAtt LatestBoto3LambdaLayerCustomResource.S3Key
      CompatibleRuntimes: 
        - python3.6
        - python3.7
      Content: 
        S3Bucket: !GetAtt LatestBoto3LambdaLayerCustomResource.S3Bucket
        S3Key: !GetAtt LatestBoto3LambdaLayerCustomResource.S3Key
      LayerName: 'boto3-python-layer'

テンプレート実行結果

CloudFormationテンプレート内で、Lambda Layersを生成するLambda FunctionがLambda-backed カスタムリソースとして実行され、結果、最新のBoto3を含むLambda Layersがアップロードされました。
image.png

まとめ

Lambda Layersを生成するLambda FunctionをLambda-backed カスタムリソースで実行することで、Lambda Layersを手作業で作ることなく、自動生成できるようになりました。
手作業でのzip圧縮&アップロードがなくなり、AttributeErrorも回避でき、非常に快適となります。

また、Lambda Function内でLinuxのコマンドが実行できること、特にpipが実行できることは、覚えておけば今後何かと役に立ちそうです。
Boto3に限らず、あらゆるパッケージをCI/CDパイプラインの中で自動的にLambda Layers化できると、非常に捗ります。

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

Amazon Linux 2でrbenvを使う

環境

  • Amazon Linux 2

インストール手順

install
# 0. 依存ライブラリのインストール
$ sudo yum install -y git gcc openssl-devel readline-devel zlib-devel
# 1. レポジトリをクローン
$ git clone https://github.com/rbenv/rbenv.git ~/.rbenv
# 2. .bash_profileにパスを追記
$ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> ~/.bash_profile
# 3. init
$ ~/.rbenv/bin/rbenv init
# 4. Restart your shell so that PATH changes take effect.
$ exec $SHELL -l
# 5. プラグイン用ディレクトリ
$ mkdir -p "$(rbenv root)"/plugins
# 6. rbenv installができるように
$ git clone https://github.com/rbenv/ruby-build.git "$(rbenv root)"/plugins/ruby-build
# 7. 確認
$ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash

使い方

usage
 # インストールできるバージョンを確認
 $ rbenv install -l

 # 指定バージョンのRubyをインストール
 $ rbenv install 2.5.1

 # グローバルのバージョン指定
 $ rbenv global 2.5.1

 # インストールされているバージョンを確認
 $ rbenv versions

参考

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

AlibabaCloud E-MapReduce(EMR)を試してみた ~プロダクト紹介編~(and AWS比較)

はじめに

Alibaba Cloud E-MapReduce(EMR)を試してみました。

EMRは大量のデータの分析と処理を可能にするビッグデータ処理ソリューションです。

HadoopやEMR自体の説明はすでにWeb上に良質な記事がたくさんありますので、
AlibabaのEMRに特化した説明とします。

AlibabaCloudとは

Alibaba Cloudは日本国内では知る人ぞ知るクラウドサービスですが、
グローバルのマーケットシェアで見ると世界5位(Amazon, Microsoft, Google, IBMに次ぐ)
につけています。

サービスラインナップとしては、AWS/Azureに匹敵するものを提供しています。

サービス比較としていい記事がありましたので興味のある方はどうぞ↓
[2019年5月版] Alibaba Cloudの中国版/国際版/日本版比較表(and AWS/Azure/GCP)

また、アリババ自身がデータ活用を原動力にして成長してきたこともあり、
ビッグデータ系ではデータ収集〜データ管理/加工〜分析〜可視化まで包括的なサービスラインナップを提供しています。

AlibabaCloud EMRのアーキテクチャ

AlibabaCloud EMRのアーキテクチャです。
OSSはオブジェクトストレージ(AWSでいうとS3)で、OSSをHDFSのように扱うことで計算資源とストレージを分離することができます。
(この点はAWS EMRと同じです)

image.png

AlibabaCloud EMRで使用可能なコンポーネント

AlibabaCloud EMRで使用可能なコンポーネントとバージョンの一覧を掲載します。
AWSとの比較もしています。

コンポーネント Alibaba EMR-3.20.0 AWS EMR-5.23.0
Hadoop 2.8.5 2.8.5
YARN 2.8.5 2.8.5
Hive 3.1.1 2.3.4
Spark 2.4.2 2.4.0
Knox 1.1.0 -
Zeppelin 0.8.1 0.8.1
Tez 0.9.1 0.9.1
ApacheDS 2.0.0 -
Ganglia 3.7.2 3.7.2
Pig 0.14.0 0.17.0
Sqoop 1.4.7 1.4.7
Hue 4.1.0 4.3.0
HBase 1.4.9 1.4.9
ZooKeeper 3.4.13 3.4.13
Presto 0.213 0.215
Impala 2.12.2 -
Flume 1.8.0 -
Livy 0.6.0 0.5.0
Superset 0.28.1 -
Ranger 1.2.0 -
Flink 1.7.2 1.7.1
Storm 1.2.2 -
Phoenix 4.14.1 4.14.1
SmartData 1.0.0 -
Bigboot 1.0.0 -
Oozie 5.1.0 5.1.0
Kafka 1.1.1 -
Kafka-Manager 1.3.3.16 -
Spark 2.3.2 2.4.0
Analytics Zoo 0.2.0 -
Jupyter 4.4.0 5.7.0
TensorFlow 1.8.0 1.12.0
Druid 0.13.0 -
JupyterHub - 0.9.4
MXNet - 1.3.1
HCatalog - 2.3.4
Mahout - 0.13.0

ご覧の通り、ほとんどAWSと同等かそれ以上のコンポーネントが揃っています。
ImpalaなどAWSにはないコンポーネントもあり、コンポーネントの豊富さは隠れた嬉しいポイントかもしれません。

AWSと比較した、Alibaba Cloud EMRの使いどころ

上記の通り、AlibabaCloud EMRではAWSとほとんど同じコンポーネントが使えます。
EMRはHadoop等のオープンソースのマネージドサービスであることもあり、
ユースケースにそれほど差異はないと言えると思います。

ではどんな時にAlibabaCloudを使うか?
使い所としては、AlibabaCloudのコストの安さに着目するのもありと思います。

上記の記事で検証されていますが、従量課金のCPU特化インスタンスとしては、
Alibabaが最もコストパフォーマンスが高いです。

EMRはステップ実行としてオンデマンド(従量課金)でインスタンスを利用することが多いと思いますので、
この場合はAlibabaCloudでコストを削減可能なケースも多いかと思います。

次回予告

次回は実際にAlibaba EMRでクラスターを作成してみます!

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

Rancherコンテナー管理プラットフォームのノードテンプレート(AWS)の設定手順

ノードテンプレートの設定手順

・右上の「User Preferences」のアイコンをクリックして、「Node Templates」をクリックします。
image.png
・開く「Node Templates」画面に、右上の「Add Template」ボタンをクリックします
image.png
・開く「Add Node Templates」画面に、下記の画面のように情報を入力して、「Create」ボタンをクリックします
参照:awsのAccess key とSecret Keyの作成/取得方法で検証用のIAMユーザーから取得してください。
image.png
・次の画面に、クラスターノードを作成する場所のAZとVPCを選択して、「Next:...」ボタンをクリックします
image.png
・次の画面に、「Security Groups」にStandardを選択して、「Next:...」ボタンをクリックします
image.png
・次の画面に、下記の画面のように情報を入力して、「Create」ボタンをクリックします
「IAM Instance Profile Name」について、AWSのCloud Provider機能(AWSのロードバランサによるK8sサービス作成や、AWS EBSによる永続化Volume作成)を使用する場合のみ、設定が必要です。初心者レベルのハンズオンの場合は不要です。
参照:IAM Instance Profile Nameの作成/取得方法で取得してください。
image.png
image.png

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

An error occurred (AccessDenied) when calling the CopyObject operation がでた時の対処法

事象

IAMユーザー単位で閲覧/保存制御をかけているS3バケットが本番用とstg用で2つあります。
日時で本番からstg用にsyncをかけるのですが、
An error occurred (AccessDenied) when calling the CopyObject operation のエラーが出ました。

対処

https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-troubleshoot-copy-between-buckets/
AWS公式をみてみると、ズバリ書いてありました。

必要なことは4つです。
1. コピー元バケット: 使用している IAM ユーザーまたはロールによって s3:ListBucket と s3:GetObject の両方を許可
2. コピー先バケット: IAM ユーザーまたはロールによって s3:ListBucket と s3:PutObject の両方を許可
3. IAM ユーザー: s3:ListBucket および s3:GetObject をコピー元バケットへ許可
4. IAM ユーザー: s3:ListBucket と s3:PutObject をコピー先バケットへ許可

私の場合、IAMロールはs3への全権限が与えられていたので、バケットポリシーだけを見直しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "huge",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": "hogehoge"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::hogebucket-production/*"
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "huge",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": "hogehoge"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::hogebucket-staging/*"
        }
    ]
}

なぜだかわからないですが、s3:ListBucketはなくてもあってもいけたので消しました。

実行

コピー元 コピー先の順で指定します。
フォルダ指定もできます。
aws s3 sync s3://hogebucket-production/uploads/huga s3://hogebucket-staging/uploads/huga

余談

バケットポリシーの書き方は初めは難しいですよね。
EffectとPrincipalの組み合わせとか。
"Version": "2012-10-17" てなんやとか。

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

An error occurred (AccessDenied) when calling the CopyObject operation とaction does not apply to any resource(s) in statement がでた時の対処法

事象

IAMユーザー単位で閲覧/保存制御をかけているS3バケットが本番用とstg用で2つあります。
日時で本番からstg用にsyncをかけるのですが、
An error occurred (AccessDenied) when calling the CopyObject operation のエラーが出ました。

対処

https://aws.amazon.com/jp/premiumsupport/knowledge-center/s3-troubleshoot-copy-between-buckets/
AWS公式をみてみると、ズバリ書いてありました。

必要なことは4つです。
1. コピー元バケット: 使用している IAM ユーザーまたはロールによって s3:ListBucket と s3:GetObject の両方を許可
2. コピー先バケット: IAM ユーザーまたはロールによって s3:ListBucket と s3:PutObject の両方を許可
3. IAM ユーザー: s3:ListBucket および s3:GetObject をコピー元バケットへ許可
4. IAM ユーザー: s3:ListBucket と s3:PutObject をコピー先バケットへ許可

私の場合、IAMロールはs3への全権限が与えられていたので、バケットポリシーだけを見直しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "huge",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": "hogehoge"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::hogebucket-production/*"
        }
    ]
}
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "huge",
            "Effect": "Deny",
            "NotPrincipal": {
                "AWS": "hogehoge"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::hogebucket-staging/*"
        }
    ]
}

なぜだかわからないですが、s3:ListBucketはなくてもあってもいけたので消しました。
ちなみにs3:ListBucket権限を付与するのにはまた罠があり、Resourceを指定する時に/*をつけてはいけません。
考えてみればバケット単位の権限なのでまあそうかという感じなのですが、普通にハマりました。
間違えると、action does not apply to any resource(s) in statementというエラーが出てバケットを保存できません。
下記のように書いてみてください。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::bucketname",
            ]
        }
    ]
}

実行

コピー元 コピー先の順で指定します。
フォルダ指定もできます。
aws s3 sync s3://hogebucket-production/uploads/huga s3://hogebucket-staging/uploads/huga

余談

バケットポリシーの書き方は初めは難しいですよね。
EffectとPrincipalの組み合わせとか。
"Version": "2012-10-17" てなんやとか。

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

CloudWatch Events から Lambda (Python スクリプト) 経由で S3 から請求情報を取得して、SNS でメールを送ってみる

0.はじめに

コスト配分タグ毎の AWS の毎月のコストレポートが欲しくて、試してみました。

大枠の流れは、以下。

名称未設定.001.jpeg

1.請求情報のコスト配分タグを付与して、S3 へ出力する。

基本的に、以下のページの手順に従って設定します。

  1. S3 を作成します。設定は、以下。
    • バケット名 : ※任意
    • バケットポリシー : ※以下参考。
    • {
      "Version": "2008-10-17",
      "Id": "Policy0000000000000",
      "Statement": [
      {
          "Sid": "Stmt0000000000000",
          "Effect": "Allow",
          "Principal": {
              "AWS": "arn:aws:iam::0000000000000:root"
          },
          "Action": [
              "s3:GetBucketAcl",
              "s3:GetBucketPolicy"
          ],
          "Resource": "arn:aws:s3:::****-billing"
      },
      {
          "Sid": "Stmt0000000000000",
          "Effect": "Allow",
          "Principal": {
              "AWS": "arn:aws:iam::0000000000000:root"
          },
          "Action": "s3:PutObject",
          "Resource": "arn:aws:s3:::****-billing/*"
      }
      ]
      }
    • FireShot Capture 097 - S3 Management Console - s3.console.aws.amazon.com.png
    • ※参考
  2. 作成した S3 へ、コスト配分タグを付与した請求データを出力します。設定は、以下。
    • コスト配分タグ
    • FireShot Capture 096 - Billing Management Console - console.aws.amazon.com.png
    • Billing の設定
      • レガシー化した請求明細レポート機能を使って、AWS の料金に関する継続的なレポートを受け取る。 : ☑︎
      • S3 バケットに保存 : 作成した S3 バケット
      • リソースとタグを含む詳細な請求レポート : ☑︎
    • FireShot Capture 095 - Billing Management Console - console.aws.amazon.com.png

  3. 作成した S3 へ、コスト配分タグを付与した請求データが出力されているか確認します。

2.SNS のトピックを作成し、所定のメールアドレスを登録する。

基本的に、以下のページの手順に従って設定します。

  1. SNS のトピックを作成します。設定は、以下。
    • トピック名 : ※任意
    • アクセスポリシー : ※以下参考。
    • {
      "Version": "2008-10-17",
      "Id": "default_policy_ID",
      "Statement": [
      {
      "Sid": "default_statement_ID",
      "Effect": "Allow",
      "Principal": {
      "AWS": "*"
      },
      "Action": [
      "SNS:GetTopicAttributes",
      "SNS:SetTopicAttributes",
      "SNS:AddPermission",
      "SNS:RemovePermission",
      "SNS:DeleteTopic",
      "SNS:Subscribe",
      "SNS:ListSubscriptionsByTopic",
      "SNS:Publish",
      "SNS:Receive"
      ],
      "Resource": "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:SysOps-Lambda-Mnt",
      "Condition": {
      "StringEquals": {
        "AWS:SourceOwner": "XXXXXXXXXXXX"
      }
      }
      }
      ]
      }
    • FireShot Capture 110 - Simple Notification Service - ap-northeast-1.console.aws.amazon.com.png

  2. 作成した SNS のトピックへ、所定のメールアドレスをサブスクリプションとして登録する。
    • FireShot Capture 111 - Simple Notification Service - ap-northeast-1.console.aws.amazon.com.png

  3. 登録後、作成した SNS のトピックへ、パブリッシュして SNS トピックが正常に設定されているか、確認する。

3.Lambda ファンクションを作成する。

  1. IAM ロールを作成します。設定は、以下。
    • ロール名 : ※任意
    • ポリシー名 : ※任意
    • ポリシー : ※以下参考。
    • {
      "Version": "2012-10-17",
      "Statement": [
      {
          "Sid": "VisualEditor0",
          "Effect": "Allow",
          "Action": [
              "s3:GetObject",
              "sns:Publish",
              "s3:ListBucket"
          ],
          "Resource": [
              "arn:aws:sns:*:XXXXXXXXXXXX:SysOps-Lambda-DLQ",
              "arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:SysOps-Lambda-Mnt",
              "arn:aws:s3:::gs-sysops-billing/*",
              "arn:aws:s3:::gs-sysops-billing"
          ]
      },
      {
          "Sid": "VisualEditor1",
          "Effect": "Allow",
          "Action": [
              "logs:CreateLogStream",
              "logs:CreateLogGroup",
              "logs:PutLogEvents"
          ],
          "Resource": "*"
      }
      ]
      }
    • FireShot Capture 107 - IAM Management Console - console.aws.amazon.com.png

  2. Lambda ファンクションを作成します。設定は、以下。
    • ファンクション名 : ※任意
    • 実行ロール : ※作成した IAM ロール
    • コード : ※以下参考。
#!/usr/bin/env python
# -*- coding: utf-8-unix; -*-
"""AWS Lambda Function - Maintenance EC2 Images
"""
from __future__ import print_function

import logging
import boto3
import os
import csv
import zipfile
import locale

# 「python - aws lambda Unable to import module 'lambda_function': No module named 'requests' - Stack Overflow」
# https://stackoverflow.com/questions/48912253/aws-lambda-unable-to-import-module-lambda-function-no-module-named-requests
from botocore.vendored import requests

# 「Pythonで翌日や翌月みたいな日付の計算をする - Qiita」
# https://qiita.com/dkugi/items/8c32cc481b365c277ec2
from dateutil.relativedelta import relativedelta

# 「Python 3 で少しだけ便利になった datetime の新機能 - Qiita」
# <https://qiita.com/methane/items/d7ac3c9af5a2c659bc51>
from datetime import datetime, timezone, timedelta
TimeZone = timezone(timedelta(hours=+9), 'JST')

# 「Lambdaの本番業務利用を考える① - ログ出力とエラーハンドリング | ナレコムAWSレシピ」
# <https://recipe.kc-cloud.jp/archives/9968>
logger = logging.getLogger()
logLevelTable={'DEBUG':logging.DEBUG,'INFO':logging.INFO,'WARNING':logging.WARNING,'ERROR':logging.ERROR,'CRITICAL':logging.CRITICAL}
if os.environ.get('logging_level') in logLevelTable :
    logger.setLevel(logLevelTable[os.environ['logging_level']])
else:
    logger.setLevel(logging.WARNING)

#
StartDateTime = datetime.now(TimeZone)

# Csv
BillingFieldNames = (
    "InvoiceID",
    "PayerAccountId",
    "LinkedAccountId",
    "RecordType",
    "RecordId",
    "ProductName",
    "RateId",
    "SubscriptionId",
    "PricingPlanId",
    "UsageType",
    "Operation",
    "AvailabilityZone",
    "ReservedInstance",
    "ItemDescription",
    "UsageStartDate",
    "UsageEndDate",
    "UsageQuantity",
    "Rate",
    "Cost",
    "ResourceId",
    "user:Biz")

# Aws
BucketName = "\*\*\*\*-billing"
ZipRearFormat = "-aws-billing-detailed-line-items-with-resources-and-tags-{0}-{1}.csv.zip"
CsvRearFormat = "-aws-billing-detailed-line-items-with-resources-and-tags-{0}-{1}.csv"

# Mail
MailSubjectTemplate = "[{0}.{1}] AWS Monthly Cost Report"

MailMessageTemplate00 = "\
\n\
★{0}\n\
\n\
 ■処理時間 : {1:%Y/%m/%d %H:%M:%S} ~ {2:%Y/%m/%d %H:%M:%S}\n\
\n\
 ■総額\n\
        ${3:>8}    [約 ¥{4:>8}]\n\
\n\
 ■内訳\
"

MailMessageTemplate01 = "{0}\n\
  ○{1}\n\
        ${2:>8}    [約 ¥{3:>8}]\
"

MailMessageTemplate02 = "{0}\n\
\n\
----\n\
"

MailMessageTemplate03 = "{0}\n\
 ■{1:<10} : ${2:>8}    [約 ¥{3:>8}]\
"

MailMessageTemplate04 = "{0}\n\
\n\
----\n\
\n\
※ $1 = ¥{1:>6}\n\
\n\
\n\
"

# ------------------------------------------------------------------------------
# Billing Data Get
# ------------------------------------------------------------------------------
def BillingDataGet(prmYear, prmMonth, prmBillingList):
    logging.info("prmYear:[%s]", prmYear)
    logging.info("prmMonth:[%s]", prmMonth)
    result = 0
    try:
        s3 = boto3.resource('s3')
        bucket = s3.Bucket(BucketName)
        # Object Key
        key = None
        for object in bucket.objects.all():
            if ZipRearFormat.format(prmYear, prmMonth) != object.key[-72:]:
                continue
            key = object.key
            break
        logging.info("key:[%s]", key)
        if key is None:
            raise Exception("No such key was found. key={}".format(key))
        tmpZipPath = "/tmp/_tmp.zip"
        tmpCsvPath = "/tmp/_tmp.csv"
        # Download
        logging.info("tmpZipPath:[%s]", tmpZipPath)
        s3.Object(BucketName, key).download_file(tmpZipPath)
        # UnZip
        logging.info("UnZip")
        zf = zipfile.ZipFile(tmpZipPath, 'r')
        for f in zf.namelist():
            if CsvRearFormat.format(prmYear, prmMonth) != f[-68:]:
                continue
            uzf = open(tmpCsvPath, 'wb')
            uzf.write(zf.read(f))
            uzf.close()
            break
        zf.close()
        # Csv
        with open(tmpCsvPath) as csvfile:
            for row in csv.DictReader(csvfile, BillingFieldNames):
                if row["LinkedAccountId"] == "LinkedAccountId":
                    continue
                prmBillingList.append(row)
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
        result = 1
        raise
    else:
        pass
    finally:
        pass
    return result

# 「[小ネタ]為替レートを簡単に取得する - Qiita」
# <https://qiita.com/chromabox/items/a1323225bae146c80bec>
def RateGet():
    result = -1
    try:
        Url = "https://www.gaitameonline.com/rateaj/getrate"
        r = requests.get(Url)
        r_quotes = r.json()["quotes"]
        for i in range(len(r_quotes)):
            if r_quotes[i].get("currencyPairCode") == "USDJPY":
                result = float(r_quotes[i].get("open"))
                break
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
        result = -1
        raise
    else:
        pass
    finally:
        pass
    return result

# 「【Python】Lambdaからメールを送信 | ハックノート」
# https://hacknote.jp/archives/35679/
def MailSend(prmSubject, prmMessage):
    result = -1
    try:
        sns = boto3.client('sns')
        response = sns.publish(
            TopicArn = 'arn:aws:sns:ap-northeast-1:454930157265:SysOps-Lambda-Mnt',
            Message = prmMessage,
            Subject = prmSubject
        )
    except Exception as e:
        logger.exception('Error dosomething: %s', e)
        result = -1
        raise
    else:
        pass
    finally:
        pass
    return result

def lambda_handler(event, context):
    try:
        global StartDateTime
        StartDateTime = datetime.now(TimeZone)
        logger.info("StartDateTime:[%s]", StartDateTime)
        #
        logger.info("event:[%s]", event)
        logger.info("context:[%s]", context)
        #
        try:
            yyyymm = event.get("YYYYMM", "")
            tdatetime = datetime.strptime(
                yyyymm[0:4] + "-" + yyyymm[4:6] + "-01 01:00:00", '%Y-%m-%d %H:%M:%S')
        except ValueError:
            tdatetime = StartDateTime - relativedelta(months=1)
        logger.info("tdatetime:[%s]", tdatetime)
        yyyy = tdatetime.year
        mm = "{0:0>2}".format(tdatetime.month)
        logger.info("yyyy:[%s] mm:[%s]", yyyy, mm)

        # Billing Data Get
        tmpBliingList = []
        BillingDataGet(yyyy, mm, tmpBliingList)

        # Rate Get
        rate = RateGet()
        logging.info("Rate:[%f]", rate)

        # ----------------------------------------------------------------------
        #  Summary
        # ----------------------------------------------------------------------

        # Summary ProductName
        dicSummary = {}
        dicSummarySub = {}
        for i in range(len(tmpBliingList)):
            if "Sign up charge for subscription:" in tmpBliingList[i]["ItemDescription"]:
                logging.info("Sign up charge for subscription - tmpBliingList:[%s]", tmpBliingList[i])
                dicSummarySub[tmpBliingList[i]["InvoiceID"]] = {
                    "InvoiceID": tmpBliingList[i]["InvoiceID"],
                    "ProductName": tmpBliingList[i]["ProductName"],
                    "UsageType": tmpBliingList[i]["UsageType"],
                    "UsageQuantity": tmpBliingList[i]["UsageQuantity"],
                    tmpBliingList[i]["ItemDescription"] : {
                        "RecordType": tmpBliingList[i]["RecordType"],
                        "Cost": tmpBliingList[i]["Cost"],
                    },
                }
                continue
            if "Tax of type CT" in tmpBliingList[i]["ItemDescription"]:
                logging.info("Tax of type CT - tmpBliingList:[%s]", tmpBliingList[i])
                if tmpBliingList[i]["InvoiceID"] in dicSummarySub:
                    dicSummarySub[tmpBliingList[i]["InvoiceID"]][tmpBliingList[i]["ItemDescription"]] = {
                        "RecordType": tmpBliingList[i]["RecordType"],
                        "Cost": tmpBliingList[i]["Cost"],
                    }
                    continue
            if "Total amount for invoice" in tmpBliingList[i]["ItemDescription"]:
                if tmpBliingList[i]["InvoiceID"] in dicSummarySub:
                    dicSummarySub[tmpBliingList[i]["InvoiceID"]][tmpBliingList[i]["ItemDescription"]] = {
                        "RecordType": tmpBliingList[i]["RecordType"],
                        "Cost": tmpBliingList[i]["Cost"],
                    }
                    continue
            #
            tmpDict = dicSummary
            tmpKey = tmpBliingList[i]["RecordType"]
            if not tmpKey in tmpDict:
                tmpDict[tmpKey] = {}
            #
            tmpDict = tmpDict[tmpKey]
            tmpKey = tmpBliingList[i]["ProductName"]
            if not tmpKey:
                tmpKey = tmpBliingList[i]["ItemDescription"]
            elif tmpKey == "Amazon Elastic Compute Cloud":
                if "EBS" in tmpBliingList[i]["UsageType"]:
                    tmpKey = tmpKey + " - EBS"
            if not tmpKey in tmpDict:
                tmpDict[tmpKey] = {}
                tmpDict[tmpKey]["SumCost"] = 0.0
            #
            tmpDict = tmpDict[tmpKey]
            tmpKey = tmpBliingList[i]["user:Biz"]
            if not tmpKey in tmpDict:
                tmpDict[tmpKey] = {}
            #
            tmpCost = float(tmpBliingList[i]["Cost"]) if tmpBliingList[i]["Cost"] else 0.0
            if not "Cost" in tmpDict[tmpKey]:
                #tmpDict[tmpKey]["Count"] = 1
                tmpDict[tmpKey]["Cost"] = tmpCost
            else:
                #tmpDict[tmpKey]["Count"] += 1
                tmpDict[tmpKey]["Cost"] += tmpCost
            tmpDict["SumCost"] += tmpCost

        # Summary Biz
        tmpSumCostAllToRedistribute = 0.0
        tmpSumCostAllNotToRedistribute = 0.0
        dicSummaryBiz = {}
        for k1 in sorted(dicSummary["LineItem"]):
            for k2 in sorted(dicSummary["LineItem"][k1]):
                if not isinstance(dicSummary["LineItem"][k1][k2], dict):
                    continue
                if not k2 in dicSummaryBiz:
                    dicSummaryBiz[k2] = {}
                    dicSummaryBiz[k2]["SumCost"] = 0.0
                    dicSummaryBiz[k2]["SumCostRedistributed"] = 0.0
                if not k1 in dicSummaryBiz[k2]:
                    dicSummaryBiz[k2][k1] = {}

                dicSummaryBiz[k2][k1]["Cost"] = dicSummary["LineItem"][k1][k2]["Cost"]
                dicSummaryBiz[k2]["SumCost"] += dicSummary["LineItem"][k1][k2]["Cost"]

                if not k2:
                    tmpSumCostAllToRedistribute += dicSummary["LineItem"][k1][k2]["Cost"]
                    continue
                if k2 == "Share":
                    tmpSumCostAllToRedistribute += dicSummary["LineItem"][k1][k2]["Cost"]
                    continue
                tmpSumCostAllNotToRedistribute += dicSummary["LineItem"][k1][k2]["Cost"]

        for k1 in sorted(dicSummaryBiz):
            if not k1:
                continue
            if k1 == "Share":
                continue
            dicSummaryBiz[k1]["SumCostRedistributed"] = dicSummaryBiz[k1]["SumCost"] + (
                tmpSumCostAllToRedistribute * dicSummaryBiz[k1]["SumCost"]) / tmpSumCostAllNotToRedistribute
        logging.info("dicSummaryBiz:[%s]", dicSummaryBiz)

        # ----------------------------------
        # Mail Create Message
        # ----------------------------------
        locale.setlocale(locale.LC_NUMERIC, 'ja_JP')

        # Subject
        tmpSubject = MailSubjectTemplate.format(yyyy, mm)

        # Message
        tmpCost = list(dicSummary["InvoiceTotal"].values())[0][""]["Cost"]
        tmpMessage = MailMessageTemplate00.format(tmpSubject, StartDateTime, datetime.now(TimeZone)
            , locale.format('%.2f', tmpCost, True), locale.format('%.0f', tmpCost*rate, True))

        for k1 in sorted(dicSummary["LineItem"]):
            tmpCost = dicSummary["LineItem"][k1]["SumCost"]
            if tmpCost == 0:
                continue
            tmpMessage = MailMessageTemplate01.format(tmpMessage, k1
                , locale.format('%.2f', tmpCost, True), locale.format('%.0f', tmpCost*rate, True))

        tmpMessage = MailMessageTemplate02.format(tmpMessage)

        for k1 in sorted(dicSummaryBiz):
            if not k1:
                continue
            if k1 == "Share":
                continue
            tmpCost = dicSummaryBiz[k1]["SumCostRedistributed"]
            tmpMessage = MailMessageTemplate03.format(tmpMessage, k1
                , locale.format('%.2f', tmpCost, True), locale.format('%.0f', tmpCost*rate, True))

        tmpMessage = MailMessageTemplate04.format(tmpMessage, locale.format('%.2f', rate, True))

        # Mail Send
        logging.info("tmpSubject:[%s]", tmpSubject)
        logging.info("tmpMessage:[%s]", tmpMessage)
        MailSend(tmpSubject, tmpMessage)

    except Exception as e:
        logger.exception('Error dosomething: %s', e)
    else:
        pass
    finally:
        pass
    #
    return "normal end"
  • FireShot Capture 103 - Lambda Management Console - ap-northeast-1.console.aws.amazon.com.png

4.CloudWatch Events のルールを作成する。

  1. CloudWatch Events のルールを作成します。設定は、以下。
    • イベントソース : ※今回は、毎月のコストレポートなので以下。
      • スケジュール : ☑︎
      • Cron 式 : 0 21 3 * ? *
    • ターゲット
      • Lambda 関数
        • ※作成した Lambda ファンクション
        • バージョンのエイリアス
          • ※任意
        • 入力の設定
          • 定数 (JSON テキスト) : {"YYYYMM", ""}
    • FireShot Capture 100 - CloudWatch Management Console - ap-northeast-1.console.aws.amazon.com.png
    • ルールの定義
      • 名前 : ※任意
      • 説明 : ※任意
      • 状態 : ☑︎ 有効「」
    • FireShot Capture 101 - CloudWatch Management Console - ap-northeast-1.console.aws.amazon.com.png

99.ハマりポイント

  • うーん…。随分前に作成したので、覚えてない…。

XX.まとめ

今回、とりあえず残しておこうかなぁ、と思って記事書いたんですが、

FireShot Capture 113 - Billing Management Console - console.aws.amazon.com.png

こういう注意事項が表示されて…、

今後は、「AWS Cost and Usage Report」を使わないといけないのかなぁ。

うーん、また新しいやり方を考えなないといけないのだろうか…。

AWS Cost and Usage Report - AWS 請求情報とコスト管理

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

SAMでSQSをイベントソースとするLambdaを構築

しようとしたら失敗したので備忘録的に解決策を残しておこうと思いました。

AWS曰く

このクールなコードでビルドできるよ!
とのこと。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example of processing messages on an SQS queue with Lambda
Resources:
  MySQSQueueFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler
      Runtime: runtime
      Events:
        MySQSEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt MySqsQueue.Arn
            BatchSize: 10
  MySqsQueue:
    Type: AWS::SQS::Queue

出典:AWS SAM Template for an Amazon SQS Application

そして失敗

エラー1:LambdaがSQSを呼ぶ権限がない

謎のエラーを吐いてCloudFormationが失敗に終わる。

The provided execution role does not have permissions to call ReceiveMessage on SQS (Service: AWSLambda; Status Code: 400; Error Code: InvalidParameterValueException

Lambdaに設定したロールには AWSLambdaSQSQueueExecutionRole が付いている。
つまりIAMロールには(おそらく)問題がない。

なぜ失敗する?

解決1:SQSにも権限の設定をする

冷静に考えたら、コンソールからキューを作る時も権限をいじった気がする。
というわけで、下記を追加する事で上述のエラーは出なくなった。

  MySqsQueuePolicy: 
    Type: AWS::SQS::QueuePolicy
    Properties: 
      PolicyDocument: 
        Id: !Ref MySqsQueue
        Statement: 
          - Sid: QueuePolicy-MySqsQueue
            Effect: Allow
            Principal: "*"
            Action: "SQS:*"
            Resource: 
              Fn::GetAtt:
              - MySqsQueue
              - Arn
      Queues:
        - !Ref MySqsQueue

エラー2:キューの可視性タイムアウトがLambdaのタイムアウトより短い

エラー1が解決したら、今度はこれが出るようになった。
コンソールから設定する時はあなたそんな事言わなかったじゃない。

Queue visibility timeout: 30 seconds is less than Function timeout: 60 seconds

解決2:可視性タイムアウトを伸ばす

明示的に指定しないとdefaultで30秒になるらしいので、キューのPropertiesにVisibilityTimeoutを設定する。
※Lambdaのタイムアウトを明示的に60秒にしていたので、もしかしたらその設定の影響かもしれない。

  MySqsQueue: 
    Type: AWS::SQS::Queue
    Properties: 
      VisibilityTimeout: 60

結論

SQSキューポリシーと、可視性タイムアウトの設定をするとうまくいきました。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Example of processing messages on an SQS queue with Lambda
Resources:
  MySQSQueueFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: handler
      Runtime: runtime
      Timeout: 60
      Events:
        MySQSEvent:
          Type: SQS
          Properties:
            Queue: !GetAtt MySqsQueue.Arn
            BatchSize: 10
  MySqsQueue:
    Type: AWS::SQS::Queue
    Properties: 
      VisibilityTimeout: 60
  MySqsQueuePolicy: 
    Type: AWS::SQS::QueuePolicy
    Properties: 
      PolicyDocument: 
        Id: !Ref MySqsQueue
        Statement: 
          - Sid: QueuePolicy-MySqsQueue
            Effect: Allow
            Principal: "*"
            Action: "SQS:*"
            Resource: 
              Fn::GetAtt:
              - MySqsQueue
              - Arn
      Queues:
        - !Ref MySqsQueue

蛇足

実はこの前段階でCloudFormationの実行ロールにSQSの権限が無かった事により一度失敗している。
CodeStarを使っているため、自動で作成されるロールに何の疑問も持たなかった事が原因ですね。私は悲しい。

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

CodeBuildの中でブランチ名を利用する。

きっかけ

CodeBuildの中でブランチ名を取得し、
Dockerレジストリにプッシュしようとしたらうまくできなかった。

[Container] 2019/05/30 03:11:39 Running command IMAGE_TAG=$(git branch | grep \* | cut -d ' ' -f2) 

[Container] 2019/05/30 03:11:39 Running command git branch 
* (no branch) 
  develop 
  master 

oh...
no branch...

取得方法

githubのwebhookからブランチ名が取れるので、それを活用した。

参考:
https://docs.aws.amazon.com/ja_jp/codebuild/latest/userguide/build-env-ref-env-vars.html

環境変数[CODEBUILD_WEBHOOK_TRIGGER]は、ブランチにコミットをプッシュした場合、
[branch/branch-name]といった形で設定が入るらしい。

以下の通り変数を参照すると、ブランチ名だけ取り出せる。

${CODEBUILD_WEBHOOK_TRIGGER#branch/}

※[#branch/]は環境変数から、文字列[branch/]を取り除くために書いています。

この通り

[Container] 2019/05/30 04:34:54 Running command IMAGE_TAG=${CODEBUILD_WEBHOOK_TRIGGER#branch/} 

[Container] 2019/05/30 04:34:54 Running command echo ${CODEBUILD_WEBHOOK_TRIGGER#branch/} 
develop 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CodeBuild設定メモ

命名例

dev-myservice-api-codebuild

ソース設定

今回はGitHub Organization内のprivate repositoryから取得。
personal access tokenで取得していたが、terraform化する時にsource.authがOAUTHしか指定出来なかったのでterraformではauth項目を設定せずにAWSコンソール上でpersonal access tokenを保存した。
personal access tokenは個人アカウントではなく、organization共用のアカウントを作ってそのユーザで生成したものを使う。

環境

ビルドをカスタムイメージ上で行って実行用イメージをECRなどにpushする場合、docker in dockerよりもDockerのマルチステージビルドで行ったほうが色々楽だった。
ECRにpushする時にdocker in dockerだとAWS CLIのインストールから認証まで面倒を見ないといけないが、AWSが提供しているマネージドイメージであればbuildspec.yml内でAWS CLIが利用できるのでCodeBuildからECRへのログインが簡単になる。
あとビルドイメージ用のECRリポジトリを用意しなくてよくなる。

動作させるサブネットを指定する時はプライベートサブネット。
VPC設定が正しいかどうかを「VPC設定の確認」というボタンで検証できるのでいちいちビルドしなくてもよい。
buildspec.yml内で利用する環境変数も設定出来る。

Buildspec

CodeBuild標準はプロジェクトルート/buildspec.ymlというファイルを置いておけば勝手に読み込んでくれるが、dev/stg/prdのような環境毎にファイルを分けることも出来る。
プロジェクトルート/codebuild/${env}-buildspec.ymlのようなディレクトリとファイルを作成している場合、CodeBuildのbuildspec名指定でどのファイルを読み込むか指定できる。
今回は環境毎にbuildspec.ymlを分けたが、基本的に一緒なのでbuildspec.yml自体は1つで各動作は環境変数で変更するようにしたほうが良さそう。
ビルド開始時にビルドするブランチを指定出来るが忘れることもありそうなので、buildspec.ymlでcheckoutする。(ビルド開始時のデフォルト設定も出来るようにしてほしい…)

アーティファクト

成果物をS3に配置することも出来る。
今回はビルドしたイメージをECRにpushしたので使わない。(pushはbuildspec.yml内で行う)

ログ

ビルド時のログを意図的に出力するようにしないと確認できないので設定する

ビルド開始

GitHubなどから取得している場合ソースバージョンが指定できる。
ブランチやタグなどが指定出来、developブランチはそのままdevelopと入力すればブランチが切り替わる。
buildspecでcheckoutするようにしているので普段は入力しなくてもいいが、buildspec.yml自体の変更がある時は明示しないといけない。

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

AWS Certified Solutions Architect 受験記

はじめに

長くやっているのですが、AWS資格は持っていない(過去に勉強しようと思ったことはあるものの。。。)ので、そろそろ資格取らないと思い、試験対策本も充実してきたところで、AWS Certified Solutions Architect (以下SAA)の取得にチャレンジしてみたので、何やったかをまとめましたです。

書いてる人の話

  • 昔はJava,今はNode.js、Python、PHPを使ってるアプリエンジニアです
    • ついでに言うと、史学科卒業(卒論は三国志)のちゃきちゃきの文系
  • でも、ちょっとハード好き(自作PC派)。家にファイルサーバーもあったり(HPのProLiant MicroServer N54L)、ラズパイが2台ぐらい転がってる
  • AWSは何気に東京リージョンくる前から仕事で使ってる
  • 好きなAWSのサービスはAWS Lambda
  • 今は停止状態だけど、ドメイン取得(管理はRoute53)して、AWS上で個人サイト構築してたり(そろそろ復活させたい)する
  • 過去の仕事の関係で、なんとなく触っているサービスはそこそこ多し

AWS認定資格ってなんやねん(整理)

以下10つ。最近Alexaのスキルビルダーの資格増えたらしいですね。。。
2019/05/28現在

詳しくは、以下参照。
AWS 認定

やったこと

試験日決める

  • ABEJAの村主さんがJAWS-UG 初心者支部#17 「AWS勉強しNight!」にて、試験日決める(つまり申し込む)と、謎のモチベーションが沸くって言うことを仰っていたので、倣いました
    • が、人それぞれのようです。そこまでモチベーション上がりませんでした(ダメな人orz)
  • 4月末に申し込みをしたので、勉強期間は1ヶ月弱です

試験対策本をひたすら読む

読んだ順番は、以下
1. AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト
2. 最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本
3. 徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書

GW特に遠出する予定もなかったので、GWは資格勉強だーと思ったものの、ちょっと出かけたりで進捗そこまで上がらず。。。

各本1回ずつ読んで、
各章についてる練習問題で間違ったり、読んでても理解が足りんなーと思うところは、
AWS BlackBeltの資料をみたりして、補いました。

昔に比べれば、勉強し易くなりましたね。。。(遠い目)

個人的には、基本的には使ったことがないサービスはわからんなーと思うことが多く、以下が最後まで苦手にしてました。。
- Amazon EFS
- EBSのボリュームタイプの使い分け
- S3/Glacierのストレージタイプの使いわけ
- ネットワークACL
- Kinesis

それぞれの本のちょっとした感想

  • AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト
    • サービスカットになっていた。
    • SAAの資格取得する上で、必要なサービスの基本的なことを学べた
  • 最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本
    • AWS Well-Architectedのフレームワークカット
    • ポイントが色づけされてて、読みやすく、AWS初心者向き
  • 徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書
    • こちらもAWS Well-Architectedのフレームワークカット
    • 模擬問題が一番難しかった感覚(一番正解率低め(たまたまかもしれませんが))

いずれにも言えることですが、
資格試験対策本ですが、AWSとは?って言う感じで、AWS初心者が読むにはいいかなと思いました。
内容がかぶるところも多々ありますが、それぞれ色はあったかなと。

あと、フレームワークカットの
「最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本」

「徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書」
を読んでから、
サービスカットの
「AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト」
を読んでもよかったかも。

練習・模擬問題

掲載数は以下の通り

  • AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト・・・30問
  • 最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本・・・65問
  • 徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書・・・65問(要ダウンロード)

とりあえず解説部分全部読んでから、3冊分一気にやりました。
上記しましたけど、「徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書」の模擬問題が一番難しかったです。

あと、AWSの公式試験サンプルも解いてます。
試験サンプル
答えがすぐ横に書いてるので、できれば最後にまとめてほしいとか思うのは、贅沢でしょうかねw

やらなかったこと

  • 公式の模擬試験
    • 受ける予定でしたが、試験勉強が遅れたので、受けている時間がなく。。。
    • でも、受けておいた方がよかったなと思いました
  • ハンズオン
    • 結局勉強中はあまり触りませんでした。。。
    • 家のNASの移行にEFS/Storage Gatewayとか使ってみるとか考えたぐらい。。。

試験

  • 試験は試験センターで実施するわけで、PSI、ピアソンVUEのいずれかで可能です
    • 私は過去にも使ったことがあり、最近追加されたピアソンVUEの会場で受験しました
  • SAとして、こう言う案件の時、どーするっていう感じの問題が多く、時々うーむとなる問題がありましたね
    • 上記もしましたが、設問の慣れという意味でも模擬試験は受けておいた方がよかったと反省
  • 思ってより、さらに難しくなってない?って思ったのも事実(これは落ちるかも。。。と)
    • もしかしたら、Cloud Practitionerが登場したことで、見直しされたんじゃないかという説
  • (公式)[https://aws.amazon.com/jp/certification/certified-solutions-architect-associate/]とか、各種資料には試験時間130分となっていたのですが、140分になってました(問題数は同じ)
    • ピアソンVUCだけ?
  • 基本的には、一択が多く、複数選択はそんなになかったイメージでした
  • 誤りを指摘する問題、模擬試験でも間違えてたんで、問題文ちゃんと読むの気をつけていたんですが、出てきませんでした
  • あと、たまに日本語おかしくね?(そんなわけないでしょうけど)って箇所もあったので、念の為英語にしてみて読んでみたりとか
  • 結局100分ぐらいかかりました
    • 今日は全休にしていたので試験終わったら、羽田空港にAirForceOneの離陸を観に行こうかと思っていたのですが、間に合いませんでしたorz(超絶余談)

結果

画面に結果が出てて、しかも喜んだような気がするんですが、結果通知(スコアレポート)をみてからにしようと確信を持とうと待っておりますが、送られて来ず。。。

5/28 19:00ごろ試験サイトを確認したところ、合格してました!
結果としては、受験者スコア:836(合格スコア:720)でした。

今後

次は以下のいずれかにしようかと思ってます。
- AWS Certified Developer
- AWS Certified Cloud Practitioner

Cloud PractitionerはSAA受かれば楽勝説もありますがどうでしょう???w。

とはいえ、落ちたら悲しいので、ちゃんと勉強します。。。

まとめ

  • 昔勉強してて、挫折(試験すら受けなかった)して、それからの再チャレンジなので、5年越しぐらいwでの合格でした
  • AWS Well-Architectedはまだまだ理解不足なところあるので、引き続き勉強
  • 業務で触っている部分はなんとかなった気がするので、実務(もしくは、ハンズオンなどで実際にサービス使ってみる)は大事
  • AWS BlackBeltの読み直しとても重要

** 最後に資格取得本の著者の皆様、JAWS-UG初心者支部運営の皆様、AWSJの皆様、弊社内の皆様に感謝の意を表します。**

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

AWS CloudFormation テンプレートの基礎

■テンプレートの概要

スクリーンショット 2019-05-24 20.51.58.png

1.AMIからEC2インスタンスを立ち上げる

AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Resources:
  CreateEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-06a17900f024535fb
#ImageIdは自分のAMI IDに書き換えてください。
      InstanceType: t2.micro 

2.Parametersを作成し、ref関数を使い参照する


AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type
#Parametersでインスタンスタイプを選択できるようにする。

Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: ami-06a17900f024535fb
      InstanceType: !Ref InstanceType
#Ref関数でParametersを参照する。
     Tags:
      - Key: Name
        Value: ec2

3.Mappings関数を使い、複数のリージョンの複数のAMIから選択できるようにする

AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type
Mappings: 
  RegionMap: 
    ap-northeast-1:
      hvm: "ami-06a17900f024535fb"
    ap-southeast-1:
      hvm: "ami-055a628643638b600"
#Mappings関数を使い、複数のリージョンの複数のAMIから選択できるようにします。東京リージョンとシンガポールリージョンのAMIにそれぞれ置き換えてください。

Description: Create EC2 Instance
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', hvm]
#FindInMap関数でMappings関数からのものを指定する。
      InstanceType: !Ref InstanceType
      Tags:
      - Key: Name
        Value: ec2

4.サブネットを指定する

AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type
  SubnetId:
    Type: String
    Default: subnet-0a17e4a1945a371a8
    AllowedValues:
    - subnet-0a17e4a1945a371a8
    - subnet-08a5374d316da6f60
    - subnet-0c7bd98f8caa00b15
    - subnet-09621152100bbd5f6
    ConstraintDescription: must be a valid SbunetID
#自分のサブネットIDを指定してください。

Mappings: 
  RegionMap: 
    ap-northeast-1:
      hvm: "ami-06a17900f024535fb"
    ap-southeast-1:
      hvm: "ami-055a628643638b600"


Description: Create EC2 Instance
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', hvm]
      InstanceType: !Ref InstanceType
      SubnetId: !Ref SubnetId
#Ref関数を指定します。
      Tags:
      - Key: Name
        Value: ec2

5.ストーレージタイプを指定できるようにする

AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type
  SubnetId:
    Type: String
    Default: subnet-0a17e4a1945a371a8
    AllowedValues:
    - subnet-0a17e4a1945a371a8
    - subnet-08a5374d316da6f60
    - subnet-0c7bd98f8caa00b15
    - subnet-09621152100bbd5f6
    ConstraintDescription: must be a valid SbunetID


Mappings: 
  RegionMap: 
    ap-northeast-1:
      hvm: "ami-06a17900f024535fb"
    ap-southeast-1:
      hvm: "ami-055a628643638b600"


Description: Create EC2 Instance
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', hvm]
      InstanceType: !Ref InstanceType
      SubnetId: !Ref SubnetId
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp2
            VolumeSize: 8
#ブロックデバイスを追加しています。もちろんParametersに設定して反映させることも可能です。
      Tags:
      - Key: Name
        Value: ec2

6.セキュリティグループとSSHを選択できるようにする。

AWSTemplateFormatVersion: "2010-09-09"
Description: Create EC2 Instance
Parameters:
  InstanceType:
    Description: WebServer EC2 instance type
    Type: String
    Default: t2.micro
    AllowedValues:
    - t1.micro
    - t2.nano
    - t2.micro
    - t2.small
    - t2.medium
    - t2.large
    ConstraintDescription: must be a valid EC2 instance type
  SubnetId:
    Type: String
    Default: subnet-0a17e4a1945a371a8
    AllowedValues:
    - subnet-0a17e4a1945a371a8
    - subnet-08a5374d316da6f60
    - subnet-0c7bd98f8caa00b15
    - subnet-09621152100bbd5f6
    ConstraintDescription: must be a valid SbunetID
  KeyName: 
    Description : Name of an existing EC2 KeyPair.
    Type: AWS::EC2::KeyPair::KeyName
    ConstraintDescription : Can contain only ASCII characters.
#KeyPairを指定する。
  SSHLocation:
    Description: IP address range that can be used to SSH to the EC2 instances
    Type: String
    MinLength: '9'
    MaxLength: '18'
    Default: 0.0.0.0/0
    AllowedPattern: (\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})
    ConstraintDescription: must be a valid IP CIDR range of the form x.x.x.x/x.
#SSHを解放する値を指定する。

Mappings: 
  RegionMap: 
    ap-northeast-1:
      hvm: "ami-06a17900f024535fb"
    ap-southeast-1:
      hvm: "ami-055a628643638b600"

Description: Create EC2 Instance
Resources:
  MyEC2Instance:
    Type: AWS::EC2::Instance
    Properties: 
      ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', hvm]
      InstanceType: !Ref InstanceType
      SubnetId: !Ref SubnetId
      BlockDeviceMappings:
        - DeviceName: /dev/xvda
          Ebs:
            VolumeType: gp2
            VolumeSize: 8
      Tags:
      - Key: Name
        Value: myInstance
      KeyName: !Ref KeyName
#Parametersで指定したKeyNameを指定できるようにする。
      SecurityGroupIds:
         - !GetAtt "InstanceSecurityGroup.GroupId"
#セキュリティグループは別のリソースになります。ここでは下のInstanceSecurityGroupをSecurityGroupIdsに指定します。

  InstanceSecurityGroup:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: connect with ssh 
      VpcId: vpc-08fbdc5732395adab
#自分のVpcIdに変更してください。
      SecurityGroupIngress:
        -
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: !Ref SSHLocation

7.VPCを構築してみる。

AWSTemplateFormatVersion: '2010-09-09'
Description:
  VPC & subnet create

Resources:
  MyVPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsSupport: 'true'
      EnableDnsHostnames: 'true'
      InstanceTenancy: default
      Tags:
      - Key: Name
        Value: CloudFormation-VPC

  PublicRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: CloudFormation-VPC-PublicRT

  PrivateRouteTable:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref MyVPC
      Tags:
      - Key: Name
        Value: CloudFormation-VPC-PrivateRT

  PublicSubnet1A:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.0.0/24
      AvailabilityZone: "ap-northeast-1a"
      Tags:
      - Key: Name
        Value: CloudFormation-public-subnet-1a

  PubSubnet1ARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1A
      RouteTableId: !Ref PublicRouteTable

  PublicSubnet1C:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.2.0/24
      AvailabilityZone: "ap-northeast-1c"
      Tags:
      - Key: Name
        Value: CloudFormation-public-subnet-1c

  PubSubnet1CRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PublicSubnet1C
      RouteTableId: !Ref PublicRouteTable

  PrivateSubnet1A:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.1.0/24
      AvailabilityZone: "ap-northeast-1a"
      Tags:
      - Key: Name
        Value: CloudFormation-private-subnet-1a

  PriSubnet1ARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1A
      RouteTableId: !Ref PrivateRouteTable

  PrivateSubnet1C:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref MyVPC
      CidrBlock: 10.0.3.0/24
      AvailabilityZone: "ap-northeast-1c"
      Tags:
      - Key: Name
        Value: CloudFormation-private-subnet-1c

  PriSubnet1CRouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId: !Ref PrivateSubnet1C
      RouteTableId: !Ref PrivateRouteTable

  myInternetGateway:
    Type: "AWS::EC2::InternetGateway"
    Properties:
      Tags:
      - Key: Name
        Value: CloudFormation-ING
  AttachGateway:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref MyVPC
      InternetGatewayId: !Ref myInternetGateway
  myRoute:
    Type: AWS::EC2::Route
    DependsOn: myInternetGateway
    Properties:
      RouteTableId: !Ref PublicRouteTable
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref myInternetGateway

Outputs:
  StackVPC:
    Description: The ID of the VPC
    Value: !Ref MyVPC
    Export:
      Name: !Sub "${AWS::StackName}-VPCID"

  StackPublicSubnet1A:
    Description: The ID of the VPC Subnet
    Value: !Ref PublicSubnet1A
    Export:
      Name: !Sub "${AWS::StackName}-PublicSubnet1A"
![スクリーンショット 2019-05-25 21.34.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/280929/c2177b63-b26a-f407-04d6-b169efd47f76.png)

  StackPublicSubnet1C:
    Description: The ID of the VPC Subnet
    Value: !Ref PublicSubnet1C
    Export:
      Name: !Sub "${AWS::StackName}-PublicSubnet1C"

  StackPrivateSubnet1A:
    Description: The ID of the VPC Subnet
    Value: !Ref PrivateSubnet1A
    Export:
      Name: !Sub "${AWS::StackName}-PrivateSubnet1A"

  StackPrivateSubnet1C:
    Description: The ID of the VPC Subnet
    Value: !Ref PrivateSubnet1C
    Export:
      Name: !Sub "${AWS::StackName}-PrivateSubnet1C"

Outputsで作成したものが以下のようにエクスポートに作成される。
スクリーンショット 2019-05-25 21.34.56.png

例えばEC2インスタンスを今作成したVPCのサブネットに作成したい場合、
EC2スタックの方に以下の通り記述することで、インポートすることができる。

SubnetId: !ImportValue naata-PublicSubnet1A

このようにいくつかのテンプレートを分けて運用する。

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

AWS EC2 時間同期

問題

たまに時間がずれてしまう

対応

Linux

Amazon Time Sync Serviceの場合(推薦)

https://aws.amazon.com/jp/blogs/news/keeping-time-with-amazon-time-sync-service/
1.インストールの場合

sudo yum erase ntp*
sudo yum -y install chrony
sudo service chronyd start

2.既存の NTP の設定の場合
confに下記追加

server 169.254.169.123 prefer iburst

ntp設置の場合

1.ntpをインストールすること. server = ntp.nict.jpを記載して。chkconfig ntpd on ;; service ntpd start する
vi /etc/sysctl.conf 末尾に追加。

xen.independent_wallclock = 1

2.sysctl -p で変更した設定ファイルの内容をロードする。

Windows

net stop w32time
w32tm /config /syncfromflags:manual /manualpeerlist:"169.254.169.123" 
w32tm /config /reliable:yes
net start w32time

メモ

今のところたまに発生するし、すぐ対応できるので手動ですが、
zabbixのアクションでの調整がいいでしょう。
しかし、手動対応感覚を維持するため頻繁ではないアラートはわざとアクション化しないことにしています。

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

aws s3の署名バージョン「4」をcurlコマンドでリクエストする

前提

  • s3の署名バージョン「2」が2019年6月24日で廃止になる
  • curlで直接s3のエンドポイントを叩く場合は、自分で署名リクエストを作る必要がある

aws公式ドキュメントに疑似コードが乗っているので、それに沿ってbashで実装していきます。

https://docs.aws.amazon.com/ja_jp/general/latest/gr/sigv4-create-canonical-request.html

署名のプロセス

  1. 正規リクエストを作成します。
  2. 正規リクエストと追加のメタデータを使用して、署名の文字列を作成します。
  3. AWS シークレットアクセスキーから署名キーを取得します。次に、署名キーと、前の手順で準備した文字列を使用して、署名を作成します。
  4. 作成した署名をヘッダーの HTTP リクエストに追加します

awsがリクエストを受診したあと、リクエストヘッダーに含まれているアクセスキー(シークレットではない方)を利用して上記の手順をもう一度実施して署名を作成します。その署名がリクエストに含まれている署名と比較して一致しているかが検証されます。

1. 正規リクエストを作成

こんな感じの文字列を作成する必要があります。

PUT
/test/test.txt #対象URI

content-type:text/plain
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3 #アップロード対象ファイルのハッシュ
x-amz-date:20190529T165716Z  #Dateの形式は %Y%m%dT%H%M%SZ

content-type;host;x-amz-content-sha256;x-amz-date #headerの一覧
c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3" #アップロード対象ファイルのハッシュ

bashで書くとこんな感じ。最後にハッシュ化も行っています。

bucket=test-bucket
dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
fileLocal=test.txt
headerList='content-type;host;x-amz-content-sha256;x-amz-date'
#アップロード対象ファイルをハッシュする
payloadHash=$(openssl dgst -sha256 -hex < "${fileLocal}" 2>/dev/null | sed 's/^.* //')
canonicalRequest="\
PUT
/test/test.txt 

content-type:text/plain 
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:${payloadHash} 
x-amz-date:${dateValueLong}

${headerList}
${payloadHash}"

canonicalRequestHash=$(printf '%s' "${canonicalRequest}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //')

2. 正規リクエストと追加のメタデータを使用して、署名の文字列を作成

こんな感じの文字列を作成する必要があります

AWS4-HMAC-SHA256
20190529T171500Z   #Dateの形式は %Y%m%d
20190529           #Dateの形式は %Y%m%dT%H%M%SZ
20190529/ap-northeast-1/s3/aws4_request 
c911d4fcad1f5d53f3d74324cf57dc254a16182ee5662b8872ebe6159f6b3ce3 #上記で取得した正規リクエストのハッシュ

bashで書くとこんな感じ。

dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
region=ap-northeast-1

stringToSign="\
AWS4-HMAC-SHA256
${dateValueLong}
${dateValueShort}/${region}/s3/aws4_request 
${canonicalRequestHash}"

3. AWS シークレットアクセスキーから署名キーを取得します。次に、署名キーと、前の手順で準備した文字列を使用して、署名を作成

こんな感じの処理をする必要があるとのこと。AWSシークレットキーを起点にしてハッシュにしたものをさらにハッシュにしていく処理です。

kSecret = your secret access key
kDate = HMAC("AWS4" + kSecret, Date)
kRegion = HMAC(kDate, Region)
kService = HMAC(kRegion, Service)
kSigning = HMAC(kService, "aws4_request")

bashで書くとこんな感じ。

awsSecret=
dateValueShort=$(date -u +'%Y%m%d')
kSecret="AWS4${awsSecret}"
region=ap-northeast-1
kDate=$(printf         '%s' "${dateValueShort}" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:${kSecret}"     2>/dev/null | sed 's/^.* //')
kRegion=$(printf       '%s' "${region}"         | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kDate}"    2>/dev/null | sed 's/^.* //')
kService=$(printf      "s3"                     | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kRegion}"  2>/dev/null | sed 's/^.* //')
kSigning=$(printf      "aws4_request"           | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kService}" 2>/dev/null | sed 's/^.* //')
signature=$(printf     '%s' "${stringToSign}"   | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kSigning}" 2>/dev/null | sed 's/^.* //')

4. 作成した署名をヘッダーの HTTP リクエストに追加

headerとともにcurlコマンド。

curl -I -L --proto-redir =https -X PUT -T "${fileLocal}" \
  -H "Content-Type: text/plain" \
  -H "Host: ${bucket}.s3.amazonaws.com" \
  -H "X-Amz-Content-SHA256: ${payloadHash}" \
  -H "X-Amz-Date: ${dateValueLong}" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=${awsAccess}/${dateValueShort}/ap-northeast-1/s3/aws4_request, SignedHeaders=${headerList}, Signature=${signature}" \
  "https://${bucket}.s3.amazonaws.com/test/test.txt"

以下のレスポンスが返って来ればOKです

HTTP/1.1 200 OK
x-amz-id-2: hLP5G3rE9Ph0OACv7v4ZsxvPZwj/0N8PuGdR7ns8XO8gl9gKN2aSeuTg+9KxF+vUY2QnDF8t4hA=
x-amz-request-id: BDFF6DF613BA53EA
Date: Wed, 29 May 2019 18:55:58 GMT
ETag: "d8e8fca2dc0f896fd7cb4cb0031ba249"
Content-Length: 0
Server: AmazonS3

はまったところ

正規リクエストに半角スペースが含まれていたりすると、he request signature we calculated does not match the signature you provided. Check your key and signing method.が出ます。しかもデバッグもできないので、ハマると結構キツいです。

全体のコード

#!/bin/bash -u

awsAccess=
awsSecret=
bucket=
fileLocal=test.txt
fileRemote=/test/test.txt
region=ap-northeast-1
dateValueShort=$(date -u +'%Y%m%d')
dateValueLong=$(date -u +'%Y%m%dT%H%M%SZ')
headerList='content-type;host;x-amz-content-sha256;x-amz-date'
#アップロード対象ファイルをハッシュする
payloadHash=$(openssl dgst -sha256 -hex < "${fileLocal}" 2>/dev/null | sed 's/^.* //')
#正規リクエストの作成
canonicalRequest="\
PUT
${fileRemote}

content-type:text/plain
host:${bucket}.s3.amazonaws.com
x-amz-content-sha256:${payloadHash}
x-amz-date:${dateValueLong}

${headerList}
${payloadHash}"

canonicalRequestHash=$(printf '%s' "${canonicalRequest}" | openssl dgst -sha256 -hex 2>/dev/null | sed 's/^.* //')

#署名の文字列を作成
stringToSign="\
AWS4-HMAC-SHA256
${dateValueLong}
${dateValueShort}/${region}/s3/aws4_request
${canonicalRequestHash}"

#署名の作成
kSecret="AWS4${awsSecret}"
kDate=$(printf         '%s' "${dateValueShort}" | openssl dgst -sha256 -hex -mac HMAC -macopt "key:${kSecret}"     2>/dev/null | sed 's/^.* //')
kRegion=$(printf       '%s' "${region}"         | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kDate}"    2>/dev/null | sed 's/^.* //')
kService=$(printf      "s3"                     | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kRegion}"  2>/dev/null | sed 's/^.* //')
kSigning=$(printf      "aws4_request"           | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kService}" 2>/dev/null | sed 's/^.* //')
signature=$(printf     '%s' "${stringToSign}"   | openssl dgst -sha256 -hex -mac HMAC -macopt "hexkey:${kSigning}" 2>/dev/null | sed 's/^.* //')

#http request
curl -I -L --proto-redir =https -X PUT -T "${fileLocal}" \
  -H "Content-Type: text/plain" \
  -H "Host: ${bucket}.s3.amazonaws.com" \
  -H "X-Amz-Content-SHA256: ${payloadHash}" \
  -H "X-Amz-Date: ${dateValueLong}" \
  -H "Authorization: AWS4-HMAC-SHA256 Credential=${awsAccess}/${dateValueShort}/${region}/s3/aws4_request, SignedHeaders=${headerList}, Signature=${signature}" \
  "https://${bucket}.s3.amazonaws.com${fileRemote}"
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS認定Associate3冠取得達成したので自分はどう勉強したのかを放流する

プロフィール

エンジニアになって1年半程度、AWSは半年〜1年未満です。
AWSはオンプレな設計思想をそのままクラウドにのっけたシステムの運用保守業務で携わっている程度なので、設計構築開発含めガッツリやってます!って人ほどは関わっていない、そんなレベル感です。

JAWS Days 2019に参加したのをきっかけにやっぱりAWS超面白い、もっと詳しくなりたいと自分に火が付いてその日から3月、4月、5月と立て続けに試験を受けて3つあるAssociate Levelをコンプリートしました。

受験履歴

2019/5/29現在、AWS認定は画像の様に全11種類あります。
私はそのうちの赤枠で囲われている3つを取得しました。
※Cloud Practitionerは入門Levelの認定になるので一旦スルーしています。

AWS認定 取得日
ソリューションアーキテクト アソシエイト (SAA) 2019/3/22
デベロッパー アソシエイト(DVA) 2019/4/24
SysOpsアドミニストレータ アソシエイト (SOA) 2019/5/28

スクリーンショット 2019-05-29 21.05.46.png

Associate Levelの難易度

実際に試験を受けてみての所感

順位 AWS認定
1位 デベロッパー アソシエイト(DVA)
2位 ソリューションアーキテクト アソシエイト (SAA)
3位 SysOpsアドミニストレータ アソシエイト (SOA)

これは試験を受ける人が普段どの様にAWSに関わっているかで変わってくる部分があるかと思います。
実際、私はDVA試験範囲のAWSサービス要素にほぼ業務では関われていない為、難易度も高く感じましたし、スコアも3つの中で一番が悪かったです…笑

また、Qiitaなどで受験記録を読み漁ってみれば気づくと思いますが、3つの中でSAAが1番難しいとされている節があって、直近3つ取った者として私はこれに異を唱えたい!!
理由としては、日本語のSAA対策本がここ数ヶ月で非常に充実してきた背景があります。

タイトル 発売日
合格対策 AWS認定ソリューションアーキテクト - アソシエイト 2016/8/17
徹底攻略 AWS認定 ソリューションアーキテクト – アソシエイト教科書 2019/1/18
最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本 2019/2/26
AWS認定資格試験テキスト AWS認定 ソリューションアーキテクト-アソシエイト 2019/4/20
AWS認定アソシエイト3資格対策 2019/6/13(予定)
この1冊で合格! AWS認定ソリューションアーキテクト - アソシエイト テキスト&問題集 2019/7/20(予定)

ソリューションアーキテクトアソシエイトだけで一体どれだけだすんだよ!ってくらいに充実してきています。
それに対してDVAやSOAに特化した日本語の対策本はまだ存在しません。
※[AWS認定アソシエイト3資格対策]は現在未発売であることと、特化本とは違うようなのでノーカウント

これだけあればSAA対策に関して迷うことはないでしょう。

学習方法

試験を受けるにあたってQiitaやその他ブログなどを読み漁り、先駆者がどの様にして対策を行ったのか情報を集めました。以下はその中でも特に参考になった記事です。全受験者必読です。

AWS認定11冠制覇したのでオススメの勉強法などをまとめてみる
 非常に参考にさせて頂きました。正直この記事だけでなんとか出来ます。

以降は上記のQiita記事を踏まえた上で自分が実際に取った対策を書いていきます。

ほぼ共通

AWS サービス別資料(Black Belt)
各種AWSサービスのBlack Beltの読み込みは基本ですね。

よくある質問
全てのQ&Aを読めませんでしたが、時間があればもっと目を通しておきたかった。

AWSドキュメント(開発者ガイド)
個々のサービスについて詳しく知りたい時は開発者ガイドが役に立ちました。
※読みにくいのが難点…

ソリューションアーキテクト アソシエイト (SAA)

使用教材 金額
最短突破 AWS認定ソリューションアーキテクト アソシエイト 合格教本 2,678円

SAA対策には先ず、対策本を1週読んだ後に付属の模擬試験65問を解きました。
1週目で6割程度正解だったと記憶しております。

その後対策本をベースに各種AWSサービスに関するBlack Beltの読み込みを実施。
ある程度サービスについての理解が進んだ後は再び対策本の付属の模擬試験を、今度は解説の内容から正解/不正解の理由を意識して100%正解になるまでやり込みました。

SAAの対策としてはそれくらいです。

ソリューションアーキテクト アソシエイト (SAA)振り返り

恐らく全ての本に共通する事ですが、市販の対策本の模擬試験のレベルと本試験のレベルは難易度が違います。AWS認定はその仕様上、過去問というものが存在しません。出回っている模擬試験は飽くまでもこれまでの出題傾向から導き出された似たような問題なのです。

1回で合格したから良かったけれど、試験中はこれはギリギリかもしれないと思いながら最後の「終了」ボタンを押しました。これからSAAを受験される方には「本試験>>本などで出回っている模擬試験」くらいの認識を持って本番に挑むといいかもしれません。この難易度感を知っているか否かでも大分心構えが違うかと思います。

デベロッパー アソシエイト(DVA)

DVAはSAAと違って日本語の対策本が全くない為、非常に苦しみました。
QiitaでUdemyでの学習を勧めていた記事があったので私もそれに習ってUdemyを使用しております。

使用教材 金額
Ultimate AWS Certified Developer Associate 2019 2,400円(キャンペーン価格)
AWS Certified Developer Associate 2019 4 Practice Tests 0円(上記とセット)

Udemyでは定価だと例えば18,000円なのに時々キャンペーンによって格安で購入することが出来ます。私もキャンペーンを狙って購入しました。

私が購入した講座は、DVAの試験範囲のAWSサービスを試験対策の観点で解説してくれ、更にはそのほぼ全てにハンズオンの動画&ハンズオン資材が含まれているおり、また65問の模擬試験&解説が4回分込み込みで2,400円なので非常にオススメです。

以下の画像が購入履歴です。

スクリーンショット 2019-05-29 23.02.17.png

※単品購入でなくセットで購入するのがベストです!
因みに定価だと30,000円…Udemyあるあるですね。

↓セット購入のイメージ
※キャンペーン時だとこれがセットで2,400円でした!

スクリーンショット 2019-05-29 23.10.21.png

Udemyの使い方

既にお気付きかと思いますが、私が購入した講座は英語です…。DVA対策の講座は英語ぐらいしか置いていないのです。では、どうやってこれを使って勉強したのかというと英字字幕をGoogleChromeでリアルタイム翻訳しました。

翻訳なので日本語に比べて理解するのが大変ではありましたが、使用したことのないサービスに関しての体系的なハンズオンもあったので役に立ったと思います。

翻訳の雰囲気はこんな感じです。

模擬試験の場合
test3.gif

講座動画の場合
test4.gif
※gifのフレームレートが最適化出来なかったので話のスピードは参考にしないでください…

Udemyで講座動画を1週見たら、模擬試験を解いて解説を読むをひたすら繰り返しました。
SAAの対策本と違い、模擬試験の解説は公式ページの開発者ガイドなどへのリンクも含まれていた為、翻訳では理解できない場合は公式へ飛んでそっちを読んだりもしました。

デベロッパー アソシエイト(DVA)振り返り

模擬試験での対策に関してはSAAより場数が踏めたとはいえやはり開発者サイドは未経験であったこともあり1ヶ月の準備期間では苦戦を強いられました。しかしそんな人間に対してもSAA同様に1回で合格出来たので今回使用した教材の質と再現性の高さは証明されたと思っています。言語の壁がなければもっとすんなりといけたんだろうなあと悔しさを噛みしめる。

SysOpsアドミニストレータ アソシエイト (SOA)

SOAに関しては実はAWS WEB問題集で学習しようという日本語で対策が可能なWEBサービスがあります。
これもQiitaなどで「ここ使っていけた」系の記事が幾つか見つかると思います。

参考までに料金形態

プラン CLF SAA SOA SAP 有効期限 料金 問題数
フリー - #1~3 #1~3 #1 なし なし -
ゴールド - 全て - - 60日 3,880円 SAA 872問
プラチナ - 全て 全て - 60日 4,280円 +SOA 315問
ダイヤモンド 全て 全て 全て 全て 90日 5,280円 +SAP 189問

プラチナ以上でSOA対策が可能です。

まあ、自分は使わなかったので内容に関しては言及できません。使わなかった理由としては上記のサービスは使用期限があるのに対して、Udemy講座は無期限で使えるので暫くして振り返って勉強する事が手軽に可能という点を重視した為です。

DVAで使用した講座と同じ講師がSOA対策の講座も出していたので、AWSサービスに関するハンズオン動画もあるのでそちら見たさにUdemy講座を購入しました。しかし、残念なことに模擬試験までは出していなかったので別の講師が出している物を別途購入しています。

使用教材 金額
Ultimate AWS Certified SysOps Administrator Associate 2019 2,400円
AWS Certified SysOps Administrator Associate Practice Exams 1,400円

※どちらもキャンペーン価格です。

学習の流れはDVAの時と同様。
今回使用した模擬試験は解説がDVAの講師のものよりも公式ページの言葉をそのまま使用しているケースが多い為か、理解しやすかった印象です。

SysOpsアドミニストレータ アソシエイト (SOA)振り返り

Associate最後ということもあり1番楽に感じました。しかし、当然ですがSOAはSAAやDVAと出題傾向が違いますのでしっかりと対策をしないと不合格になりるなと実感しました。

その事を訴えた記事が最近投稿されたのでここにも載せておきます。
AWS Sysops 試験に失敗した話(2019年5月)

そしていつだって本試験は模擬試験より難易度は高いです。高かったです。

全体振り返り

Associate3冠取得に向けての対策として、情報収集→教材選定→教材読み込み&視聴→(模擬試験&解説読み込み)×n回の一連の流れをセットにして本試験に挑んだ感想としては、闇雲に公式ページや公式PDFを隅から隅まで読むよりかはポイントを抑えて効率的に学習出来て良かったです。

高得点を狙うならそれら公式の読み込みに加えてもっと実際にAWSを動かして弄る必要があるなと課題も残りました。今の業務だと求める内容には遠いので自学を兎に角頑張って1年後あたりにはProfessionalに挑戦して5冠を目指したいところです!

以上、これからAssociate Levelの認定に挑戦したい誰かの役に立てればと思います。

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