Amazon Transcribeを調べてみた

Amazon Transcribeとは

Amazon Transcribeは一言で言うと”Speech to Text”と呼ばれる、音声をテキスト情報に変換するサービスです。
話されている言語を機械学習の技術で識別し、テキスト情報に変換します。

この技術は以下のような新しいサービスやプロダクトの提供に役立ちます。

  • 映像ファイルから音声を認識し、クローズドキャプションを生成
  • コールセンター業務などでの問い合わせ内容の分析
  • 医療分野や法律分野での活用

また、”Amazon Translate”や”Amazon Polly”と連携して、生成したテキスト情報を翻訳し、再度翻訳した言語で音声に変換することなどもできます。

何ができるのか

Amazon Transcribeには以下の3つのオペレーションがあります。

  • StartTranscriptionJob : 非同期で音声をテキストに書き起こす
  • ListTranscriptionJobs : 開始された音声認識ジョブのリストを返す。 返して欲しいJobをステータスで絞り込むことができる
  • GetTranscriptionJob : 音声認識の結果を返す。結果にはJSON形式に変換された結果へのリンクが含まれている

Speech Input

インプットするファイルはS3 bucketに保管されている必要があります。
インプットファイルの仕様は以下のみ

  • FLAC、MP3、MP4、WAV
  • 尺は2時間未満

言語、フォーマット、サンプリングレートを指定する必要があります。

  • PCM 16ビットエンコーディングのFLACやWAVなどのロスレスフォーマットを使用。
  • サンプリングレートは8000 ~ 16000Hz

Amazon TranscribeのS3とその中のファイルへのアクセスを許可する必要があります。

Output JSON

Jobが完了するとJSONが含まれた結果が生成され、テキストファイルがS3に置かれます。
ファイルのIDはユーザー固有のURIとなっており、そのURIを利用することで結果を取得できます。

Ex)

 {
      "jobName":"job ID",
      "accountId":"account ID",
      "results": {
         "transcripts":[
            {
               "transcript":" that's no answer",
               "confidence":1.0
            }
         ],
         "items":[
            {
               "start_time":"0.180",
               "end_time":"0.470",
               "alternatives":[
                  {
                     "confidence":0.84,
                     "word":"that's"
                  }
               ]
            },
            {
               "start_time":"0.470",
               "end_time":"0.710",
               "alternatives":[
                  {
                     "confidence":0.99,
                     "word":"no"
                  }
               ]
            },
            {
               "start_time":"0.710",
               "end_time":"1.080",
               "alternatives":[
                  {
                     "confidence":0.874,
                     "word":"answer"
                  }
               ]
            }
         ]
      },
      "status":"COMPLETED"
   }
 

始め方

始めるにはAWSアカウント、ID、IAMユーザーが必要です。CLIの利用も可能です。

Step.1 AWSアカウント設定

いつも通りなので割愛

Step.2 CLI設定

いつも通りなので割愛

Step.3 コンソールでの利用開始

Jobの作成

1.各種情報の入力
 - Transcription job name : AWSアカウント毎にユニークである必要がある
 - Amazon s3 input URL: 音声ファイルが格納されているS3 busket。Transcribeと同一リージョンである必要がある
 - Language:インプットファイルの言語を選択
 - Format:インプットファイルのフォーマットを選択
 - Media sampling rate(Hz):インプットファイルのサンプリングレートを8000 ~ 48000Hzの間で指定。8000~16000Hzが推奨
2.「Create」を押す

Jobの確認

Jobのリストを表示。「Availability」にサーバーに結果が保管される残り期間が表示される。結果の保管期間は90日
Jobをクリックすると、詳細(Job名、残りの保管期間、I/OのファイルのS3パス)と結果の文字列が表示される。
「Code Samples」で該当JobについてのJSONファイルを取得可能

Step.4 API

CLI

Transcribeのテストをする場合

1.InputファイルをS3 バケットに配置する(Transcribeと同じリージョンに)
2. ファイル情報を含んだJSONファイルを作成する

{
    "TranscriptionJobName": "request ID", 
    "LanguageCode": "en-US", 
    "MediaFormat": "wav", 
    "Media": {
        "MediaFileUri": "https://S3 endpoint/test-transcribe/answer2.wav"
    }
}

3.下記コマンドを実行
json
aws transcribe start-transcription-job
--endpoint-url endpoint
--region region
--cli-input-json file://test-start-command.json

・下記レスポンスが返れば成功
“`json

{
“TranscriptionJob”: {
“TranscriptionJobName”: “request ID”,
“LanguageCode”: “en-US”,
“TranscriptionJobStatus”: “IN_PROGRESS”,
“Media”: {
“MediaFileUri”: “https://S3 endpoint/test-transcribe/answer2.wav”
},
“CreationTime”: timestamp,
“MediaFormat”: “wav”
}
}
“`

Jobのリストを取得

1.Jobが完了していた場合、下記コマンドでステータスを取得する

aws transcribe get-transcription-job-results 
   --endpoint-url endpoint 
   --region endpoint 
   --request-id "DocTest-01"

・成功すればレスポンスは下記通り返ってくる

{
    "TranscriptionJob": {
        "TranscriptionJobName": "request ID",
        "LanguageCode": "en-US",
        "TranscriptionJobStatus": "COMPLETED",
        "Media": {
            "MediaFileUri": "input URI"
        },
        "CreationTime": timestamp,
        "CompletionTime": timestamp,
        "Transcript": {
            "TranscriptFileUri": "output URI"
        }
    }
}
```json

2.Output URIを使って翻訳されたテキストを取得

```json
{
      "jobName":"job ID",
      "accountId":"account ID",
      "results": {
         "transcripts":[
            {
               "transcript":" that's no answer",
               "confidence":1.0
            }
         ],
         "items":[
            {
               "start_time":"0.180",
               "end_time":"0.470",
               "alternatives":[
                  {
                     "confidence":0.84,
                     "word":"that's"
                  }
               ]
            },
            {
               "start_time":"0.470",
               "end_time":"0.710",
               "alternatives":[
                  {
                     "confidence":0.99,
                     "word":"no"
                  }
               ]
            },
            {
               "start_time":"0.710",
               "end_time":"1.080",
               "alternatives":[
                  {
                     "confidence":0.87,
                     "word":"answer"
                  }
               ]
            }
         ]
      },
      "status":"COMPLETED"
   }

SDK for Python(Boto)

・InputファイルをS3 バケットに配置する(Transcribeと同じリージョンに)。ファイル情報を含んだJSONファイルを作成する

from __future__ import print_function
import time
import boto3
transcribe = boto3.client('transcribe')
job_name = "job name"
job_uri = "https://S3 endpoint/test-transcribe/answer2.wav"
transcribe.start_transcription_job(
    TranscriptionJobName=job_name,
    Media={'MediaFileUri': job_uri},
    MediaFormat='wav',
    LanguageCode='en-US'
)
while True:
    status = transcribe.get_transcription_job(TranscriptionJobName=job_name)
    if status['TranscriptionJob']['TranscriptionJobStatus'] in ['COMPLETED', 'FAILED']:
        break
    print("Not ready yet...")
    time.sleep(5)
print(status)

・成功すればレスポンスは下記通り返ってくる

 {
      "jobName":"job ID",
      "accountId":"account ID",
      "results": {
         "transcripts":[
            {
               "transcript":" that's no answer",
               "confidence":1.0
            }
         ],
         "items":[
            {
               "start_time":"0.180",
               "end_time":"0.470",
               "alternatives":[
                  {
                     "confidence":0.84,
                     "word":"that's"
                  }
               ]
            },
            {
               "start_time":"0.470",
               "end_time":"0.710",
               "alternatives":[
                  {
                     "confidence":0.99,
                     "word":"no"
                  }
               ]
            },
            {
               "start_time":"0.710",
               "end_time":"1.080",
               "alternatives":[
                  {
                     "confidence":0.87,
                     "word":"answer"
                  }
               ]
            }
         ]
      },
      "status":"COMPLETED"
   }

認証とアクセスコントロール

・AWS Transcribeの利用にはCredentialが必要です。Credencialには認証とアクセスコントロールの設定を行う必要があります。

認証

・認証には以下のいずれかを使用します。
– AWS Account ルートユーザー(非推奨)
– IAM role
  (1) ユーザーごとの許可
  (2) AWSサービスのからのアクセス
  (3) EC2からのアクセス許可

アクセスコントロール

TranscribeへアクアセスするためのPermissionの管理

アクセスとアクションの確認

・誰が何にアクセスするかはは下記方法で定義します。
 - IAMポリシー:IAMユーザーやIAM roleに権限を付加する
 - リソースベースのポリシー:各AWSのサービスにAPIでアクセスする際に要求されるポリシー主にResource,Action,Effect,Principalなど

TranscribeのためのIAM ポリシー

・StartTranscriptionJobを実行するためのIAM roleの例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "transcribe:StartTranscriptionJob"
             ],   
            "Resource": "*"
        }
    ]
}

・コンソールでTranscribeを使用するためのIAM roleの例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "transcribe:*"
            ],
            "Resource": "*",
            "Effect": "Allow"
        }
    ]
}

・音声を取得するためにIAM roleの例

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "transcribe.amazonaws.com"
            },
            "Action": "s3:GetObject",
            "Resource": "arn:aws:s3:::bucket name/*"
        }
    ]
}

・KMSを使ったS3の暗号化を行うための IAM roleの例

{
      "Sid": "Allow-Transcribe",
      "Effect": "Allow",
      "Principal": {
        "Service": "transcribe.amazonaws.com”
      },
      "Action": [
        "kms:Encrypt",
        "kms:Decrypt",
        "kms:ReEncrypt*",
        "kms:GenerateDataKey*",
        "kms:DescribeKey"
      ],
      "Resource": "*"
    }

APIに対する権限のリファレンス

・Amazon TranscribeのAPIは下記通り
 - GetTranscriptionJob:
   API: transcribe:GetTranscriptionJob
   Resource:*
 - ListTranscriptionJobs:
   API: transcribe:ListTranscriptionJobs
   Resource:*
 - StartTranscriptionJob:
   API: transcribe:StartTranscriptionJob
   Resource:*

ベータ版のガイドラインと制限

・現在は下記リージョンのみ
 -リージョン:US East (N. Virginia)
 - Endpoint::https://transcribe.us-east-1.amazonaws.com
 - プロトコル:HTTPS
・推奨素材は以下
 - ロスレスFLAC、ロスレスWAV、PCM(16ビット)
 - サンプリングレート 8000 ~ 16000Hz
・制限は以下の通り
 - 尺は最長2h

APIリファレンス

・別紙参照(https://docs.aws.amazon.com/ja_jp/transcribe/latest/dg/API_GetTranscriptionJob.html)

続きを読む

Amazon S3 に Git LFS サーバを超簡単に立てる

元ネタはこちら。
Serverless Git LFS for Game Development – Alan Edwardes
Amazon API Gateway + Amazon Lambda + Amazon S3 を使うんだそうです。

準備: AWS のアカウントをとる

AWSのアカウントがなければとります。
作成の流れは 公式のドキュメント でだいじょうぶです。

アカウントをとったら初期設定しておきます。以下のエントリが参考になりました。
AWSアカウントを取得したら速攻でやっておくべき初期設定まとめ – Qiita

サーバ作成手順

Screen_Shot_2018-01-21_at_22_29_22-fullpage.png

  • 「次へ」をクリックします → オプションの設定ページはスルーで「次へ」をクリックします。
  • 「AWS CloudFormation によって IAM リソースが作成される場合があることを承認します。」にチェックして「作成」をクリックします。

Screen_Shot_2018-01-21_at_23_05_08-fullpage.png

  • スタックのリストに新しいスタックができて(スタックがなければリロードして下さい)、「CREATE_COMPLETE」と表示されたら成功です。

Screen_Shot_2018-01-21_at_23_10_18-fullpage.png

  • スタックの詳細画面に入って、出力を確認すると、Git LFSのエンドポイントのURLが表示されているので、リンクをコピーします。

Screen_Shot_2018-01-21_at_22_34_53-fullpage.png

Git LFS に lfs.url を設定

Git LFS は導入してあることとします。

以下のコマンドで、 .lfsconfig を作成します。

git config -f .lfsconfig lfs.url <表示されたエンドポイントのURL>

git lfs env で確認して、Endpoint= の行が設定したURLになってたらOKです。

git push すると、S3の方にLFSオブジェクトがpushされるようになります。

問題点

URL を見ればわかると思いますが、この方法で作成するとリージョンが 欧州 (アイルランド) に作成されます。
東京リージョンにS3を立てたいと思って、 URL の region=eu-west-1 の部分を region=ap-northeast-1 に変えてみましたが、リージョンを変えると成功しないみたいです。

alanedwardes/Estranged.Lfs – Github

どなたか検証お願いします。 m(_ _)m

続きを読む

【AWS SDK for PHP】1000以上のファイルがあるS3バケットの内容を取得する

やりたいこと

PHPでS3バケットにあるファイルのリストを取得する際、AWS SDK for PHPのListObjectsメソッドListObjectsV2メソッドが使えます。v1とv2ではパラメーターの設定方法や返り値が若干異なるだけで中身は同じです。どっちでもいいのですが今回はv2でいきます。

しかし困ったことに一度に1,000件までしか取得できない…さすがにそれは不便すぎる

解決策

バケットにオブジェクトが1000より多く存在する時、ListObjectV2の返り値のIsTruncatedがtrueになることを利用します。
ちなみに’truncated’は切り捨てという意味で、取りこぼしたやついるよ〜っていう主張でしょうね。
さて、IsTruncatedがtrueの時、返り値のNextContinuationTokenに、今取得したリストの次から取得するためのトークンが入っています。これをリクエストパラメーターContinuationTokenに入れてあげてもう一回1000件取得…を繰り返していけばいいわけです。

結果的にコードは次のようになりました。

use AwsS3S3Client;

$config = [
    'credentials' => [
        'key' => '*** アクセスキー ***',
        'secret' => '*** シークレットキー ***',
    ],
    'region' => '*** リージョン ***',
    'version' => '2006-03-01'
];
$bucket = '*** バケット名 ***';
$prefix = '*** プレフィックス ***';


$client = S3Client::factory($config);

$object_list = GetObjectList($client, $bucket, $prefix);



function GetObjectList($client, $bucket, $prefix, $continuous_token=null)
{
    $result = $client->ListObjectsV2([
        'Bucket' => $bucket,
        'Prefix' => $prefix,
        'ContinuationToken' => $continuous_token
    ]);

    $object_list = [];
    if (isset($result['Contents'])) {
        foreach ($result['Contents'] as $object) {
            // 名前がxxxx/のオブジェクト(フォルダ)はリストに含めない
            if(substr($object['Key'], -1, 1) !== '/') {
                $object_list[] = 's3://'.$bucket.'/'.$object['Key'];
            }
        }

        if($result['IsTruncated'] === true) {
            $object_list = array_merge($object_list, GetObjectList($client, $bucket, $prefix, $result['NextContinuationToken']));
        }
    }

    return $object_list;
}

続きを読む

AWSLambdaからUserPoolの属性を変更

はじめに

Cognitoのユーザ属性にカスタム属性として「セーブ数」を登録して、管理者が許可したらその属性を修正し、アプリ側でカスタム属性を取得したいなぁ…と思ったら案外めんどくさかった、という話。

まとめると

・AWS管理コンソールから属性の値を登録することも更新することもできない(と思う)。
・でもAPIからなら普通に更新ができる。
・値も普通に取得できる。

ユーザのカスタム属性の値を修正したいんですけど

ユーザを初期登録するときには設定できる。
でも修正ができない。
管理コンソールに修正するところがあるんじゃないか?と思って眺めても…ない。
ネットを調べてもない。
Userpool自体の初期登録の仕方なのか?と思ったがそうでもない様子。

そもそもカスタム属性は初期登録時のみなのか??

メソッド名に「adminUpdateUserAttributes」なんてのがあるんだから更新できるんじゃないの?
と思って、せめてAPI経由でも更新させてくれよーと思ったら…

awsのフォーラムにありました。

var AWS = require('aws-sdk');
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
var params = {
UserAttributes: [ / required /
{
Name: 'custom:attributename', / required /
Value: 'value'
},
/ more items /
],
UserPoolId: 'ap-southeast-2_abcdefghi', / required /
Username: 'username'/ required /
};
cognitoidentityserviceprovider.adminUpdateUserAttributes(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
};

なるほどできるんだなぁ…と思って、やってみた。
/required/
ってコメントは抜かないとエラーになるんだなぁ…

index.js(lambda)
var AWS = require('aws-sdk');
var cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
exports.handler = (event, context, callback) => {
var params = {
    UserAttributes: [
    {
    Name: 'custom:SavePoint', 
    Value: '2'
    },
    ],
    UserPoolId: 'ap-northeast-1_hohohoho',
    Username: 'hoge'
};
cognitoidentityserviceprovider.adminUpdateUserAttributes(params, function(err, data) {
    if (err) console.log(err, err.stack); // an error occurred
    else console.log(data); // successful response
    });
};

これだけだとアクセス権とか大丈夫なのか?と思ったら案の定ロール定義が必要だった。
ロール定義するためにはポリシー定義が必要でした。
無題.jpg
必要なポリシーは「cognito User Pools」のみ。
そのポリシーと新しく作ったロールを関連付ける。
そして、上記関数のロールにする。

実行すると…案外素直に動きました。
カスタム属性はnumber型なのですが、valueの指定は”を付けるんだね。

無題2.jpg

値が変わったことは画面から確認できる。
なお、もともと値が登録されてなくてもadminUpdateUserAttributesを使えば登録できる。

値の取得

これもまた微妙に面倒…

日本語で書かれたチュートリアルがあるのだが、なんだかわかりにくい。
結局SDKのサンプルをみて作成した。

hoge.js(clientside)
AWS.config.update({
          accessKeyId: 'XXXXXXXXX',  //IAMのアクセスキーID
          secretAccessKey: 'YYYYYYY',  //アクセスキーID作成時のみ表示
          region:'ap-northeast-1',
          IdentityPoolId: 'ap-northeast-1:XX-XXXX-xxxx-xxxx-xx'
        });

signin処理は省略signin時にアクセストークン取得できる。)

var SavePoint = 0;
var cognitoSP = new AWS.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});


cognitoSP.getUser({AccessToken:data.AuthenticationResult.AccessToken},function(err,data){
  if(err) console.log(err,err.stack);
  else {
    data.UserAttributes.forEach(function(val,id,arary) {
      if (val.Name == 'custom:SavePoint') {
        SavePoint = val.Value;
      }
    });
  }
});

途中の「data.AuthenticationResult.AccessToken」にはアクセストークンを入れる必要がある。
アクセストークンはsignin時に取得できる。(ここでは途中端折ってる)
これでSavePointにcognitoのカスタム属性(custom:SavePoint)の値が入りました。

最後に

案外簡単なのはわかったが、ここまで来るのに4時間ぐらいかかった。
値の変更を画面からでも追加、修正できるようになればもう少し簡単になるのだけど。

続きを読む

【備忘録】AWS ECRにdocker pushした際にno basic auth credentialsとなった場合の解決方法

問題点

ECRにコンテナイメージをdocker pushした際にno basic auth credentialsとなってしまい、pushが出来なかった。

$ aws ecr get-login --no-include-email --region ap-northeast-1
docker login -u AWS -p xxxxx== https://xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

$ docker login -u AWS -p xxxxx== https://xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

$ docker push xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx:latest
The push refers to repository [xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx]
dbee35a328d0: Preparing
77bf014019f2: Preparing
82cdb9879505: Preparing
...

no basic auth credentials

参考

Amazon ECR 使用時の Docker コマンドのエラーのトラブルシューティング – Amazon ECR

公式サイトのトラブルシューティングではno basic auth credentialsの原因がいくつか取り上げられていたが、自分の環境では該当しそうなものがなかった。

  • 別のリージョンに対して認証されている
  • トークンの有効期限が切れた。
  • wincred 認証情報マネージャーのバグ

原因

docker pushを実行してる環境のアクセスキーが別アカウントのものになっていた。

docker pushを実行してる環境のアクセスキー情報

$ aws configure
AWS Access Key ID [****************IUJD]:
AWS Secret Access Key [****************EuiN]:
Default region name [ap-northeast-1]:
Default output format [json]:

ECRを利用しているAWSコンソールのアクセスキー情報

ecr.png

解決方法

AWS CLIにECRを利用いているAWSアカウントのアクセスキーを設定して、 Dockerクライアントの認証とプッシュコマンドを再度実行したらdocker pushが成功した。

$ aws configure
AWS Access Key ID [****************IUJD]: ****************IXYQ
AWS Secret Access Key [****************EuiN]: ********************
Default region name [ap-northeast-1]:
Default output format [json]:
$ aws ecr get-login --no-include-email --region ap-northeast-1
docker login -u AWS -p xxxxx== https://xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

$ docker login -u AWS -p xxxxx== https://xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

$ docker push xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx:latest
The push refers to repository [xxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/xxxxx]
dbee35a328d0: Pushed
77bf014019f2: Pushed
82cdb9879505: Pushed
...

latest: digest: sha256:d4c79d7f9b26762a139b150fdf65fa55c47d1673f4cc55659ed9969ddf03db8b size: 5947

続きを読む

EC2 拡張ネットワーキング 有効化手順

EC2 拡張ネットワーキング 有効化手順

ちゃんとした情報は公式をみよう。

インスタンスタイプに応じて、ixgbevf ドライバ、もしくは amzn-drivers を利用出来る状態に設定し、AMIもしくはインスタンスに利用可能とフラグ付けを行う事によって拡張ネットワーキングが利用可能となる。

インスタンスタイプ ドライバ
c3/c4/d2/i2/r3/m4(16xlarge以外) ixgbevf
c5/f1/g3/h1/i3/m5/p2/p3/r4/x1/m4.16xlarge amzn-drivers

確認事項

Amazon Linuxの場合

通常、対応するためのカーネルモジュールがインストールされている為、作業は不要。

CentOS 6の場合

カーネルモジュールの更新が必要。

CentOS 7の場合

少なくとも 7.4.1708 以降ではカーネルモジュールがインストールされている為、作業は不要。

モジュールの確認

ixgbevfena のカーネルモジュールの状態を確認し、バージョン情報を確認する。
ixgbevf は特定のバージョン以下の場合はカーネルモジュールの更新が必要となる。

対応バージョン

ドライバ 必要バージョン
ixgbevf 2.14.2 以降
ena インストールされている事
$ modinfo ixgbevf
filename:       /lib/modules/3.10.0-693.11.6.el7.x86_64/kernel/drivers/net/ethernet/intel/ixgbevf/ixgbevf.ko.xz
version:        3.2.2-k-rh7.4
license:        GPL
description:    Intel(R) 10 Gigabit Virtual Function Network Driver
author:         Intel Corporation, <linux.nics@intel.com>
rhelversion:    7.4
srcversion:     45F26A06C9B8C3202EA1ADC
alias:          pci:v00008086d000015C5sv*sd*bc*sc*i*
alias:          pci:v00008086d000015A9sv*sd*bc*sc*i*
alias:          pci:v00008086d000015A8sv*sd*bc*sc*i*
alias:          pci:v00008086d00001564sv*sd*bc*sc*i*
alias:          pci:v00008086d00001565sv*sd*bc*sc*i*
alias:          pci:v00008086d00001530sv*sd*bc*sc*i*
alias:          pci:v00008086d00001515sv*sd*bc*sc*i*
alias:          pci:v00008086d0000152Esv*sd*bc*sc*i*
alias:          pci:v00008086d000010EDsv*sd*bc*sc*i*
depends:
intree:         Y
vermagic:       3.10.0-693.11.6.el7.x86_64 SMP mod_unload modversions
signer:         CentOS Linux kernel signing key
sig_key:        2C:BC:98:70:54:63:43:CA:3A:E1:20:C2:BC:EB:98:44:01:95:59:62
sig_hashalgo:   sha256
parm:           debug:Debug level (0=none,...,16=all) (int)
$ modinfo ena
filename:       /lib/modules/3.10.0-693.11.6.el7.x86_64/kernel/drivers/net/ethernet/amazon/ena/ena.ko.xz
version:        1.0.2
license:        GPL
description:    Elastic Network Adapter (ENA)
author:         Amazon.com, Inc. or its affiliates
rhelversion:    7.4
srcversion:     3A6B9F1766C9A0B5CBC7D01
alias:          pci:v00001D0Fd0000EC21sv*sd*bc*sc*i*
alias:          pci:v00001D0Fd0000EC20sv*sd*bc*sc*i*
alias:          pci:v00001D0Fd00001EC2sv*sd*bc*sc*i*
alias:          pci:v00001D0Fd00000EC2sv*sd*bc*sc*i*
depends:
intree:         Y
vermagic:       3.10.0-693.11.6.el7.x86_64 SMP mod_unload modversions
signer:         CentOS Linux kernel signing key
sig_key:        2C:BC:98:70:54:63:43:CA:3A:E1:20:C2:BC:EB:98:44:01:95:59:62
sig_hashalgo:   sha256
parm:           debug:Debug level (0=none,...,16=all) (int)

使用中のドライバ確認

利用可能なインスタンスタイプで起動している場合に、使用中のドライバを確認し、ixgbevf もしくは ena が使用されている事を確認する。
使用されていない場合(driverが vif となっている)は拡張ネットワーキングが有効になっていないので確認が必要。

無効時の例

$ ethtool -i eth0
driver: vif
version:
firmware-version:
bus-info: vif-0
supports-statistics: no
supports-test: no
supports-eeprom-access: no
supports-register-dump: no
supports-priv-flags: no

ixgbevfが有効時の例

$ ethtool -i eth0
driver: ixgbevf
version: 3.2.2-k-rh7.4
firmware-version:
expansion-rom-version:
bus-info: 0000:00:03.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: no
supports-register-dump: yes
supports-priv-flags: no

カーネルモジュールのインストール

事前準備

kernel kernel-headers kernel-develのバージョンを合わせる必要がある。

最新のkernelにアップデートする事を推奨しますが、旧カーネルの場合は kernel-headerskernel-devel をバージョン指定してインストールを行う事により対応が可能となる。

CentOS 6の中でも最新マイナーバージョン以下を利用する際はyumリポジトリが既にミラーから削除されている為、vault.centos.orgのリポジトリを利用する様に修正が必要となる。

# cat /etc/centos-release
CentOS release 6.2 (Final)

# sed -i.bak -E 's/^mirrorlist=/#mirrorlist=/' /etc/yum.repos.d/CentOS-Base.repo
# sed -i -E 's@^#?baseurl=http://mirror.centos.org/centos/$releasever/@baseurl=http://vault.centos.org/6.2/@' /etc/yum.repos.d/CentOS-Base.repo

CentOSのバージョンに合わせてsedの 6.2 となっている部分を修正する。

また、dkms によるカーネルアップデート時の自動コンパイル設定を行う事を推奨するため、epel リポジトリを有効化する。

準備

epelが設定されていない場合

# yum install -y epel-release
# sed -i -E 's/^enabled=1$/enabled=0/' /etc/yum.repos.d/epel*

各種パッケージのインストール

先にkernel-headersとkernel-develをインストールしておく。
また、ビルド時にperlが要求される場合があるので合わせてインストールする。

# yum install -y kernel-headers-$(uname -r) kernel-devel-$(uname -r) perl

既にkernel-headersがインストールされており、かつkernelよりバージョンが新しいものとなっている場合はダウングレードを行う。

# yum downgrade kernel-headers-$(uname -r)

dkmsはEPELからインストールを行う。

# yum --enablerepo=epel install -y dkms

ixgbevf のインストール

VER_IXGBEVF=4.3.3

curl -L -o "ixgbevf-${VER_IXGBEVF}.tar.gz" "https://downloads.sourceforge.net/project/e1000/ixgbevf%20stable/${VER_IXGBEVF}/ixgbevf-${VER_IXGBEVF}.tar.gz?r=https%3A%2F%2Fsourceforge.net%2Fprojects%2Fe1000%2Ffiles%2Fixgbevf%2520stable%2F${VER_IXGBEVF}%2Fixgbevf-${VER_IXGBEVF}.tar.gz%2Fdownload%3Fuse_mirror%3Djaist&ts=`date +%s`"
tar -xzvf ixgbevf-${VER_IXGBEVF}.tar.gz
rm -f ixgbevf-${VER_IXGBEVF}.tar.gz
mv ixgbevf-${VER_IXGBEVF} /usr/src/

echo 'PACKAGE_NAME="ixgbevf"
PACKAGE_VERSION="'${VER_IXGBEVF}'"
CLEAN="cd src/; make clean"
MAKE="cd src/; make BUILD_KERNEL=${kernelver}"
BUILT_MODULE_LOCATION[0]="src/"
BUILT_MODULE_NAME[0]="ixgbevf"
DEST_MODULE_LOCATION[0]="/updates"
DEST_MODULE_NAME[0]="ixgbevf"
AUTOINSTALL="yes"' | tee /usr/src/ixgbevf-${VER_IXGBEVF}/dkms.conf

dkms add -m ixgbevf -v ${VER_IXGBEVF}
dkms build -m ixgbevf -v ${VER_IXGBEVF}
dkms install -m ixgbevf -v ${VER_IXGBEVF}

amzn-drivers (ena) のインストール

VER_AMZN_DRIVERS=1.5.0

curl -L -o "ena_linux_${VER_AMZN_DRIVERS}.tar.gz" "https://github.com/amzn/amzn-drivers/archive/ena_linux_${VER_AMZN_DRIVERS}.tar.gz"
tar -xzvf ena_linux_${VER_AMZN_DRIVERS}.tar.gz
rm -f ena_linux_${VER_AMZN_DRIVERS}.tar.gz
mv amzn-drivers-ena_linux_${VER_AMZN_DRIVERS} amzn-drivers-${VER_AMZN_DRIVERS}
mv amzn-drivers-${VER_AMZN_DRIVERS} /usr/src/

echo 'PACKAGE_NAME="ena"
PACKAGE_VERSION="'${VER_AMZN_DRIVERS}'"
CLEAN="make -C kernel/linux/ena clean"
MAKE="make -C kernel/linux/ena/ BUILD_KERNEL=${kernelver}"
BUILT_MODULE_LOCATION[0]="kernel/linux/ena"
BUILT_MODULE_NAME[0]="ena"
DEST_MODULE_LOCATION[0]="/updates"
DEST_MODULE_NAME[0]="ena"
AUTOINSTALL="yes"' | tee /usr/src/amzn-drivers-${VER_AMZN_DRIVERS}/dkms.conf

dkms add -m amzn-drivers -v ${VER_AMZN_DRIVERS}
dkms build -m amzn-drivers -v ${VER_AMZN_DRIVERS}
dkms install -m amzn-drivers -v ${VER_AMZN_DRIVERS}

カーネルモジュールの有効化

depmod
cp /boot/initramfs-$(uname -r).img /boot/initramfs-$(uname -r).img.bak
dracut -f

この後、インスタンスを停止する。

拡張ネットワーキングの有効化

属性確認

インスタンスに属性が付与されているかを確認する。
属性が付与されていない場合は無効化されている状態のため、設定を行う。

これらはコンソール上では現れない情報の為、CLIにて作業を行う。

SriovNetSupport の確認

# aws ec2 describe-instance-attribute --attribute sriovNetSupport --region ap-northeast-1 --instance-id <インスタンスID>
{
    "InstanceId": "<インスタンスID>",
    "SriovNetSupport": {}
}

SriovNetSupportが空の場合は無効な為、有効化する必要がある。

EnaSupport の確認

# aws ec2 describe-instances --query 'Reservations[].Instances[].EnaSupport' --region ap-northeast-1 --instance-ids <インスタンスID>
[
]

EnaSupportが空の場合は無効な為、有効化する必要がある。

SriovNetSupport の設定

# aws ec2 modify-instance-attribute --sriov-net-support simple --region ap-northeast-1 --instance-id <インスタンスID>

# aws ec2 describe-instance-attribute --attribute sriovNetSupport --region ap-northeast-1 --instance-id <インスタンスID>
{
    "InstanceId": "<インスタンスID>",
    "SriovNetSupport": {
        "Value": "simple"
    }
}

EnaSupport の有効化

# aws ec2 modify-instance-attribute --ena-support --region ap-northeast-1 --instance-id <インスタンスID>

# aws ec2 describe-instances --query 'Reservations[].Instances[].EnaSupport' --region ap-northeast-1 --instance-ids <インスタンスID>
[
    true
]

属性の設定後、インスタンスを対応タイプで起動し、ethtool -i eth0 で確認する。

続きを読む

mapを使った読みやすいterraform variablesの書き方

スクリーンショット 2018-01-19 0.51.28.png

ディレクトリ構成

以下のように、terraform/provider/aws/env/stgとしました。
変数ファイルであるvariables.tfもメイン処理をするec2.tfも同じディレクトリに置きます。

環境ごとに完全にファイルを分断するイメージで、tfstateファイルも環境ごとに別にします。(保管場所はS3です)
なお、module化はしません。

 %tree 
.
└── provider
    └── aws
        └── env
            └── stg
                ├── backend.tf
                ├── ec2.tf
                └── variables.tf

変数の書き方

Before

これまでterraformの変数ファイルはこんな感じで書いてました。

before_variables.tf
variable "ami" {
  default = "ami-4af5022c"
}

variable "instance_type" {
  default = "t2.micro"
}

variable "instance_key" {
  default = "id_rsa"
}

実際のEC2のパラメータはこれだけではありません。もっとたくさんある上に、AWSのリソースは他にもあるとなるとかなり長い変数ファイルとなります。

特徴としては、variableが毎回並んでしまって行数が増える。読みにくい見にくい探しづらい。

これを解決するのにmapを使いました。

A map value is a lookup table from string keys to string values. This is useful for selecting a value based on some other provided value.
A common use of maps is to create a table of machine images per region, as follows:

sample.tf
variable "images" {
  type    = "map"
  default = {
    "us-east-1" = "image-1234"
    "us-west-2" = "image-4567"
  }
}

After

一つのvariableに対してリソースの値をまとめます。
map型の宣言は省略可能みたいです。

after_variables.tf
variable "ec2_config" {
  type = "map" #省略化
  default = {
    ami = "ami-4af5022c" 
    instance_type = "t2.micro" 
    instance_key = "id_rsa" 
  }
}

こうすることで、リソースごとに値がまとまるので読みやすくなりました。
ではメイン処理をするec2.tfを見ていきましょう。

map型でのec2.tfの書き方

Before

これまでのリソース定義の書き方はこちらです。

before_ec2.tf
resource "aws_instance" "vtryo-web01" {
  ami              = "${var.ami}"
  instance_type    = "${var.instance_type}"
  instance_key     = "${var.instance_key}"

    tags {
    Name = "vtryo-web01"
  }
}

そしてafter版に対するvariableに対応するリソース定義の仕方です。
map型で変数を格納したので、lookup関数を使って値を参照させます。

beforeのときよりやや長くなったように見えますが、ルールを覚えれば(後述)そこまで複雑ではない上、柔軟性は良くなっています。

after_ec2.tf
resource "aws_instance" "vtryo-web" {
  ami              = "${lookup(var.ec2_config, "ami")}" 
  instance_type    = "${lookup(var.ec2_config, "instance_type")}" 
  key_name         = "${lookup(var.ec2_config, "instance_key")}" 

  tags {
    Name = "vtryo-${format("web%02d", count.index + 1)}" 
  }

注意点

resource定義内のami, instance_type, key_nameはterraform側で名前が決まっています。variables.tf内の変数名は任意ですが、こちらは自由には決められないので注意しましょう。
aws_instance

lookup

上記の書き方は、lookup関数でKeyを直接指定する方法を取っています。
こちらを参考にしています。
Terraformのoutputでmapを利用する方法

たとえば以下であれば
ami = "${lookup(var.ec2_config, "ami")}"

variables.tf内のec2_configamiの値を参照する」という意味になります。

format

最終行の"vtryo-${format("web%02d", count.index + 1)}"についてですが、変数格納後の値はvtryo-web01になります。
ちなみに"web%02d"を、"web%01d"にするとvtryo-web1になってしまうので注意です。

terraform init

さて実行です。

% terraform init

Initializing provider plugins...
- Checking for available provider plugins on https://releases.hashicorp.com...
- Downloading plugin for provider "aws" (1.7.0)...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.aws: version = "~> 1.7"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

terraform plan

 % terraform plan
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.vtryo-web
      id:                           <computed>
      ami:                          "ami-4af5022c"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     "id_rsa"
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "vtryo-web01"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

変数が格納されました!

ちょっと応用

さきほどのec2.tfでNameには"vtryo-${format("web%02d", count.index + 1)}"を書きました。
この出力結果はvtryo-web01です。
これをstg-vtryo-web01のように、環境を先頭に入れる方法を書いておきます。

variable “env”

variables.tfにvariable "env { }"を書きます。

variables.tf
variable "env" { }

variable "ec2_config" {
  default = {
    ami           = "ami-4af5022c"
    instance_type = "t2.micro"
    instance_key  = "id_rsa"
  }
}

{ }の中にdefaultを入れてもよいです。その場合は以下のように書きます。

variables.tf
variable "env" { 
  default = "text message..."
 }

${var.env}

一方でec2.tfには以下を書きます。

resource "aws_instance" "vtryo-web" {
  ami                      = "${lookup(var.ec2_config, "ami")}"
  instance_type            = "${lookup(var.ec2_config, "instance_type")}"
  key_name                 = "${lookup(var.ec2_config, "instance_key")}"
  tags {
    Name = "${var.env}-vtryo-${format("web%02d", count.index + 1)}"
  }
}

${var.env}をつけることで、terraform planしたときに入力を求められます。
入力した内容が、${var.env}に格納されるという仕組みです。

terraform plan

Enter a valueを求められるので今回はstgにしました。
すると、stgという文字列が格納されます。

% terraform plan
var.env
  Enter a value: stg

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.


------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  + aws_instance.vtryo-web
      id:                           <computed>
      ami:                          "ami-4af5022c"
      associate_public_ip_address:  <computed>
      availability_zone:            <computed>
      ebs_block_device.#:           <computed>
      ephemeral_block_device.#:     <computed>
      instance_state:               <computed>
      instance_type:                "t2.micro"
      ipv6_address_count:           <computed>
      ipv6_addresses.#:             <computed>
      key_name:                     "id_rsa"
      network_interface.#:          <computed>
      network_interface_id:         <computed>
      placement_group:              <computed>
      primary_network_interface_id: <computed>
      private_dns:                  <computed>
      private_ip:                   <computed>
      public_dns:                   <computed>
      public_ip:                    <computed>
      root_block_device.#:          <computed>
      security_groups.#:            <computed>
      source_dest_check:            "true"
      subnet_id:                    <computed>
      tags.%:                       "1"
      tags.Name:                    "stg-vtryo-web01"
      tenancy:                      <computed>
      volume_tags.%:                <computed>
      vpc_security_group_ids.#:     <computed>


Plan: 1 to add, 0 to change, 0 to destroy.

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

ちなみにterraform plan -var "env=stg"とすることで入力なしで格納することが可能です。

今回の補足というか余談

「terraformのbest-practice、わかりづらくない?」

開発者が推奨している構成だとはわかっていますが、素直にそんな感想がありました。
terraform best-practice
(もちろんQiitaにかかれている記事も読んでいます
Terraform Best Practices in 2017

ファイルの中に何度も宣言をしないといけないようですし、どうにも読みづらい印象があります。
また、「Environmentで環境を分ける」構成になっていますが、公式曰く

Workspaces can be used to manage small differences between development, staging, and production, but they should not be treated as the only isolation mechanism.

とあります。我らがGoogle翻訳を使うとこんなことを言ってます。

ワークスペースは、開発、ステージング、およびプロダクションの小さな違いを管理するために使用できますが、唯一の分離メカニズムとして扱うべきではありません。

初見で解読するには骨がいる内容です。何度もterraformを使い込んだらわかるんでしょうか。
ま、単純に私の技術力が追いついていない可能性の方が高いですが(爆)

とはいえ誰でも彼でもterraformの技術力が高いわけではないので、より読みやすくメンテナンスしやすい書き方をしても良いと思っています。

terraformは一歩間違えるとインフラそのものが削除されることがあるので、それを踏まえて確実な方が良いだろうというきもちです。

今回はQiitaの記事にだいぶ助けられたこともあったので、Qiitaに書くことにしました。

シンプルに書きたい欲

terraformにはmodule化が出来る機能があって、それがやや難易度を上げているように思います。

変数をmodule化できることでメリットもありますが、利用ができなければ意味がない。terraformにはmoduleのレジストリがありますが、仕組みを理解しないとやっぱり使うのは大変です。

学習コストばかりかかるくらいなら、いっそ使わずにシンプルにコードを書いて行こうということでした。

best-practiceに固執しない

重要なのは可読性ととっつきやすさだと思ったので、あえて固執せずにディレクトリを構成し、terraformを見やすく書く方法を考えました。
誰かの参考になれば幸いです。

参考

Terraformのoutputでmapを利用する方法 – Qiita
Terraform Best Practices in 2017 – Qiita
TerraformでのAWS環境構築の設定を分ける – Qiita
terraform
さらに一歩楽しむterraform. moduleでIAM UserとPolicy管理を簡素化しよう
本記事はこちらのクローンです。

続きを読む

Cronから実行するEC2スナップショットスクリプト

実行条件

  • awscliがインストールされている
  • IAMロールなどでEC2周りの権限を解放しておく
#!/bin/sh

# 取得したインスタンスのidを並べる
INSTANCE_ID=(i-xxxxx1 i-xxxxx2 i-xxxxx3)

SHELLDIR=`dirname ${0}`
SHELLDIR=`cd ${SHELLDIR}; pwd`
SHELLNAME=`basename $0`

LOG_DIR="/var/log"
LOG_SAVE_PERIOD=14
LOG_FILE="${LOG_DIR}/${SHELLNAME}.log"
echo $LOG_FILE

REGION=ca-central-1
SNAPSHOTS_PERIOD=2

AWS="/usr/bin/aws --region ${REGION}"


rotate_log() {
    (( cnt=${LOG_SAVE_PERIOD} ))
    while (( cnt > 0 ))
    do
        logfile1=${LOG_FILE}.$cnt
        (( cnt=cnt-1 ))
        logfile2=${LOG_FILE}.$cnt
        if [ -f $logfile2 ]; then
            mv $logfile2 $logfile1
        fi
    done

    if [ -f $LOG_FILE ]; then
        mv ${LOG_FILE} ${LOG_FILE}.1
    fi
    touch $LOG_FILE
}

print_msg() {
    echo "`date '+%Y/%m/%d %H:%M:%S'` $1" | tee -a ${LOG_FILE}
}

create_snapshot() {
    for ID in `echo $@`
    do
        print_msg "Create snapshot Start"
        VOL_ID=`${AWS} ec2 describe-instances --instance-ids ${ID} --output text | grep EBS | awk '{print $5}'`
        if [ -z ${VOL_ID} ] ; then
            echo ${VOL_ID}
            print_msg "ERR:ec2-describe-instances"
            logger -f ${LOG_FILE}
            exit 1
        fi
        print_msg "ec2-describe-instances Success : ${VOL_ID}"
        ${AWS} ec2 create-snapshot --volume-id ${VOL_ID} --description "Created by SYSTEMBK(${ID}) from ${VOL_ID}" >> ${LOG_FILE} 2>&1
        if [ $? != 0 ] ; then
            print_msg "ERR:${SHELLDIR}/${SHELLNAME} ec2-create-snapshot"
            logger -f ${LOG_FILE}
            exit 1
        fi
        print_msg "Create snapshot End"
    done
}

delete_old_snapshot() {
    for ID in `echo $@`
    do
        VOL_ID=`${AWS} ec2 describe-instances --instance-ids ${ID} --output text | grep EBS | awk '{print $5}'`
        print_msg "Delete old snapshot Start"
        SNAPSHOTS=`${AWS} ec2 describe-snapshots --output text | grep ${VOL_ID} | grep "Created by SYSTEMBK" | wc -l`
        while [ ${SNAPSHOTS} -gt ${SNAPSHOTS_PERIOD} ]
        do
            ${AWS} ec2 delete-snapshot --snapshot-id `${AWS} ec2 describe-snapshots --output text | grep ${VOL_ID} | grep "Created by SYSTEMBK" | sort -k 11,11 | awk 'NR==1 {print $10}'` >> ${LOG_FILE} 2>&1
            if [ $? != 0 ] ; then
                print_msg "ERR:${SHELLDIR}/${SHELLNAME} ec2-delete-snapshot"
                logger -f ${LOG_FILE}
                exit 1
            fi
            SNAPSHOTS=`${AWS} ec2 describe-snapshots | grep ${VOL_ID} | grep "Created by SYSTEMBK" | wc -l`
        done
        print_msg "Delete old snapshot End"
    done
}

rotate_log

print_msg "INF:$SHELLDIR/${SHELLNAME} START"
create_snapshot ${INSTANCE_ID[@]}
delete_old_snapshot ${INSTANCE_ID[@]}
print_msg "INF:$SHELLDIR/${SHELLNAME} END"

exit 0

続きを読む

AWS Fargate

AWS ECS Fargate とは Docker コンテナを動かすための仕組み。Docker コンテナを起動して外から URL でアクセス出来るようになるまでやってみた。

  • コンテナは 8001 (WebSocket) と 8002 (HTTP) の2つのポートを受け付ける。
  • 外から両方のポートを使いたい。

AWS ECS には Clusters, Task Definitions, Repository の3つの設定項目がある

  • Clusters:

    • 複数の Service をまとめて動作させる箱。
    • Service:
      • 複数の Task をまとめて動作させる箱。
  • Task Definitions:
    • Task とは、Docker コンテナの動作単位。使う Docker image は AWS の Repositories から取ってきても良いし、他の Docker repository を使っても良い。
  • Repositories:
    • AWS 用の Docker Repository. Docker image を保存する場所。

作業手順は次のようになる。

  1. Repositories に Docker image を登録
  2. Task Definition に Docker repository を指定
  3. Cluster を作成
  4. Application Load Balancer を作成
  5. Cluster 内に Service と Task を作成
  6. Security Group の設定

AWS Fargate First run

  • https://qiita.com/riywo/items/b223bdad2b3ae3bebf55 の通りに First runチュートリアル試した。
  • 次へ次へと押すだけ
  • Unable to assume the service linked role. Please verify that the ECS service linked role exists というエラーが出る。
  • 二回目同じ事をやると何故か問題なく完了した。
  • Cluster : default > sample-app-service
  • AWS Console > Elastic Container Service > Clusters : default > Tasks > Task をクリック
  • IP が割り当てられている事を確認。

AWS Cluster を削除する時の注意点

  • The vpc ‘vpc-xxx’ has dependencies and cannot be deleted. のエラーが出て消せなかった。
  • EC2 security group が参照していたので消したら OK。

AWS ECS Repositories に Docker image を登録

AWS FargateでFlaskを動かしてみる を参考に作業。

  • Console で Region N. Virginia を選択
  • AWS Console > Elastic Container Service (ECS) > Repositories
  • Create repository
  • Repository name: hoge-websocket-server
  • Repository URL: xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server

画面に表示されるコマンドを実行してログイン

$ aws ecr get-login --no-include-email --region us-east-1
docker login -u AWS -p eyJwYXlsb2...
$ docker login -u AWS -p eyJwYXlsb2... (表示された長いコマンドをそのまま実行)

先程出来た Docker image にタグを追加

$ docker tag hoge-websocket-server:latest xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest

Docker image を Registory に追加

$ docker push xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest
file integrity checksum failed for "usr/lib/x86_64-linux-gnu/libjpeg.a"

Docker image が大きいと上のようなエラーが出た。小さい node のイメージを使ってイメージをダイエットすると成功。

Task Definition に Docker repository を指定

ここで Docker image の場所や使いたいリソースを定義する。

  • AWS Console > ECS > Task Definitions > Create a new definition

    • 1: Select launch type compatibility

      • FARGATE
    • 2: Configure task and container definitions
      • Task Definition Name: hoge-websocket-server
      • Task memory (GB): 0.5GB
      • Task CPU (vCPU): 0.25 vCPU
      • Add container
        • Container name: hoge-websocket-server
        • Image: xxx.dkr.ecr.us-east-1.amazonaws.com/hoge-websocket-server:latest
        • Port mappings
          • 8001
          • 8002
      • Create

Cluster を作成

  • AWS Console > ECS > Clusters > Create Cluster

    • 1: Select cluster template

      • Networking only
    • 2: Configure cluster
      • Culster name: hoge-cluster
      • Create VPC: チェック (意味はよくわからない)
        • 作成されるネットワーク情報に目を通しておく。
        • デフォルトで2つのサブネットが定義されるようだが、そのままにしておいた。
    • View Cluster

これで Cluster 本体の他、色々な Cluster Resources が作成される。これらの大事な情報を後から参照する方法が分からなかったので必ずメモっておく。

Load Balancer の作成

このままだと起動停止のたびに IP アドレスが変わってしまうので、Application Load Balanceer (ALB) を使ってドメイン名を割り当てる。

  • AWS Console > EC2 > Load Balancers
  • Application Load Balancer > Create
  • 1: Configure Load Balancer
    • Name: hoge
    • Scheme: internet-facing
    • IP address type: ipv4
    • Listeners
      • HTTP: 8001 (Websocket も HTTP で良い)
      • HTTP: 8002 (HTTP 用の Listener は ECS Service 設定時に出来るのでここで作らなくて良い)
    • Availability Zones
      • VPC (Create Cluster で設定した VPC)
      • 下の Subnet は2つとも選択する
    • Tag
      • project : hoge
  • 2: Configure Security Settings
    • 設定なし
  • 3: Security Groups
    • Create a new security group
    • Security group name: hoge-security-group
    • 使いたいポート番号を設定
    • あとで AWS Console > ECS > Clusters > hoge-cluster > Tasks > Task : a44f… から編集出来る。
  • 4: Configure Routing
    • ヘルスチェックの設定らしい
    • Name: hoge-8002
    • Port: 8002
  • 5: Register Targets
    • 何も設定しない

Cluster 内に Service を作成

  • AWS Console > ECS > Clusters > hoge-cluster > Services > Create

    • 1: Configure service

      • Launch type: FARGATE
      • Task Definition: (作成した Task)
      • Service name: hoge-service
      • Number of taksk: 1
    • 2: Configure network
      • Cluster VPC: (Create Cluster で設定した VPC)
      • Subnets: (Create Cluster で設定した subnet)
      • Security groups > Edit
        • Assigned security groups: Select existing security group
        • Load Balancer で作った hoge-security-group を選択
      • Load balancing
        • Load balancer type: Application Load Balancer
        • Load balancer name: hoge
      • Container to load balance
        • ここで設定すると、コンテナの IP が変わっても自動的に一つのポートだけ Load Balancer に割り当てられる。
        • hoge-websocket-server:8002 > Add to load balancer
          • Listner port: 8002:HTTP
          • Path pattern: /*
          • Evaluation order: 1
          • Health check path: /
        • hoge-websocket-server:8002
    • 3: Set Auto Scaling
      • デフォルトのまま
    • 4: Review
      • 特に VPC Id と Subnets に気をつける。間違うとややこしい事になる。
    • Create Service

コンテナに割り当てられた IP アドレスの確認

  • AWS Console > ECS > Clusters > hoge-cluster > Tasks > Task : a44f… を選択
  • Private IP と Public IP が表示される。

もう一つのポート Port 8001 の設定

ECS Service の作成時に Load Balancer を割り当てると、IP が変化しても Load Balancer が勝手に面倒を見てくれる。ただしポート番号は一つしか設定出来ない!!!!この例のように2つ目のポートを Load Balancer に見せるには、手動で IP を指定した Target Groups を作る必要があった。なので、更新の際にはこの IP をわざわざ再入力する必要がある。

  • AWS Console > EC2 > LOAD BALANCING Target Groups

    • Target group name: hoge-8001
    • Protocol: HTTP
    • Port: 8001
    • Target type: ip
    • VPC: Cluster の VPC
    • Create
  • AWS Console > EC2 > LOAD BALANCING Target Groups > hoge-8001
    • Targets > Edit
    • Task の IP を登録
  • AWS Console > EC2 > LOAD BALANCING Load Balancers > hoge > Listeners
    • HTTP:8001 > View/edit rules
    • THEN に hoge-8001 を登録

Docker image の更新

  • AWS Console > ECS > Clusters > hoge-cluster > hoge-service > Update

    • Force new deployment: Check
    • 次へ次へ。。。
  • しばらく待つと新しいコンテナが起動して古いコンテナが停止する。
  • IP アドレスが変わっているので Load Balancer の Target Group も更新する。

Task 削除

Task は service が管理するので、直接止められないらしい。Service の Update で Number of tasks を 0 にすると止められた。

  • AWS Console > ECS > Clusters > hoge-cluster > hoge-service > Update
  • Number of tasks: 0
  • 次へ次へ

やりたかった事は node 一つの単純なサーバをテストしたいだけだったんだけど、随分大げさな構成になってしまった。

続きを読む

SSL証明書の有効期限監視をLambda+CloudWatchで実装する

SSL証明書の有効期限をLambdaで取得し、CloudWatchにカスタムメトリクスを送り、CloudWatch Alarmで通知する

AWS上のEC2インスタンスや各種サービスの監視を行う際、CloudWatch Alarmを活用しているケースは多いと思います。
このような場面を想定し、Lambdaで毎日SSL証明書の有効期限チェックを行い、CloudWatchに各ドメインの残日数を送り、その日数をターゲットとしたCloudWatch Alarmを設定することができるようにしたいと思います。

環境

ランタイム:node.js 6.10
開発環境:AWS Cloud9

監視対象の管理

監視対象のドメインは適宜増減することが想定されます。
これらを簡単に管理するために、今回はAWS Systems Manager のパラメータストアにて管理を行います。

パラメータストアに、下記のようなパラメータを登録します。

項目 内容
キー名 /monitor/certificate/domains
タイプ 文字列
[ “hogehoge.com”, “example.com” ]

上記のようにチェックを行うドメインをJSON配列形式で設置します。

Lambdaコード

下記ので実装できます。
※child-process-promise を使用していますが、モジュールのインストールを別途行わなくてもよい
 child_processを使っても良いと思います。(全てをPromiseで統一したかったのであえてchild-process-promiseを使っています)

index.js
const AWS = require('aws-sdk');
var SSM;
var CW;

exports.handler = (event, context, callback) => {
  if(SSM===undefined){
    SSM = new AWS.SSM({region: 'ap-northeast-1'});
  }
  if(CW===undefined){
    CW = new AWS.CloudWatch({region: 'ap-northeast-1'});
  }

  Promise.resolve()
  .then(()=>{
    return new Promise((resolve)=>{
      let ssm_params = {
        Name: "/monitor/certificate/domains"
      };
      let ssm_parameter = SSM.getParameter(ssm_params).promise();
      ssm_parameter.then((data)=>{
        resolve(JSON.parse(data.Parameter.Value));
      });
    });
  })
  .then((domains)=>{
    return new Promise((resolve)=>{
      let check_expire_list = [];
      domains.forEach((domain)=>{
        check_expire_list.push(checkExpire(domain));
      });
      Promise.all(check_expire_list)
      .then((result_valid)=>{
        resolve(result_valid);
      });
    });
  })
  .then((valid_list)=>{
    return new Promise((resolve)=>{
      let metric_list = [];
      valid_list.forEach((result)=>{
        var params = {
          MetricData: [
            {
              MetricName: 'valid_days',
              Dimensions: [
                {
                  Name: 'domain',
                  Value: result.domain
                }
              ],
              Unit: 'None',
              Value: result.valid_days
            }
          ],
          Namespace: 'SSLCertificate'
        };
        metric_list.push(CW.putMetricData(params).promise());
      });
      Promise.all(metric_list)
      .then((data)=>{
        resolve();
      });
    });
  })
  .then(()=>{
    callback();
  });
};

var checkExpire = (domain)=>{
  return new Promise((resolve)=>{
    let exec = require('child-process-promise').exec;
    let cmd = "openssl s_client -connect " + domain + ":443 -servername " + domain + " < /dev/null 2> /dev/null | openssl x509 -text | grep Not | sed -e 's/^  *//g' | sed -e 's/Not.*: //g'";
    let now = new Date();
    exec(cmd)
    .then((result)=>{
      let not = result.stdout.split("n");
      let expire_date = new Date(not[1]);
      let valid_days = Math.floor((expire_date - now) / 1000 / 3600 / 24);
      console.log(domain+" の残り日数は "+valid_days + " です");
      resolve({'domain': domain,'valid_days': valid_days});
    });
  });
};

SAM Template(CloudFormation)

上記のコードをデプロイするためのSAMテンプレートとして、下記のテンプレートでデプロイすることで、CloudWatchEventsにて毎日実行する設定を同時に行われます。

template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: 'AWS::Serverless-2016-10-31'
Description: SSL certificate monitoring function.
Resources:
  monitorCertExpire:
    Type: 'AWS::Serverless::Function'
    Properties:
      Handler: monitorCertExpire/index.handler
      Runtime: nodejs6.10
      Description: ''
      MemorySize: 256
      Timeout: 300
      Events:
        CheckExpire:
          Type: Schedule
          Properties:
            Schedule: rate(1 day)
            Input: "{}"

CloudWatch

上記をデプロイすることで、CloudWatchメトリクスに 名前空間:SSLCertificate メトリック:valid_days で有効期限切れまでの残り日数が毎日送信されます。

このメトリックデータを使用し、間隔:1日 統計:最小 期間:1中1のデータポイント にて、アラートさせたい日数をしきい値としてアラームを設定することでこれまで通り他の監視と同様に扱うことができます。

参考にさせていただきました

https://qiita.com/zwirky/items/25b1a66dac534f67ca03
https://blog.manabusakai.com/2016/07/lambda-cert-expire/

続きを読む