のぴぴのメモ

自分用のLinuxとかの技術メモ

Amazon S3のバケットのバージョニング設定とオブジェクトのバージョンの遷移を確認してみた

はじめに

バケットのバージョニング設定により、オブジェクトのバージョンがどのように遷移するのかがいまいちわかっていなかったので、実際に動かして整理してみました。

まとめ

  • バケットのバージョニング未設定の場合、オブジェクトのVersionIdはnullになる
  • バージョニングを有効化した後は
    • 既存オブジェクトのVersionIdはnullのまま
    • バージョニング有効化後にPUTしたオブジェクトには、VersionIdに一意のIDが付与
  • バージョニングをサスペンドした後は
    • サスペンド後にPUTしたオブジェクトのVersionIdは、nullになる
    • 既存でVersionIdがnullのオブジェクトが存在する場合、そのオブジェクトが更新される
    • 既存にVersionIdがnullのオブジェクトが存在しない場合は、新規にVersionIdがnullのオブジェクトが作成される

バージョニング有効化前から存在するオブジェクトのバージョン遷移

f:id:nopipi:20200601164859p:plain:w600

バージョニング有効化後に新規にPUTしたオブジェクトのバージョン遷移

f:id:nopipi:20200601165631p:plain:w600

確認手順と結果

事前準備

#プロファイル指定
PROFILE=default #他のプロファイルを利用する場合はここを変更

#検証用バケットの設定用パラメータ
BUCKET_NAME="versioning-test-bucket-$( od -vAn -to1 </dev/urandom  | tr -d " " | fold -w 10 | head -n 1)"
REGION=$(aws --profile ${PROFILE} configure get region)

#テストデータ生成
dd if=/dev/urandom of=test-data-1.dat bs=4096 count=1
dd if=/dev/urandom of=test-data-2.dat bs=4096 count=2
dd if=/dev/urandom of=test-data-3.dat bs=4096 count=3

ls -l test*

バケットの作成

#バケット作成
aws --profile ${PROFILE} \
    s3api create-bucket \
        --bucket ${BUCKET_NAME} \
        --create-bucket-configuration LocationConstraint=${REGION};

#バージョン設定の確認
#未設定なので何もレスポンスが無いはずです。
aws --profile ${PROFILE} s3api get-bucket-versioning --bucket "${BUCKET_NAME}"

#データが空であることを確認
aws --profile ${PROFILE} s3 ls "s3://${BUCKET_NAME}"

バージョニング未設定時のオブジェクト確認

オブジェクトをPUTする
#バージョニングを設定していない状態でS3にデータを確認
aws --profile ${PROFILE} s3 cp test-data-1.dat s3://${BUCKET_NAME}/userdata01.dat
aws --profile ${PROFILE} s3 cp test-data-1.dat s3://${BUCKET_NAME}/userdata02.dat
状態を確認する

(1) オブジェクトの一覧表示
PUTした2つのオブジェクトが表示されます。

aws --profile ${PROFILE} s3 ls "s3://${BUCKET_NAME}"
2020-05-31 18:49:51       4096 userdata01.dat
2020-05-31 18:49:52       4096 userdata02.dat

(2) オブジェクトのバージョン確認
バケットに対してバージョニングが未設定の状態でオブジェクトをPUTしているため、userdata01.dat、userdata02.datともVersionIdは"null"になります。

Object 投入タイミング 更新時間 VersionID IsLatest
userdata01.dat Versioning設定前 09:49:51 null true
userdata02.dat Versioning設定前 09:49:52 null true
aws --profile ${PROFILE} s3api list-object-versions --bucket ${BUCKET_NAME}
{
    "Versions": [
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata01.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-05-31T09:49:51.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-05-31T09:49:52.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        }
    ]
}

バージョニング有効化

バケットに対してバージョニングを有効にします。

バージョニング有効化設定
aws --profile ${PROFILE} s3api \
    put-bucket-versioning \
        --bucket "${BUCKET_NAME}" --versioning-configuration Status=Enabled;
設定の確認
aws --profile ${PROFILE} s3api get-bucket-versioning --bucket "${BUCKET_NAME}"
{
    "Status": "Enabled"
}

バージョニング有効後にPUTしたオブジェクト確認

  • 既存のuserdata02.datに新たなファイルをPUTします
  • 新規にuserdata03.datをPUTします
データの投入
aws --profile ${PROFILE} s3 cp test-data-2.dat s3://${BUCKET_NAME}/userdata02.dat
aws --profile ${PROFILE} s3 cp test-data-2.dat s3://${BUCKET_NAME}/userdata03.dat
状態を確認する

(1) オブジェクトの一覧表示
userdata02.datが更新され、新規にuserdata03.datが追加され、合計3つのオブジェクトが表示されます。

aws --profile ${PROFILE} s3 ls "s3://${BUCKET_NAME}"
2020-05-31 18:49:51       4096 userdata01.dat
2020-06-01 14:52:14       8192 userdata02.dat
2020-06-01 14:52:16       8192 userdata03.dat

(2) オブジェクトのバージョン確認

  • バージョニング設定前のオブジェクトは、VersionIDがnullのまま
  • userdata02.datは、既存のオブジェクトIsLatestがfalseに変更
Object 投入タイミング 更新時間 VersionID IsLatest メモ
userdata01.dat Versioning設定前 09:49:51 null true 更新なし
userdata02.dat Versioning設定後(1) 05:52:14 0qDa.... true 新規追加
userdata02.dat Versioning設定前 09:49:52 null false LsLatestがfalseに変更
userdata03.dat Versioning設定後(1) 05:52:16 TX.M.... true 新規追加
aws --profile ${PROFILE} s3api list-object-versions --bucket ${BUCKET_NAME}
{
    "Versions": [
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata01.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-05-31T09:49:51.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "0qDa2pi27gEqqGOdQhK5UVJVcBj_drRr",
            "IsLatest": true,
            "LastModified": "2020-06-01T05:52:14.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "null",
            "IsLatest": false,
            "LastModified": "2020-05-31T09:49:52.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "TX.MGfR96qMJ4mrIFBT61d8.Bs5McjzB",
            "IsLatest": true,
            "LastModified": "2020-06-01T05:52:16.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        }
    ]
}

もう一度オブジェクトをPUTし確認

データを投入
aws --profile ${PROFILE} s3 cp test-data-3.dat s3://${BUCKET_NAME}/userdata02.dat
aws --profile ${PROFILE} s3 cp test-data-3.dat s3://${BUCKET_NAME}/userdata03.dat
状態を確認する

(1) オブジェクトの一覧表示
userdata02.datとuserdata03.datが更新され、同じく3つのオブジェクトが表示されます。

aws --profile ${PROFILE} s3 ls "s3://${BUCKET_NAME}"
2020-05-31 18:49:51       4096 userdata01.dat
2020-06-01 15:27:39      12288 userdata02.dat
2020-06-01 15:27:41      12288 userdata03.dat

(2) オブジェクトのバージョン確認

  • userdata02.datとuserdata03.datで、IsLatestがtrueのオブジェクトが追加され、既存オブジェクトはLsLatestがfalseに変更
Object 投入タイミング 更新時間 VersionID IsLatest メモ
userdata01.dat Versioning設定前 09:49:51 null true 更新なし
userdata02.dat Versioning設定後(2) 06:27:39 Um.k.... true 新規追加
userdata02.dat Versioning設定後(1) 05:52:14 0qDa.... false LsLatestがfalseに変更
userdata02.dat Versioning設定前 09:49:52 null false 更新なし
userdata03.dat Versioning設定後(2) 06:27:41 WZ8d.... true 新規追加
userdata03.dat Versioning設定後(1) 05:52:16 TX.M.... false LsLatestがfalseに変更
aws --profile ${PROFILE} s3api list-object-versions --bucket ${BUCKET_NAME}
{
    "Versions": [
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata01.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-05-31T09:49:51.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "Um.kAFeAkaayk3botsDRJmyvLQp6dUZr",
            "IsLatest": true,
            "LastModified": "2020-06-01T06:27:39.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "0qDa2pi27gEqqGOdQhK5UVJVcBj_drRr",
            "IsLatest": false,
            "LastModified": "2020-06-01T05:52:14.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "null",
            "IsLatest": false,
            "LastModified": "2020-05-31T09:49:52.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "WZ8dIE4sY1OzI1h6lCzESy_.W33I4xtt",
            "IsLatest": true,
            "LastModified": "2020-06-01T06:27:41.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "TX.MGfR96qMJ4mrIFBT61d8.Bs5McjzB",
            "IsLatest": false,
            "LastModified": "2020-06-01T05:52:16.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        }
    ]
}

バージョニング をsuspend

バケットのバージョニングをsuspendします。

バージョニングのsuspend設定
aws --profile ${PROFILE} s3api \
    put-bucket-versioning \
        --bucket "${BUCKET_NAME}" --versioning-configuration Status=Suspended;
設定の確認
aws --profile ${PROFILE} s3api get-bucket-versioning --bucket "${BUCKET_NAME}"
{
    "Status": "Suspended"
}

Suspended状態でオブジェクトをPUTし確認

データを投入
aws --profile ${PROFILE} s3 cp test-data-3.dat s3://${BUCKET_NAME}/userdata02.dat
aws --profile ${PROFILE} s3 cp test-data-3.dat s3://${BUCKET_NAME}/userdata03.dat
状態を確認する

(1) オブジェクトの一覧表示
userdata02.datとuserdata03.datが更新され、同じく3つのオブジェクトが表示されます。

aws --profile ${PROFILE} s3 ls "s3://${BUCKET_NAME}"
2020-05-31 18:49:51       4096 userdata01.dat
2020-06-01 15:46:08      12288 userdata02.dat
2020-06-01 15:46:10      12288 userdata03.dat

(2) オブジェクトのバージョン確認

Object 投入タイミング 更新時間 VersionID IsLatest メモ
userdata01.dat Versioning設定前 09:49:51 null true 更新なし
userdata02.dat Suspended状態 06:46:08 null true 既存のnullバージョンのものを更新
userdata02.dat Versioning設定後(2) 06:27:39 Um.k.... false LsLatestがfalseに変更
userdata02.dat Versioning設定後(1) 05:52:14 0qDa.... false 更新なし
userdata02.dat Versioning設定前 - - - 上書き更新され消滅
userdata03.dat Suspended状態 T06:46:10 null true 新規追加
userdata03.dat Versioning設定後(2) 06:27:41 WZ8d.... false LsLatestがfalseに変更
userdata03.dat Versioning設定後(1) 05:52:16 TX.M.... false 更新なし
aws --profile ${PROFILE} s3api list-object-versions --bucket ${BUCKET_NAME}
{
    "Versions": [
        {
            "ETag": "\"dadc49ab6df3592cca0ce5edd9e03886\"",
            "Size": 4096,
            "StorageClass": "STANDARD",
            "Key": "userdata01.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-05-31T09:49:51.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-06-01T06:46:08.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "Um.kAFeAkaayk3botsDRJmyvLQp6dUZr",
            "IsLatest": false,
            "LastModified": "2020-06-01T06:27:39.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata02.dat",
            "VersionId": "0qDa2pi27gEqqGOdQhK5UVJVcBj_drRr",
            "IsLatest": false,
            "LastModified": "2020-06-01T05:52:14.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "null",
            "IsLatest": true,
            "LastModified": "2020-06-01T06:46:10.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"c3cfd43243650a7c2095f744d1ad796e\"",
            "Size": 12288,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "WZ8dIE4sY1OzI1h6lCzESy_.W33I4xtt",
            "IsLatest": false,
            "LastModified": "2020-06-01T06:27:41.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        },
        {
            "ETag": "\"a41d74143fff00d45a6b9997e400a32a\"",
            "Size": 8192,
            "StorageClass": "STANDARD",
            "Key": "userdata03.dat",
            "VersionId": "TX.MGfR96qMJ4mrIFBT61d8.Bs5McjzB",
            "IsLatest": false,
            "LastModified": "2020-06-01T05:52:16.000Z",
            "Owner": {
                "DisplayName": "nobuyuf",
                "ID": "135708a85354d2fa2c74a4f52b3a2256c9ce912005f4677f36fd73af25be2793"
            }
        }
    ]
}

検証データのクリーンナップ

オブジェクトの削除
# 削除マーカーがついているオブジェクトの削除
aws --profile ${PROFILE} --output text \
      s3api list-object-versions \
        --bucket ${BUCKET_NAME} \
    --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' | while read key versionid
do
    aws --profile ${PROFILE} \
        s3api delete-object \
            --bucket ${BUCKET_NAME} \
            --key ${key} \
            --version-id ${versionid}
done 

# それ以外のオブジェクトの削除
aws --profile ${PROFILE} --output text \
      s3api list-object-versions \
        --bucket ${BUCKET_NAME} \
    --query 'Versions[].{Key:Key,VersionId:VersionId}' | while read key versionid
do
    aws --profile ${PROFILE} \
        s3api delete-object \
            --bucket ${BUCKET_NAME} \
            --key ${key} \
            --version-id ${versionid}
done 
バケットの削除
aws --profile ${PROFILE} \
    s3api delete-bucket \
        --bucket "${BUCKET_NAME}"

バージョニングが有効なS3バケットをAWS CLIで空にする手順(オブジェクト1000個以下)

S3のバケットを削除する場合、まずバケットを空にする必要があります。ここではAWS CLIを利用しバケットを空にし、その後にバケットを削除する手順を記載します。

具体的には list-object-versionsでオブジェクトをリストアップし、delete-objectでオブジェクトを一つづつ削除します。なお記載する例は、バケットの中のオブジェクトが1000個未満の場合に利用できます。(list-object-versionsの1回の実行でリストされるオブジェクトの最大個数がデフォルトでは1000のため*1 )

ちなみにaws s3 rm s3://バケット名 --recursiveaws s3 rb s3://バケット名 --forceで最初試しましたが、バージョニングが有効な場合これらのコマンドでは削除マークが付くだけですし、バージョニングされた古いオブジェクトは何もされず、オブジェクトが消えないため、このようなやり方をする必要がありました。(ユーザーガイド*2にもバージョニングが有効で無い場合に限ってと注釈がありますね )

削除手順

前提

  • バケットの中のオブジェクトが1000個未満の場合(古いバージョン含めた合計)
  • bash上で、AWS CLIが実行可能であること
  • AWS CLIのプロファイル設定は実施済みであること

変数設定

PROFILE="<AWS CLIのプロファイルを指定する。デフォルトの場合はdefault>"
BUCKET_NAME="<対象のバケット名を設定する>"

バケットを空にする

# 削除マーカーがついているオブジェクトの削除
aws --profile ${PROFILE} --output text \
      s3api list-object-versions \
        --bucket ${BUCKET_NAME} \
    --query 'DeleteMarkers[].{Key:Key,VersionId:VersionId}' | while read key versionid
do
    aws --profile ${PROFILE} \
        s3api delete-object \
            --bucket ${BUCKET_NAME} \
            --key ${key} \
            --version-id ${versionid}
done 

# それ以外のオブジェクトの削除
aws --profile ${PROFILE} --output text \
      s3api list-object-versions \
        --bucket ${BUCKET_NAME} \
    --query 'Versions[].{Key:Key,VersionId:VersionId}' | while read key versionid
do
    aws --profile ${PROFILE} \
        s3api delete-object \
            --bucket ${BUCKET_NAME} \
            --key ${key} \
            --version-id ${versionid}
done 

バケットの削除

aws --profile ${PROFILE} \
    s3api delete-bucket \
        --bucket "${BUCKET_NAME}"

AWS NitroインスタンスのLinuxでEBSボリュームを特定する手順

はじめに

m5・c5・t3などのNitroタイプのインスタンスを利用している場合にOS上で認識しているボリュームがどのEBSボリュームかを、Volume IDベースで確認する手順です。
なおXenベースのインスタンス(m4以前、c5以前、t2以前など)は調べた限りでは、残念ながらVolume IDでのボリューム特定方法は見つかりませんでした。(Xenの準仮想化ドライバ blk_frontでデバイス情報を表示する実装がそもそもなさそう)

AWSでEBSのVolume ID確認

マネコンでの確認

対象インスタンスのEBSのデバイスをクリックするとVolume IDが表示されます。
f:id:nopipi:20200409144901p:plain:w500

CLIでの確認

CLIaws ec2 describe-instancesで対象インタンスにアタッチされているEBSボリュームの情報を確認することができます。

 aws ec2 describe-instances --instance-ids i-03e048ef20e38a8d7 \
    --query 'Reservations[].Instances[].BlockDeviceMappings' ;
[
    [
        {
            "DeviceName": "/dev/xvda",
            "Ebs": {
                "AttachTime": "2020-04-09T05:46:34.000Z",
                "DeleteOnTermination": true,
                "Status": "attached",
                "VolumeId": "vol-0b5cbc3d4d6d5a965"
            }
        }
    ]
]

LinuxOS上でのVolume ID確認によるEBSの特定

RHELの場合

nvmeのCLIをインストールしてnvmeデバイスの情報を取得します。4行目のsnの部分に表示されますが、vol-xxxxxxxxxxxの"-"がないのでコピーして利用する時は注意してください。

#CLIのインストール
sudo yum -y install nvme-cli

#ボリュームの情報取得
sudo nvme id-ctrl /dev/nvme0|head -n 5
NVME Identify Controller:
vid     : 0x1d0f
ssvid   : 0x1d0f
sn      : vol0b5cbc3d4d6d5a965
mn      : Amazon Elastic Block Store 

Amazon Linuxの場合

Amazon Linux専用で、EBSのVolume IDを表示する/sbin/ebsnvme-idコマンドがあります。ちなみに、RHELで記載したnvmeコマンドでも取得可能です。

#ボリュームの情報取得
sudo /sbin/ebsnvme-id /dev/nvme0n1
Volume ID: vol-0b5cbc3d4d6d5a965
xvda

(蛇足)どのようにVolume id情報を取得しているか

RHEL(nvmeコマンド)

コマンドの中では指定したデバイスに対して、ioctlでデバイス情報の取得をしています。straceで実際に取得する部分でを表示すると以下の通りです。 ioctl()のシステムコールでオペレーションコードにNVME_IOCTL_ADMIN_CMDを指定し情報取得をしています。

# nvmeコマンドのstrace結果(抜粋)

open("/dev/nvme0", O_RDONLY)            = 3
fstat(3, {st_mode=S_IFCHR|0600, st_rdev=makedev(245, 0), ...}) = 0
ioctl(3, NVME_IOCTL_ADMIN_CMD, 0x7ffc7436f9c0) = 0
fstat(1, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f0224788000
close(3) 

Amazon Linux(ebsnvme-idコマンド)

ebsnvme-idはpythonで実装されており、こちらもnvmeコマンドと同様ioctl()システムコールで情報取得していますが、オペレーションコードにNVME_IOCTL_ADMIN_CMDを指定し情報取得をしています。

# ebsnvme-idコマンドのstrace結果(抜粋)

openat(AT_FDCWD, "/dev/nvme0", O_RDONLY) = 3
fstat(3, {st_mode=S_IFCHR|0600, st_rdev=makedev(248, 0), ...}) = 0
ioctl(3, NVME_IOCTL_ADMIN_CMD, 0x7ffdf50dddb0) = 0
close(3)    

PostgreSQL11.6のクライアントの共有ライブラリ&開発ツール(32bit)をソースコードからビルドする手順(on RHEL8)

はじめに

諸事情で、PostgreSQL11.6 クライアントとクライアントの開発環境(ECPG - C言語による埋め込みSQLのアプリ用ビルド環境)をRHEL8上で、32bitで準備する必要があって手順を確認したものです。32bitでビルドするポイントはconfigureの時にオプションで--build="i686-pc-linux-gnu" "CFLAGS=-m32" "CXXFLAGS=-m32" "LDFLAGS=-m32"とすることです。

環境

手順

ビルドに必要なパッケージのインストール

bzip2はtarの解凍に、glibc-devel.i686 readline-devel.i686 zlib-devel.i686は、PostgreSQL11.7(32bit)のビルドの前提です。

sudo yum -y install gcc bzip2 make
sudo yum -y install glibc-devel.i686 readline-devel.i686 zlib-devel.i686

ソースコードのダウンロードと展開

curl https://ftp.postgresql.org/pub/source/v11.6/postgresql-11.6.tar.bz2 --output postgresql-11.6.tar.bz2
tar -jxf postgresql-11.6.tar.bz2
cd postgresql-11.6

PostgreSQL11.6 のビルドとインストール

インストール先はデフォルトは/usr/local/pgsql/になります。ここではインストール先を--prefix=XXXXオプションで/usr/local/pgsql-i686に指定します。

./configure --build="i686-pc-linux-gnu" "CFLAGS=-m32" "CXXFLAGS=-m32" "LDFLAGS=-m32" --prefix="/usr/local/pgsql-i686"
make

sudo make install

確認

32bitのバイナリーが出来上がっていることを確認します。

cd /usr/local/pgsql-i686/

#ファイルの確認
file lib/libpq.so.5.11 
lib/libpq.so.5.11: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, BuildID[sha1]=a8582904f50478302be75a9aea0174f414dc9b04, not stripped

実行パスの追加

インストールしたPostgreSQL11.6 クライアント(32bit)のコマンドが実行できるようにパスを追加します。

#プロファイルにパスを追加
echo 'PATH="$PATH:/usr/local/pgsql-i686/bin"' >> ~/.bashrc
source ~/.bashrc

#パスの確認
which ecpg
/usr/local/pgsql-i686/bin/ecpg   <-- ecpgコマンドが表示されることを確認

サンプルプログラムでの動作確認

ECPGによるC言語による埋め込みSQLのサンプルプログラムを利用した32bitアプリケーションのビルドとDBへの接続テストを行います。

サンプルコード(sample.pgc)

ファイル名sample.pgcで、下記サンプルコードを書きます。
こちらのコードは、DBにコネクトして、DB名の取得とカレンと時間を取得するだけのDB接続テスト用のサンプルコードです。

#include <stdio.h>
#include <string.h>

#define BUFF_SIZE 1024

EXEC SQL BEGIN DECLARE SECTION;
    char    dbname[BUFF_SIZE];
    char    tmpstr[BUFF_SIZE];
    char    target[BUFF_SIZE];
    char    user[BUFF_SIZE];
    char    pass[BUFF_SIZE];
EXEC SQL END DECLARE SECTION;

int main(int argc, char **argv)
{
    if( argc > 3 ){
        strncpy(target, *(argv+1), BUFF_SIZE);
        strncpy(user,   *(argv+2), BUFF_SIZE);
        strncpy(pass,   *(argv+3), BUFF_SIZE);
    }else{
        printf("Invalid argument(s): ./sample 'dbname@host:port' 'user' 'password'\n");
        return(1);
    }

    printf("target=%s user=%s pass=%s\n",target, user, pass);

    EXEC SQL CONNECT TO :target USER :user IDENTIFIED BY :pass;

    EXEC SQL SELECT current_database() INTO :dbname;
    printf("current_database=%s \n", dbname);
    
    EXEC SQL select cast(current_timestamp as varchar) INTO :tmpstr;
    printf("current_timestamp=%s \n", tmpstr);

    EXEC SQL DISCONNECT;
    return 0;
}

ビルド

下記コマンドでビルドします。

ecpg sample.pgc
gcc -o sample -m32 -I/usr/local/pgsql-i686/include/ -L/usr/local/pgsql-i686/lib/ -lecpg -lpq -Wl,--rpath=/usr/local/pgsql-i686/lib/ sample.c
  • ecpg: 埋め込みSQLプリプロセッサです。EXEC SQL BEGIN DECLARE SECTIONの埋め込みSQLをライブラリの関数呼び出し形式に変換してコンパイル可能なCのコードを出力します。
  • gcc: 実行バイナリーを作成します。

テスト

下記コマンドを実行し、データベース名と現在の時刻が表示されれば接続成功です。

./sample "DBNAME@Host:Port" "UserName" "Password"
current_database=データベース名が表示 
current_timestamp=現在の時刻が表示

AWS Security Hubのマルチアカウント一括設定スクリプトを使ってみる

はじめに

AWSが提供しているAWS Security Hubの一括設定ツールを試したのでそのメモです。
なんでこのツールを利用したかと言うと、AWS Security Hubはリージョン単位のサービスのためリージョンごとの設定が必要で、かつマルチアカウント構成の場合アカウント毎の設定になるので、作業量が「対象AWSアカウント数 x 対象リージョン数」となり手動でのセットアップは骨が折れるので、楽をしたいと言うのが発端です。

ツール

AWS Security Hubのマルチアカウント一括設定ツール
github.com

上記ツールですが、下記のマネコンAWS Security Hubの「設定」で出る「GitHubスクリプトを実行します」のリンク先のスクリプトになります。

構成環境

作成するAWS Security Hub構成

マルチアカウントのベストプラクティスである Landing Zoneにならい、下記アカウントがあるマルチアカウント構成を前提とします。

  • 支払いアカウント(OrganizationsのMaster Account)
  • メンバーアカウント
    • 監査アカウント
    • ログアカウント(CloudTrailのログなどを集中的に保管管理するアカウント)
    • 複数のリソースアカウント(VPCやEC2をデプロイする環境)

AWS Security Hubは、監査アカウントをMasterアカウントとして、他のアカウントはメンバーアカウントとして招待し作成したHubに所属させます。
f:id:nopipi:20200121231214p:plain

ツールの実行環境と実行用IAMロール

ツール実行に利用するIAMロールとインスタンス構成を説明します。 これらは手順の中で作成します。

  • IAMロール
    • ManageSecurityHubRole : Security Hubなどの実際の作業用に利用するIAMロール
    • ManageSecurityHubInstanceRole : スクリプトを実行するインスタンスに割り当てる、インスタンスロール。ManageSecurityHubRoleへのAssumeRoleのみ許可。

f:id:nopipi:20200121234222p:plain

作業前提

  • 作業環境
    • bashが動作する環境(Linux or Mac)であること
    • AWS CLIのインストールと各AWSアカウントへのアクセスのセットアップが完了していること
  • インスタンス環境
    • インスタンスを起動させるVPC、Subnet、SecurityGroupが事前準備されていること
    • VPCからインターネットアクセスできること

準備: IAMロールの作成

全アカウントにツールを実行するためのIAMロールを作成します。
Gitに含まれるCloudFormation(EnableSecurityHub.yaml)で、IAMロールを作成することもできますが、ここではCLIで作成しています。

事前準備

PROFILE=<auditアカウントのIAM管理可能なプロファイル>

監査アカウントのインスタンスIAMロール

スクリプトを実行するEC2にアタッチするインスタンスロールを作成します。

ASSUMEROLE_POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}'

ROLE_POLICY='{
    "Version": "2012-10-17",
    "Statement": [
        {
           "Sid": "AssumeToManageSecurityHubRoleOfMemberAccounts",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::*:role/ManageSecurityHubRole"
        }
    ]
}'
#IAMロールの作成
aws --profile ${PROFILE} \
    iam create-role \
        --role-name "ManageSecurityHubInstanceRole" \
        --assume-role-policy-document "${ASSUMEROLE_POLICY}" \
        --max-session-duration 3600

#インラインポリシーのアタッチ
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "ManageSecurityHubInstanceRole" \
        --policy-name "ManageSecurityHubPolicy" \
        --policy-document "${ROLE_POLICY}"

#インスタンスプロファイルの作成
aws --profile ${PROFILE} \
    iam create-instance-profile \
        --instance-profile-name "ManageSecurityHubInstanceRole";

aws --profile ${PROFILE} \
    iam add-role-to-instance-profile \
        --instance-profile-name "ManageSecurityHubInstanceRole" \
        --role-name "ManageSecurityHubInstanceRole" ;


#IAMロールのARN取得
ARN_ManageSecurityHubInstanceRole=$(
    aws --profile ${PROFILE} --output text \
        iam get-role --role-name "ManageSecurityHubInstanceRole" \
        --query 'Role.Arn' )

echo ${ARN_ManageSecurityHubInstanceRole}

Security Hub管理用のIAMロール

各アカウントに、Security Hub管理用のIAMロールを作成します。
このIAMロールは、先に作成した「監査アカウントのインスタンスIAMロール」と信頼関係を結びます。

#一連の設定をSecurity Hubを利用する全AWSアカウントに設定します。
PROFILE=<対象のAWSアカウントのIAM管理者のプロファイル>

ASSUMEROLE_POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "AWS": [
            "'"${ARN_ManageSecurityHubInstanceRole}"'"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}'

ROLE_POLICY='{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Condition": {
                "StringLike": {
                    "iam:AWSServiceName": [
                        "securityhub.amazonaws.com",
                        "config.amazonaws.com"
                    ]
                }
            },
            "Action": "iam:CreateServiceLinkedRole",
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": "securityhub:*",
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": [
                "config:DescribeConfigurationRecorders",
                "config:DescribeDeliveryChannels",
                "config:DescribeConfigurationRecorderStatus",
                "config:DeleteConfigurationRecorder",
                "config:DeleteDeliveryChannel",
                "config:PutConfigurationRecorder",
                "config:PutDeliveryChannel",
                "config:StartConfigurationRecorder"
            ],
            "Resource": "*",
            "Effect": "Allow"
        },
        {
            "Action": "iam:PassRole",
            "Resource": "arn:aws:iam::*:role/aws-service-role/config.amazonaws.com/AWSServiceRoleForConfig",
            "Effect": "Allow"
        },
        {
            "Action": [
                "s3:CreateBucket",
                "s3:PutBucketPolicy",
                "s3:ListBucket"
            ],
            "Resource": "arn:aws:s3:::config-bucket-*",
            "Effect": "Allow"
        }
    ]
}'

#IAMロールの作成
aws --profile ${PROFILE} \
    iam create-role \
        --role-name "ManageSecurityHubRole" \
        --assume-role-policy-document "${ASSUMEROLE_POLICY}" \
        --max-session-duration 3600

#インラインポリシーのアタッチ
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "ManageSecurityHubRole" \
        --policy-name "ManageSecurityHubPolicy" \
        --policy-document "${ROLE_POLICY}"

スクリプト実行環境作成

auditアカウントに、スクリプト実行用のEC2環境を準備します。

プロファイル準備

PROFILE=<auditアカウントのEC2管理可能なプロファイル>

インスタンスの起動

#起動パラメータ設定(環境に合わせて変更するもの)
KEYNAME="CHANGE_KEY_PAIR_NAME" #環境に合わせてキーペア名を設定してください。  
SUBNETID=subnet-f59858bd  #"パブリックサブネットのIDを設定"
SG_ID=sg-071417e793b795090  #"SSHログインできるセキュリティーグループのIDを設定"

#起動パラメータ設定(固定設定)
INSTANCE_TYPE="t2.micro"
AL2_AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????.?-x86_64-gp2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "SecurityHubMgr"
            }
        ]
    }
]'

#ユーザデータ設定
USER_DATA='
#!/bin/bash -xe

#RPM最新化&ホスト名設定          
yum -y update
hostnamectl set-hostname SecurityHubMgr

#パッケージインストール
yum -y install python2-pip python2 git

#boto3インストール
sudo pip install boto3

#AWS CLIアップデート
curl -o "get-pip.py" "https://bootstrap.pypa.io/get-pip.py" 
python get-pip.py
pip install --upgrade

# ec2-userのAWS CLI初期設定
Region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
sudo -u ec2-user aws configure set region ${Region}
sudo -u ec2-user aws configure set output json

#スクリプトのセットアップ
cd /home/ec2-user
sudo -u ec2-user git clone https://github.com/awslabs/aws-securityhub-multiaccount-scripts.git
'

#インスタンスの起動
aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${AL2_AMIID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${SUBNETID} \
        --security-group-ids ${SG_ID} \
        --associate-public-ip-address \
        --iam-instance-profile "Name=ManageSecurityHubInstanceRole" \
        --tag-specifications "${TAGJSON}" \
        --user-data "${USER_DATA}" \
        --monitoring Enabled=true ;

スクリプトによるSecurityHubの有効化

Security Hub管理インスタンスへのログイン

MgrIP=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=SecurityHubMgr" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].PublicIpAddress' )

ssh ec2-user@${MgrIP}

SecurityHubのメンバーアカウント一覧(CSV)作成

Security Hubの有効化し、SecurityHubのMasterAccount(ここではauditアカウント)の配下のメンバーとなるAWSアカウントの一覧をCSV形式で作成します。1行1アカウントで、"AccountId,EmailAddress"形式で作成します。

CSVファイルの作成例(securityhub_account_list.csv)

111111111111,master@mail_address
222222222222,logging@mail_address
333333333333,resource1@mail_address
444444444444,resource2@mail_address

有効化対象リージョンの設定

スクリプトのデフォルトではSecurityHubが利用可能な全てのリージョンで有効化をしようとします。しかし、最近開設されたリージョンは、デフォルトではリージョン自身が無効化されており、そのまま実行するとスクリプトがエラーとなります。そのため、SecurityHubを有効化するリージョンを明示的に指定する必要があります。

Security Hubが利用可能なリージョン一覧の確認

CLIではコマンドがないので、boto3を利用しpythonワンライナーでSecurityHubが利用可能なリージョン一覧を取得します。

python -c "import boto3,json; print('{}'.format( json.dumps( boto3.session.Session().get_available_regions('securityhub') ) ) );"
有効化対象リージョン一覧作成

上記で取得した一覧情報から、有効化対象のリージョンを抽出し、カンマ区切りの文字列にします。

  • 変更点
    • デフォルトで無効化されているリージョンを削除。
    • カンマカンマ
ENABLE_REGIONS="ap-northeast-1,ap-northeast-2,ap-south-1,ap-southeast-1,ap-southeast-2,ca-central-1,eu-central-1,eu-north-1,eu-west-1,eu-west-2,eu-west-3,sa-east-1,us-east-1,us-east-2,us-west-1,us-west-2"

Security Hubの有効化

enablesecurityhub.pyスクリプトを利用し、対象アカウントの対象リージョンのSecurity Hub有効化を行います。

# Security Hubのマスターアカウントとして監査アカウントを指定
MASTER_ACCOUNT=$(aws sts get-caller-identity --output text --query 'Account')

#その他設定
ASSUME_ROLE="ManageSecurityHubRole"
CSV_FILE_NAME="securityhub_account_list.csv"

#実行
python2 ./aws-securityhub-multiaccount-scripts/enablesecurityhub.py \
    --master_account ${MASTER_ACCOUNT} \
    --assume_role ${ASSUME_ROLE} \
    --enabled_regions ${ENABLE_REGIONS} \
    ${CSV_FILE_NAME} ;

下記のようにスクリプトでのAWS Config有効化に失敗したAWSアカウント/リージョンがある場合、マネージメントコンソールのAWS Configで手動でConfigの有効化をしてから、enablesecurityhub.pyを再実行してください。

スクリプトによるSecurityHubの無効化

SecurityHubの無効化です。

MASTER_ACCOUNT=$(aws sts get-caller-identity --output text --query 'Account')
ENABLE_REGIONS="ap-northeast-1,ap-northeast-2,ap-south-1,ap-southeast-1,ap-southeast-2,ca-central-1,eu-central-1,eu-north-1,eu-west-1,eu-west-2,eu-west-3,sa-east-1,us-east-1,us-east-2,us-west-1,us-west-2"

#無効化実行
python2 ./aws-securityhub-multiaccount-scripts/disablesecurityhub.py \
    --master_account ${MASTER_ACCOUNT} \
    --assume_role ${ASSUME_ROLE} \
    --enabled_regions ${ENABLE_REGIONS}  \
    ${CSV_FILE_NAME} ;

PowerShell v7.0.0-rc.1 をWindowsServer 2019にセットアップする

手順

PowerShell v7.0.0-rc1をダウンロードする

Invoke-WebRequest -Uri https://github.com/PowerShell/PowerShell/releases/download/v7.0.0-rc.1/PowerShell-7.0.0-rc.1-win-x64.msix -OutFile PowerShell-7.0.0-rc.1-win-x64.msix -UseBasicParsing
  • Uri ディストリビューションのイメージファイルのURLを指定します。
  • OutFile : 出力先のファイル名を指定します。
  • UseBasicParsing : コマンドでIEエンジンを利用しないようにします。

管理者モードに変更する

デフォルトのMicrosoft Store アプリでは、PowerShell v7.0.0のインストールでエラーになるため、Windows開発者モードに変更します。

管理者権限のPowerShell起動

管理者権限で、PowerShellを起動します。起動すると別ウィンドウでPowerShellが起動します。

Start-Process powershell.exe -Verb runas
管理者モード変更

管理者権限のPowershellで、レジストリ操作を行い、管理者モードに変更します。変更後はexitで管理者権限のPowerShellを終了します。

reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"

exit

PowerShellインストール

Add-AppxPackage PowerShell-7.0.0-rc.1-win-x64.msix

AWS CLIで最新のAMI IDを取得する

タイトルの通り、Amazon Linux2、RHEL8、Windows Server2019の最新AMIのAMI IDを取得するAWS-CLIのコマンドです。(元ネタは、EC2のユーザーガイド)

AWS CLIの"ec2 describe-images"でAMI IDを確認しますが、その時に下記オプションを指定することで最新AMIのID取得を実現しています。

  • "--filters"で、特定のOS(例えばAmazon Linux2とか)のAMIのみを抽出し
  • "--query "で、sort_byとreverseで作成日付(降順)にソートし最初の配列のみ取得

最新AMI ID取得例

Amazon Linux2

AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????.?-x86_64-gp2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' );

echo ${AMIID}

RHEL

RHEL8 の最新AMI ID
AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --filters 'Name=name,Values=RHEL-8.?.?_HVM-????????-x86_64-*-GP2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' );

echo ${AMIID}
RHEL7 の最新AMI ID
AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --filters 'Name=name,Values=RHEL-7.?_HVM-????????-x86_64-*-GP2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' );

echo ${AMIID}

Windows Server

Windows Server 2019 Japanese Full-Base の最新AMI ID
AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=Windows_Server-2019-Japanese-Full-Base-????.??.??' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;

echo ${AMIID}

AWS Storage Gateway(VPC エンドポイントあり)環境をCLIで構築し運用する手順

はじめに

AWS Storage GatewayのファイルゲートウェイVPCエンドポイントを利用したVPC閉塞環境を、CLIで構築する手順です。この手順ではNFS接続、SMB(ゲスト接続)、SMB(ActiveDirectory認証)の構成を作成します。またキャッシュリフレッシュ、ソフトウェアップデート、およびリフレッシュ完了やS3アップロード完了のCloud Watch Event連携のセットアップ手順も整理しています。 f:id:nopipi:20191206015552p:plain:w600

作成手順

(1)事前設定

(1)-(a) 作業環境の準備

作業環境として、下記を準備します。

  • bashが利用可能な環境(LinuxMacの環境)
  • aws-cliのセットアップ
  • AdministratorAccessポリシーが付与され実行可能な、aws-cliのProfileの設定

(1)-(b) CLI実行用の事前準備

これ以降のAWS-CLIで共通で利用するパラメータを環境変数で設定しておきます。

export PROFILE=<設定したプロファイル名称を指定。デフォルトの場合はdefaultを設定>
export REGION=ap-northeast-1

(2)VPCの作成(CloudFormation利用)

IGWでインターネットアクセス可能で、パブリックアクセス可能なサブネットx2、プライベートなサブネットx2の合計4つのサブネットを所有するVPCを作成します。 f:id:nopipi:20191206015556p:plain:w600

(2)-(a)テンプレートのダウンロード

私が作成し利用しているVPC作成用のCloudFormationテンプレートを利用します。まず、githubからテンプレートをダウンロードします。

curl -o vpc-4subnets.yaml https://raw.githubusercontent.com/Noppy/CfnCreatingVPC/master/vpc-4subnets.yaml

(2)-(b)VPC作成

ダウンロードしたテンプレートを利用し、VPCをデプロイします。

CFN_STACK_PARAMETERS='
[
  {
    "ParameterKey": "DnsHostnames",
    "ParameterValue": "true"
  },
  {
    "ParameterKey": "DnsSupport",
    "ParameterValue": "true"
  },
  {
    "ParameterKey": "InternetAccess",
    "ParameterValue": "true"
  },
  {
    "ParameterKey": "EnableNatGW",
    "ParameterValue": "false"
  },
  {
    "ParameterKey": "VpcName",
    "ParameterValue": "StorageGWPoCVPC"
  },
  {
    "ParameterKey": "VpcInternalDnsName",
    "ParameterValue": "sgwpoc.local."
  }
]'

#CloudFormation スタック作成
aws --profile ${PROFILE} cloudformation create-stack \
    --stack-name SGWPoC-VPC \
    --template-body "file://./vpc-4subnets.yaml" \
    --parameters "${CFN_STACK_PARAMETERS}" \
    --capabilities CAPABILITY_IAM ;

(3) VPCEndpoint設定

必要となるVPC Endpointを作成します。この手順ではエンドポイントポリシーは設定してません。必有れば追加設定してください。 f:id:nopipi:20191206020925p:plain:w600

(3)-(a) 構成情報取得

#構成情報取得
VPCID=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`VpcId`].[OutputValue]')

VPC_CIDR=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`VpcCidr`].[OutputValue]')

PublicSubnet1Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PublicSubnet1Id`].[OutputValue]')

PublicSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PublicSubnet2Id`].[OutputValue]')

PrivateSubnet1Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet1Id`].[OutputValue]')

PrivateSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet2Id`].[OutputValue]')

PrivateSubnet1RouteTableId=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet1RouteTableId`].[OutputValue]')

PrivateSubnet2RouteTableId=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet2RouteTableId`].[OutputValue]')

echo -e "VPCID=$VPCID\nVPC_CIDR=$VPC_CIDR\nPublicSubnet1Id =$PublicSubnet1Id\nPublicSubnet2Id =$PublicSubnet2Id\nPrivateSubnet1Id=$PrivateSubnet1Id\nPrivateSubnet2Id=$PrivateSubnet2Id\nPrivateSubnet1RouteTableId=$PrivateSubnet1RouteTableId \nPrivateSubnet2RouteTableId=$PrivateSubnet2RouteTableId"

(3)-(b) VPC: VPCEndpointの作成

#VPC Endpoint用SecurityGroup作成
VPCENDPOINT_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name VpcEndpointSG \
        --description "Allow https" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${VPCENDPOINT_SG_ID} \
        --tags "Key=Name,Value=VpcEndpointSG" ;

aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${VPCENDPOINT_SG_ID} \
        --protocol tcp \
        --port 443 \
        --cidr ${VPC_CIDR} ;

#Storage Gateway専用のSecurityGroup作成
VPCENDPOINT_STORAGEGW_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name SGW-VpcEndpointSG \
        --description "Allow https" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${VPCENDPOINT_STORAGEGW_SG_ID} \
        --tags "Key=Name,Value=SGW-VpcEndpointSG" ;

# AWS API通信
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${VPCENDPOINT_STORAGEGW_SG_ID} \
        --protocol tcp \
        --port 443 \
        --cidr ${VPC_CIDR} ;

#client-cp接続:Port 1026, proxy-app接続:Port1028
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${VPCENDPOINT_STORAGEGW_SG_ID} \
        --protocol tcp \
        --port 1026-1028 \
        --cidr ${VPC_CIDR} ;

#dp-1接続:Port1031
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${VPCENDPOINT_STORAGEGW_SG_ID} \
        --protocol tcp \
        --port 1031 \
        --cidr ${VPC_CIDR} ;

# Support-channel用(GWからEndpintの2222にsshする)
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${VPCENDPOINT_STORAGEGW_SG_ID} \
        --protocol tcp \
        --port 2222 \
        --cidr ${VPC_CIDR} ;

#S3用VPCEndpoint作成
aws --profile ${PROFILE} \
    ec2 create-vpc-endpoint \
        --vpc-id ${VPCID} \
        --service-name com.amazonaws.${REGION}.s3 \
        --route-table-ids ${PrivateSubnet1RouteTableId} ${PrivateSubnet2RouteTableId}

#StorageGateway用VPCEndpoint作成
aws --profile ${PROFILE} \
    ec2 create-vpc-endpoint \
        --vpc-id ${VPCID} \
        --vpc-endpoint-type Interface \
        --service-name com.amazonaws.${REGION}.storagegateway \
        --subnet-id ${PrivateSubnet1Id} ${PrivateSubnet2Id} \
        --security-group-id ${VPCENDPOINT_STORAGEGW_SG_ID} ;

#SSM用PCEndpoint作成
aws --profile ${PROFILE} \
    ec2 create-vpc-endpoint \
        --vpc-id ${VPCID} \
        --vpc-endpoint-type Interface \
        --service-name com.amazonaws.${REGION}.ssm \
        --subnet-id ${PrivateSubnet1Id} ${PrivateSubnet2Id} \
        --security-group-id ${VPCENDPOINT_SG_ID} ;

aws --profile ${PROFILE} \
    ec2 create-vpc-endpoint \
        --vpc-id ${VPCID} \
        --vpc-endpoint-type Interface \
        --service-name com.amazonaws.${REGION}.ec2messages \
        --subnet-id ${PrivateSubnet1Id} ${PrivateSubnet2Id} \
        --security-group-id ${VPCENDPOINT_SG_ID} ;

aws --profile ${PROFILE} \
    ec2 create-vpc-endpoint \
        --vpc-id ${VPCID} \
        --vpc-endpoint-type Interface \
        --service-name com.amazonaws.${REGION}.ssmmessages \
        --subnet-id ${PrivateSubnet1Id} ${PrivateSubnet2Id} \
        --security-group-id ${VPCENDPOINT_SG_ID} ;

(4) Storage Gateway管理用のIAMロール(管理サーバ用)作成

f:id:nopipi:20191206021035p:plain:w600

(4)-(a) IAMロール作成

POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}'
#IAMロールの作成
aws --profile ${PROFILE} \
    iam create-role \
        --role-name "Ec2-StorageGW-AdminRole" \
        --assume-role-policy-document "${POLICY}" \
        --max-session-duration 43200

#AWS管理ポリシーのアタッチ
# AWSStorageGatewayFullAccessのアタッチ
aws --profile ${PROFILE} \
    iam attach-role-policy \
        --role-name "Ec2-StorageGW-AdminRole" \
        --policy-arn arn:aws:iam::aws:policy/AWSStorageGatewayFullAccess

# CloudWatch管理者権限のアタッチ
aws --profile ${PROFILE} \
    iam attach-role-policy \
        --role-name "Ec2-StorageGW-AdminRole" \
        --policy-arn arn:aws:iam::aws:policy/CloudWatchLogsFullAccess

# ReadOnlyAccessのアタッチ
aws --profile ${PROFILE} \
    iam attach-role-policy \
        --role-name "Ec2-StorageGW-AdminRole" \
        --policy-arn arn:aws:iam::aws:policy/ReadOnlyAccess

#インスタンスプロファイルの作成
aws --profile ${PROFILE} \
    iam create-instance-profile \
        --instance-profile-name "Ec2-StorageGW-AdminRole-Profile";

aws --profile ${PROFILE} \
    iam add-role-to-instance-profile \
        --instance-profile-name "Ec2-StorageGW-AdminRole-Profile" \
        --role-name "Ec2-StorageGW-AdminRole" ;

(5) Windows/Linuxクライアントインスタンス、管理用インスタンス作成

f:id:nopipi:20191206021121p:plain:w600

(5)-(a) セキュリティーグループ作成(Bastion)

(i) Client - SSHログイン用 Security Group

# SSHログイン用セキュリティーグループ作成
SSH_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name SshSG \
        --description "Allow ssh" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${SSH_SG_ID} \
        --tags "Key=Name,Value=SshSG" ;

# セキュリティーグループにSSHのinboundアクセス許可を追加
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SSH_SG_ID} \
        --protocol tcp \
        --port 22 \
        --cidr 0.0.0.0/0 ;

(ii) Client - RDPログイン用 Security Group

# RDPログイン用セキュリティーグループ作成
RDP_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name RdpSG \
        --description "Allow rdp" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${RDP_SG_ID} \
        --tags "Key=Name,Value=RdpSG" ;

# セキュリティーグループにRDPのinboundアクセス許可を追加
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${RDP_SG_ID} \
        --protocol tcp \
        --port 3389 \
        --cidr 0.0.0.0/0 ;

(iii) Client識別用 Security Group

# クライアント識別用セキュリティーグループ作成
CLIENT_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name ClientSG \
        --description "Allow rdp" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${CLIENT_SG_ID} \
        --tags "Key=Name,Value=ClientSG" ;

(iv) Manager - SSHログイン用 Security Group

# SSHログイン用セキュリティーグループ作成
MGR_SSH_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name Mgr-SshSG \
        --description "Allow ssh" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${MGR_SSH_SG_ID} \
        --tags "Key=Name,Value=Mgr-SshSG" ;

# セキュリティーグループにSSHのinboundアクセス許可を追加
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${MGR_SSH_SG_ID} \
        --protocol tcp \
        --port 22 \
        --cidr 0.0.0.0/0 ;

(iiv) セキュリティーグループ設定情報の確認

SSH_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=SshSG' \
        --query 'SecurityGroups[].GroupId');

RDP_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=RdpSG' \
        --query 'SecurityGroups[].GroupId');

CLIENT_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=ClientSG' \
        --query 'SecurityGroups[].GroupId');

MGR_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=Mgr-SshSG' \
        --query 'SecurityGroups[].GroupId');

#設定情報の表示
echo -e "SSH_SG_ID   =${SSH_SG_ID}\nRDP_SG_ID   =${RDP_SG_ID}\nCLIENT_SG_ID=${CLIENT_SG_ID}\nMGR_SG_ID   =${MGR_SG_ID}"

(5)-(b)インスタンス作成用の事前情報取得

KEYNAME="CHANGE_KEY_PAIR_NAME"  #環境に合わせてキーペア名を設定してください。  

#最新のAmazon Linux2のAMI IDを取得します。
AL2_AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=amzn2-ami-hvm-2.0.????????.?-x86_64-gp2' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;

WIN2019_AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=Windows_Server-2019-Japanese-Full-Base-????.??.??' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;
echo -e "KEYNAME=${KEYNAME}\nAL2_AMIID=${AL2_AMIID}\nWIN2019_AMIID=${WIN2019_AMIID}"

(5)-(c) Liunux-Client作成

#インスタンスタイプ設定
INSTANCE_TYPE="t2.micro"
#INSTANCE_TYPE="m5d.8xlarge"

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Linux-Client"
            }
        ]
    }
]'

#ユーザデータ設定
USER_DATA='
#!/bin/bash -xe
                
yum -y update
yum -y install bind bind-utils
hostnamectl set-hostname Linux-Client
'
# サーバの起動
aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${AL2_AMIID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${PublicSubnet1Id} \
        --security-group-ids ${SSH_SG_ID} ${CLIENT_SG_ID}\
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" \
        --user-data "${USER_DATA}" ;

(5)-(d) Windows-Client作成

#インスタンスタイプ設定
INSTANCE_TYPE="t2.micro"
#INSTANCE_TYPE="m5d.8xlarge"

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Windows-Client"
            }
        ]
    }
]'

# サーバの起動
aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${WIN2019_AMIID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${PublicSubnet1Id} \
        --security-group-ids ${RDP_SG_ID} ${CLIENT_SG_ID}\
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" ;

(5)-(e) Manager(Linux)の作成

ファイルゲートウェイアクティベーション作業を行う管理インスタンスを起動します。

#インスタンスタイプ設定
INSTANCE_TYPE="t2.micro"

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Manager-Linux"
            }
        ]
    }
]'
#ユーザデータ設定
USER_DATA='
#!/bin/bash -xe
                
yum -y update
yum -y install bind bind-utils
hostnamectl set-hostname Mgr-linux
'
# サーバの起動
aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${AL2_AMIID} \
        --instance-type t2.micro \
        --key-name ${KEYNAME} \
        --subnet-id ${PublicSubnet2Id} \
        --security-group-ids ${MGR_SSH_SG_ID}\
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" \
        --user-data "${USER_DATA}" \
        --iam-instance-profile "Name=Ec2-StorageGW-AdminRole-Profile";

(6) Storage Gateway作成(事前準備)

Storage Gatewayで利用するS3のバケットと、S3アクセス用にStorage Gatewayが利用するIAMロールを作成します。 f:id:nopipi:20191206021326p:plain:w600

(6)-(a) Storage Gateway用のSecurityGroup作成

(i) SGW用 Security Group

# セキュリティーグループID取得

#Security Group ID取得
CLIENT_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=ClientSG' \
        --query 'SecurityGroups[].GroupId');

MGR_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=Mgr-SshSG' \
        --query 'SecurityGroups[].GroupId');

echo -e "CLIENT_SG_ID=${CLIENT_SG_ID}\nMGR_SG_ID   =${MGR_SG_ID}"

# SGW用セキュリティーグループ作成
SGW_SG_ID=$(aws --profile ${PROFILE} --output text \
    ec2 create-security-group \
        --group-name SGWSG \
        --description "Allow gateway" \
        --vpc-id ${VPCID}) ;

aws --profile ${PROFILE} \
    ec2 create-tags \
        --resources ${SGW_SG_ID} \
        --tags "Key=Name,Value=StorageGWSG" ;

# セキュリティーグループにStorage Gatewayに必要となるinboundアクセス許可を追加
# gatewayへのアクティベーションコード取得のため
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 80 \
        --source-group ${MGR_SG_ID} ;

# gatewayへのコンソールログインのため
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 22 \
        --source-group ${MGR_SG_ID} ;

# クライアントとのSMB接続(1)
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 139 \
        --source-group ${CLIENT_SG_ID} ;

# クライアントとのSMB接続(2)
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 445 \
        --source-group ${CLIENT_SG_ID} ;

# クライアントとのNFS接続(1) NFS
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 2049 \
        --source-group ${CLIENT_SG_ID} ;

# クライアントとのNFS接続(2) rpcbind/sunrpc for NFSv3
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 111 \
        --source-group ${CLIENT_SG_ID} ;

# クライアントとのNFS接続(3) gensha for NFSv3
aws --profile ${PROFILE} \
    ec2 authorize-security-group-ingress \
        --group-id ${SGW_SG_ID} \
        --protocol tcp \
        --port 20048 \
        --source-group ${CLIENT_SG_ID} ;

(6)-(b) KMSキー作成

S3暗号化用のKMSのCMK(Customer Master Key)を作成します。キーポリシーはIAMロール作成後に設定します。

KEY_ID=$( \
aws --profile ${PROFILE} --output text \
    kms create-key \
       --description "CMK for S3 buckets" \
       --origin AWS_KMS \
   --query 'KeyMetadata.KeyId' )

aws --profile ${PROFILE} \
    kms create-alias \
        --alias-name alias/Key_For_S3Buckets \
        --target-key-id ${KEY_ID}

(6)-(c) Storage Gateway用S3バケット作成

(i) バケット作成

BUCKET_NAME="storagegw-bucket-$( od -vAn -to1 </dev/urandom  | tr -d " " | fold -w 10 | head -n 1)"
REGION=$(aws --profile ${PROFILE} configure get region)

aws --profile ${PROFILE} \
    s3api create-bucket \
        --bucket ${BUCKET_NAME} \
        --create-bucket-configuration LocationConstraint=${REGION};

(ii) バケットポリシー設定

特定のCMKでの暗号化を強制するバケットポリシーを設定します。

#情報の収集
#BUCKET_NAME=<バケット名を設定>
KEY_ARN=$(aws --profile ${PROFILE} --output text \
    kms describe-key \
        --key-id alias/Key_For_S3Buckets \
    --query 'KeyMetadata.Arn' \
)
#バケットポリシーJSON生成
POLICY='{
    "Version": "2012-10-17",
    "Id": "S3KeyPolicy",
    "Statement": [
        {
            "Sid": "Force KMS Key",
            "Effect": "Deny",
            "Principal": "*",
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::'"${BUCKET_NAME}"'/*",
            "Condition": {
                "StringNotEquals": {
                    "s3:x-amz-server-side-encryption-aws-kms-key-id": "'"${KEY_ARN}"'"
                }
            }
        }
    ]
}'
#バケットポリシー設定
aws --profile ${PROFILE} s3api \
    put-bucket-policy \
        --bucket ${BUCKET_NAME} \
        --policy "${POLICY}"

(6)-(d) Storage Gateway ファイル共有-S3アクセス用 IAMRole作成

POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "storagegateway.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}'
#IAMロールの作成
aws --profile ${PROFILE} \
    iam create-role \
        --role-name "StorageGateway-S3AccessRole" \
        --assume-role-policy-document "${POLICY}" \
        --max-session-duration 43200

#S3バケットアクセス用 In-line Policyの追加
POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "OperatBucket",
      "Effect": "Allow",
      "Action": [
        "s3:GetAccelerateConfiguration",
        "s3:GetBucketLocation",
        "s3:GetBucketVersioning",
        "s3:ListBucket",
        "s3:ListBucketVersions",
        "s3:ListBucketMultipartUploads"
      ],
      "Resource": [
        "arn:aws:s3:::'"${BUCKET_NAME}"'"
      ]
    },
    {
      "Sid": "PuAndGetObject",
      "Effect": "Allow",
      "Action": [
        "s3:AbortMultipartUpload",
        "s3:DeleteObject",
        "s3:DeleteObjectVersion",
        "s3:GetObject",
        "s3:GetObjectAcl",
        "s3:GetObjectVersion",
        "s3:ListMultipartUploadParts",
        "s3:PutObject",
        "s3:PutObjectAcl"
      ],
      "Resource": [
        "arn:aws:s3:::'"${BUCKET_NAME}"'/*"
      ]
    }
  ]
}'
#インラインポリシーの設定
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "StorageGateway-S3AccessRole" \
        --policy-name "AccessS3buckets" \
        --policy-document "${POLICY}";

#CloudWatch Logs用 In-line Policyの追加
POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "AllowLogs",
      "Effect": "Allow",
      "Action": [
            "logs:CreateLogStream",
            "logs:PutLogEvents"
      ],
      "Resource": [
        "*"
      ]
    }
  ]
}'
#インラインポリシーの設定
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "StorageGateway-S3AccessRole" \
        --policy-name "PutCLoudWatchLogs" \
        --policy-document "${POLICY}";

(6)-(e) Storage Gateway管理用Roleに上記IAMのPassRole権限付与

この後のStorage Gatewayのファイル共有作成時(CreateSmbFileShare)に、ファイル共有からS3にPUT/GETするためのIAMロールを指定し割り当ています。この作業時に管理者のIAM権限としてeにPassRoleのアクションを許可する必要があります。

S3AccessRole_ARN=$(aws --profile ${PROFILE} --output text \
    iam get-role \
        --role-name "StorageGateway-S3AccessRole" \
    --query 'Role.Arn') ;

POLICY='{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PassRole",
      "Effect": "Allow",
      "Action": [
        "iam:PassRole"
      ],
      "Resource": [
        "'"${S3AccessRole_ARN}"'"
      ]
    }
  ]
}'
#インラインポリシーの設定
aws --profile ${PROFILE} \
    iam put-role-policy \
        --role-name "Ec2-StorageGW-AdminRole" \
        --policy-name "PassRole" \
        --policy-document "${POLICY}";

(6)-(f) CMKキーポリシー設定

#情報取得
ADMIN_ARN=$(aws --profile ${PROFILE} --output text \
    sts get-caller-identity --query 'Arn')

ACCOUNT_ID=$(aws --profile ${PROFILE} --output text \
    sts get-caller-identity --query 'Account')

S3AccessRole_ARN=$(aws --profile ${PROFILE} --output text \
    iam get-role \
        --role-name "StorageGateway-S3AccessRole" \
    --query 'Role.Arn') ;

STORAGEGW_ADMIN_ROLE_ARN=$(aws --profile ${PROFILE} --output text \
    iam get-role \
        --role-name "Ec2-StorageGW-AdminRole" \
    --query 'Role.Arn') ;


KEY_ARN=$( aws --profile ${PROFILE} --output text \
    kms describe-key \
        --key-id "alias/Key_For_S3Buckets"  \
    --query 'KeyMetadata.Arn')

#キーポリシー用JSON作成
POLICY='{
    "Version": "2012-10-17",
    "Id": "key-default-1",
    "Statement": [
        {
            "Sid": "Administrator",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "'"${ADMIN_ARN}"'",
                    "'"${STORAGEGW_ADMIN_ROLE_ARN}"'",
                    "arn:aws:iam::'"${ACCOUNT_ID}"':role/OrganizationAccountAccessRole"
                ]
            },
            "Action": "kms:*",
            "Resource": "*"
        },
        {
            "Sid": "User",
            "Effect": "Allow",
            "Principal": {
                "AWS": [
                    "'"${S3AccessRole_ARN}"'"
                ]
            },
            "Action": [
                "kms:DescribeKey",
                "kms:Decrypt",
                "kms:GenerateDataKey"
            ],
            "Resource": "*"
        }
    ]
}'

#キーポリシー設定
aws --profile ${PROFILE} \
    kms put-key-policy \
        --key-id "${KEY_ARN}" \
        --policy-name "default" \
        --policy "${POLICY}" \
        --no-bypass-policy-lockout-safety-check ;

(6)-(g) NTP接続不可回避用のRoute53 Private Hosted Zone設定

ファイルゲートウェイに設定されているNTPサーバ(同期先)は、インターネット上のNTPサーバ(x.amazon.pool.ntp.org )です。そのためファイルゲートウェイをインターネット接続ができない環境に設置した場合、時刻同期処理を行うことができません。この手順は、Route53のPrivate Hosted Zoneを活用し、x.amazon.pool.ntp.orgのアクセス先をAWS time sync(169.254.169.123)にアクセスするようにしてます。

#設定
REGION=$(aws --profile ${PROFILE} configure get region)

#Private Hosted zoneの作成
aws --profile ${PROFILE} \
    route53 create-hosted-zone \
        --name "amazon.pool.ntp.org" \
        --caller-reference $(date '+%Y-%m-%d-%H:%M') \
        --vpc VPCRegion=${REGION},VPCId=${VPCID} ;

HOSTED_ZONE_ID=$(aws --profile ${PROFILE} --output text \
    route53 list-hosted-zones-by-name \
        --dns-name "amazon.pool.ntp.org" \
    --query 'HostedZones[].Id' | sed -e 's/\/hostedzone\///') ;

#レコード登録
CHANGE_BATCH_JSON='{
  "Comment": "CREATE NTP records ",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "0.amazon.pool.ntp.org",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "169.254.169.123"
          }
        ]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "1.amazon.pool.ntp.org",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "169.254.169.123"
          }
        ]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "2.amazon.pool.ntp.org",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "169.254.169.123"
          }
        ]
      }
    },
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "Name": "3.amazon.pool.ntp.org",
        "Type": "A",
        "TTL": 300,
        "ResourceRecords": [
          {
            "Value": "169.254.169.123"
          }
        ]
      }
    }
  ]
}'
#x.amazon.poo..ntp.orgのAレコード登録
aws --profile ${PROFILE} \
    route53 change-resource-record-sets \
            --hosted-zone-id ${HOSTED_ZONE_ID} \
            --change-batch "${CHANGE_BATCH_JSON}";

(7) ファイルゲートウェイの作成

ゲートウェイを作成、アクティベーションして利用可能な状態にします。 f:id:nopipi:20191206021614p:plain:w600

(7)-(a) ファイルゲートウェイインスタンスの作成

(i)インスタンスの起動

# FileGatewayの最新のAMIIDを取得する
FGW_AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=aws-storage-gateway-??????????' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' );

#Security Group ID取得
SGW_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=SGWSG' \
        --query 'SecurityGroups[].GroupId');


#ファイルゲートウェイインスタンスの起動
INSTANCE_TYPE=c5.4xlarge
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Fgw"
            }
        ]
    }
]'
BLOCK_DEVICE_MAPPINGS='[
    {
        "DeviceName": "/dev/xvda",
        "Ebs": {
            "DeleteOnTermination": true,
            "VolumeType": "io1",
            "Iops": 4000,
            "VolumeSize": 350,
            "Encrypted": false
        }
    },
    {
        "DeviceName": "/dev/sdm",
        "Ebs": {
            "DeleteOnTermination": true,
            "VolumeType": "io1",
            "Iops": 3000,
            "VolumeSize": 16384,
            "Encrypted": false
        }
    }
]'

aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${FGW_AMIID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${PrivateSubnet1Id} \
        --security-group-ids ${SGW_SG_ID} \
        --block-device-mappings "${BLOCK_DEVICE_MAPPINGS}" \
        --tag-specifications "${TAGJSON}" \
        --monitoring Enabled=true ;

(ii)AutoRecovery設定

#情報取得
export REGION=ap-northeast-1

GatewayInstanceID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=Fgw" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].InstanceId' )

#AutoRecovery設定
aws --profile ${PROFILE} \
    cloudwatch put-metric-alarm \
        --region "${REGION}" \
        --alarm-name "recover-ec2-instance-${GatewayInstanceID}" \
        --alarm-description "recover-FileGateway-ec2-instance" \
        --alarm-actions \
            "arn:aws:automate:${REGION}:ec2:recover" \
        --namespace AWS/EC2 \
        --metric-name StatusCheckFailed_System \
        --dimensions  Name=InstanceId,Value=${GatewayInstanceID} \
        --comparison-operator GreaterThanThreshold \
        --unit Count \
        --statistic Average \
        --period 60 \
        --threshold 1 \
        --evaluation-periods 1

(7)-(b) Mgr-Linuxへのログインとセットアップ

以後の作業で、Mgr-Linuxを利用するため、sshログインとセットアップを行います。

(i) 作業端末からMgr-Linuxへのログイン(ここではMAC前提)

MgrIP=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=Manager-Linux" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].PublicIpAddress' )

ssh-add
ssh -A ec2-user@${MgrIP}

(ii) Mgr-Linuxのセットアップ

以下はSSHログインした、Mgr-Linux上での作業となります。

# ec2-userログインしてからの作業となります。

#AWS Cliアップデート
curl -o "get-pip.py" "https://bootstrap.pypa.io/get-pip.py" 
sudo python get-pip.py
pip install --upgrade --user awscli
echo 'export PATH=$HOME/.local/bin:$PATH' >> ~/.bashrc
. ~/.bashrc

# AWS cli初期設定
Region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
aws configure set region ${Region}
aws configure set output json

#動作確認
aws sts get-caller-identity

#利用するプロファイル設定
export PROFILE=default

(iii) 構成情報の取得

export PROFILE=default
#構成情報取得
VPCID=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`VpcId`].[OutputValue]')

VPC_CIDR=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`VpcCidr`].[OutputValue]')

PublicSubnet1Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PublicSubnet1Id`].[OutputValue]')

PublicSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PublicSubnet2Id`].[OutputValue]')

PrivateSubnet1Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet1Id`].[OutputValue]')

PrivateSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet2Id`].[OutputValue]')

PrivateSubnet1RouteTableId=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet1RouteTableId`].[OutputValue]')

PrivateSubnet2RouteTableId=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet2RouteTableId`].[OutputValue]')

echo -e "VPCID=$VPCID\nVPC_CIDR=$VPC_CIDR\nPublicSubnet1Id =$PublicSubnet1Id\nPublicSubnet2Id =$PublicSubnet2Id\nPrivateSubnet1Id=$PrivateSubnet1Id\nPrivateSubnet2Id=$PrivateSubnet2Id\nPrivateSubnet1RouteTableId=$PrivateSubnet1RouteTableId \nPrivateSubnet2RouteTableId=$PrivateSubnet2RouteTableId"

(7)-(c) アクティベーションキーの取得

ファイルゲートウェイから、 アクティベーションキーを取得します。

(i)アクティベーション用のURL作成

#構成情報取得
GatewayIP=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=Fgw" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].PrivateIpAddress' )
REGION=$(aws --profile ${PROFILE} configure get region)
VPCEndpointDNSname=$(aws --profile ${PROFILE} --output text \
    ec2 describe-vpc-endpoints \
        --filters \
            "Name=service-name,Values=com.amazonaws.ap-northeast-1.storagegateway" \
            "Name=vpc-id,Values=${VPCID}" \
    --query 'VpcEndpoints[*].DnsEntries[0].DnsName' );
echo ${GatewayIP} ${REGION} ${VPCEndpointDNSname}

#アクティベーション先のURL生成
ACTIVATION_URL="http://${GatewayIP}/?gatewayType=FILE_S3&activationRegion=${REGION}&vpcEndpoint=${VPCEndpointDNSname}&no_redirect"
echo ${ACTIVATION_URL}

(ii)アクティベーションキーの取得

ACTIVATION_KEY=$(curl "${ACTIVATION_URL}")
echo ${ACTIVATION_KEY}

参考: https://docs.aws.amazon.com/ja_jp/storagegateway/latest/userguide/gateway-private-link.html#GettingStartedActivateGateway-file-vpc

(7)-(d) ゲートウェイアクティベーション

ファイルゲートウェイアクティベーションします。

REGION=$(aws --profile ${PROFILE} configure get region)
aws --profile ${PROFILE} \
    storagegateway activate-gateway \
        --activation-key ${ACTIVATION_KEY} \
        --gateway-name SgPoC-Gateway-1 \
        --gateway-timezone "GMT+9:00" \
        --gateway-region ${REGION} \
        --gateway-type FILE_S3

#作成したGatewayのARN取得
# atewayState"が "RUNNING"になるまで待つ
#ARNがわからない場合は、下記コマンドで確認
#aws --profile ${PROFILE} storagegateway list-gateways
aws --profile ${PROFILE} storagegateway describe-gateway-information --gateway-arn <GATEWAYのARN>

<参考 gateway-typeの説明>

  • "STORED" : VolumeGateway(Store type)
  • "CACHED" : VolumeGateway(Cache tyep)
  • "VTL" : VirtualTapeLibrary
  • "FILE_S3": File Gateway

(7)-(e) ローカルディスク設定

#ローカルストレージの確認
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')
DiskIds=$(aws --profile ${PROFILE} --output text storagegateway list-local-disks --gateway-arn ${GATEWAY_ARN} --query 'Disks[*].DiskId'| sed -e 's/\n/ /')
echo ${DiskIds}

#ローカルストレージの割り当て
aws --profile ${PROFILE} storagegateway \
    add-cache \
        --gateway-arn ${GATEWAY_ARN} \
        --disk-ids ${DiskIds}

#ローカルストレージの確認
# "DiskAllocationType"が"CACHE STORAGE"で、"DiskStatus"が"present"であることを確認
aws --profile ${PROFILE} --output text \
    storagegateway list-local-disks \
        --gateway-arn ${GATEWAY_ARN}

参照:https://docs.aws.amazon.com/ja_jp/storagegateway/latest/userguide/create-gateway-file.html

(7)-(f) CloudWatch Logs設定

ファイルゲートウェイインスタンスから、Logsへのログ出力設定を行います。LogsにはS3へのDenyAccess情報などが記録されます。

#情報取得
GATEWAY_ARN=$(aws --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ match($0, /arn:aws:storagegateway:\S*/); print substr($0, RSTART, RLENGTH) }')
#Region=$(curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone | sed -e 's/.$//')
#AccuntId=$(curl -s http://169.254.169.254/latest/meta-data/identity-credentials/ec2/info|awk '/AccountId/{ i=gsub( /"/, "", $3); print $3}')
echo "GATEWAY_ARN=$GATEWAY_ARN  Region=${Region} AccuntId=${AccuntId}"

#CloudWatch Logsログストリーム作成
LOG_GROUP_NAME=${SgPoC-Gateway-1}
aws --profile ${PROFILE} \
    logs create-log-group \
        --log-group-name ${LOG_GROUP_NAME};

LOG_GROUP_ARN=$(aws --profile ${PROFILE} --output text \
    logs describe-log-groups \
        --log-group-name-prefix ${LOG_GROUP_NAME} \
    --query 'logGroups[].arn' );

#ファイルゲートウェイへの出力先ロググループ設定
aws --profile ${PROFILE} \
    storagegateway update-gateway-information \
        --gateway-arn ${GATEWAY_ARN} \
        --cloud-watch-log-group-arn ${LOG_GROUP_ARN}

(8) File Gateway - ファイル共有設定(NFS)

NFSのファイル共有を作成し、LinuxクライアントからNFS接続します。 f:id:nopipi:20191206021823p:plain:w600

(8)-(a)情報の確認と設定(S3, IAMロール、ゲートウェイ)

Linux Managerで下記設定を実行します。 上記(6)で作成したS3バケット以外のバケットを利用する場合は、(6)-(c)で作成した、"StorageGateway-S3AccessRole"ロールのリソース句に該当のS3バケットを追加してください。

#情報取得
BUCKET_NAME=<バケット名を個別に設定>
BUCKETARN="arn:aws:s3:::${BUCKET_NAME}"

ROLE="StorageGateway-S3AccessRole"
ROLEARN=$(aws --profile  ${PROFILE} --output text \
    iam get-role \
        --role-name "StorageGateway-S3AccessRole" \
    --query 'Role.Arn')

GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')
CLIENT_TOKEN=$(cat /dev/urandom | base64 | fold -w 38 | sed -e 's/[\/\+\=]/0/g' | head -n 1)
KEY_ARN=$( aws --profile ${PROFILE} --output text \
    kms describe-key \
        --key-id "alias/Key_For_S3Buckets"  \
    --query 'KeyMetadata.Arn')
echo -e "BUCKET=${BUCKETARN}\nROLE_ARN=${ROLEARN}\nGATEWAY_ARN=${GATEWAY_ARN}\nCLIENT_TOKEN=${CLIENT_TOKEN}\nKEY_ARN=${KEY_ARN}"

(8)-(b)ファイル共有(NFS)の作成

#NFSデフォルト設定
#設定はこちらを参照: https://docs.aws.amazon.com/storagegateway/latest/APIReference/API_NFSFileShareDefaults.html#StorageGateway-Type-NFSFileShareDefaults-OwnerId
FILE_SHARE_DEFAULT_JSON='{
    "FileMode": "0666",
    "DirectoryMode": "0777",
    "GroupId": 65534,
    "OwnerId": 65534
}'

#NFSファイル共有作成
aws --profile ${PROFILE} storagegateway \
    create-nfs-file-share \
        --client-token ${CLIENT_TOKEN} \
        --gateway-arn "${GATEWAY_ARN}" \
        --location-arn "${BUCKETARN}" \
        --role "${ROLEARN}" \
        --nfs-file-share-defaults "${FILE_SHARE_DEFAULT_JSON}" \
        --client-list "0.0.0.0/0" \
        --squash "RootSquash" \
        --kms-encrypted \
        --kms-key ${KEY_ARN} ;

(8)-(c) Linuxクライアントからの接続

作業端末から、Linuxクライアントに接続し、NFSマウントを実行します。

(i) 作業端末からMgr-Linuxへのログイン(ここではMAC前提)

以後の作業で、Linux-Clientを利用するため、sshログインとセットアップを行います。

LinuxClinetIP=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=Linux-Client" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].PublicIpAddress' )

ssh-add
ssh -A ec2-user@${LinuxClinetIP}

Linux-Clientにユーザ"ec2-user"でログイン完了した後に、下記コマンドでNFSマウントします。

sudo -i

#情報の設定と確認
FGWIP=<ファイルGWのPrivateIPを設定>
EXPORT_PATH=<ファイル共有のエクスポートパス情報を(/から始まる情報)を設定>
echo -e "FGWIP=${FGWIP}\nEXPORT_PATH=${EXPORT_PATH}"

情報確認後、マウント設定を行います。

mkdir /nfs

#/etc/fstabへのマウントポイント追加 $ mount実行
echo "${FGWIP}:${EXPORT_PATH}   /nfs     nfs    nolock,hard       0   2" >> /etc/fstab

mount -a
df

(9) File Gateway - ファイル共有設定(SMB - Guest Access)

ActiveDirectoryを利用しない、ゲストアクセスタイプのSMBのファイル共有を作成し、WindowsクライアントからSMB(ゲストアクセス)接続します。 Linux Managerで下記設定を実行します。 f:id:nopipi:20191206021911p:plain:w600

(9)-(a) SMB設定(SMBSecurityStrategy)

GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')

aws --profile ${PROFILE} storagegateway \
    update-smb-security-strategy \
        --gateway-arn ${GATEWAY_ARN} \
        --smb-security-strategy MandatoryEncryption

(9)-(b) ゲストアクセス用の SMB ファイル共有を設定

PASSWORD="HogeHoge@"
aws --profile ${PROFILE} storagegateway \
    set-smb-guest-password \
        --gateway-arn ${GATEWAY_ARN} \
        --password ${PASSWORD}

(9)-(c) SMBファイル共有

上記(6)で作成したS3バケット以外のバケットを利用する場合は、(6)-(c)で作成した、"StorageGateway-S3AccessRole"ロールのリソース句に該当のS3バケットを追加してください。

#情報取得
BUCKET_NAME=<バケット名を個別に設定>
BUCKETARN="arn:aws:s3:::${BUCKET_NAME}"

ROLE="StorageGateway-S3AccessRole"
ROLEARN=$(aws --profile  ${PROFILE} --output text \
    iam get-role \
        --role-name "StorageGateway-S3AccessRole" \
    --query 'Role.Arn')
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')
CLIENT_TOKEN=$(cat /dev/urandom | base64 | fold -w 38 | sed -e 's/[\/\+\=]/0/g' | head -n 1)
KEY_ARN=$( aws --profile ${PROFILE} --output text \
    kms describe-key \
        --key-id "alias/Key_For_S3Buckets"  \
    --query 'KeyMetadata.Arn')
echo -e "BUCKET=${BUCKETARN}\nROLE_ARN=${ROLEARN}\nGATEWAY_ARN=${GATEWAY_ARN}\nCLIENT_TOKEN=${CLIENT_TOKEN}\nKEY_ARN=${KEY_ARN}"

#実行
aws --profile ${PROFILE} storagegateway \
    create-smb-file-share \
        --client-token ${CLIENT_TOKEN} \
        --gateway-arn "${GATEWAY_ARN}" \
        --location-arn "${BUCKETARN}" \
        --role "${ROLEARN}" \
        --object-acl bucket-owner-full-control \
        --default-storage-class S3_STANDARD \
        --guess-mime-type-enabled \
        --authentication GuestAccess \
        --kms-encrypted \
        --kms-key ${KEY_ARN} ;

(9)-(d) Windows-ClinetからのSMBアクセス

Windows ClinetにRDPログインし、SMB接続をします。 説明は省略します。

(10) File Gateway - ファイル共有設定(SMB - Active Directory)

ファイルゲートウェイとクライアントのWindowsサーバをADに参加させ、AD認証でファイル共有する手順です。この手順ではADに AWS Directory ServiceのAWS Managed Microsoft ADを利用しています。 f:id:nopipi:20191206022012p:plain:w600

(10)-(a) AWS Managed Microsoft AD作成

作業端末で作業を実施ます。

(i)情報収集

PROFILE=<プロファイルを指定>

#構成情報取得
VPCID=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`VpcId`].[OutputValue]')

PublicSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PublicSubnet2Id`].[OutputValue]')

PrivateSubnet1Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet1Id`].[OutputValue]')

PrivateSubnet2Id=$(aws --profile ${PROFILE} --output text \
    cloudformation describe-stacks \
        --stack-name SGWPoC-VPC \
        --query 'Stacks[].Outputs[?OutputKey==`PrivateSubnet2Id`].[OutputValue]')

echo -e "VPCID=$VPCID\nPublicSubnet2Id=${PublicSubnet2Id}\nPrivateSubnet1Id=$PrivateSubnet1Id\nPrivateSubnet2Id=$PrivateSubnet2Id\n"

(ii) Managed Microsoft AD(MAD)作成のための情報設定

AD_NAME="sgwpoc.local"
AD_EDITION="Standard"           #Enterprise or Standard
KEYNAME="CHANGE_KEY_PAIR_NAME"  #環境に合わせてキーペア名を設定してください。 
AD_PASSWORD="$( cat /dev/urandom | base64 |tr -dc 'a-zA-Z0-9' | fold -w 16 | head -n 1)${RANDOM}"

#パスワードを控える
echo ${AD_PASSWORD}  #¥表示されたパスワードは後の手順で利用するためメモしておく
  • ADのパスワードは、8~64 文字で指定し、「admin」という語は含めず、英小文字、英大文字、数字、特殊文字の 4 つのカテゴリのうちの 3 つを含める必要があります。

(iii) Managed Microsoft AD(MAD)作成

MADを作成します。MADのENIに付与されるセキュリティーグループは、ADサービスが自動的に作成します。

# MAD作成
aws --profile ${PROFILE} ds \
    create-microsoft-ad \
        --name "${AD_NAME}" \
        --short-name "SgwPoC" \
        --description "AD for StorageGateway PoC" \
        --password "${AD_PASSWORD}" \
        --edition "${AD_EDITION}" \
        --vpc-settings "VpcId=${VPCID},SubnetIds=${PrivateSubnet1Id},${PrivateSubnet2Id}" ;

(10)-(b) AD管理用のWindows-AD-Mgr作成

(i)構成情報取得

KEYNAME="CHANGE_KEY_PAIR_NAME"  #環境に合わせてキーペア名を設定してください。 
INSTANCE_TYPE="t2.micro"

#以下は自動取得
WIN2019_AMIID=$(aws --profile ${PROFILE} --output text \
    ec2 describe-images \
        --owners amazon \
        --filters 'Name=name,Values=Windows_Server-2019-Japanese-Full-Base-????.??.??' \
                  'Name=state,Values=available' \
        --query 'reverse(sort_by(Images, &CreationDate))[:1].ImageId' ) ;

RDP_SG_ID=$(aws --profile ${PROFILE} --output text \
        ec2 describe-security-groups \
                --filter 'Name=group-name,Values=RdpSG' \
        --query 'SecurityGroups[].GroupId');

#設定確認
echo -e "KEYNAME=${KEYNAME}\nINSTANCE_TYPE=${INSTANCE_TYPE}\nWIN2019_AMIID=${WIN2019_AMIID}\nRDP_SG_ID=${RDP_SG_ID}"

(ii) インスタンス作成

#タグ設定
TAGJSON='
[
    {
        "ResourceType": "instance",
        "Tags": [
            {
                "Key": "Name",
                "Value": "Windows-AD-Mgr"
            }
        ]
    }
]'

# サーバの起動
aws --profile ${PROFILE} \
    ec2 run-instances \
        --image-id ${WIN2019_AMIID} \
        --instance-type ${INSTANCE_TYPE} \
        --key-name ${KEYNAME} \
        --subnet-id ${PublicSubnet2Id} \
        --security-group-ids ${RDP_SG_ID} \
        --associate-public-ip-address \
        --tag-specifications "${TAGJSON}" ;

(10)-(c) AD管理用のWindows-AD-MgrへのAD管理ツールセットアップとAD参加

  • Windows-AD-MgrにRDPでログインします。
  • AD管理に必要なツールをPowerShellでインストールします。
Import-Module ServerManager
Install-WindowsFeature -Name GPMC,RSAT-AD-PowerShell,RSAT-AD-AdminCenter,RSAT-ADDS-Tools,RSAT-DNS-Server
  • 以下の手順でドメイン参加させます。
  • DNSの参照先をADのDNSアドレス(IPアドレス)に変更します。
Get-NetAdapter | Set-DnsClientServerAddress -ServerAddresses <ADの1つ目のIPアドレス>,<ADの2つ目のIPアドレス>

#設定の確認
Get-NetAdapter | Get-DnsClientServerAddress
#ドメインに参加させます。実行すると adminのパスワードを聞かれるので、AD作成時(create-microsoft-ad)に設定したパスワードを入力します。
Add-Computer -DomainName sgwpoc.local -Credential admin

#有効にするためリブートします。
Restart-Computer
  • RDPで、ドメインsgwpoc.local、ユーザAdminでログインします。
  • Power Shellを起動しsgwpoc.localドメイン情報を参照できることを確認します。
 Get-ADDomain -Identity sgwpoc.local

(10)-(d) Windowsクライアントのドメイン参加

Windows-Clientインスタンスについて、(10)-(C)と同じ手順で、ADに参加させます。

(10)-(e) ファイルゲートウェイドメイン参加

(i) ファイルゲートウェイDNS設定変更

ファイルゲートウェイDNS参照先を、ADに変更します。変更は、ファイルゲートウェイssh接続して変更します。

#Linux-Mgrにログイン
MgrIP=$(aws --profile ${PROFILE} --output text \
    ec2 describe-instances  \
        --filters "Name=tag:Name,Values=Manager-Linux" "Name=instance-state-name,Values=running" \
    --query 'Reservations[*].Instances[*].PublicIpAddress' )

ssh-add
ssh -A ec2-user@${MgrIP}
#ゲートウェイのIP取得
GatewayIP=$(aws --output text ec2 describe-instances --query 'Reservations[*].Instances[*].PrivateIpAddress' --filters "Name=tag:Name,Values=Fgw" "Name=instance-state-name,Values=running")

#sshログイン
ssh admin@${GatewayIP}

ファイルゲートウェイの下記メニューが表示されたら、以下の箇条書きの内容を参考にDNS設定を行います。

   AWS Storage Gateway - Configuration

    #######################################################################
    ##  Currently connected network adapters:
    ##
    ##  eth0: 10.1.163.138 
    #######################################################################

    1: HTTP/SOCKS Proxy Configuration
    2: Network Configuration
    3: Test Network Connectivity
    4: View System Resource Check (0 Errors)
    5: License Information
    6: Command Prompt

    Press "x" to exit session

        Enter command: 
  • Network Configurationを選び、Edit DNS Configurationを選択
  • Available adapterseth0を入力
  • Assign by DHCP [y/n]:は、DNSを静的設定するためnを選択
  • Enter primary DNSEnter secondary DNSに、ADのDNSアドレスIPアドレスを指定
  • Apply config [y/n]:yで設定反映する
  • 終了する

(ii)ゲートウェイのAD参加

#一般設定
PROFILE="default"
#AD設定
AD_DOMAIN_NAME="sgwpoc.local"
AD_USER="admin"
AD_PASSWORD="< (10)-(a) (ii)で設定したパスワード>"

#ゲートウェイID取得
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')

# 実行
aws --profile ${PROFILE} \
    storagegateway join-domain \
        --gateway-arn ${GATEWAY_ARN} \
        --domain-name ${AD_DOMAIN_NAME} \
        --user-name ${AD_USER} \
        --password ${AD_PASSWORD} ; 

(iii) SMBファイル共有(AD認証)の作成

上記(6)で作成したS3バケット以外のバケットを利用する場合は、(6)-(c)で作成した、"StorageGateway-S3AccessRole"ロールのリソース句に該当のS3バケットを追加してください。

#情報取得
BUCKET_NAME=storagegw-bucket-smb-ad #<バケット名を個別に設定>
BUCKETARN="arn:aws:s3:::${BUCKET_NAME}"

ROLE="StorageGateway-S3AccessRole"
ROLEARN=$(aws --profile  ${PROFILE} --output text \
    iam get-role \
        --role-name "StorageGateway-S3AccessRole" \
    --query 'Role.Arn')
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')
CLIENT_TOKEN=$(cat /dev/urandom | base64 | fold -w 38 | sed -e 's/[\/\+\=]/0/g' | head -n 1)
KEY_ARN=$( aws --profile ${PROFILE} --output text \
    kms describe-key \
        --key-id "alias/Key_For_S3Buckets"  \
    --query 'KeyMetadata.Arn')
echo -e "BUCKET=${BUCKETARN}\nROLE_ARN=${ROLEARN}\nGATEWAY_ARN=${GATEWAY_ARN}\nCLIENT_TOKEN=${CLIENT_TOKEN}\nKEY_ARN=${KEY_ARN}"

#実行
aws --profile ${PROFILE} storagegateway \
    create-smb-file-share \
        --client-token ${CLIENT_TOKEN} \
        --gateway-arn "${GATEWAY_ARN}" \
        --location-arn "${BUCKETARN}" \
        --role "${ROLEARN}" \
        --object-acl bucket-owner-full-control \
        --default-storage-class S3_STANDARD \
        --guess-mime-type-enabled \
        --authentication ActiveDirectory \
        --kms-encrypted \
        --kms-key ${KEY_ARN} ;

(iv) Windows-Clientからの接続

クライアントから接続確認します。

net use [WindowsDriveLetter]: \\10.1.163.138\storagegw-bucket-smb-ad

(11)運用その他

(11)-(a) キャッシュリフレッシュ

(i)対象バケット全体をリフレッシュする

FILE_SHARE_ARN="<操作したいファイル共有のARNを指定する>"
FOLDER_LIST='/'

#リフレッシュの実行(デフォルトの動作)
aws --profile ${PROFILE} storagegateway \
    refresh-cache \
        --file-share-arn ${FILE_SHARE_ARN} \
        --folder-list ${FOLDER_LIST} \
        --recursive ;
  • デフォルト(パラメータ未指定)では、下記条件でリフレッシュされる
    • --folder-list: "/"(バケット全体が対象)
    • --recursive: フォルダーリストで指定したフォルダから、登録済みのサブフォルダを再帰的にリフレッシュする

(ii)特定のフォルダをリフレッシュする

FILE_SHARE_ARN=ファイル共有のARNを設定
FOLDER_LIST='/test2 /test3'

#ファイル共有のARNは、
#”aws --profile ${PROFILE} storagegateway list-file-shares”で確認

#リフレッシュの実行(デフォルトの動作)
aws --profile ${PROFILE} storagegateway \
    refresh-cache \
        --file-share-arn ${FILE_SHARE_ARN} \
        --folder-list ${FOLDER_LIST} \
        --recursive ;

(11)-(b) ソフトウェアアップデート

(i)構成確認

#構成情報の取得
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')

#アップデート状況の確認
#"NextUpdateAvailabilityDate"と、"LastSoftwareUpdate"を確認します。
#"NextUpdateAvailabilityDate"が表示されない場合、現時点で予定されているアップデートはありません。
aws --profile ${PROFILE} storagegateway \
    describe-gateway-information \
        --gateway-arn ${GATEWAY_ARN};

(ii)ソフトウェアアップデート

aws --profile ${PROFILE} storagegateway \
    update-gateway-software-now \
        --gateway-arn ${GATEWAY_ARN};

(iii)アップデート確認

#アップデート状況の確認
#"NextUpdateAvailabilityDate"が表示されなくなり、"LastSoftwareUpdate"が直近の時間に変更されていることを確認します。
aws --profile ${PROFILE} storagegateway \
    describe-gateway-information \
        --gateway-arn ${GATEWAY_ARN};

(11)-(c) イベント連携(リフレッシュ完了通知のイベント連携)

f:id:nopipi:20191206022228p:plain:w600

(i)SNS準備

以下の作業は、AdministratorAccessのIAMポリシーがある作業端末で実行します。

EMAIL_ADDRESS='email@address'

#Topicの作成
TOPIC_ARN=$(aws --profile ${PROFILE} --output text \
    sns create-topic \
        --name Sgw-Topic)

#Sbscribeでメールを設定
aws --profile ${PROFILE} sns \
    subscribe \
        --topic-arn ${TOPIC_ARN} \
        --protocol email \
        --notification-endpoint ${EMAIL_ADDRESS}
        
#設定したメールアドレスに、確認メールが届くので"Confirm subscription"をクリックし、承認します。

#メール送信テスト
aws --profile ${PROFILE} sns \
    publish \
        --topic-arn ${TOPIC_ARN} \
        --message 'Hello World!!' ;

(ii)CloudWatch Events Roule作成とSNS Topicリソースポリシー変更

Storage Gatewayのリフレッシュキャッシュの完了通知を受けて、作成したSNS Topicに連携する、Eventsのルールを作成します。

#構成情報の取得
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')

FILE_SHARE_ARNs=$(aws --profile ${PROFILE} --output text \
    storagegateway list-file-shares \
        --gateway-arn ${GATEWAY_ARN} \
    --query FileShareInfoList[].FileShareARN )

TOPIC_ARN=$( aws --profile ${PROFILE} --output text \
    sns list-topics | awk '/Sgw-Topic/{print $2}' )

ACCOUNT_ID=$(aws --profile ${PROFILE} --output text \
    sts get-caller-identity --query 'Account')

#ルールパターン用JSON生成
EVENT_PATTERN='
{
  "source": [
    "aws.storagegateway"
  ],
  "resources":['"$( echo $(for i in $FILE_SHARE_ARNs;do echo '"'"${i}"'",';done)|sed -e 's/,$//')"'
  ],
  "detail-type": [
    "Storage Gateway Refresh Cache Event"
  ]
}'

# Event Rule作成
aws --profile ${PROFILE} --output text events \
    put-rule \
        --name SgwPoc-Finish-RefleshCache \
        --description "Receive notification of completion of specified file gateways RefreshCache and notify SNS topic" \
        --event-pattern "${EVENT_PATTERN}" \
        --schedule-expression "" \
        --state ENABLED ;

#ルールにターゲット設定
aws --profile ${PROFILE} events \
    put-targets \
        --rule SgwPoc-Finish-RefleshCache \
        --targets "Id=1,Arn=${TOPIC_ARN},Input=,InputPath=";

#SNS Topicのリソースポリシーにeventsのallowを追加
JSON='{
  "Version": "2012-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": "'"${TOPIC_ARN}"'",
      "Condition": {
        "StringEquals": {
          "AWS:SourceOwner": "'"${ACCOUNT_ID}"'"
        }
      }
    },
    {
      "Sid": "AWSEvents_SgwPoc-Finish-RefleshCache_1",
      "Effect": "Allow",
      "Principal": {
        "Service": "events.amazonaws.com"
      },
      "Action": "sns:Publish",
      "Resource": "'"${TOPIC_ARN}"'"
    }
  ]
}'
#
aws --profile ${PROFILE} sns \
    set-topic-attributes \
        --topic-arn ${TOPIC_ARN} \
        --attribute-name Policy \
        --attribute-value "${JSON}"

(iii)リフレッシュ実行

FILE_SHARE_ARN="<操作したいファイル共有のARNを指定する>"
FOLDER_LIST='/'

#リフレッシュの実行(デフォルトの動作)
aws --profile ${PROFILE} \
    storagegateway refresh-cache \
        --file-share-arn ${FILE_SHARE_ARN} \
        --folder-list ${FOLDER_LIST} \
        --recursive  ;

(11)-(d) イベント連携(ファイルアップロード完了通知)

(11)-(c)のリフレッシュ完了通知と同じSNS Topicを利用し、ファイルのアップロード完了通知を行います。本作業の前提として、(11)-(c)の設定済みであることとします。

(i) CloudWatch Events設定

#構成情報の取得
GATEWAY_ARN=$(aws --profile ${PROFILE} --output text storagegateway list-gateways |awk '/SgPoC-Gateway-1/{ print $4 }')

FILE_SHARE_ARNs=$(aws --profile ${PROFILE} --output text \
    storagegateway list-file-shares \
        --gateway-arn ${GATEWAY_ARN} \
    --query FileShareInfoList[].FileShareARN )

TOPIC_ARN=$( aws --profile ${PROFILE} --output text \
    sns list-topics | awk '/Sgw-Topic/{print $2}' )

#ルールパターン用JSON生成
EVENT_PATTERN='
{
  "source": [
    "aws.storagegateway"
  ],
  "resources":['"$( echo $(for i in $FILE_SHARE_ARNs;do echo '"'"${i}"'",';done)|sed -e 's/,$//')"'
  ],
  "detail-type": [
    "Storage Gateway File Upload Event"
  ]
}'

# Event Rule作成
aws --profile ${PROFILE} --output text events \
    put-rule \
        --name SgwPoc-Finish-File-Upload \
        --description "Receive notification of completion of specified file gateways files upload and notify SNS topic" \
        --event-pattern "${EVENT_PATTERN}" \
        --schedule-expression "" \
        --state ENABLED ;

#ルールにターゲット設定
aws --profile ${PROFILE} events \
    put-targets \
        --rule SgwPoc-Finish-File-Upload  \
        --targets "Id=1,Arn=${TOPIC_ARN},Input=,InputPath=";

(ii) アップロード完了通知設定

#必要に応じ、FILE_SHARE_ARNを設定
FILE_SHARE_ARN="<操作したいファイル共有のARNを指定する>"

#何らかファイルコピー実行と同時に、下記のnotify-when-uploadedを実行
aws --profile ${PROFILE} storagegateway \
    notify-when-uploaded \
        --file-share-arn ${FILE_SHARE_ARN};

Windows Server 2019へのWindows Subsystem for Linuxのセットアップと利用Tips

はじめに

Windows Subsystem for Linuxを使う必要があってPower Shellでセットアップした時のメモ。です。手順は、(1)Enable-WindowsOptionalFeatureでSubsystem for Linuxを有効かして、(2)Invoke-WebRequest でUbuntuイメージをダウンロードして、(3)Add-AppxPackageでダウンロードしたイメージをインストール、という流れです。
基本的には、Manually download Windows Subsystem for Linux distro packagesの手順に従ってセットアップしたものです。

セットアップ手順

Power Shellを起動

検索でキーワードPower Shellで探し、Power shellを起動します。

Subsystem for Linuxの有効化

Power Shell上でEnable-WindowsOptionalFeatureコマンド*1で Subsystem for Linuxを有効化します。

Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux
  • -Online : 指定した役割を有効化する
  • -FeatureName : 役割名の指定。ここでは `Microsoft-Windows-Subsystem-Linux`を指定

結果は、Get-WindowsOptionalFeatureで確認することができます。

Get-WindowsOptionalFeature -Online -FeatureName Microsoft-Windows-Subsystem-Linux

ディストリビューション(ubuntu)のダウンロード

Invoke-WebRequestコマンド*2で、Ubuntu18.04をUbuntu.appxというファイル名でダウンロードします。

Invoke-WebRequest -Uri https://aka.ms/wsl-ubuntu-1804 -OutFile Ubuntu.appx -UseBasicParsing

ディストリビューションのインストール

Add-AppxPackageコマンドでダウンロードしたUbuntuをインストールします。

Add-AppxPackage .\Ubuntu.appx

Ubuntuの起動

WindowsメニューからUbuntuを選び起動します。必要に応じてタスクバーなどにピン留めしておきます。

Tips

Ubuntsのインストール先

Ubuntsのイメージは下記リンク先の下にあります。(~はホームディレクトリで、Administratorの場合は、C:¥Users¥Administrator)

 cd ~/AppData/Local/Packages/CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc/

ドライブをマウントする

ローカルドライブ

ローカルドライブ(C:など)はデフォルトで、/mnt/cのようにマウント済みであるはずです。

リモートドライブのマウント

リモートのドライブは手動でmount -t drvfs <デバイス名> <マウントポイント>コマンドでマウントする必要があります。例えばF:ドライブにマウントされているリモートドライブをSubsystem for Linuxでマウントする場合は以下のように操作します。

sudo mkdir /mnt/f
sudo mount -t drvfs F: /mnt/f
mount 

S3のCopyObjectによるコピーの並列実行によるコピー時間短縮の検証

はじめに

検証用に、S3バケット上に1MBのファイル多量に準備したいという話があり、色々試行錯誤した経緯のメモです。
アプローチとしては、オリジナルファイルをインスタンスから都度アップロードするのはインスタンスに負荷がかかり(特にCPU、ネットワーク)効率が上がらないため、マスターデータをあらかじめS3にアップロードし、S3上にあるマスターファイルをcopy-object APIを利用し、実際のコピー処理をS3にオフロードさせるようにしています。
またCopyObject APIは、S3でのコピー完了を持って応答が帰ってくる*1ため、極力並列でAPIを実行すること処理時間の短縮を図っています。

なお検証結果から導かれるのCopyObjectによるコピーの並列実行のポイントは、以下の通りです。

  • CopyObjectを利用したバケット内でのオブジェクトコピーによりコピー処理を高速化可能
  • S3のプレフィックス単位のAPIロットリング PUT/DELETEの3500回/秒が上限の目安として、多重度を調整
  • (今回の構成では)、多重度800で、インスタンスタイプは8xlarge(32vcpu)が効率的
  • S3のスロットリング は、boto3の再実行回数引き上げで対処
  • 上記対応で、実測 バケット内でのCopyObjectによる1MBファイルのコピー、3500回/秒までの実行を確認

検証構成

環境構成概要図

f:id:nopipi:20191014234500p:plain:w500

データ構成

マスターデータ

マスターデータは同一プレフィクスへのアクセス集中を避けるためファイルごとに、"XXXXXXXXXX-original-data"(XXXXXXXXXXは、ランダムな文字列)というプレフィクスをつけて(フォルダに)格納します。
GETは、 「プレフィクス毎に秒間5,500 回以上の GET/HEAD リクエスト」となるので念の為それを意識した構成としています。(結果として、PUTのスロットリングが先に来ているので、ランダムな文字列は不要かもしれないです)

コピー先ディレクトリ構成

使い勝手の観点から、「/年/月/日/時刻/」というプレフィクス構成としています。

f:id:nopipi:20191015030029p:plain:w300

検証コード

検証コードはこちらです。
github.com

環境準備

(1)AWSリソース準備

  • IAMユーザ
    • EC2インスタンスにセットするIAMユーザ(理由は文末の「その他」参照)
    • IAMユーザには下記のAWS 管理ポリシーを付与
      • AmazonS3FullAccess
      • ReadOnlyAccess
  • KMS
    • マネコンで、キーポリシーの「キーユーザー」に上記IAMユーザを追加
  • S3
  • 検証用S3バケット
    • デフォルト暗号化: KMS暗号化(作成したKMSのCMK利用)
    • バージョニング:なし
    • アクセスログ取得: なし
  • VPC
    • S3のVPCEndpointがあるVPC
  • インスタンス
    • 検証プログラム実行用
    • OS: Amazon Linux2
    • インスタンスタイプ: とりあえず、m5a.largeあたり(テスト内でタイプ変更)

(2)インスタンス初期設定

# AWS CLIとboto3(AWS Python SDK)のセットアップ
curl -o "get-pip.py" "https://bootstrap.pypa.io/get-pip.py" 
sudo python get-pip.py
sudo pip install boto3
sudo pip install --upgrade awscli

# AWS CLI設定
aws configure set aws_access_key_id <作成したIAMユーザのアクセスキー>
aws configure set aws_secret_access_key <作成したIAMユーザのシークレットキー>
aws configure set region ap-northeast-1
aws configure set output json

# 検証用プログラムのダウンロード
sudo yum -y install git
git clone https://github.com/Noppy/GenerateCsvOfTestFilesList.git

(3)テスト用のマスターファイルの作成とS3アップロード

#マスターファイル用のディレクトリ作成と移動
mkdir master && cd master ; pwd

#マスターファイル生成
dd if=/dev/urandom of=test-001MB.dat bs=1024 count=1024

#マスターファイルのS3アップロード
Profile=default
MasterFileList="test-001MB.dat"
Bucket=s3-100million-files-test

for src in ${MasterFileList}
do
    hash=$( cat /dev/urandom | base64 | fold -w 10 | sed -e 's/[\/\+\=]/0/g' | head -n 1 )
    aws --profile=${Profile} s3 cp ${src} "s3://${Bucket}/${hash}-original-data/"
done

(4)CSV生成pythonの設定ファイル作成

# configration JSON用のテンプレート生成
cd ~/GenerateCsvOfTestFilesList

#要件に応じて設定
NumberOfFiles="219120"
StartDate="2015/1/1"
EndDate="2019/12/31"
Bucket=s3-100million-files-test

#Jsonファイル生成
./gen_json.sh "${NumberOfFiles}" "${StartDate}" "${EndDate}" "${Bucket}"

#作成したJSONファイルの確認
cat config.json

(5)ターゲット用CSV生成

#CSV生成
./generate_testfiles_list.py

#生成したCSVの確認
wc -l list_of_copy_files.csv   #行数確認(NumberOfFilesと同じ行数が作成される)

検証-1 AWS CLIpythonクラッチプログラムの比較

検証概要

AWS CLI(aws s3 cp)でオブジェクトごとにCLIバケット間コピーした場合と、Pythonで作成したプログラム(clinetで取得して、for文でCopyObjectを実行)した場合の実行速度のを比較します。

検証前提

  • オブジェクト
    • コピーオブジェクト数:10800個(1フォルダに5オブジェクト、2160のフォルダに格納)
    • オブジェクトサイズ: 1MB
  • 実行方法

検証手順

(1)検証用のターゲットリスト生成

NumberOfFiles="10800"
StartDate="2015/1/1"
EndDate="2015/3/31"
Bucket=s3-100million-files-test

#JSON作成
./gen_json.sh "${NumberOfFiles}" "${StartDate}" "${EndDate}" "${Bucket}"

#ターゲットリスト作成
./generate_testfiles_list.py
wc -l list_of_copy_files.csv

(2)検証実行
(2)-(a) AWS CLIでの実行
実行し、timeの"real"の時間を取得

time awk -F ',' -e '{print $1; print $2}' ./list_of_copy_files.csv | xargs -P 100 -L 2 aws s3 cp

(2)-(b) Pythonでの実行
test1_result_summary.csvに出力される実行時間を取得

./S3_CopyObject_ParallelExecution.sh 100 test1_result_summary.csv

検証結果

結果は以下の通りです。Pythonクラッチプログラムの方が圧倒的に早いです。AWS CLIはオブジェクト都度CLI実行しているので、オーバヘッド分*2やはり処理が重くなります。

実行方法 実行多重度 コピーオブジェクト数 実行時間 秒間実行数
AWS CLI 100 10800 364.8 秒 29.6 回/秒
Python 100 10800 19.6 秒 551.0 回/秒

一方、今回のPythonクラッチプログラムは、AWSのセッション取得後はfor文でひたすらCopyObjectを実行し続ける分、AWS CLIと比較すると圧倒的に早いです。

検証-2 インスタンスタイプと並列度の検証

検証概要

ここでは、検証-1で利用したPythonプログラム、適正な実行多重度と実行するインスタンスの適正なサイズ(vcpu数)を見極めることを目的としています。

検証前提

  • オブジェクト
    • コピーオブジェクト数:219120個(1フォルダに5オブジェクト、43824のフォルダに格納)
    • オブジェクトサイズ: 1MB
  • インスタンス
    • m5a_4xlarge(16vcpu), m5a_8xlarge(32vcpu), m5a_12xlarge(48vcpu)を利用
  • 実行方法

検証手順

(1)ulimitの増加設定
OSのulimitデフォルトでは、ユーザが同時実行可能なプロセス(nproc)は1024、同時オープンファイル(nofile)は4096です。このままでは、多重実行の検証中にリソース不足でエラーが発生する恐れがあるため、ulimitの設定を引き上げます。

sudo -i
cat > /etc/security/limits.d/99-s3-test.conf
*          hard    nofile    500000
*          soft    nofile    500000

*          hard    nproc     100000
*          soft    nproc     100000

別コンソールで、sshログインし下記コマンドで設定反映を確認
ulimit -Ha

(2)検証用のターゲットリスト生成

NumberOfFiles="219120"
StartDate="2015/1/1"
EndDate="2019/12/31"
Bucket=s3-100million-files-test
#JSON作成
./gen_json.sh "${NumberOfFiles}" "${StartDate}" "${EndDate}" "${Bucket}"

#ターゲットリスト作成
./generate_testfiles_list.py
wc -l list_of_copy_files.csv

(3)検証実行
インスタンスタイプを変更(都度再起動)しながら、下記コマンドを実行します。
検証結果は、test_3_results_summary.csvに集約

nohup ./test_3_s3tos3_python_parallels.sh &

検証結果

(1) 多重度とCopyObject API実行回数
インスタンスタイプをあげても処理時間の平均3000〜3500回/秒あたりで頭打ちとなり、それ以上多重度をあげるとS3のスロットリングが発生します。*3

f:id:nopipi:20191014230750p:plain:w500

並列度数CopyObject実行数( 回/秒 )
m5a_4xlarge(16vcpu)m5a_8xlarge(32vcpu)m5a_12xlarge(48vcpu)
100654.1695.6672.1
2001259.31320.01328.0
4001767.12407.92407.9
8001660.03043.33844.2
1000N/A2213.33652.0
1500N/A2577.9
(SlowDown発生)
2331.1
(SlowDown発生)
2000N/A2282.5
(SlowDown発生)
2672.2
(SlowDown発生)
(2) 多重度の上限とインスタンスタイプの妥当性
m5a.12xlarge(48vcpu)で、800多重度よりあげてもCPU利用率は80%で頭打ちであることと、前のAPI秒間実行回数結果から、多重度を800以上あげても今回の構成では、S3がネックとなり、それ以上処理能力をあげることができないことがわかります。そのことから多重度の上限は、800あたりであると判断します。

次にその多重度800を実行するのに最適なインスタンスタイプについて、m5a.12xlarge(48vcpu)ではCPUに余力があることから、その一つ下のm5a.8xlarge(32vcpu)がもっとも効率よくインスタンスリソースを利用できていることがわかります。

f:id:nopipi:20191014233524p:plain:w500

f:id:nopipi:20191014233536p:plain:w500

検証考察

(1) S3のスロットリング

この検証から「S3のAPI実行回数、3000〜3500回/秒程度」を越えるとS3スロットリング(SlowDown)が発生していることが読み取れます。この内容から検索すると、S3の開発者ガイドの「ベストプラクティスの設計パターン: Amazon S3 パフォーマンスの最適化」に以下の記載がありました。この内容から今回の検証では「プレフィックスごとに 1 秒あたり 3,500 回以上の PUT/COPY/POST/DELETE リクエスト」に合致している可能性が高いと思われます。

アプリケーションは、Amazon S3 からストレージをアップロードおよび取得する際にリクエストパフォーマンスで 1 秒あたり何千ものトランザクションを簡単に達成できます。Amazon S3 は高いリクエスト率に自動的にスケールされます。たとえば、アプリケーションでバケット内のプレフィックスごとに 1 秒あたり 3,500 回以上の PUT/COPY/POST/DELETE リクエストと 5,500 回以上の GET/HEAD リクエストを達成できます。

S3は負荷に応じて内部で自動スケーリングしますが、自動スケーリングを上手く利用しさらなる改善を図るための方策として以下のものがあります。
具体的対応については、次の検証で検討します。

  • プレフィックス分割と、プレフィクス先頭へのランダム文字列挿入
  • スロースタートし段階的に多重度をあげる(指数的にあげていく)
  • アプリケーションでのリトライ処理の実装(SDKではリトライ回数4回(デフォルト))

参考情報

(2) KMSのスロットリング

KMSによる暗号化を利用している場合は、KMSのAPIロットリングにも注意が必要です。
KMSのCMK利用に関するAPI(Decrypt,Encryptなど)は、東京リージョンで「1アカウントあたり5500回/秒(カテゴリのAPIで共有)」になります。S3のPUT/COPY/POST/DELETEのAPIが 3,500 回/秒という目安があるので、他にKMSのAPIを多用するサービスと同時利用していなければそこまで留意しなくても良いと考えます。

参考情報

(3) 多重度とインスタンスの最適化

これまでの内容から、今回の構成でS3のバケット内でCopyObjectを実施するときの多重度とインスタンスタイプの組み合わせを以下とします。

検証-3 200万ファイルのランニング検証とS3スロットリング対策

検証概要

検証-2の10倍の200万ファイルで、安定的にAPIを発行できるかの確認と、S3スロットリング対策を検証します。

検証前提

  • オブジェクト
    • コピーオブジェクト数:2191200個(1フォルダに50オブジェクト、43824のフォルダに格納)
    • オブジェクトサイズ: 1MB
  • 実行方法
  • インスタンス: m5a_8xlarge(32vcpu)

検証 3-(a) S3スロットリング対策:プレフィクスでの対策

プレフィクスを「年/月/日/時」から、逆にし先頭にハッシュをつけることで分散処理するようにする対策です。
具体的にはS3格納先のプレフィクスを「XXX-時/日/月/年」とします。

手順
(1)検証用のターゲットリスト生成

NumberOfFiles="2191200"
StartDate="2015/1/1"
EndDate="2019/12/31"
Bucket=s3-100million-files-test
#JSON作成
./gen_json.sh "${NumberOfFiles}" "${StartDate}" "${EndDate}" "${Bucket}"

#ターゲットリスト作成
./generate_testfiles_list.py --reverse
wc -l list_of_copy_files.csv

(2)検証実行

nohup ./S3_CopyObject_ParallelExecution.sh 800 test_2million_summary.csv &

(3)検証結果
理由を正確には確認できないですが、プレフィクスを変更してもSlowDownの比率は大きく変わりませんでした。
もしかしたらオートスケールするまでのタイムラグがあったのかもしれませんが。

検証 3-(b) 再実行による対策

SlowDownが発生したときに再実行して対処する方策です。
アプリケーションで再実行のロジックを実装する手段もありますが、今回はAWS SDK(boto3)のコンフィグで再実行回数を引き上げて対応しています。
具体的には、client()の引数に設定します。

    # Get session
    config = Config(
        retries=dict(
            max_attempts = args.retry
        )
    )
    s3 = boto3.client('s3', config=config)

手順
(1)検証用のターゲットリスト生成

#ターゲットリスト作成
./generate_testfiles_list.py --reverse
wc -l list_of_copy_files.csv

(2)検証実行
(2)-(a) 再実行回数デフォルト

nohup ./S3_CopyObject_ParallelExecution.sh 800 test_2million_summary.csv &

(2)-(b) 再実行回数を10回そのまま実行

nohup ./S3_CopyObject_ParallelExecution.sh 800 test_2million_summary.csv 10 &

(3)検証結果
再実行回数を10回に引き上げSDKでリトライすることで全量コピーができるようになりました。

対象数再実行回数実行時間秒間実行回数実行結果(オブジェクト数)
成功失敗合計
219120046923166.5218841927812191200
219120046983139.3219011610842191200
219120046883184.9218967715232191200
2191200105623898.9219120002191200
2191200105563941.0219120002191200
2191200105633892.0219120002191200

まとめ

簡易的ですが、SDKの再実行回数の引き上げでSlowDownを抑えることができようになりました。

全体まとめ

今回のS3のCopyObjectによるコピーの並列実行によるコピー時間短縮の検証のまとめです。

  • AWS CLI(aws s3 cp)はオーバーヘッドが大きいため数十万以上のオブジェクトコピーには向かない
  • S3のプレフィックス単位のAPIロットリング PUT/DELETEの3500回/秒が上限の目安として、多重度を調整
  • (今回の構成では)、多重度800で、インスタンスタイプは8xlarge(32vcpu)が効率的
  • S3のスロットリング は、boto3の再実行回数引き上げで対処(プレフィクスは直ぐには効果が出ない?)

その他

検証で、インスタンスロールでなくIAMユーザを利用する理由

EC2インスタンス上に、IAMユーザのアクセスキーとキーポリシーを設定するのは、AWSのベストプラクティス(セキュリティ観点)ではバッドプラクティスとされています。
しかしインスタンスロールの場合、ロールのクレデンシャルをインスタンスメタデータから取得*4するのですが、今回のようにプロセスを数百起動しメタデータに同時アクセスすると同時アクセスエラーが発生しクレデンシャル取得に失敗します。
本番運用向けであれば、共通機能でクレデンシャルを取得しキャッシュして必要なプロセスからの要求に応じてクレデンシャルを渡すような実装をするのがいいのかもしれないですが実装コストがかかるため、今回はアクセスキーとシークレットキーを利用する方式にしました。アクセスキーとシークレットキーの場合は、"~/.aws/credentials"ファイルに格納されておりファイルへのアクセスであれば同時アクセスがいくらあってもエラーにはならないです。

*1:copy-object APIの記載: "If the copy is successful, you receive a response with information about the copied object."

*2:オーバヘッド:プロセス生成してpythonモジュールをロードし&解析して、AWSのクレデンシャル取得して、認証&セッション取得てという、CopyObject実行までに至る前処理

*3:"ERROR:root:An error occurred (SlowDown) when calling the CopyObject operation (reached max retries: 4): Please reduce your request rate."エラーが発生。S3から"HTTP 503 Slow Down 応答があったことを示唆。

*4:インスタンスロールのクレデンシャルは"http://169.254.169.254/latest/meta-data/iam/security-credentials/ロール名"で取得できます

IAMでサーバ証明書をインポートするCLI手順

ACM(AWS Certificate Manager)ではなく、IAMでサーバ証明書をインポートするときのCLI手順のメモです。

サーバ証明書のインポート

PROFILE=default

aws --profile ${PROFILE} iam upload-server-certificate \
    --server-certificate-name hoge.com \
    --certificate-body file://hoge.com.crt \
    --private-key file://hoge.com.key 

インポートしたサーバ証明書の確認

aws --profile ${PROFILE} iam  list-server-certificates

KMS暗号化されてるEC2インスタンスのAMI取得し別リージョンにコピーする検証(AWS CLI)

はじめに

KMS鍵(CMK)で暗号化したEBSを持つEC2インスタンスのAMIを取得し、別リージョンにコピーする場合、鍵がどうなるのかという確認をCLIで確認したエビデンスです。

結論としては、他リージョンにAMIをコピーする時にコピー先リージョンのCMKを指定することになります。
こちらのRDSの検証と同じ結果です。
nopipi.hatenablog.com

検証環境

  • この検証はAWS CLIをセットアップしたMacで実施
    • bash環境なので、Linuxなどbash環境下であれば実行はできると思います。
  • AWS CLIのプロファイルは設定済み前提
  • プロファイルのIAMロール(IAMユーザ)にはAdministratorAccessポリシーがある前提

AWS CLIによる検証手順

(1) 検証設定

CLIに渡すパラメータのいくつかを、シェルの変数として事前設定しています。

#作業端末のプロファイル指定(デフォルトでない場合適時変更して下さい)
PROFILE=ExSPoC

#東京リージョンの設定
# 実際に検証する環境に合わせて、RDSをデプロイする先のVPCのIPと、サブネットのIDを変更してください。
REGION_SRC=ap-northeast-1
REGION_SRC_VPCID=vpc-8e6c5ce9
REGION_SRC_SUBNET=subnet-48cfe313
REGION_SRC_AMZL2_AMI_ID=ami-0ff21806645c5e492
REGION_SRC_EC2_KEY_PAIR=KEY_PAIR_NAME

#シドニーリージョンの設定
# 実際に検証する環境に合わせて、RDSをデプロイする先のVPCのIPと、サブネットのIDを変更してください。
REGION_DST=ap-southeast-2
REGION_DST_VPCID=vpc-01cfe566
REGION_DST_SUBNET=subnet-1f292656
REGION_DST_EC2_KEY_PAIR=KEY_PAIR_NAME

AWS_ACCOUNT=$(
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    sts get-caller-identity --query 'Account' )

(2) KMSキー作成

EC2インスタンス(EBS)暗号化に利用するKMSキーを、コピー元リージョン、コピー先リージョンそれぞれで作成します。

(2)-(a) コピー元リージョンでのKMSキー作成
SRC_KEY_ID=$( \
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    kms create-key \
	    --description "CMK to test Ec2 instance encryption" \
	    --origin AWS_KMS \
	--query 'KeyMetadata.KeyId' )

aws --profile ${PROFILE} --region ${REGION_SRC} \
    kms create-alias \
	    --alias-name alias/Key_To_Test_Instance_Encription \
	    --target-key-id ${SRC_KEY_ID}
(2)-(b) コピー先リージョンでのKMSキー作成
DST_KEY_ID=$( \
aws --profile ${PROFILE} --region ${REGION_DST} --output text \
    kms create-key \
	    --description "CMK to test Ec2 instance encryption" \
	    --origin AWS_KMS \
	--query 'KeyMetadata.KeyId' )

aws --profile ${PROFILE} --region ${REGION_DST} \
    kms create-alias \
	    --alias-name alias/Key_To_Test_Instance_Encription \
	    --target-key-id ${DST_KEY_ID}

(3)コピー元リージョンのEC2作成

セキュリティーグループを作成し、EC2インスタンスを作成します。

(3)-(a) SecurityGroup作成
SRC_EC2_SG_ID=$( \
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    ec2 create-security-group \
        --group-name SgForEc2 \
        --description "SG for EC2 Instances" \
        --vpc-id ${REGION_SRC_VPCID} \
	--query 'GroupId' )

aws --profile ${PROFILE} --region ${REGION_SRC} \
    ec2 authorize-security-group-ingress \
        --group-id ${SRC_EC2_SG_ID} \
        --protocol tcp \
        --port 22 \
        --cidr 0.0.0.0/0
(3)-(b) EC2インスタンス作成
# Block-device-mappings用のJSON作成
JsonBlockDeviceMappings='
[
    {
        "DeviceName":"/dev/xvda",
        "Ebs":{
            "VolumeType": "gp2",
            "VolumeSize":8,
            "DeleteOnTermination":true,
            "Encrypted": true,
            "KmsKeyId": "'"${SRC_KEY_ID}"'"
        }
    }
]
'

# EC2インスタンスの起動
aws --profile ${PROFILE} --region ${REGION_SRC} \
    ec2 run-instances \
        --count 1 \
        --instance-type t2.micro \
        --image-id ${REGION_SRC_AMZL2_AMI_ID} \
        --key-name ${REGION_SRC_EC2_KEY_PAIR} \
        --monitoring Enabled=true \
        --security-group-ids ${SRC_EC2_SG_ID} \
        --subnet-id ${REGION_SRC_SUBNET} \
        --associate-public-ip-address \
        --block-device-mappings "${JsonBlockDeviceMappings}" \
        --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=EC2-KMS-TEST-SRC}]'
    
# EC2インスタンスのID取得
SRC_INSTANCE_ID=$(
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    ec2 describe-instances \
        --filters "Name=tag:Name,Values=EC2-KMS-TEST-SRC" \
    --query 'Reservations[*].Instances[*].InstanceId'
)

(4) (オプション)コピー元リージョンのEC2でファイルを作成する

作成したEC2にsshログインし、ファイルを作成します。

#sshで、RDSのあるVPC上のEC2にログインして下記を実行

echo 'Hello World!!' > test.txt 

(5)コピー元リージョンのEC2のAMI取得

作成したEC2インスタンスのAMIを取得します。
データの静止点を取るため最初にインスタンスを停止し、AMIを取得します。
EBSが暗号化されている場合、AMI(正確にはAMI用に取得されたEBSのスナップショット)もインスタンスのEBSと同じCMKで暗号化されます。

(5)-(a) EC2インスタンス停止
aws --profile ${PROFILE} --region ${REGION_SRC} \
    ec2 stop-instances \
        --instance-ids ${SRC_INSTANCE_ID}
(5)-(b) AMI取得
#AMI取得
aws --profile ${PROFILE} --region ${REGION_SRC} \
    ec2 create-image \
        --instance-id ${SRC_INSTANCE_ID} \
        --name "SRC_INSTANCE_AMI"

#取得したAMIのID取得
SRC_AMI_ID=$(
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    ec2 describe-images \
        --filters "Name=name,Values=SRC_INSTANCE_AMI"  \
    --query 'Images[*].{ID:ImageId}'
)

(6)取得したAMIの他リージョンへのコピー

コピー元リージョンで取得したAMIを、コピー先リージョンにコピーします。
コピー先のシンガポールリージョンで実行しているところに注意ください。(--reogionオプション指定を見てください)

#AMIのコピー
aws --profile ${PROFILE} --region ${REGION_DST} \
    ec2 copy-image \
        --source-image-id ${SRC_AMI_ID} \
        --source-region ${REGION_SRC} \
        --name COPY_AMI_FROM_SRC_REGION \
        --encrypted \
        --kms-key-id ${DST_KEY_ID}

#コピーしたAMIのID取得
DST_AMI_ID=$(
aws --profile ${PROFILE} --region ${REGION_DST} --output text \
    ec2 describe-images \
        --filters "Name=name,Values=COPY_AMI_FROM_SRC_REGION"  \
    --query 'Images[*].{ID:ImageId}'
)

(7)コピー先リージョンでAMIからインスタンスを起動

セキュリティーグループを作成し、コピー元リージョンからコピーしたAMIからインスタンスを起動します。

(7)-(a) SecurityGroup作成
DST_EC2_SG_ID=$( \
aws --profile ${PROFILE} --region ${REGION_DST} --output text \
    ec2 create-security-group \
        --group-name SgForEc2 \
        --description "SG for EC2 Instances" \
        --vpc-id ${REGION_DST_VPCID} \
	--query 'GroupId' )

aws --profile ${PROFILE} --region ${REGION_DST} \
    ec2 authorize-security-group-ingress \
        --group-id ${DST_EC2_SG_ID} \
        --protocol tcp \
        --port 22 \
        --cidr 0.0.0.0/0
(7)-(b) EC2インスタンス作成
# EC2インスタンスの起動
aws --profile ${PROFILE} --region ${REGION_DST} \
    ec2 run-instances \
        --count 1 \
        --instance-type t2.micro \
        --image-id ${DST_AMI_ID} \
        --key-name ${REGION_DST_EC2_KEY_PAIR} \
        --monitoring Enabled=true \
        --security-group-ids ${DST_EC2_SG_ID} \
        --subnet-id ${REGION_DST_SUBNET} \
        --associate-public-ip-address \
        --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=EC2-KMS-TEST-DST}]'

(8) (オプション)コピー元リージョンのEC2でファイルを確認する

作成したEC2にsshログインし、コピー元リージョンで作成したファイルがあるか確認します。

#sshで、RDSのあるVPC上のEC2にログインして下記を実行

ll
cat test.txt 

(追加確認)EBSスナップショットの他リージョンコピーの場合

EBSスナップショットを他リージョンにコピーする場合も、スナップショットコピー時にコピー先リージョンのCMKを指定します。

#コピー元リージョンで、AMI作成時に作成されたEBSスナップショットを設定
#"Created by CreateImage(i-xxxxx) for ami-xxxx"という説明のスナップショット
SRC_SNAPSHOT_ID=snap-06e05677d40f66db5

#EBSスナップショットを他リージョンにコピー
aws --profile ${PROFILE} --region ${REGION_DST} \
    ec2 copy-snapshot \
        --source-snapshot-id ${SRC_SNAPSHOT_ID} \
        --source-region ${REGION_SRC} \
        --encrypted \
        --kms-key-id ${DST_KEY_ID}

RDS KMS暗号化されてるDBインスタンスをスナップショットで別リージョンにコピーする検証(AWS CLI)

はじめに

RDSをKMSの鍵で暗号化する場合、RDSインスタンスがあるそれぞれのリージョンKMS鍵(CMK:Customer Master Key)で暗号化しますが、DBスナップショットで、他リージョンにDBをコピーする場合、鍵はどうなるのかということを確認するための検証手順です。

結論としては、他リージョンにDBスナップショットをコピーする時にコピー先リージョンのCMKを指定することになります。

f:id:nopipi:20191008201217p:plain:w500

検証環境

  • この検証はAWS CLIをセットアップしたMacで実施
    • bash環境なので、Linuxなどbash環境下であれば実行はできると思います。
  • AWS CLIのプロファイルは設定済み前提
  • プロファイルのIAMロール(IAMユーザ)にはAdministratorAccessポリシーがある前提
  • 検証ではRDS(MySQL)を利用
  • オプションのMySQL書き込み用のEC2インスタンスが既存であることが前提

AWS CLIによる検証手順

(1) 検証設定

CLIに渡すパラメータのいくつかを、シェルの変数として事前設定しています。

#作業端末のプロファイル指定(デフォルトでない場合適時変更して下さい)
PROFILE=default

#東京リージョンの設定
# 実際に検証する環境に合わせて、RDSをデプロイする先のVPCのIPと、サブネットのIDを変更してください。
REGION_SRC=ap-northeast-1
REGION_SRC_VPCID="vpc-8e6c5ce9"
REGION_SRC_SUBNETS="subnet-48cfe313 subnet-a2448189 subnet-0214e64a"

#シンガポールリージョンの設定
# 実際に検証する環境に合わせて、RDSをデプロイする先のVPCのIPと、サブネットのIDを変更してください。
REGION_DST=ap-southeast-1
REGION_DST_VPCID="vpc-0bcdca6c"
REGION_DST_SUBNETS="subnet-8307a0da subnet-1eae4578 subnet-58cdd511"

#RDS設定
RDS_INSTANCE_CLASS=db.m5.large
RDS_ENGIN=MySQL
RDS_ENGIN_VERSION=8.0.16
RDS_DB_ADMIN=admin
RDS_DB_ADMIN_PASSWD=password

AWS_ACCOUNT=$(
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    sts get-caller-identity --query 'Account' )

(2) KMSキー作成

RDS暗号化に利用するKMSキーを、東京リージョン、シンガポールリージョンそれぞれで作成します。

f:id:nopipi:20191008201222p:plain:w500

(2)-(a) 送付元リージョンでのKMSキー作成
SRC_KEY_ID=$( \
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    kms create-key \
	    --description Key_For_Source_Snapshot \
	    --origin AWS_KMS \
	--query 'KeyMetadata.KeyId' )

aws --profile ${PROFILE} --region ${REGION_SRC} \
    kms create-alias \
	    --alias-name alias/Key_For_Source_Snapshot \
	    --target-key-id ${SRC_KEY_ID}
(2)-(b) 送付先リージョンでのKMSキー作成
DST_KEY_ID=$( \
aws --profile ${PROFILE} --region ${REGION_DST} --output text \
    kms create-key \
	    --description Key_For_Source_Snapshot \
	    --origin AWS_KMS \
	--query 'KeyMetadata.KeyId' )

aws --profile ${PROFILE} --region ${REGION_DST} \
    kms create-alias \
	    --alias-name alias/Key_For_Destination_Snapshot \
	    --target-key-id ${DST_KEY_ID}

(3)東京リージョンのRDS作成

セキュリティーグループを作成とRDSのサブネットグループを作成し、RDSを作成します。

f:id:nopipi:20191008201231p:plain:w500

(3)-(a) SecurityGroup作成
SRC_RDS_SG_ID=$( \
aws --profile ${PROFILE} --region ${REGION_SRC} --output text \
    ec2 create-security-group \
        --group-name SgForRdsMySQL \
        --description "SG for RDS MySQL" \
        --vpc-id ${REGION_SRC_VPCID} \
	--query 'GroupId' )

aws --profile ${PROFILE} --region ${REGION_SRC} \
    ec2 authorize-security-group-ingress \
        --group-id ${SRC_RDS_SG_ID} \
        --protocol tcp \
        --port 3306 \
        --cidr 0.0.0.0/0
(3)-(b) RDS作成(サブネットグループ作成)
aws --profile ${PROFILE} --region ${REGION_SRC} \
    rds create-db-subnet-group \
        --db-subnet-group-name rds_mysql \
        --db-subnet-group-description "KMS TEST for RDS MySQL" \
        --subnet-ids ${REGION_SRC_SUBNETS}
(3)-(c) RDS作成(MySQLインスタンス作成)
aws --profile ${PROFILE} --region ${REGION_SRC} \
    rds create-db-instance \
    --db-instance-identifier Test-RDS-MySQL \
    --db-instance-class ${RDS_INSTANCE_CLASS} \
    --engine ${RDS_ENGIN} \
    --engine-version ${RDS_ENGIN_VERSION} \
    --allocated-storage 20 \
    --master-username ${RDS_DB_ADMIN} \
    --master-user-password ${RDS_DB_ADMIN_PASSWD} \
    --backup-retention-period 3 \
    --vpc-security-group-ids ${SRC_RDS_SG_ID} \
    --no-publicly-accessible \
    --db-subnet-group-name rds_mysql \
    --storage-encrypted \
    --kms-key-id ${SRC_KEY_ID}

(4) (オプション)東京リージョンRDS(MySQL)にデータを書き込む

RDSと同じVPC上のEC2から、RDSにアクセスしテーブル作成とデータインサートを行います。

#sshで、RDSのあるVPC上のEC2にログインして下記を実行

sudo yum -y install mysql

mysql -h <RDSのエンドポイントURL> -u admin -p
#パスワード(password)入力

MySQL > CREATE DATABASE sampledb;
MySQL > USE sampledb;
MySQL > 
CREATE TABLE IF NOT EXISTS `sample` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(255) NOT NULL DEFAULT 'hoge',
  PRIMARY KEY(`id`)
);
MySQL > INSERT INTO sample( name ) VALUES ( '鈴木' );
MySQL > COMMIT;
MySQL > SELECT * FROM sample;
MySQL > quit

(5)東京リージョンのRDS スナップショットの取得

RDSを作成し、DBインスタンスが起動したらスナップショットを手動取得します。
暗号化されたRDSから取得されたスナップショットは暗号されており、KMSでDBを暗号化している場合同じCMKでスナップショットも暗号化されます。

f:id:nopipi:20191008201235p:plain:w500

aws --profile ${PROFILE} --region ${REGION_SRC} \
    rds create-db-snapshot \
        --db-instance-identifier Test-RDS-MySQL \
        --db-snapshot-identifier snapshot-for-cross-region-replication-src-db

(6)取得したスナップショットのシンガポールリージョンにコピー

東京リージョンで取得したスナップショットを、シンガポールリージョンにコピーします。
コピー先のシンガポールリージョンで実行しているところに注意ください。(--reogionオプション指定を見てください)

f:id:nopipi:20191008201241p:plain:w500

aws --profile ${PROFILE} --region ${REGION_DST} \
    rds copy-db-snapshot \
        --source-db-snapshot-identifier arn:aws:rds:${REGION_SRC}:${AWS_ACCOUNT}:snapshot:snapshot-for-cross-region-replication-src-db \
        --target-db-snapshot-identifier snapshot-for-cross-region-replication-dst-db \
        --source-region ${REGION_SRC} \
        --kms-key-id ${DST_KEY_ID}

(7)シンガポールリージョンでスナップショットからRDSを作成

セキュリティーグループを作成とRDSのサブネットグループを作成し、東京リージョンからコピーしたスナップショットからRDSインスタンスを複製します。スナップショットからのインスタンスの複製には" restore-db-instance-from-db-snapshot"を利用します。

f:id:nopipi:20191008201247p:plain:w500

(7)-(a) SecurityGroup作成
DST_RDS_SG_ID=$( \
aws --profile ${PROFILE} --region ${REGION_DST} --output text \
    ec2 create-security-group \
        --group-name SgForRdsMySQL \
        --description "SG for RDS MySQL" \
        --vpc-id ${REGION_DST_VPCID} \
	--query 'GroupId' )

aws --profile ${PROFILE} --region ${REGION_DST} \
    ec2 authorize-security-group-ingress \
        --group-id ${DST_RDS_SG_ID} \
        --protocol tcp \
        --port 3306 \
        --cidr 0.0.0.0/0
(7)-(b) RDS作成(サブネットグループ作成)
aws --profile ${PROFILE} --region ${REGION_DST} \
    rds create-db-subnet-group \
        --db-subnet-group-name rds_mysql \
        --db-subnet-group-description "KMS TEST for RDS MySQL" \
        --subnet-ids ${REGION_DST_SUBNETS}
(7)-(c) RDS作成(MySQLインスタンスのスナップショットからの復元)
aws --profile ${PROFILE} --region ${REGION_DST} \
    rds restore-db-instance-from-db-snapshot \
        --db-instance-identifier Test-RDS-MySQL-DST \
        --db-snapshot-identifier snapshot-for-cross-region-replication-dst-db \
        --vpc-security-group-ids ${DST_RDS_SG_ID} \
        --db-subnet-group-name rds_mysql

(8) (オプション)シンガポールリージョンRDS(MySQL)のデータを確認する

復元したRDSで、東京リージョンでインサートしたデータがあるか確認します。

#sshで、RDSのあるVPC上のEC2にログインして下記を実行

sudo yum -y install mysql

mysql -h <RDSのエンドポイントURL> -u admin -p
#パスワード(password)入力

MySQL > USE sampledb;
MySQL > SELECT * FROM sample;
MySQL > quit

AWS CLIでVPCエンドポイントを作成する時のポリシー(JSON)の指定方法

CLIVPCエンドポイントを作成するときに、エンドポイントポリシーのJSONをどうやって渡せばいいのかを悩んだので、そのメモ書きです。
JSONの文字列をシェルの変数に投入してあげるという解決策です。

実行例

AWS_PROFILE=default

POLICY='
{
    "Statement": [
        {
            "Action": "*",
            "Effect": "Allow",
            "Resource": "*",
            "Principal": "*"
        }
    ]
}'

aws --profile ${AWS_PROFILE} ec2 create-vpc-endpoint \
    --vpc-endpoint-type Interface \
    --vpc-id vpc-f1dcfc96 \
    --subnet-ids subnet-cde80585 subnet-0bf40020 subnet-742e712f \
    --security-group-ids sg-beca3fc2 \
    --service-name com.amazonaws.ap-northeast-1.sqs \
    --policy-document "${POLICY}";

解説

ミソは、エンドポイントのポリシーをシェル変数(ここでは、POLICY)に登録して、--policy-document の引数に "${POLICY}"と入れること。
引数に入れる"${POLICY}"は、必ずダブルクォーテーションで括ること。(bashのコマンド文字列解析時に、JSONの途中のスペースを引数の区切りと誤認するのを回避するため)

Linuxでファイル名がSJIS(CP932)のものをUTF-8のファイル名に一括変換する

convmvをインストール

sudo yum -y install convmv

使い方

cd  <変換したいファイルがあるディレクトリ>
convmv -r -f cp932 -t utf-8 --notest *
  • -r 再帰的に変換を実行
  • -f cp932 : 変換元のコード指定。cp932は、windowsSJISコード名称
  • -t utf-8: 変換先のコード指定。utf-8に変換する
  • --notest: 変換を実行する(デフォルトではテストだけのため)