のぴぴのメモ

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

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: 変換を実行する(デフォルトではテストだけのため)

JSONの文字列変換(JSON.dumps)をワンライナーのコマンド実行で実現する

コマンドでの作業で、pythonのコードを書くまででない場合の方法。
変数"POLICY"に設定したJSONを、pythonで文字列に変換して、変数"POLICY_ESCAPE"に格納する。

JSON→文字列にエンコード

やること

このJSONを、

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1565607714000",
            "Effect": "Allow",
            "Action": [
                "iam:List*",
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}

これに変換します。

"{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Sid\": \"Stmt1565607714000\", \"Effect\": \"Allow\", \"Action\": [ \"iam:List*\", \"iam:PassRole\" ], \"Resource\": [ \"*\" ] } ] }\n"

コマンド

POLICY='{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Stmt1565607714000",
            "Effect": "Allow",
            "Action": [
                "iam:List*",
                "iam:PassRole"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}'
POLICY_ESCAPE=$(echo ${POLICY} | python -c 'import json; import sys; print(json.dumps(sys.stdin.read()))')

文字列→JSONにデコード

上記の逆です。

POLICY_ESCAPE="{ \"Version\": \"2012-10-17\", \"Statement\": [ { \"Sid\": \"Stmt1565607714000\", \"Effect\": \"Allow\", \"Action\": [ \"iam:List*\", \"iam:PassRole\" ], \"Resource\": [ \"*\" ] } ] }\n"

POLICY=$(echo ${POLICY_ESCAPE} | python -c 'import json; import sys; str=sys.stdin.read(); dic=json.loads(str.replace("\\n","")); print("{}".format(json.dumps(dic,indent=4)))' )

実行結果

$ echo $POLICY
{ "Version": "2012-10-17", "Statement": [ { "Action": [ "iam:List*", "iam:PassRole" ], "Resource": [ "*" ], "Effect": "Allow", "Sid": "Stmt1565607714000" } ] }

蛇足

もっといい方法があるような気がしますが、わかったらアップデートします。

EC2の有償OSの課金コード("billingProducts")

"billingProducts"の確認方法

課金コード(billingProduct)をどうやって取得するのかという話ですが、そのものズバリの記載が下記にあり、下記コマンドで取得できます。

curl http://169.254.169.254/latest/meta-data/product-codes

RHEL8での確認

OSに課金が発生するRHEL8の場合、"billingProducts"に"bp-6fa54006" というのが見えます。

$ curl http://169.254.169.254/latest/dynamic/instance-identity/document/ 
{
  "accountId" : "270025184181",
  "availabilityZone" : "ap-northeast-1a",
  "kernelId" : null,
  "ramdiskId" : null,
  "pendingTime" : "2019-06-02T14:03:10Z",
  "architecture" : "x86_64",
  "privateIp" : "172.31.32.137",
  "version" : "2017-09-30",
  "region" : "ap-northeast-1",
  "devpayProductCodes" : null,
  "marketplaceProductCodes" : null,
  "imageId" : "ami-03c6a4362c5fb8c61",
  "billingProducts" : [ "bp-6fa54006" ],
  "instanceId" : "i-0abf28d14a86f3c6c",
  "instanceType" : "m5a.large"
}

Amazon Linux2

無料のAmazon Linux2の場合、"billingProducts"がnullですね。

$ curl http://169.254.169.254/latest/dynamic/instance-identity/document/ 
{
  "devpayProductCodes" : null,
  "marketplaceProductCodes" : null,
  "accountId" : "270025184181",
  "availabilityZone" : "ap-northeast-1a",
  "kernelId" : null,
  "ramdiskId" : null,
  "pendingTime" : "2019-06-02T14:03:47Z",
  "architecture" : "x86_64",
  "privateIp" : "172.31.37.11",
  "version" : "2017-09-30",
  "region" : "ap-northeast-1",
  "imageId" : "ami-0f9ae750e8274075b",
  "billingProducts" : null,
  "instanceId" : "i-0d1ca8d3b54832823",
  "instanceType" : "m5a.2xlarge"
}

調べるに至った経緯

AMI作成手順のドキュメントにこんな記載があるのですが、「 billingProduct コード」をどうやったら確認できるのか書いてなかったので調べたものです。

Red Hat Enterprise Linux (RHEL) や SUSE Linux Enterprise Server (SLES) などの一部の Linux ディストリビューションは、AMI に関連付けられた Amazon EC2 の billingProduct コードを使用して、パッケージの更新に関するサブスクリプションのステータスを確認します

docs.aws.amazon.com

AWS CodeCommitでgit認証にAWS CLIの認証情報ヘルパーを使用する

CodeCommitに作成したgitレポジトリに接続するための認証に、AWS CLIを利用する方法です。gitの認証ヘルパーに"aws codecommit credential-helper"を利用するのがポイントです。

1.手順: IAMユーザ作成とAWS CLIプロファイル設定

1.1 CodeCommit用のIAMユーザを作成

グループ"CodeCommitUserGrp"を作成し、AWSCodeCommitPowerUserポリシーを付与。
ユーザ"User01"を、"CodeCommitUserGrp"で作成する。

  • グループ設定
    • グループ名: CodeCommitUserGrp
    • アタッチするポリシ: AWSCodeCommitPowerUser
# 設定
aws iam create-group --group-name CodeCommitUserGrp
aws iam attach-group-policy --group-name CodeCommitUserGrp --policy-arn arn:aws:iam::aws:policy/AWSCodeCommitPowerUser
# 設定確認
aws iam get-group --group-name CodeCommitUserGrp
aws iam list-attached-group-policies --group-name CodeCommitUserGrp
  • ユーザ設定
    • ユーザ名: User01
    • 所属グループ: CodeCommitUserGrp
# 設定
aws iam create-user --user-name User01
aws iam add-user-to-group --user-name User01 --group-name CodeCommitUserGrp 
# 設定確認
aws iam get-user --user-name User01
aws iam list-groups-for-user --user-name User01

1.2 ユーザのアクセスキーとシークレットキーを生成

PCのAWS CLIに設定するアクセスキーとシークレットキーを生成します。AccessKeyIdとSecretAccessKeyを控えます。(漏洩しないよう注意!!)

aws iam create-access-key --user-name User01
{
    "AccessKey": {
        "UserName": "User01", 
        "Status": "Active", 
        "CreateDate": "2019-08-12T09:55:52Z", 
        "SecretAccessKey": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", 
        "AccessKeyId": "YYYYYYYYYYYYYYYYYYYY"
    }
}

1.3 AWS CLIにプロファイルを作成

AWS CLIがセットアップされていない場合は、こちらの記事を参照してCLIをインストールして下さい。
AWS CLIをセットアップ後に、aws CLIにcodecommitというプロファイルを作成し、先ほど生成したアクセスキーとシークレットキーを登録します。

aws --profile codecommit configure
AWS Access Key ID [None]: YYYYYYYYYYYYYYYYYYYY
AWS Secret Access Key [None]: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

2.手順: gitの認証ヘルパー設定

gitコマンドがインストールされていない場合は、先にgitをインストールします。(インストール方法は記載しないので、別途検索して下さい)

2.1 gitの初期設定(ユーザ設定&pushモード設定)

(1)メールアドレス、(2)名前、(3)push時のモードを設定します。

git config --global user.email "xxxxxxx@gmail.com"
git config --global user.name "xxxxxxxx"
git config --global push.default simple

git config --global -l

2.2 gitの認証ヘルパーにAWSコマンドを設定

公式ドキュメントの情報を参考に、下記コマンドで認証ヘルパーとしてAWSコマンドを指定します。

git config --global credential.helper '!aws --profile codecommit codecommit credential-helper $@'
git config --global credential.UseHttpPath true

ただし、私の環境では上記コマンドで正しく反映されなかったため、"~/.gitconfig"のcredential句の部分を、下記の通り正しい内容に修正しました。(こちらの記事を参考にしました)

[credential]
   helper = "!aws --profile codecommit codecommit credential-helper $@"
   UseHttpPath = true

3.手順: CodeCommitのレポジトリからcloneのテスト

git clone <レポジトリhttpsのURL>

VPCのsubnetのCIDRの割り当てのメモ

VPCのCIDRを"10.1.0.0/16"とした時のCIDR例。
私は検証用途での利用でIPが枯渇するほどインスタンスを起動するわけではなので、IPアドレス範囲をキツキツには設計していない。

  • ポイント
    • Privateサブネットは少し大きめの19bitで切る。
    • Publicサブネットは、10.1.0.0/19の範囲をさらに24bitで切って利用する
    • AZ間は間違えないように離れた番号を利用するようにアドレスを割り当てる

サブネットのCIDR割り当て例

#大区分小区分項目CIDRIPアドレス範囲
1PublicAZ-1PublicSubnet110.1.0.0/2410.1.0.1-10.1.0.254
2未使用10.1.1.0/24

10.1.15.0/24
10.1.1.1-10.1.15.254
3AZ-2PublicSubnet210.1.16.0/2410.1.16.1-10.1.16.254
4未使用10.1.17.0/24

10.1.31.0/24
10.1.0.1-10.1.7.254
5PrivateAZ-1PrivateSubnet110.1.32.0/1910.1.32.1-10.1.63.254
6未使用10.1.64.0/1910.1.64.1-10.1.95.254
7未使用10.1.96.0/1910.1.96.1-10.1.127.254
8AZ-2PrivateSubnet210.1.128.0/1910.1.128.1-10.1.159.254
9未使用10.1.160.0/1910.1.160.1-10.1.191.254
10未使用10.1.192.0/1910.1.192.1-10.1.223.254
11未使用未使用未使用10.1.224.0/1910.1.224.1-10.1.255.254

参考

"10.1.0.0/16"の範囲を19bitで分割した時のCIDR一覧

CIDRIP範囲IPアドレス(2進数)
Subnet11111111 11111111 11100000 00000000
10.1.0.0/1910.1.0.1 - 10.1.31.25400001010 00000001 00000000 00000000
10.1.32.0/1910.1.32.1 - 10.1.63.25400001010 00000001 00100000 00000000
10.1.64.0/1910.1.64.1 - 10.1.95.25400001010 00000001 01000000 00000000
10.1.96.0/1910.1.96.0 - 10.1.127.25500001010 00000001 01100000 00000000
10.1.128.0/1910.1.128.0 - 10.1.159.25500001010 00000001 10000000 00000000
10.1.160.0/1910.1.160.0 - 10.1.191.25500001010 00000001 10100000 00000000
10.1.192.0/1910.1.192.0 - 10.1.223.25500001010 00000001 11000000 00000000
10.1.224.0/1910.1.224.0 - 10.1.255.25500001010 00000001 11100000 00000000

AWS CloudFormation 起動テンプレート+CloudFormationヘルパースクリプトを使ってFowardProxyインスタンスを複数作成する

やりたいこと

CloudFormationでsquidのFowardProxyインスタンスをCloudFormationヘルパースクリプトを利用してインスタンス起動時にセットアップするようにしています。このインスタンスをAutoscalingは使用せず複数個作成したいと思い、EC2の起動テンプレートを組み合わせることで、効率良く起動するようにしています。

構成

f:id:nopipi:20190427234531p:plain
CloudFormation:起動テンプレートとCfnヘルパーの組み合わせで複数のProxyを作成する

ポイント

  • Cfnヘルパースクリプト用の定義を、EC2起動テンプレートのリソースのMetadataとして定義
  • Cfnヘルパースクリプトのオプション
    • 起動テンプレートのUserDataでcfn_initコマンドでcfnヘルパーを実行するようにする
    • その時Cloudformationの指定リソースを、”--resource ProxyLunchTemplate ”と起動テンプレートのリソースを指定する

CloudFormationテンプレート例

AWSTemplateFormatVersion: '2010-09-09'
Description: Outbound from the On-premises Environment
#----------------------------------------------
Parameters:
  Environment:
    Type: String
    Default: Dev
    AllowedPattern: "[a-zA-Z0-9=-@+?[?]?<?>._]*"
    ConstraintDescription: Can contain only ASCII characters.
  #------------------
  VpcId:
    Type: AWS::EC2::VPC::Id
  Subnet1Id:
    Type: AWS::EC2::Subnet::Id
    Description: Specify 1st Subnet to run Proxy instances.
  Subnet2Id:
    Type: AWS::EC2::Subnet::Id
    Description: Specify 2nd Subnet to run Proxy instances.
  #------------------
  KeyName:
    Type: AWS::EC2::KeyPair::KeyName
    Description: (required) Name of an existing EC2 key pair
  ProxyInstanceType: 
    Type: String 
    Default: m5.large 
    ConstraintDescription : Must be a valid EC2 instance type
    AllowedValues: 
      - t2.micro 
      - t2.small 
      - t2.medium 
      - t2.large 
      - t2.xlarge 
      - t2.2xlarge 
      - m5.large 
      - m5.xlarge 
      - m5.2xlarge 
      - m5.4xlarge 
      - m5.12xlarge 
      - m5.24xlarge 
  ProxyAmiId:
    Type: AWS::EC2::Image::Id
    Description: (required) AMI ID
    Default: ami-0f9ae750e8274075b  #for Tokyo-region
    #Default: ami-0b419c3a4b01d1859  #for singapore-region
  ProxyAutoRecoveryMinutes:
    Type: Number
    Description: Auto Recovery Time(Minutes)
    Default: 1
    MinValue: 1
    MaxValue: 120
  #------------------
Metadata: 
  AWS::CloudFormation::Interface: 
    ParameterGroups: 
      -
        Label:
          default: "General"
        Parameters:
          - Environment
      -
        Label:
          default: "Network"
        Parameters:
          - VpcId
          - Subnet1Id
          - Subnet2Id
      -
        Label:
          default: "Forward Proxy"
        Parameters:
          - KeyName
          - ProxyInstanceType
          - ProxyAmiId
          - ProxyAutoRecoveryMinutes
#----------------------------------------------
Resources:
  ProxySG:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupName: !Sub ${Environment}-ProxySecurityGroup
      GroupDescription: Allow Proxy inbound access
      VpcId: !Ref VpcId
      SecurityGroupIngress:
        -
          IpProtocol: tcp
          FromPort: 3128
          ToPort: 3128
          CidrIp: 0.0.0.0/0
        -
          IpProtocol: tcp
          FromPort: 22
          ToPort: 22
          CidrIp: 0.0.0.0/0
      Tags: 
        - Key: Name
          Value: !Sub ${Environment}-ProxySecurityGroup
  #------------------
  ProxyLunchTemplate:
    Type: AWS::EC2::LaunchTemplate
    Properties:
      LaunchTemplateName: FowardProxyLunchTemplate
      LaunchTemplateData:
        ImageId: !Ref ProxyAmiId
        InstanceType: !Ref ProxyInstanceType
        KeyName: !Ref KeyName
        SecurityGroupIds:
          - !Ref ProxySG
        Monitoring:
          Enabled: true
        TagSpecifications:
          -
            ResourceType: instance
            Tags:
              - Key: Name
                Value: !Sub ${Environment}-ProxyInstance
        UserData:
          Fn::Base64: !Sub | 
            #!/bin/bash -xe
            yum update -y 
            yum install -y aws-cfn-bootstrap cloud-init aws-cli 
            /opt/aws/bin/cfn-init -v --stack ${AWS::StackName} --resource ProxyLunchTemplate --region ${AWS::Region} --configsets proxy_setup
            /opt/aws/bin/cfn-signal -e $? --stack ${AWS::StackName} --resource ProxyLunchTemplate --region ${AWS::Region}
    Metadata:
      AWS::CloudFormation::Init: 
        configSets: 
          proxy_setup:
            - install_squid 
            - setup_squid 
        install_squid: 
          packages: 
            yum: 
              squid: [] 
        setup_squid: 
          files: 
            "/etc/squid/squid.conf": 
              content: !Sub | 
                # define ip address 
                acl localnet src 127.0.0.0/8 
                acl localnet src ::1/128 
 
                acl SSL_ports port 443 
                acl Safe_ports port 443
                acl CONNECT method CONNECT 
                http_access deny !Safe_ports 
 
                # Deny CONNECT to other than secure SSL ports 
                http_access deny CONNECT !SSL_ports 
 
                # Only allow cachemgr access from localhost 
                http_access allow localhost manager 
                http_access deny manager 
 
                # from where browsing should be allowed 
                http_access allow localnet 
 
                # include url white list 
                acl whitelist dstdomain "/etc/squid/whitelist" 
                http_access allow whitelist 
 
                # And finally deny all other access to this proxy 
                http_access deny all 
                #------------------------------------------ 
                http_port 3128

                # Leave coredumps in the first cache dir
                coredump_dir /var/spool/squid
 
                # anonymouse host name
                visible_hostname unknown
              mode: '000640'
              owner: root
              group: squid
            "/etc/squid/whitelist":
              content: !Sub |
                .google.co.jp
                github.com
                gist.github.com
              mode: '000640'
              owner: root
              group: squid
          services:
            sysvinit:
              squid:
                enabled: true
                ensureRunning: true
                files:
                  - "/etc/squid/squid.conf"
                  - "/etc/squid/whitelist.conf"
  #------------------
  ProxyInstance1: 
    Type: AWS::EC2::Instance 
    Properties:
      LaunchTemplate:
        LaunchTemplateName: FowardProxyLunchTemplate
        Version: !GetAtt ProxyLunchTemplate.LatestVersionNumber
      SubnetId: !Ref Subnet1Id
      SourceDestCheck: false
  ProxyInstance2: 
    Type: AWS::EC2::Instance 
    Properties:
      LaunchTemplate:
        LaunchTemplateName: FowardProxyLunchTemplate
        Version: !GetAtt ProxyLunchTemplate.LatestVersionNumber
      SubnetId: !Ref Subnet2Id
      SourceDestCheck: false
  #------------------
Outputs:
  ProxyInstance1PrivateIp:
    Description: 'Foward Proxy #1 Private IP'
    Value: !GetAtt ProxyInstance1.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}-ProxyInstance1PrivateIp"
  ProxyInstance2PrivateIp:
    Description: 'Foward Proxy #2 Private IP'
    Value: !GetAtt ProxyInstance2.PrivateIp
    Export:
      Name: !Sub "${AWS::StackName}-ProxyInstance2PrivateIp"

(AWS) UserDataとCloudFormationヘルパースクリプトの定義情報取得や実行契機の違い

定義箇所とインスタンス内からの定義情報取得方法の違い

UserDataはEC2インスタンスの機能で、CloudFormationヘルパースクリプトはCloudFormationの機能です。
UserDataはインスタンスメタデータ*1から情報を取得するため、実質VPC設定やIAMロールを考慮する必要がありません。一方CloudFormationヘルパースクリプトは、CloudFormationのパブリックにあるエンドポイントから取得するためInternet GatewayやCloudFormationのVPC Endpointを用意してパブリックのエンドポイントにアクセスできるようにする必要があります。

機能 機能を提供しているサービス 定義 インスタンスからの取得方法 通信要件
UserData EC2 EC2インスタンス起動オプション インスタンスメタデータから取得 不要
Cfnヘルパー CloudFormation CloudFormationのテンプレート(ResourceのMetadata定義) CloudFormationのエンドポイントから取得 IGW経由またはVPCEndpoint経由でCloudFormationのパブリックエンドポイントにアクセス可能なこと

絵に描くとこんな感じでしょうか。(逆に判りずらいかも)

f:id:nopipi:20190427235711p:plain

実行モジュールと実行契機の違い

AWSが提供するAMIを利用した場合UserDataは、cloud-initというツールの中で実行されておりOS起動時に自動実行されます。一方CloudFormationヘルパースクリプトは自動実行されないため明示的に実行する必要があり、通常はUserDataにコマンドを記載して、UserDataから実行させます。

機能 パッケージ 起動契機 インスタンス無いでの設定ファイル
UserData cloud-init*2 OS起動時に自動実行(systemdからキックされる) /etc/cloud/配下
Cfnヘルパー aws-cfn-bootstrap*3 明示的に実行(UserDataに実行コマンドを埋め込む) なし(CfnのスタックのMetadataに定義)

VSCodeでAWS CloudFormation(YAML)を作成する環境を整備する

VSCodeAWS CloudFormationをYAMLで作成する環境の設定手順です。

やること

  1. VSCodeのセットアップ
  2. YAMLの構文チェック用にエクステンション"YAML Language Support by Red Hat"をVSCodeに追加
  3. CloudFormationの構文チェック用のカスタム設定をVSCode設定に追加

VSCodeのセットアップ

MacへのVSCodeインストール(とgit連携)はこちらを参照
nopipi.hatenablog.com

YAMLエクステンションの追加

VSCoderedhatYAMLエクステンションを追加します。ここではCUIGUIの両方の手順を記載します。

CUIでインストールする場合

下記コマンドでYAMLエクステンションを追加します。

code --install-extension redhat.vscode-yaml

GUIでインストールする場合

  1. 左のバーから"Extentions"をクリック
  2. 検索ボックスでYAMLでエクステンションを検索し、"YAML Language Support by Red Hat"を選択
  3. "Install"でエクステンションを追加する

(1)左バーからExtentionボタンをクリックし、(2)キーワードYAMLで検索、(3)redhat YAMLをインストール
GUI操作でのYAMLエクステンションインストール

CloudFormationの構文チェック用のカスタム設定追加

下記設置を追加します

  1. CloudFormation仕様の追加
  2. CloudFormationのカスタムタグ定義の追加

設定(1) CloudFormation仕様の追加

VSCodeの設定ファイル(Macの場合: $HOME/Library/Application Support/Code/User/settings.json)*1に、CloudFormationのリソース仕様を追加します。この設定でをすると、拡張子がcf.yamlのファイル、cfn/、cloudformation配下のYAMLファイルがCloudFormation用のYAMLとして指定したソース仕様にしたがってチェックされます。

  1. コマンドパレット(⇧⌘P)を開いて >settings (JSON) と打ち込みsettings.jsonを開く。
  2. 下記定義を追加する
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },

設定(2) CloudFormationのカスタムタグ定義の追加

デフォルトのままだと!Sub, !RefなどのCloudFormation固有のスキーマYAMLの構文エラーとなるため、これら固有スキーマを追加します。内容はこちらのIssueAndyJPhillipsさんのコメント内容を流用しています。

    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]

(参考)setting.json全体の内容

設定(1)&(2)をすると、既存の設定に、下記設定が追加された形になります。

{
    <既存の設定>
    "yaml.schemas": {
        "https://d33vqc0rt9ld30.cloudfront.net/latest/gzip/CloudFormationResourceSpecification.json": [
            "*.cf.yaml",
            "*.cf.yml",
            "cfn/*.yaml",
            "cfn/*.yml",
            "cloudformation/*.yaml",
            "cloudformation/*.yml"
        ]
    },
    "yaml.customTags": [
        "!And sequence",
        "!Equals sequence",
        "!If sequence",
        "!Not sequence",
        "!Or sequence",
        "!Base64",
        "!Cidr sequence",
        "!FindInMap sequence",
        "!GetAtt",
        "!GetAZs",
        "!ImportValue",
        "!Join sequence",
        "!Select sequence",
        "!Split sequence",
        "!Sub",
        "!Ref"
    ]
}

mac版PowerPoint消えなくなった自動回復ファイルを手動削除する手順

Mac版のPowerPointを利用していて、PowerPointを起動するたびに古い自動回復ファイルが開いて対処するのが面倒でした。なんとか古い自動回復ファイルを消せないかなと調べた内容を備忘録で残します。

手順

MacPowerPointの自動回復ファイルの保存先フォルダパス

~/Library/Containers/com.microsoft.Word/Data/Library/Preferences/AutoRecovery/

削除手順

  • (1)PowerPointを停止して
  • (2)コンソールを開いて下記コマンドで自動回復ファイルを削除する
cd ~/Library/Containers/com.microsoft.Word/Data/Library/Preferences/AutoRecovery/
rm -i *