FargateのvCPU性能と価格感等雑感

こちらはAWS Fargate Advent Calendar 2017の12/13分の記事です。

普段の業務ではECS上でGoで書いたアプリケーションを運用しています。

日本からre:Inventのライブストリームを視聴していた勢ですが、予想通りEKSとLambdaのGo対応がアナウンスされ感激していたら、さらにFargateまで来てしまい、とても興奮した二日間になりました。

今回はFargateのアドベントカレンダーにお声掛け頂いたので小ネタと雑感などを記したいと思います。

Fargateで提供されるvCPUの性能について

ECSであれば自身でプロビジョニングしたコンテナインスタンスを使用しているためCPU性能なども把握できますが、FargateではAWSが用意したコンテナインスタンス上でtaskが実行されるため、どのようなインスタンスタイプ上で実行されるのかわかりません。

そこで、今回は lscpu コマンドでCPU種別の確認、openssl コマンドに付属するベンチマークツールを実行して、そのvCPU性能を測定しました。すでに $ openssl speed -evp aes-gcm | aes-ctr の結果を集めるスレ にいくつか測定結果があるため、これと同じベンチを実行しました。

ベンチマーク方法

一般にEC2ではCPU世代に応じてインスタンスタイプが設定されています(m3, m4, m5など)。Taskを5つ実行し、インスタンスタイプの推定とベンチマークを実行しました。

vCPUとメモリの割り当てによって変化があるか確認するため、Fargateでは以下の組み合わせで検証しました。

  • Fargate 0.25vCPU/0.5GB
  • Fargate 1vCPU/0.5GB
  • Fargate 2vCPU/0.5GB
  • Fargate 4vCPU/8GB
  • Fargate 4vCPU/30GB

実際のインスタンスタイプとの比較のため以下のインスタンスタイプでも検証しました。

  • t2.nano
  • m4.large
  • c4.xlarge
  • c5.xlarge (これは明らかに自明なので1度だけ)

測定方法は以下の通りです。

  • Task(またはインスタンス)を5つ起動させる。
  • lscpu を実行しCPU種別とクロック数を確認。すでに測定済みだった場合はベンチマークはスキップ。
  • openssl speed 2種を3回実行し、中央値を採取し、結果はマージして記述。
for i in $(seq 1 3); do openssl speed -evp aes-128-ctr; done | tee ctr.log
for i in $(seq 1 3); do openssl speed -evp aes-128-gcm; done | tee gcm.log

測定結果を以下に纏めました。

Fargate 0.25vCPU/0.5 GB

2種類のCPUが観測されました。

4 Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
1 Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Model name:            Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
Stepping:              2
CPU MHz:               2400.060
BogoMIPS:              4800.09
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              30720K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     494148.25k  1605834.24k  3177746.43k  4007733.93k  4371120.13k
aes-128-gcm     379332.99k   899127.24k  1915460.84k  2328437.22k  2779490.25k


Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    1
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2508.184
CPU max MHz:           3000.0000
CPU min MHz:           1200.0000
BogoMIPS:              4600.16
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     483999.89k  1565855.91k  3104190.12k  3898957.82k  4233057.62k
aes-128-gcm     404526.70k   932581.21k  2032351.23k  2862813.18k  3520877.91k

0.25vCPUですが、CPU数は2になっているようですね。

Fargate 1vCPU/2GB

2種類のCPUが観測されました。

2 Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
3 Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 62
Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
Stepping:              4
CPU MHz:               2494.051
BogoMIPS:              4988.07
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm fsgsbase smep erms xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     446142.23k  1325294.61k  2699386.88k  3366615.04k  3668706.65k
aes-128-gcm     276915.95k   702377.22k   989968.75k  1072276.48k  1107867.46k


Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2299.918
BogoMIPS:              4600.08
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     479575.66k  1364312.82k  2938265.78k  3846555.88k  4217560.18k
aes-128-gcm     400038.38k   918257.05k  2025422.18k  2867529.81k  3485180.59k

Fargate 2vCPU/4GB

3 Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
2 Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 62
Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
Stepping:              4
CPU MHz:               2500.097
BogoMIPS:              5000.11
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm fsgsbase smep erms xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     447763.51k  1325036.91k  2706711.89k  3402086.06k  3667615.74k
aes-128-gcm     277453.59k   705053.22k   985322.24k  1079016.79k  1100259.33k


Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2300.202
BogoMIPS:              4600.05
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     478271.40k  1528868.80k  3034935.13k  3887648.43k  4241097.03k
aes-128-gcm     399735.44k   919618.99k  2017273.00k  2868418.22k  3493538.47k

Fargate 4vCPU/8GB

3 Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
2 Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 62
Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
Stepping:              4
CPU MHz:               2494.257
BogoMIPS:              4988.11
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0-3
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm fsgsbase smep erms xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     437667.62k  1332726.42k  2703931.05k  3409378.65k  3687243.78k
aes-128-gcm     277972.59k   707628.54k   989001.56k  1084320.43k  1108792.66k


Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2299.860
BogoMIPS:              4600.18
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0-3
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     483260.30k  1544428.31k  3058699.35k  3910503.42k  4249572.69k
aes-128-gcm     403065.87k   939698.82k  2021811.71k  2889371.65k  3527876.61k

Fargate 4vCPU/30GB

この組み合せはFargateで選択できる最大値です(CPUとメモリ的にはr4相当ですが、もちろんその他いろいろ違います)。

この検証はメモリの量もCPU種別に影響しているのか確かめるためです。

3 Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
2 Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 62
Model name:            Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
Stepping:              4
CPU MHz:               2494.028
BogoMIPS:              4988.07
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0-3
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm fsgsbase smep erms xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     448949.71k  1323984.96k  2703453.01k  3395558.74k  3671018.15k
aes-128-gcm     278020.09k   702252.10k   983557.97k  1079190.87k  1108407.64k


Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2699.102
CPU max MHz:           3000.0000
CPU min MHz:           1200.0000
BogoMIPS:              4600.19
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0-3
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch fsgsbase bmi1 hle avx2 smep bmi2 erms invpcid rtm rdseed adx xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     481306.55k  1540346.60k  3057142.87k  3902812.84k  4238532.61k
aes-128-gcm     401507.38k   926114.69k  2024385.37k  2873183.23k  3508360.53k

t2.nano (1vCPU/0.5GB)

t2.nanoはバースト時間は70分程度こそ短いですが、逆に言えばその時間中はフルに使えます。

CPU種別はすべて同じで、かつFargateで観測したものでした。

5 Model name:            Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz

念のため1度だけベンチを取りましたが、Fargateでの結果とほぼ同一でした。

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                1
On-line CPU(s) list:   0
Thread(s) per core:    1
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Model name:            Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
Stepping:              2
CPU MHz:               2400.062
BogoMIPS:              4800.12
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              30720K
NUMA node0 CPU(s):     0
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     503626.65k  1638487.98k  3363840.09k  3968865.96k  4447092.74k
aes-128-gcm     396037.09k   957863.32k  1980559.87k  2462240.43k  2872603.99k

m4.large (2vCPU/8GB)

CPU種別はすべて同じで、かつFargateで観測したものでした。

5 Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

念のため1度だけベンチを取りましたが、Fargateでの結果とほぼ同一でした。

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 79
Model name:            Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz
Stepping:              1
CPU MHz:               2300.076
BogoMIPS:              4600.15
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              46080K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     483720.54k  1548166.29k  3058795.01k  3911671.13k  4252426.24k
aes-128-gcm     404151.85k   931442.39k  2036643.50k  2901392.73k  3536415.40k

c4.xlarge (4vCPU/8GB)

CPU種別はすべて同じでしたが、Fargateでは観測していないCPU種別でした。

5 Model name:            Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz
Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                4
On-line CPU(s) list:   0-3
Thread(s) per core:    2
Core(s) per socket:    2
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 63
Model name:            Intel(R) Xeon(R) CPU E5-2666 v3 @ 2.90GHz
Stepping:              2
CPU MHz:               2900.080
BogoMIPS:              5800.16
Hypervisor vendor:     Xen
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              256K
L3 cache:              25600K
NUMA node0 CPU(s):     0-3
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     573103.07k  1838973.67k  3616666.11k  4640284.67k  5036687.36k
aes-128-gcm     438607.02k  1053641.13k  2199977.47k  2688752.64k  3118830.93k

クロック数が1.2倍程度になっており、結果もそれによって向上しているようです。

c5.large (2vCPU/4GB)

こちらは参考値として測定しています。

Architecture:          x86_64
CPU op-mode(s):        32-bit, 64-bit
Byte Order:            Little Endian
CPU(s):                2
On-line CPU(s) list:   0,1
Thread(s) per core:    2
Core(s) per socket:    1
Socket(s):             1
NUMA node(s):          1
Vendor ID:             GenuineIntel
CPU family:            6
Model:                 85
Model name:            Intel(R) Xeon(R) Platinum 8124M CPU @ 3.00GHz
Stepping:              3
CPU MHz:               3000.000
BogoMIPS:              6000.00
Hypervisor vendor:     KVM
Virtualization type:   full
L1d cache:             32K
L1i cache:             32K
L2 cache:              1024K
L3 cache:              25344K
NUMA node0 CPU(s):     0,1
Flags:                 fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm 3dnowprefetch fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f rdseed adx smap clflushopt clwb avx512cd xsaveopt xsavec xgetbv1 ida arat

OpenSSL 1.0.2g  1 Mar 2016
built on: reproducible build, date unspecified
options:bn(64,64) rc4(16x,int) des(idx,cisc,16,int) aes(partial) blowfish(idx)
compiler: cc -I. -I.. -I../include  -fPIC -DOPENSSL_PIC -DOPENSSL_THREADS -D_REENTRANT -DDSO_DLFCN -DHAVE_DLFCN_H -m64 -DL_ENDIAN -g -O2 -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -Wl,-Bsymbolic-functions -Wl,-z,relro -Wa,--noexecstack -Wall -DMD32_REG_T=int -DOPENSSL_IA32_SSE2 -DOPENSSL_BN_ASM_MONT -DOPENSSL_BN_ASM_MONT5 -DOPENSSL_BN_ASM_GF2m -DSHA1_ASM -DSHA256_ASM -DSHA512_ASM -DMD5_ASM -DAES_ASM -DVPAES_ASM -DBSAES_ASM -DWHIRLPOOL_ASM -DGHASH_ASM -DECP_NISTZ256_ASM
The 'numbers' are in 1000s of bytes per second processed.
type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
aes-128-ctr     373601.31k  1349180.10k  2611213.91k  3216150.87k  3460380.25k
aes-128-gcm     646483.14k  1477658.58k  2778828.03k  4233357.31k  5216542.72k

c4.xlargeと比較してCPU自体の性能向上がはっきりわかります。

考察

この検証ではt2, m4, c4ではCPU種別のバラつきはありませんでしたが、Fargateではバラつきがありました。Fargateで25taskを調べた結果、3種類観測できました(Iyv Bridge多い…)

11 Intel(R) Xeon(R) CPU E5-2670 v2 @ 2.50GHz
4  Intel(R) Xeon(R) CPU E5-2676 v3 @ 2.40GHz
10 Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

vCPUが2以下の場合でもコンテナにCPUが2個見えているようでした。CPUのモデルナンバーを見るとt2, m3, m4で提供されるCPU世代とクロック数になっているようです。AWS の CPU の歴史とそこから見えてくる戦略を合せて読むと、第3世代、第4世代(m3, m4)の余剰リソースを使っているように見えます。

vCPUやメモリのリソースを増やしてもCPUの世代やクロック数はスケールアップせず、あくまでvCPU数(4vCPUにするとCPU数も増える)とメモリだけのようです。したがって、vCPUを増やしてもアプリケーションが複数のCPUを使う構成でない場合、十分なパフォーマンスが得られない可能性があります。Goなら大丈夫ですね。

起動までの時間はdesired countによらずおおよそ60秒程度でrunningになりました。

興味深いのはベンチ結果を見ると、実のところvCPUの値が増えてもさしてスコアが上がっていません。

Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

0.25vCPU
  type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
  aes-128-gcm     404526.70k   932581.21k  2032351.23k  2862813.18k  3520877.91k

1vCPU
  type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
  aes-128-gcm     400038.38k   918257.05k  2025422.18k  2867529.81k  3485180.59k

負荷を掛けながら mpstat コマンドで詳細を確認しました。

Intel(R) Xeon(R) CPU E5-2686 v4 @ 2.30GHz

0.25vCPU
  Single thread (with AES-NI enabled)
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-gcm     397859.26k   939669.16k  2028740.50k  2852892.67k  3501850.62k

    14:44:41     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    14:44:42     all   13.07    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   86.93
    14:44:42       0   25.74    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   73.27
    14:44:42       1    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00  100.00

  Single thread, 2 process (with AES-NI enabled)
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-gcm     400801.31k   917082.84k  2014668.13k  2888170.44k  3444925.05k
    aes-128-gcm     397097.51k   934670.82k  1999300.72k  2846025.96k  3449496.22k

    15:33:05     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    15:33:06     all   12.56    0.00    0.50    0.00    0.00    0.00    0.00    0.00    0.00   86.93
    15:33:06       0   12.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   88.00
    15:33:06       1   14.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   86.00

1vCPU
  Single thread (with AES-NI enabled)
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-gcm     400761.52k   919781.78k  2018643.29k  2859059.20k  3481364.73k

    15:16:20     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    15:16:21     all   50.25    0.00    0.50    0.00    0.00    0.00    0.00    0.00    0.00   49.25
    15:16:21       0  100.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00
    15:16:21       1    0.99    0.00    1.98    0.00    0.00    0.00    0.00    0.00    0.00   97.03

  Single thread, 2 process (with AES-NI enabled)

    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-gcm     239594.50k   493130.86k  1034230.53k  1450766.34k  1761996.12k
    aes-128-gcm     241122.42k   501019.10k  1056970.26k  1498347.32k  1733738.50k

    15:22:08     CPU    %usr   %nice    %sys %iowait    %irq   %soft  %steal  %guest  %gnice   %idle
    15:22:09     all   51.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   49.00
    15:22:09       0   50.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00    0.00   50.00
    15:22:09       1   51.49    0.00    0.99    0.00    0.00    0.00    0.00    0.00    0.00   47.52

確かにCPU時間はきちんとcapされているようですが、シングルスレッド、シングルスレッド * 2同時実行でのスコアは期待するような変化ありませんでした。ベンチマークの方法に問題があるのか、AES-NIの影響なのか、あるいはコンテナホストのリソースが空いてれば使えるのか、今後の調査にしたいと思います(あるいは誰か教えてほしい)。

価格についての感想

ここでは常時起動しているワークロードの場合を想定して検討します。なぜかというと、ECSでそのように運用しているからです。

Fargateで一番安いのは 0.25vCPU, 0.5GBで約$14/月です。t2.nanoは約$4/月です。例えばまったくリソースは不要だけど常時起動していて欲しい(ボットなど)時はt2.nanoのほうが安いですね…。インスタンスの管理が不要とはいえ、一番安いところでは t2.nano と張り合ってほしいです。

これはたぶんvCPUとメモリの下限を緩和して 0.1vCPU/0.128GB みたいな組み合せができればいけそうな気がします。

(37.6464/vCPU * 0.1) + (9.4488/GB * 0.128) = $4.97

これは欲しい…!!(特にGo製のアプリは省エネなので)

9日目の記事10日目の記事に価格の話がありました。通じて言えるのは適切なvCPUとメモリの割り当てだと解釈しています。すでにECSで運用している人はECSサービスのメトリクスを見ることで予約した値に対しての使用率が確認できます。使用率が5〜6割程度になる組み合せで価格を検証すると納得感がでてきそうです。記事にあるようにバッファ用やデプロイ時用などのために余剰インスタンスをプロビジョニングする必要がないのもFargateのよい点だと思います。

同じマネージドであるRDSではMultiAZでEC2インスタンス代に対してだいたい1.5〜倍に設定されていますが、リザーブドがあるため、価格差はもう少し小さくなります。ここはぜひFargate版のリザーブドを期待しつつ、記事を締めたいと思います。

付録

Fargate上のコンテナにsshして調査するためのsshd用のコンテナイメージを作成しました。

https://github.com/nabeken/docker-simple-sshd

docker run -d -e GITHUB_USER_NAME=foobar -P local/docker-simple-sshd

として起動(あるいは同等のタスク定義を書く)すると、起動時にGithubからfoobarユーザーの公開鍵を取得してsshdを起動してくれます。テストのお供にどうぞ。

参考: https://dev.classmethod.jp/cloud/aws/dive-into-aws-fargate/

続きを読む

Raspberry Pi 3 でAlexaと対話する

re:Invent 2017AlexaをRaspberryPi経由で利用する方法のワークショップに参加した。

ワークショップでは、構成済みのMicroSDを使って、単純にコマンドを流したり、支持されたようにファイルを書き換えたりしただけだったので、まっさらなMicroSDの状態から同様に動くようになるまでをやってみたいと思う。

用意するもの

Raspberry Pi 3 Model B
USBマイク < 多分これ
イヤホン
USBキーボード
USBマウス
HDMIケーブル
ディスプレイ
MicroSD Class10 16G

事前準備(Macでの作業)

インストーラーのダウンロード

こちらからNOOBSをダウンロードする
今回ダウンロードしたバージョンは v2.4.5

MicroSDにコピーする

ダウンロードしたNOOBS_v2_4_5.zipを解凍しFAT32でフォーマット済みのMicroSDにファイルをコピーする。

コマンド例
cp -a Downloads/NOOBS_v2_4_5/* /Volumes/UNTITLED

Raspbianのインストール

先ほどのMicroSDをRaspberry Piに刺して電源を入れるとインストーラーが立ち上がるので、Raspbian [RECOMMENDED]にチェックを入れて、左上のInstallをクリックしてインストールすればOK



事前準備(Raspberry Piでの準備)

一応OSのバージョンチェック

こんな記載があるので、Raspbian Stretchかどうか一応チェック

This guide provides step-by-step instructions to set up the Alexa Voice Service (AVS) Device SDK on a Raspberry Pi running Raspbian Stretch with Desktop

$ cat /etc/os-release
PRETTY_NAME="Raspbian GNU/Linux 9 (stretch)"
NAME="Raspbian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=raspbian
ID_LIKE=debian
HOME_URL="http://www.raspbian.org/"
SUPPORT_URL="http://www.raspbian.org/RaspbianForums"
BUG_REPORT_URL="http://www.raspbian.org/RaspbianBugs"

ネットワークに接続

有線LANもあるので有線でつなげる場合には、何もしなくて大丈夫
Wi-Fiを利用する場合には右上にマークがあるのでそこから接続設定する

SSH/VNCの有効化

有効化しなくてもOKだけど、リモートから行える作業はできるだけリモートで行うと楽なので有効化しておく


ビルド環境の構築+ビルド

基本的にはこちらにある通りに行う
https://github.com/alexa/avs-device-sdk

依存関係のあるライブラリ、AVS Device SDK, Sensory wake word engine のインストール

Sensory wake word engineはオープンソースは非商用限定のライセンスなので注意すること

cd /home/pi/
mkdir sdk-folder
cd sdk-folder
mkdir sdk-build sdk-source third-party application-necessities
cd application-necessities
mkdir sound-files

sudo apt-get update

sudo apt-get -y install 
  git gcc cmake build-essential libsqlite3-dev libcurl4-openssl-dev 
  libfaad-dev libsoup2.4-dev libgcrypt20-dev libgstreamer-plugins-bad1.0-dev 
  gstreamer1.0-plugins-good libasound2-dev doxygen

cd /home/pi/sdk-folder/third-party
wget -c http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz
tar zxf pa_stable_v190600_20161030.tgz
cd portaudio
./configure --without-jack
make

pip install commentjson

cd /home/pi/sdk-folder/sdk-source
git clone git://github.com/alexa/avs-device-sdk.git

cd /home/pi/sdk-folder/third-party
git clone git://github.com/Sensory/alexa-rpi.git
cd ./alexa-rpi/bin/
./license.sh

ビルド

make のオプションは -j4までいけるけど、オーバーヒートに注意しましょう(と書いてある)

cd /home/pi/sdk-folder/sdk-build
cmake /home/pi/sdk-folder/sdk-source/avs-device-sdk 
  -DSENSORY_KEY_WORD_DETECTOR=ON 
  -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=/home/pi/sdk-folder/third-party/alexa-rpi/lib/libsnsr.a 
  -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=/home/pi/sdk-folder/third-party/alexa-rpi/include 
  -DGSTREAMER_MEDIA_PLAYER=ON 
  -DPORTAUDIO=ON 
  -DPORTAUDIO_LIB_PATH=/home/pi/sdk-folder/third-party/portaudio/lib/.libs/libportaudio.a 
  -DPORTAUDIO_INCLUDE_DIR=/home/pi/sdk-folder/third-party/portaudio/include
make SampleApp -j2

Alexa Voice Serviceへの登録

先ほどビルドしたSampleAppからAlexa Voice Serviceへ接続するためには設定を作る必要があるので作成する。

Amazon Developerに登録

こちらからログインする
日本でもEchoが発売されたのでamazon.co.jpアカウントでもログインできるらしい

Alexa Voice Service に製品を登録する

!!! 一度作成した設定は削除できないようなので注意 !!!

Alexa Voice Service を選ぶ

CREATE PRODUCTを選ぶ

プロダクト情報を入力する

Product Name: 任意
Product ID: 任意(あとで使うのでメモっておくこと)
Is your product an app or device?: Device
Will your device use —–: 任意(Deviceを選ぶと出てくる)
Product category: 任意
Brief product description: 任意
How will end users —–: Hands-free
Upload an image: 任意
Do you intend to —–: No
Is this a children’s —–: No

セキュリティプロファイルの設定を作る

CREATE NEW PROFILEを選択すると、下にプロファイル名と説明を入れる欄が出るので、入力する。

URLの登録

次で使うので、Client ID, Client Secretをメモに取る
http://localhost:3000を入力してADDボタンを押す
同様にAllowed return URLsにはhttp://localhost:3000/authresponseを入力してADDボタンを押す

アプリケーション側に設定を反映

その前にvimのインストール

nano, edとかは入ってるけど辛いのでvimをインストール

sudo apt-get install -y vim

設定ファイルの書き換え

YOUR_CLIENT_SECRET, YOUR_CLIENT_ID, YOUR_PRODUCT_IDを先ほどメモったものに書き換えて以下を実行する

cat <<EOF >/home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json
{
    "authDelegate":{
        "clientSecret":"YOUR_CLIENT_SECRET",
        "deviceSerialNumber":"123456",
        "refreshToken":"",
        "clientId":"YOUR_CLIENT_ID",
        "productId":"YOUR_PRODUCT_ID"
   },
   "alertsCapabilityAgent":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/alertsCapabilityAgent.db"
   },
   "settings":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/settings.db",
        "defaultAVSClientSettings":{
            "locale":"en-US"
        }
   },
   "certifiedSender":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/certifiedSender.db"
   },
   "sampleApp":{
       "displayCardsSupported":false
   }
}
EOF

音の設定

cat <<EOF >~/.asoundrc
pcm.!default {
  type asym
   playback.pcm {
     type plug
     slave.pcm "hw:0,0"
   }
   capture.pcm {
     type plug
     slave.pcm "hw:1,0"
   }
}
EOF

リフレッシュトークンの取得

先ほど書き換えず空のままだったrefreshTokenを取得する

まずは、下記コマンドを実行して認証用Webサーバーを立ち上げる

cd /home/pi/sdk-folder/sdk-build && python AuthServer/AuthServer.py

直接もしくはVNCを利用して、Raspberry Pi内のブラウザを立ち上げて http://localhost:3000/ にアクセスするとSign in to [セキュリティプロファイル名] using your Amazon accountのページにリダイレクトされるので、ログインする

ログインが完了すると立ち上げてWebサーバーにリダイレクトされて戻ってきて、The file is written successfully.
Server is shutting down, so you can close this window.
という表示がされている

サンプルアプリケーションの実行

$ cd /home/pi/sdk-folder/sdk-build/SampleApp/src
$ TZ=UTC ./SampleApp 
  /home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json 
  /home/pi/sdk-folder/third-party/alexa-rpi/models
#############################
#       Connecting...       #
#############################

########################################
#       Alexa is currently idle!       #
########################################

///ここに大量のエラーが出ているけど、一旦動作には関係なさそうなので放置///

                  #    #     #  #####      #####  ######  #    #
                 # #   #     # #     #    #     # #     # #   #
                #   #  #     # #          #       #     # #  #
               #     # #     #  #####      #####  #     # ###
               #######  #   #        #          # #     # #  #
               #     #   # #   #     #    #     # #     # #   #
               #     #    #     #####      #####  ######  #    #

       #####                                           #
      #     #   ##   #    # #####  #      ######      # #   #####  #####
      #        #  #  ##  ## #    # #      #          #   #  #    # #    #
       #####  #    # # ## # #    # #      #####     #     # #    # #    #
            # ###### #    # #####  #      #         ####### #####  #####
      #     # #    # #    # #      #      #         #     # #      #
       #####  #    # #    # #      ###### ######    #     # #      #

+----------------------------------------------------------------------------+
|                                  Options:                                  |
| Wake word:                                                                 |
|       Simply say Alexa and begin your query.                               |
| Tap to talk:                                                               |
|       Press 't' and Enter followed by your query (no need for the 'Alexa').|
| Hold to talk:                                                              |
|       Press 'h' followed by Enter to simulate holding a button.            |
|       Then say your query (no need for the 'Alexa').                       |
|       Press 'h' followed by Enter to simulate releasing a button.          |
| Stop an interaction:                                                       |
|       Press 's' and Enter to stop an ongoing interaction.                  |
| Privacy mode (microphone off):                                             |
|       Press 'm' and Enter to turn on and off the microphone.               |
| Playback Controls:                                                         |
|       Press '1' for a 'PLAY' button press.                                 |
|       Press '2' for a 'PAUSE' button press.                                |
|       Press '3' for a 'NEXT' button press.                                 |
|       Press '4' for a 'PREVIOUS' button press.                             |
| Settings:                                                                  |
|       Press 'c' followed by Enter at any time to see the settings screen.  |
| Speaker Control:                                                           |
|       Press 'p' followed by Enter at any time to adjust speaker settings.  |
| Info:                                                                      |
|       Press 'i' followed by Enter at any time to see the help screen.      |
| Quit:                                                                      |
|       Press 'q' followed by Enter at any time to quit the application.     |
+----------------------------------------------------------------------------+

こんな感じで動いてとりあえずひと段落、Alexaと呼びかけるか、t + EnterでLinteningモードに入る
そうすると、以下のように、ステータスが遷移して、Speakingで話してくれるはず、、、はず、、、

############################
#       Listening...       #
############################

###########################
#       Thinking...       #
###########################

###########################
#       Speaking...       #
###########################

########################################
#       Alexa is currently idle!       #
########################################

あれれ?

音が出ない

音が出ない何故???と思ったら、ディスプレイ側のピンジャックにイヤホンを繋げたら声が聞こえる
ということはHDMI側に信号が流れてしまっているみ???

インターフェイスの優先度的な問題かと思い、HDMIを抜いたり、刺したり、設定変えたりしたけれども解決の糸口が見えないのでとっても困ってます

例えば、以下を実行してもやっぱりHDMI経由で音が出力される・・・

$ amixer cset numid=3 1
numid=3,iface=MIXER,name='PCM Playback Route'
  ; type=INTEGER,access=rw------,values=1,min=0,max=2,step=0
  : values=0

上の状況でYouTubeを確認したところ、ピンジャックからの出力になっていたので、ここの設定はAlexaには関係なさそう
また、ワークショップで使ったMicroSDを刺して起動した場合には、しっかりとピンジャックから音が出力されるので何らかの設定ミスの線が濃厚

RaspberryPiの知見も、Linuxで音を出す的な知見も全くないので、お手上げ状態です
どなたかわかる方いらっしゃったら教えてもらえたら嬉しいです

音が切れる

もう一つの問題として、Alexaの話が途中で切れることWhat's your nameときくとMy name is Aleという感じで途中で切れてしまう
これも設定で何とかできるんじゃないかと思っているけれども、今の所、どう調べたらいいものやらという感じで止まっている

音がピンジャックから出ない問題と同様に教えてもらえたら嬉しいです

助けてもらえるときのために、関連しそうな情報を末尾に載せておきます

今後のやりたいこと

単純にAlexaと声をかけて、声で操作するだけならEchoを使えばいいだけなので、すぐに思いつくようなことだけではあるけれども、以下のようなことを考えている

  • Wake word engineが独立しているので、Alexa以外の言葉に反応させられたら、専用で作る意味が出てくるので、試したい
  • Wake wordだけでなくコマンドから入力受付状態にできるので、受付システムとかできそうなので試したい

音の設定関連の情報

$ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
$ aplay -L
null
    Discard all samples (playback) or generate zero samples (capture)
default
sysdefault:CARD=ALSA
    bcm2835 ALSA, bcm2835 ALSA
    Default Audio Device
dmix:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct sample mixing device
dmix:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct sample mixing device
dsnoop:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct sample snooping device
dsnoop:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct sample snooping device
hw:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Direct hardware device without any conversions
hw:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Direct hardware device without any conversions
plughw:CARD=ALSA,DEV=0
    bcm2835 ALSA, bcm2835 ALSA
    Hardware device with all software conversions
plughw:CARD=ALSA,DEV=1
    bcm2835 ALSA, bcm2835 IEC958/HDMI
    Hardware device with all software conversions
$ amixer cset numid=3
numid=3,iface=MIXER,name='PCM Playback Route'
  ; type=INTEGER,access=rw------,values=1,min=0,max=2,step=0
  : values=0

続きを読む

「私、いくつに見える?」への返答機能をRoBoHoNに実装する(AWS Rekognition連携)

モバイル型ロボット電話 RoBoHoN に、AWS Rekognition APIを連携させ、ユーザーからの「私、いくつに見える?」という問いかけに返事をできるように実装しました。

キャプチャ.PNG

環境

Windows 7 SP1 64bit
Android Studio 2.3.1
RoBoHoN_SDK 1.2.0
RoBoHon端末ビルド番号 02.01.00
AWS SDK for Android 2.6.9

AWS Mobile SDK for Android 導入

  • 導入要件

 Android 2.3.3 (API Level 10) or higher

RoBoHoN は Android 5.0.2 (API Level 21 (Lollipop))

  • 参考

[AWS] Set Up the AWS Mobile SDK for Android
http://docs.aws.amazon.com/mobile/sdkforandroid/developerguide/setup.html

  1. Get the AWS Mobile SDK for Android.
  2. Set permissions in your AndroidManifest.xml file.
  3. Obtain AWS credentials using Amazon Cognito.

[qiita] Amazon Rekognitionで犬と唐揚げを見分けるアプリを作ってみた
https://qiita.com/unoemon/items/2bdf933127b6e225d036

  • 追加ライブラリ
compile 'com.amazonaws:aws-android-sdk-core:2.6.9'
compile 'com.amazonaws:aws-android-sdk-rekognition:2.6.9'
compile 'com.amazonaws:aws-android-sdk-cognito:2.6.9'

RoBoHoN実装

RoBoHoN独自の発語用 VoiceUI 、撮影用ライブラリ、そして Rekognition 通信を行き来をする実装を書いていきます。

  • Rekognition 通信部分参考

[AWS] Documentation » Amazon Rekognition » 開発者ガイド » Amazon Rekognition の開始方法 » ステップ 4: API の使用開始 » 演習 2: 顔の検出 (API)
http://docs.aws.amazon.com/ja_jp/rekognition/latest/dg/get-started-exercise-detect-faces.html

  • インターネット疎通とストレージパーミッション
AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
  • RoBoHonシナリオ関連定義類
ScenarioDefinitions.java
/**
*  Guess my age? シーン、accost、通知設定
*/
public static final String SCN_CALL = PACKAGE + ".guess.call";
public static final String SCN_RES = PACKAGE + ".guess.res";

public static final String ACC_CALL = ScenarioDefinitions.PACKAGE + ".guess.call";
public static final String ACC_RES = ScenarioDefinitions.PACKAGE + ".guess.res";


public static final String FUNC_CALL = "guess_call";
public static final String FUNC_RES = "guess_res";


/**
* memory_pを指定するタグ
*/
public static final String MEM_P_RES = ScenarioDefinitions.TAG_MEMORY_PERMANENT + ScenarioDefinitions.PACKAGE + ".res"; 
  • HVML(ユーザーからの呼びかけで起動、年齢判定のための写真撮影の声かけ、年齢判定を通知する)
home.hvml
<?xml version="1.0" ?>
<hvml version="2.0">
    <head>
        <producer>com.dev.zdev.rekog</producer>
        <description>いくつにみえる?のホーム起動シナリオ</description>
        <scene value="home" />
        <version value="1.0" />
        <situation priority="78" topic_id="start" trigger="user-word">${Local:WORD_APPLICATION} eq
            いくつにみえる
        </situation>
        <situation priority="78" topic_id="start" trigger="user-word">
            ${Local:WORD_APPLICATION_FREEWORD} eq いくつにみえる
        </situation>
    </head>
    <body>
        <topic id="start" listen="false">
            <action index="1">
                <speech>${resolver:speech_ok(${resolver:ok_id})}</speech>
                <behavior id="${resolver:motion_ok(${resolver:ok_id})}" type="normal" />
                <control function="start_activity" target="home">
                    <data key="package_name" value="com.dev.zdev.rekog" />
                    <data key="class_name" value="com.dev.zdev.rekog.MainActivity" />
                </control>
            </action>
        </topic>
    </body>
</hvml>
rekog_call.hvml
<?xml version="1.0" ?>
<hvml version="2.0">
    <head>
        <producer>com.dev.zdev.rekog</producer>
        <description>Guess my age? 撮影呼びかけ</description>
        <scene value="com.dev.zdev.rekog.guess.call" />
        <version value="1.0" />
        <accost priority="75" topic_id="call" word="com.dev.zdev.rekog.guess.call" />
    </head>
    <body>
        <topic id="call" listen="false">
            <action index="1">
                <speech>お顔をよーく見せて……。写真を撮るよ…。<wait ms="300"/></speech>
                <behavior id="assign" type="normal" />
            </action>
            <action index="2">
                <control function="guess_call" target="com.dev.zdev.rekog"/>
            </action>
        </topic>
    </body>
</hvml>
rekog_res.hvml
<?xml version="1.0" ?>
<hvml version="2.0">
    <head>
        <producer>com.dev.zdev.rekog</producer>
        <description>Guess my age? 結果通知</description>
        <scene value="com.dev.zdev.rekog.guess.res" />
        <version value="1.0" />
        <accost priority="75" topic_id="call" word="com.dev.zdev.rekog.guess.res" />
    </head>
    <body>
        <topic id="call" listen="false">
            <action index="1">
                <speech>${memory_p:com.dev.zdev.rekog.res}にみえるみたいだよ</speech>
                <behavior id="assign" type="normal" />
            </action>
            <action index="2">
                <control function="guess_res" target="com.dev.zdev.rekog"/>
            </action>
        </topic>
    </body>
</hvml>
  • MainActivity(各所抜粋)
MainActivity.java

private boolean hascall;

//onCreate ファンクション にカメラ連携起動結果取得用レシーバー登録
        mCameraResultReceiver = new CameraResultReceiver();
        IntentFilter filterCamera = new IntentFilter(ACTION_RESULT_TAKE_PICTURE);
        registerReceiver(mCameraResultReceiver, filterCamera);

//onResume ファンクション にScenn有効化追記
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCN_CALL);
        VoiceUIManagerUtil.enableScene(mVoiceUIManager, ScenarioDefinitions.SCN_RES);

//onResume ファンクション にScenn有効化後、即時発話(初回のみ)

        if (mVoiceUIManager != null && !hascall) {
            VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_CALL);
            VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
        }

//onPause ファンクション にScene無効化
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCN_CALL);
        VoiceUIManagerUtil.disableScene(mVoiceUIManager, ScenarioDefinitions.SCN_RES);        

//onDestroy ファンクション にカメラ連携起動結果取得用レシーバー破棄
        this.unregisterReceiver(mCameraResultReceiver);

    /**
     * VoiceUIListenerクラスからのコールバックを実装する.
     */
    @Override
    public void onExecCommand(String command, List<VoiceUIVariable> variables) {
        Log.v(TAG, "onExecCommand() : " + command);
        switch (command) {
            case ScenarioDefinitions.FUNC_CALL:
                // 写真呼びかけ状況設定
                hascall =true;
                //写真を撮る
                sendBroadcast(getIntentForPhoto(false));
                break;
            case ScenarioDefinitions.FUNC_RES:
                finish();
                break;
            case ScenarioDefinitions.FUNC_END_APP:
                finish();
                break;
            default:
                break;
        }
    }


    /**
     * カメラ連携の結果を受け取るためのBroadcastレシーバー クラス
     * それぞれの結果毎に処理を行う.
     */
    private class CameraResultReceiver extends BroadcastReceiver {
        @Override
        public void onReceive(Context context, Intent intent) {
            String action = intent.getAction();
            Log.v(TAG, "CameraResultReceiver#onReceive() : " + action);
            switch (action) {
                case ACTION_RESULT_FACE_DETECTION:
                    int result = intent.getIntExtra(FaceDetectionUtil.EXTRA_RESULT_CODE, FaceDetectionUtil.RESULT_CANCELED);
                    break;
                case ACTION_RESULT_TAKE_PICTURE:
                    result = intent.getIntExtra(ShootMediaUtil.EXTRA_RESULT_CODE, ShootMediaUtil.RESULT_CANCELED);
                    if(result == ShootMediaUtil.RESULT_OK) {
                        // 1. 撮影画像ファイルパス取得 
                        final String path = intent.getStringExtra(ShootMediaUtil.EXTRA_PHOTO_TAKEN_PATH);
                        Log.v(TAG, "PICTURE_path : " + path);
                        Thread thread = new Thread(new Runnable() {public void run() {
                            try {
                                // 2. APIリクエスト、レスポンス取得
                                String res = (new GetAge()).inquireAge(path, getApplicationContext());
                                Log.v(TAG, "onExecCommand: RoBoHoN:" + res);
                                int ret = VoiceUIVariableUtil.setVariableData(mVoiceUIManager, ScenarioDefinitions.MEM_P_RES, res);
                                VoiceUIManagerUtil.stopSpeech();
                                // 3. RoBoHon 結果発話
                                if (mVoiceUIManager != null) {
                                    VoiceUIVariableListHelper helper = new VoiceUIVariableListHelper().addAccost(ScenarioDefinitions.ACC_RES);
                                    VoiceUIManagerUtil.updateAppInfo(mVoiceUIManager, helper.getVariableList(), true);
                                }
                            } catch (Exception e) {
                                Log.v(TAG, "onExecCommand: Exception" +  e.getMessage());
                            };
                        }});
                        thread.start();}
                    }
                    break;
                case ACTION_RESULT_REC_MOVIE:
                    result = intent.getIntExtra(ShootMediaUtil.EXTRA_RESULT_CODE, ShootMediaUtil.RESULT_CANCELED);
                    break;
                default:
                    break;
            }
        }
    }
  • AWS Rekognition通信
MainActivity.java
package com.dev.zdev.rekog;

/**
 * Created by zdev on 2017/12/10.
 */

import android.util.Log;
import android.content.Context;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

import com.amazonaws.auth.CognitoCachingCredentialsProvider;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.rekognition.AmazonRekognitionClient;

import java.io.ByteArrayOutputStream;
import java.nio.ByteBuffer;
import java.util.List;

import com.amazonaws.services.rekognition.model.DetectFacesRequest;
import com.amazonaws.services.rekognition.model.DetectFacesResult;
import com.amazonaws.services.rekognition.model.FaceDetail;
import com.amazonaws.services.rekognition.model.AgeRange;
import com.amazonaws.services.rekognition.model.Image;

public class GetAge {
    private static final String TAG = GetAge.class.getSimpleName();
    private static int newWidth = 326;
    private static int newHeight = 244;

    private String resMsg = "ゼロさいからひゃくさいの間 ";
    AmazonRekognitionClient amazonRekognitionClient = null;

    private Bitmap resizeImag(String path){
        return Bitmap.createScaledBitmap(BitmapFactory.decodeFile(path), newWidth, newHeight, true);
    };

    private void setAmazonRekognitionClient(Context appcontext){
        // Amazon Cognito 認証情報プロバイダーを初期化します
        CognitoCachingCredentialsProvider credentialsProvider = new CognitoCachingCredentialsProvider(
            appcontext,
            "us-east-1:XXXXXXXXXXXX", // ID プールの ID
            Regions.US_EAST_1 // リージョン
        );
        this.amazonRekognitionClient =  new AmazonRekognitionClient(credentialsProvider);
    };

    public synchronized String inquireAge(String path, Context appcontext) throws Exception {

        try {
            Bitmap img = resizeImag(path);

            if (this.amazonRekognitionClient == null) {
                setAmazonRekognitionClient(appcontext);
            }

            ByteBuffer imageBytes = null;
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            img.compress(Bitmap.CompressFormat.JPEG, 100, baos);
            imageBytes = ByteBuffer.wrap(baos.toByteArray());

            DetectFacesRequest request = new DetectFacesRequest()
                    .withImage(new Image()
                            .withBytes(imageBytes))
                    .withAttributes("ALL");

            DetectFacesResult result = amazonRekognitionClient.detectFaces(request);
            List<FaceDetail> faceDetails = result.getFaceDetails();

            Log.v(TAG, "inquireAge: " + faceDetails.toString());

            for (FaceDetail face: faceDetails) {
                if (request.getAttributes().contains("ALL")) {
                    AgeRange ageRange = face.getAgeRange();
                    Log.v(TAG, "inquireAge: " + ageRange.getLow().toString() + " and " + ageRange.getHigh().toString() + " years old.");
                    this.resMsg = ageRange.getLow().toString() + " さいから " + ageRange.getHigh().toString() + " さいの間 ";
                } else { // non-default attributes have null values.
                    Log.v(TAG, "inquireAge: " + "Here's the default set of attributes:");
                }
            }

            return this.resMsg;

        } catch (Exception e) {
            Log.v(TAG, "inquireAge: exception: " + e.toString());
            return this.resMsg;
        }
    }
}

実行の様子と感想

年末も近いし、色々とひとが集まる機会に使えたらいいな、と、パーティアプリにしたく、実装しました。
(Microsoft「How-Old.net」が流行ったときのように、1~2回/人 試して場が沸いたら上出来!という)

家族で試したところ、下記のようになりました。

  • 30代後半男性: 45 ~ 63 才 (!)の間
  • 30代後半女性: 26 ~ 38 才の間
  • 9才 … 6 ~ 13 才の間
  • 4才 … 4 ~ 7 才の間、4 ~ 9 才の間

私(30代後半)は 「14 ~ 23 才の間」というスコアも叩き出せたので、家族が沸き「ロボホンがひいきしてる!(子供たちの声)」「なんか匙加減して実装してない?」など、”結果をロボホンが発声する”1 という文脈も楽しめました。



  1. ロボホンSDK内規定(0201_SR01MW_Personality_and_Speech_Regulations_V01_00_01)には “ロボホンはユーザに対して誠実です。ユーザを裏切ることはありません(中略) 不確実な情報を話すときは、それとわかる言い回しをする(例:「雨になるよ」→「予報によると、雨になるみたいだよ」” があります。そのため、今回も結果通知の語尾に「~才にみえるよ」でなく「~才にみえるみたいだよ」とつけています。(また、同規定書には “主観を持たない”というくだり(”ロボホンはロボットです 。ロボットは 、基本的に プログラムされたとおりに動くものです 。ロボホン自身の主観(好き嫌いや感想など ロボホン自身の主観(好き嫌いや感想など 、人によって感じ方が変わるもの 、人によって感じ方が変わるもの )は 持たないのが基本的考え方です 。”)ともあります) 

続きを読む

TerraformとDataDogで始めるMonitoring as Code入門

はじめに

この記事は、dwango advent calenderの12日目の記事です!
今年に入ってから、自分の担当しているプロダクトではDataDogを利用してシステムの監視を行なっています。
DataDogを導入したキッカケの一つとして、Terraformで監視設定を構成管理配下に置いてコード化したい!ということがありました。
同じ設定をGUIでぽちぽちするのはなかなかに辛いですし、ドキュメントを書き続けるのも辛いので、すでにAWSのインフラ環境構築で行なっていることと同じようなフローでコード化が行えるのは魅力の一つでした。
ということで、今回は簡単なサンプルコードと共に、TerraformとDataDogで始めるMonitoring as Code入門したいと思います。

事前に必要な作業

  • AWSアカウント、アクセスキー/シークレットキーの準備

    • 1インスタンスぽこっと立ち上げます
  • terraformのインストール
    • 今回は0.11.x系を対象に
    • tfenvが便利
  • DataDogの API Key, Application Keyの払い出し
  • DataDogのslack Integration連携

Terraform DataDog Providerでは何を操作できるのか

2017/12現在、TerraformのDataDog Providerでは以下のリソースの操作を行うことができます。

この記事では、入門ということで、monitorのみ設定します。
コードはこちらにあげてあります。

AWS環境の立ち上げ

  • 1. 上記のリポジトリをgit clone後、下記のようなコマンドでインスタンスに登録するkey_pair用の秘密鍵/公開鍵を作成します
    ※AWS構築時のアクセスキーやプロファイルの設定については割愛します
$ cd aws/
$ ssh-keygen -t rsa -N "" -f batsion
$ mv batsion batsion.pem
  • 2. secret.tfvars.templateをコピーし、作成した公開鍵とagentのインストール時に利用するDataDogのAPI Keysを埋めます
$ cp secret.tfvars.template secret.tfvars
$ vim secret.tfvars
bastion_public_key    = "実際の公開鍵"
datadog_api_key = "実際のAPI Key"
  • 3. terraformを実行し、VPC作成〜インスタンス作成まで行います(apply時にapproveを求められるのでyesを入力
# terraform provider取り込み
$ terraform init
# plan実行
$ terraform plan  -var-file=secret.tfvars
# apply実行
$ terraform apply -var-file=secret.tfvars

以上で監視対象のインスタンスが作成されました。
追加されるとこんな感じにDataDogの方に現れます。
スクリーンショット 2017-12-12 1.49.40.png

DataDogの監視設定追加

さて、続けてmonitor設定の追加を行います。

  • 1. secret.tfvars.templateをコピーし、DataDogのAPI Keys, Application Keysを埋めます
$ cp secret.tfvars.template secret.tfvars
$ vim secret.tfvars
datadog_api_key = "実際のAPI Key"
datadog_app_key = "実際のApplication Key"
  • 2. terraformを実行し、monitor作成まで行います(AWSの時同様にapply時にapproveを求められるのでyesを入力
    bash
    # terraform provider取り込み
    $ terraform init
    # plan実行
    $ terraform plan -var-file=secret.tfvars
    # apply実行
    $ terraform apply -var-file=secret.tfvars

以上でmonitor設定の追加が行われました。
今回はsystem.cpu.user(インスタンスのCPU usertime)の5分平均が50%以上であればwarnnig、60%以上であればcriticalとし、事前に設定したslackチャンネルに通知するようにしています。
これらは、variable.tf にてデフォルト値が設定指定あるので、変更したい場合は適宜変更してみてください。
※例えば下記のように

datadog_monitor_slack_channel = "slack-system-alert"
datadog_monitor_cpu_usertime_thresholds_warning = "60"
datadog_monitor_cpu_usertime_thresholds_critical = "70"

アラートテストを行う

さて、監視がうまくいってるかどうか確認、ということで作成したインスタンスにログインし、インスタンスに負荷を適当にかけてみます
※デフォルトのSecurity Groupでは、サンプルということでどこからでもSSHが可能なようにしているため、batsion_ingress_cidr_blocksの値を適宜変更すると安全かもしれません

# ログイン
$ ssh -i bastion.pem ec2-user@[インスタンス EIP]
# 負荷をかける
$ yes >> /dev/null &

上記を実施後、しばらくすると下記のようにアラートが飛んできます!
スクリーンショット 2017-12-12 1.57.16.png

ということで、yesコマンドを停止し、復旧通知を確認して終了です。おつかれさまでした。
スクリーンショット 2017-12-12 2.11.52.png

なお、作成したインスタンスはterraform destroy -var-file=secret.tfvarsを実行することで削除可能です。

終わりに

簡単でしたが、Monitoring as Code入門でした。
DataDogには、今回のような簡単な監視だけでなく、他にも様々なメトリクスアラートやもっと高度な機械学習型のアラートが存在するので、よりうまい具合に活用しつつ、Monitoring as Codeを推し進めていきたいな、と思います。

続きを読む

はじめてのServerless ✕ Webpack ✕ TypeScript


このエントリーはaratana Advent Calendar 201712日目のエントリーです。

こんばんは!最近Google Home MiniAmazon echo dotを購入したはいいが置き場所に困っている蔭山です。
みなさんはどのような場所に置かれているのでしょうか。。。

前日は新卒エンジニアには決して見えない安定感をお持ちの猿渡くんの「NoSQLについて何か。」という記事でした!
NoSQL?あぁ、聞いたことはある。
みたいな僕でもわかりやすい記事でした!
最近AWSに興味が出始めたところでしたので、ぜひDynamoDBを使って軽い画像投稿サービスでも作ってみます!

さて今回はServerless ✕ Webpack ✕ TypeScriptの組み合わせで使えるように手順をまとめてみたいと思います!

動作環境

今回の動作環境は以下になります!

$ sw_vers
ProductName:    Mac OS X
ProductVersion: 10.12.6
BuildVersion:   16G1114

$ node -v
v8.2.1

$ serverless -v
1.24.0

$ webpack -v
3.5.6

環境準備

nodeやserverlessのインストールについては下記記事を参考に。。。

とりあえずやってみる

まずはServerlessのプロジェクトを作ってみましょう

$ serverless create -t aws-nodejs -p hogehoge
Serverless: Generating boilerplate...
Serverless: Generating boilerplate in "/Users/fugafuga/hogehoge"
 _______                             __
|   _   .-----.----.--.--.-----.----|  .-----.-----.-----.
|   |___|  -__|   _|  |  |  -__|   _|  |  -__|__ --|__ --|
|____   |_____|__|  \___/|_____|__| |__|_____|_____|_____|
|   |   |             The Serverless Application Framework
|       |                           serverless.com, v1.24.0
 -------'

Serverless: Successfully generated boilerplate for template: "aws-nodejs"
$ cd ./hogehoge

JSer御用達npmを使って必要なパッケージをダウンロードしましょう。

$ npm init
$ npm install --save-dev serverless-webpack serverless-offline ts-loader typescript webpack

インストールが終わり次第、各種設定を行います。
今回はTypescript -> ES2015へのコンパイルを目的に設定させていただきます。
細かい設定内容に関しては割愛します。

./serverless.yml
service: hogehoge

provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  region: ap-northeast-1

plugins:
- serverless-webpack
- serverless-offline

functions:
  hello:
    handler: handler.hello
    events:
     - http:
         path: hello
         method: get
./webpack.config.js
module.exports = {
  entry: './handler.ts',
  target: 'node',
  module: {
    loaders: [{
      test: /\.ts$/,
      loader: 'ts-loader'
    }]
  },
  resolve: {
    extensions: ['.ts']
  },
  output: {
    libraryTarget: 'commonjs',
    path: __dirname + '/.webpack',
    filename: 'handler.js'
  }
};
./tsconfig.json
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs"
  },
  "exclude": [
    "node_modules"
  ]
}

準備はできたので、次はTypeScriptでコーディングしてみましょう!

./handler.ts
export * from './src/ts/functions/hello';
./src/ts/functions/hello.ts
export function hello(event, context, callback): void {
  const response = {
    statusCode: 200,

    headers: {
    },

    body: JSON.stringify({
      "message": "Hello Serverless x Webpack x TypeScript!!"
    })
  };

  callback(null, response);
};

コードが書けたらローカル環境で動作確認

$ sls offline
・・・・・・・・・・・・・・・・
途中は割愛。m(__)m
・・・・・・・・・・・・・・・・
Serverless: Routes for hello:
Serverless: GET /hello

Serverless: Offline listening on http://localhost:3000

きちんと動作するか確認。

$ curl -X GET http://localhost:3000/hello
{"message":"Hello Serverless x Webpack x TypeScript!!"}

動作が問題なければ、早速デプロイしてみましょう!

$ sls deploy
・・・・・・・・・・・・・・・・
途中は割愛。m(__)m
・・・・・・・・・・・・・・・・
api keys:
  None
endpoints:
  GET - https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
functions:
  hello: hogehoge-dev-hello

デプロイが完了したようです。
では早速動作確認。

$ curl -X GET https://hogehoge.execute-api.ap-northeast-1.amazonaws.com/dev/hello
{"message":"Hello Serverless x Webpack x TypeScript!!"}

ちゃんと動きましたね!

最後に

無理やりTypeScriptを使った感が凄まじいですね。。。申し訳ありません><
僕個人がTypeScriptを使ったことがなかったため使ってみたかったんです

明日は新卒田村くんの「Ctagsで自由な翼を得たVimについて」です!
お楽しみに!

参考

主にこちらの記事を参考にさせて頂きました!ありがとうございますm(__)m

続きを読む

Beanstalk運用の日常風景

ハンズラボ Advent Calendar 2017 11日目の記事です。

Elastic Beanstalkの運用をそれなりに続けてきたので、溜め込んだTIPS+失敗事例を放出します。
プラットフォームはPHPです。

Daily Buildしましょう

「いやいやうちはデイリーどころか1日複数回buildしてdeployですよ」という方もいらっしゃるでしょうが、すべてのアプリケーションに対して毎日、というわけではないのでは?
「同じソースコードをeb deployして昨日は通ったのに今日は落ちる」ということがあります。
AWSは日夜プロダクトを改善していて、ユーザとして恩恵に預かっているわけですが、ときに牙をむくことがあります。
ということで、平日の出勤時間帯にdeployスクリプトをスケジュール実行しておいて、deploy成功していると安心して出勤できます。

  • 2016年9月、eb-activity.logにて、ascii以外の文字列が入っているエラーが出てdeploy失敗しました。
    (コメントに入ってもNG)
  • 本番以外の環境で、immutableかrolling with additional batchでのdeploy検証できていると、安心です。この二つはEC2を新規に起動するので、後述のpreinit hookのスクリプトから順に動くためです。
    • と言いつつ、.ebextensionsでシンボリックリンクを貼る、みたいなことをしているときに、already existsで落ちるケースも。本番deployして初めて失敗する、みたいなケースは辛い・・・。

ライブラリのバージョンを固定しましょう

常に最新版のライブラリを適用するのがセキュリティ的には望ましいですが、なかなかそうはいかないのが悩ましいところです。。。
検証環境では最新のライブラリ、本番環境は検証済みライブラリ、とかで分けて管理できればいいのですが・・・。なかなか腰が重いです。

  • とあるpeclライブラリをバージョン指定せずにpecl installしていたところ、最新版がPHP7のみのサポートになってdeployに失敗しました。。。
  • プラットフォームのバージョンは検証環境のみ「管理された更新」を適用しています。これも便利。動作に問題がなければ本番環境へ。

eb-activity.logを読みましょう

Beanstalkが管理しているEC2が起動するときや、Application Versionをdeployするときにeb-activity.logが更新されます。
実際に動いてるのは/opt/elasticbeanstalk/hooks配下のスクリプトです。ここに、.ebextensionsで書いた設定やらシェルスクリプトやらも入ってきます。

$ pwd
/opt/elasticbeanstalk/hooks
$ ls
appdeploy  configdeploy  postinit  preinit  restartappserver
  • deployがtimeoutする原因について調べていたところ、composer updateは–no-devがついて実行されていたのに、composer installはオプション無しで実行されていました。.ebextensionsで記述していないAWS製のdeployスクリプトの中で、EC2新規起動時はcomposer installを実行する作りになっていました。
  • 試行錯誤の結果、下記のようにcomposer_optionsで–no-dev指定することにしました。合わせて、hirakさんのprestissimoを使ってcomposer install/updateの並列実行を実現しています。
    • EC2の起動が遅い問題、C5/M5インスタンスが東京リージョンに来てパッと解決してほしい・・・。
composer.config
commands:
  01_update_composer:
    command: export COMPOSER_HOME=/root && /usr/bin/composer.phar self-update 1.5.2 && /usr/bin/composer.phar global require hirak/prestissimo

option_settings:
  - namespace: aws:elasticbeanstalk:application:environment
    option_name: COMPOSER_HOME
    value: /root
  - namespace: aws:elasticbeanstalk:container:php:phpini
    option_name: composer_options
    value: --no-dev

Time Based Scaling しましょう

Elastic BeanstalkはAuto Scalingもよしなにやってくれますが、スパイクアクセスには弱いです。
日常的にiOS/Androidアプリへモバイルプッシュなどを行っていると、プッシュのタイミングでスパイクアクセスが発生します。
プッシュを登録する担当者と相談して、プッシュ送信する時間帯を制限し、その時間帯はスケールアウトしておくことで対策しています。
BeanstalkだけでなくDynamoDBもTime Based Scalingに対応しましたね!(こちらはまだAWS CLIのみで設定可能・・・)

  • BeanstalkのメトリクスだけではELBへの負荷がわからない場合があります。その場合はELBのメトリクスを参照しましょう。AWS CLIでcloudwatchのメトリクスとるときも、NamespaceはELBのものを使います。
  • CPU負荷、デフォルトの平均じゃなくて最大でみたほうがいいことがあります。CPU使用率の平均40%だから平気平気、と思ってたらELBが503返してて、CPU使用率を最大で見たら90%超えててEC2が死んでた、とかあるので・・・。
  • サポートの方に「503頻発してELB足りないぽいから日常的にPreWarmingお願いします」と依頼したら、「SpillOverCount(過剰数)のカウントが上がっていますのでEC2増やしてください」と返答ありました。AWSサポートの皆様、スキル高くて頼りになります。
  • NLB化も検討したいところ。

まとめ

Elastic Beanstalk、AWSにおまかせできる部分が多くて楽ができますが、特有の癖みたいなものがあるので気をつけて使うと安全安心です。

ハンズラボ Advent Calendar 2017 明日12日目は@sr-mtmtです!

続きを読む

AWS SDK for JavaScript with Angular で Upload to S3.

欠員が出たということで、穴埋めさせていただきます。

概要

本記事は、AngularAWS SDK for JavaScriptを利用して、S3にファイルをアップロードするという内容です。
Angular メインですので、AWSサービスの使い方や設定については割愛いたします。ご了承ください。

環境

$ uname -a
Linux ip-10-4-0-188 4.9.62-21.56.amzn1.x86_64 #1 SMP Thu Nov 16 05:37:08 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
$ ng -v

    _                      _                 ____ _     ___
   /    _ __   __ _ _   _| | __ _ _ __     / ___| |   |_ _|
  / △  | '_  / _` | | | | |/ _` | '__|   | |   | |    | |
 / ___ | | | | (_| | |_| | | (_| | |      | |___| |___ | |
/_/   __| |_|__, |__,_|_|__,_|_|       ____|_____|___|
               |___/

Angular CLI: 1.5.5
Node: 8.9.1
OS: linux x64
Angular: 5.0.0
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

@angular/cli: 1.5.5
@angular-devkit/build-optimizer: 0.0.35
@angular-devkit/core: 0.0.22
@angular-devkit/schematics: 0.0.41
@ngtools/json-schema: 1.1.0
@ngtools/webpack: 1.8.5
@schematics/angular: 0.1.10
@schematics/schematics: 0.0.10
typescript: 2.4.2
webpack: 3.8.1
package.json
{
  "name": "qiita",
  "version": "0.0.0",
  "license": "MIT",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "^5.0.0",
    "@angular/common": "^5.0.0",
    "@angular/compiler": "^5.0.0",
    "@angular/core": "^5.0.0",
    "@angular/forms": "^5.0.0",
    "@angular/http": "^5.0.0",
    "@angular/platform-browser": "^5.0.0",
    "@angular/platform-browser-dynamic": "^5.0.0",
    "@angular/router": "^5.0.0",
    "core-js": "^2.4.1",
    "rxjs": "^5.5.2",
    "zone.js": "^0.8.14"
  },
  "devDependencies": {
    "@angular/cli": "1.5.5",
    "@angular/compiler-cli": "^5.0.0",
    "@angular/language-service": "^5.0.0",
    "@types/jasmine": "~2.5.53",
    "@types/jasminewd2": "~2.0.2",
    "@types/node": "~6.0.60",
    "codelyzer": "^4.0.1",
    "jasmine-core": "~2.6.2",
    "jasmine-spec-reporter": "~4.1.0",
    "karma": "~1.7.0",
    "karma-chrome-launcher": "~2.1.1",
    "karma-cli": "~1.0.1",
    "karma-coverage-istanbul-reporter": "^1.2.1",
    "karma-jasmine": "~1.1.0",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "~5.1.2",
    "ts-node": "~3.2.0",
    "tslint": "~5.7.0",
    "typescript": "~2.4.2"
  }
}

手順

1) AWS SDK for JavaScriptをインストール

$ npm i --save-prod aws-sdk

2) src/app/tsconfig.app.json を編集

tsconfig.app.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "es2015",
-    "types": []
+    "types": ["node"]
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ]
}

参照: Usage_with_TypeScript

3) S3アップロード用コンポネントを作成

$ ng g component s3-upload
  create src/app/s3-upload/s3-upload.component.css (0 bytes)
  create src/app/s3-upload/s3-upload.component.html (28 bytes)
  create src/app/s3-upload/s3-upload.component.spec.ts (643 bytes)
  create src/app/s3-upload/s3-upload.component.ts (280 bytes)
  update src/app/app.module.ts (408 bytes)

4) 各種ファイルを編集

src/app/app.component.html

+ <app-s3-upload></app-s3-upload>

src/app/app.module.ts

import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { NgModule } from '@angular/core';


import { AppComponent } from './app.component';
import { S3UploadComponent } from './s3-upload/s3-upload.component';
import { S3Service } from './s3-upload/s3.service';

@NgModule({
  declarations: [
    AppComponent,
    S3UploadComponent,
  ],
  imports: [
    BrowserModule,
    HttpClientModule,
  ],
  providers: [
    S3Service,
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }

src/app/s3-upload/s3-upload.html

<div class="form">
  <div class="form-group">
    <label class="col-xs-2">select file</label>
    <div class="col-xs-10">
      <input type="file" (change)="upload($event)">
    </div>
  </div>
</div>
<div class="debug_print">
  <p>httpUploadProgress {{ httpUploadProgress | json }}</p>
</div>

src/app/s3-upload/s3-upload.component.ts

import { Component, OnInit } from '@angular/core';
import { S3Service } from './s3.service';
import * as AWS from 'aws-sdk';

@Component({
  selector: 'app-s3-upload',
  templateUrl: './s3-upload.component.html',
  styleUrls: ['./s3-upload.component.css']
})
export class S3UploadComponent implements OnInit
{
  public httpUploadProgress: {[name: string]: any} = {
    ratio : 0,
    style : {
      width: '0',
    }
  };


  /**
   * @desc constructor
   */
  constructor(private s3Service: S3Service)
  {
    this.s3Service.initialize()
      .subscribe((res: boolean) => {
        if (! res) {
          console.log('S3 cognito init error');
        }
      })
  }


  /**
   * @desc Angular LifeCycle
   */
  ngOnInit()
  {
    this.s3Service.progress
      .subscribe((res: AWS.S3.ManagedUpload.Progress) => {
        this.httpUploadProgress.ratio = res.loaded * 100 / res.total;
        this.httpUploadProgress.style.width = this.httpUploadProgress.ratio + '%';
      });
  }


  /**
   * @desc file upload
   */
  public upload(event: Event): void
  {
    this.httpUploadProgress.ratio = 0;
    this.httpUploadProgress.style.width = '0';
    this.s3Service.onManagedUpload((<HTMLInputElement>event.target).files[0]);
  }
}

src/app/s3-upload/s3.service.ts

import { Injectable, EventEmitter } from '@angular/core';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/observable/of';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/map';
import * as AWS from 'aws-sdk';
import { AWS_ENV } from '../../environments/environment';

@Injectable()
export class S3Service
{
  private s3: AWS.S3;
  public progress: EventEmitter<AWS.S3.ManagedUpload.Progress> = new EventEmitter<AWS.S3.ManagedUpload.Progress>();


  constructor(private http: HttpClient) { }


  /**
   * @desc set CognitoIdentityId
   * 
   * @return string IdentityId: ex) ap-northeast-1:01234567-9abc-df01-2345-6789abcd
   */
  public initialize(): Observable<boolean>
  {
    return this.http.get('/assets/cognito.php')
      .map((res: any) => {
        // resに対する例外処理を追加する
        // ...

        // Amazon Cognito 認証情報プロバイダーを初期化します
        AWS.config.region = AWS_ENV.region;
        AWS.config.credentials = new AWS.CognitoIdentityCredentials({
          IdentityId: res.results[0].IdentityId,
        });
        this.s3 = new AWS.S3({
          apiVersion: AWS_ENV.s3.apiVersion,
          params: {Bucket: AWS_ENV.s3.Bucket},
        });
        return true;
      })

      .catch((err: HttpErrorResponse) => {
        console.error(err);
        return Observable.of(false);
      });
  }

  /**
   * @desc AWS.S3
   */
  public onManagedUpload(file: File): Promise<AWS.S3.ManagedUpload.SendData>
  {
    let params: AWS.S3.Types.PutObjectRequest = {
      Bucket: AWS_ENV.s3.Bucket,
      Key: file.name,
      Body: file,
    };
    let options: AWS.S3.ManagedUpload.ManagedUploadOptions = {
      params: params,
      partSize: 64*1024*1024,
    };
    let handler: AWS.S3.ManagedUpload = new AWS.S3.ManagedUpload(options);
    handler.on('httpUploadProgress', this._httpUploadProgress.bind(this));
    handler.send(this._send.bind(this));
    return handler.promise();
  }

  /**
   * @desc progress
   */
  private _httpUploadProgress(progress: AWS.S3.ManagedUpload.Progress): void
  {
    this.progress.emit(progress);
  }

  /**
   * @desc send
   */
  private _send(err: AWS.AWSError, data: AWS.S3.ManagedUpload.SendData): void
  {
    console.log('send()', err, data);
  }
}

src/environments/environment.ts

// The file contents for the current environment will overwrite these during build.
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
// The list of which env maps to which file can be found in `.angular-cli.json`.

export const environment = {
  production: false
};

export const AWS_ENV = {
  region: 'YOUR_REGION',
  s3: {
    apiVersion: 'YOUR_VERSION',
    Bucket: 'YOUR_BACKET',
  },
};

5) ビルド&実行&確認

test.png

$ aws s3 ls s3://YOUR_BACKET/
2017-12-10 08:13:54   10485760 10MB.bin

解説

AWS SDKを使ってファイルをアップロードするには、PutObject()を使うのが手っ取り早いですが
JSからファイルをアップロードするときには、UI/UXの観点からプログレスを表示してあげるのがよいので
それに対応したメソッドである、ManagedUpload()を利用しました。

5.0.0 では、zoneを意識することなく、プログレスがきちんとレンダリングされましたので
プログレスバーの実装は容易に行なえます。

以上、穴埋め記事でした。

7日目は、@segawm さんです。

続きを読む

Route53 で S3 バケットへ alias レコードを作った際のリダイレクトのふるまい

 Amazon S3 は、全てのリクエストをリダイレクトや、パスに応じたリダイレクトを設定できる機能を持っています。これは Static Web Hosting という設定を入れることにより実現可能です。実際の技術詳細については以下の公式ドキュメントを参照してください。

リダイレクト設定

gochiusa.53ningen.com に対して gochiusa.com へのリダイレクト設定作業は以下の 3 STEP です

  1. S3 バケットを gochiusa.53ningen.com という名前で作成する
  2. Static Web Hosting 機能を有効にして、すべてのリクエストを gochiusa.com にリダイレクトする設定を入れる
  3. Route 53 に gochiusa.53ningen.com に対して先ほど作ったバケットへのエイリアスを張る A レコードを作成する

動作確認

作業後、設定を dig で確認します

% dig gochiusa.53ningen.com @ns-771.awsdns-32.net.

; <<>> DiG 9.8.3-P1 <<>> gochiusa.53ningen.com @ns-771.awsdns-32.net.
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23376
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;gochiusa.53ningen.com.     IN  A

;; ANSWER SECTION:
gochiusa.53ningen.com.  5   IN  A   52.219.68.26

;; AUTHORITY SECTION:
53ningen.com.       172800  IN  NS  ns-1431.awsdns-50.org.
53ningen.com.       172800  IN  NS  ns-2010.awsdns-59.co.uk.
53ningen.com.       172800  IN  NS  ns-393.awsdns-49.com.
53ningen.com.       172800  IN  NS  ns-771.awsdns-32.net.

;; Query time: 59 msec
;; SERVER: 205.251.195.3#53(205.251.195.3)
;; WHEN: Sat Dec  2 02:45:43 2017
;; MSG SIZE  rcvd: 192

解決される 52.219.68.26 は S3 の静的配信サーバーの IP の模様で、振る舞い的には Host ヘッダと同じ名前を持つ S3 バケットへリダイレクトをかけるという形のようです。それを確認するために次のようなリクエストを送ってみます。

% curl -I 52.219.68.26
HTTP/1.1 301 Moved Permanently
x-amz-error-code: WebsiteRedirect
x-amz-error-message: Request does not contain a bucket name.
x-amz-request-id: C6B9BBFE84DAC0E0
x-amz-id-2: L0Hv8iRFv2kIRyLUUlhre6cqJpGKfdPo+LYF4EfjgcIaA3W4L+TOzVhs+U+H5W/J3NRp4Jfnn2A=
Location: https://aws.amazon.com/s3/
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 17:50:44 GMT
Server: AmazonS3

x-amz-error-message: Request does not contain a bucket name. というメッセージからわかるようにバケットネームの指定がないというエラーで AWS S3 のトップページに飛ばされます。次に Host ヘッダをつけるとどうなるか試してみましょう。

% curl -I -H "Host: gochiusa.com" 52.219.68.26
HTTP/1.1 404 Not Found
x-amz-error-code: NoSuchBucket
x-amz-error-message: The specified bucket does not exist
x-amz-error-detail-BucketName: gochiusa.com
x-amz-request-id: DF0A27572D2A69C2
x-amz-id-2: 6PAUKw9BxMEQjZ7ELLQELCOfXdCAMYpQEuH9fN8pdvK3HVCHwUTU3DIZTAaC+vJTevpEsi+jo4I=
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 17:53:07 GMT
Server: AmazonS3

The specified bucket does not exist というエラーメッセージに変わりました。ではそろそろ真面目に先ほど作った gochiusa.53ningen.com という値を Host ヘッダにつけて叩いてみましょう。

% curl -I 52.219.68.26 -H "Host: gochiusa.53ningen.com"
HTTP/1.1 301 Moved Permanently
x-amz-id-2: JIFBN5bduooIXFt6ps6DKsGca1jEWKmojrNJqTx+7MPXEBeHUK3A/BMehFQlsjyYRvzgPcZ2U4w=
x-amz-request-id: 53093E7405B86943
Date: Fri, 01 Dec 2017 17:54:49 GMT
Location: http://gochiusa.com/
Content-Length: 0
Server: AmazonS3

正常に 301 で http://gochiusa.com/ にリダイレクトされていることが確認できました。

実験からわかること

  1. S3 バケットへの alias レコードセットの制約の理由が推測できる

    • Route53 で S3 static web hosting しているバケットに alias を張る際は、公式ドキュメントに記載されているように Record Set の Name と S3 バケット名が一致している必要がある
    • これは Host ヘッダをみて、対象の S3 静的配信コンテンツへのリダイレクトを行なっている仕組みから生まれる制約だろうということが、上記の実験から分かる
  2. S3 バケットへの alias を張っているドメインを指す CNAME を張ると正常に動作しない
    • 実際に gochiusa2.53ningen.com に対して gochiusa.53ningen.com を指すような CNAME レコードセットを作ると以下のような実験結果になる
% dig gochiusa2.53ningen.com @ns-771.awsdns-32.net.

; <<>> DiG 9.8.3-P1 <<>> gochiusa2.53ningen.com @ns-771.awsdns-32.net.
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2232
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 4, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;gochiusa2.53ningen.com.        IN  A

;; ANSWER SECTION:
gochiusa2.53ningen.com. 300 IN  CNAME   gochiusa.53ningen.com.
gochiusa.53ningen.com.  5   IN  A   52.219.0.26

;; AUTHORITY SECTION:
53ningen.com.       172800  IN  NS  ns-1431.awsdns-50.org.
53ningen.com.       172800  IN  NS  ns-2010.awsdns-59.co.uk.
53ningen.com.       172800  IN  NS  ns-393.awsdns-49.com.
53ningen.com.       172800  IN  NS  ns-771.awsdns-32.net.

;; Query time: 120 msec
;; SERVER: 2600:9000:5303:300::1#53(2600:9000:5303:300::1)
;; WHEN: Sat Dec  2 03:10:55 2017
;; MSG SIZE  rcvd: 216

% curl -I 52.219.68.26 -H "Host: gochiusa2.53ningen.com"
HTTP/1.1 404 Not Found
x-amz-error-code: NoSuchBucket
x-amz-error-message: The specified bucket does not exist
x-amz-error-detail-BucketName: gochiusa2.53ningen.com
x-amz-request-id: 294AEC84E239A2AC
x-amz-id-2: VAWMR0xq0Gw1cJOdipIgJBRsx6RMYaaP679GD/hKomU3yOFJ6m7/n62h9wraTQoirBYKofnO2WM=
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 18:04:22 GMT
Server: AmazonS3

続きを読む

ラズパイでスマートスピーカーを自作(stretch)

myalexa.jpeg

ポストスマホの有力候補といえばスマートスピーカーということで、とりあえずデバイス側を作ってみました。
今回は米国で既に先行しているamazon Alexaとやり取りしたいと思います。
なんか色々アレクサと対話したいので英語、米語、ドイツ語、日本語と切り替えれるようにしました。

用意するもの

  • プロト基盤:Raspberry Pi3(2でも良いがwifiが付いているので3)
  • スピーカー:USB,3.5mmでもいいしHDMIでもいい
  • mircoSD:RSPIとの依存があるのでこの中から選ぶことを強くおすすめします。
  • USBキーボード:ラズパイ操作用
  • USBマウス:ラズパイ操作用
  • HDMIケーブル:モニターにつなぐため
  • HDMIモニタ:ラズパイ操作用
  • macbook:OSダウンロード用

大まかな流れ

ラズパイの設定
1. OSインストール
2. OSセットアップ
AVSの構築
1. SDKインストールと環境設定
2. SDKのビルド
3. AVSの認証
4. 実行

はい、では行ってみよう。

ラズパイの設定

1. OSインストール
まずはOS(raspbian)をインストールしましょう。
1. 1 OSのダウンロード
このサイトからダウンロード(https://www.raspberrypi.org/downloads/)

今回はraspbianというDebianベースのOSを使います。
ここにはNoobsという初心者用のインストーラーもあるけど、イケてる君は
Bootイメージを直接扱った方が時短になるので迷わずRasbian stretch2017-09-07を選ぼう。
※国内外の文献ではAVSのSDKがstretchに対応していないと書いているけど、そこはクリアしているので大丈夫。

1.2 SDカードのフォーマット

  • macbookにmicroSDカードを差す。
  • マウントが出来たらSDカードのフォーマットを行う。
  • SDカードの場所を確認
    $diskutil list
    これでSDカードのディレクトリが分かります。(ex:/dev/disk2)

  • SDカードのフォーマット(FATね)
    $sudo diskutil eraseDisk FAT32 RASBIAN MBRFormat /dev/disk2

    SDカードのサイズが30GB以下はFAT32とし、それ以上のサイズはexFATする。
    RASBIANは任意で好きな名前を付けてね

  • SDカードをアンマウントし、コピーができる状態が完了
    $sudo diskuitl unmount /dev/disk2
    disk2をアンマウントする

1.3 SDカードのコピー

  • ダウンロードしたOSを解凍
    $tar xzf 2017-09-07-raspbian-stretch.zip

  • 解凍したイメージをSDカードにコピー
    $sudo dd bs=1m if=2017-09-07-raspbian-stretch.img of=/deb/rdsik2

    なぜデバイス名rdiskになってるの?とお気づきですか。
    それはrdiskの方が書き込む速度が早いからです。
    disk2もrdisk2も同じ場所ですが、disk2とした場合、ランダムアクセスが可能となるデバイスとして転送データがUserSpaceという4KBのバッファを経由して処理されます。
    これはマルチタスクがゆえのことなのですが、これをrdiskで指定することにより、バッファを経由せずにダイレクトでシーケンシャルに処理が進むためにdiskよりも高速になります。
    興味と時間のある方はdisk指定で4GBのコピーの旅をお楽しみ下さい。

2 OSセットアップ
いよいよラズパイを動かしましょう。

2.1 OSの起動
RSPI3にモニタ、キーボード、マウス、SDカードを入れて電源ON
rasbian.jpeg

2.2 初期セットアップ

  • ターミナルを起動し次の設定を行う
    sudo raspi-configでもいいしGUIでもOK。とにかくこれを設定
    ・タイムゾーンの設定
    ・wifiの設定
    ・SSHの設定
    ・キーボード設定
    ※お好みに合わせてVNCも設定

AVSの構築

ここから本番。
1. SDKインストールと環境設定
1.1 まずは最低限必要なパッケージの更新とインストール

  • パッケージの更新
    sudo apt-get update

  • で、必要なパッケージをインストール

sudo apt-get -y install git gcc cmake build-essential libsqlite3-dev libcurl4-openssl-dev libfaad-dev libsoup2.4-dev libgcrypt20-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-good libasound2-dev doxygen

1.2 開発環境のディレクトリを作成

cd /home/pi/ && mkdir sdk-folder && cd sdk-folder && mkdir sdk-build sdk-source third-party application-necessities && cd application-necessities && mkdir sound-files

1.3 フリーの音声ライブラリ(portaudio)を拝借
ディレクトリ移動&ダウンロード&解凍&configure&makeを一気に行います

cd /home/pi/sdk-folder/third-party && wget -c http://www.portaudio.com/archives/pa_stable_v190600_20161030.tgz && tar zxf pa_stable_v190600_20161030.tgz && cd portaudio && ./configure --without-jack && make

1.4 commentjsonのインストール
AVS認証時に必要になるAlexaClientSDKConfig.jsonに書き込みを行うために必要
pip install commentjson

1.5 タイマーとアラーム音をアマゾンのサイトからダウンロード

cd /home/pi/sdk-folder/application-necessities/sound-files/ && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_02._TTH_.mp3 && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_02_short._TTH_.wav && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_01._TTH_.mp3 && wget -c https://images-na.ssl-images-amazon.com/images/G/01/mobile-apps/dex/alexa/alexa-voice-service/docs/audio/states/med_system_alerts_melodic_01_short._TTH_.wav

2. SDKのビルド
2.1 AVSのSDKをクローンします

cd /home/pi/sdk-folder/sdk-source && git clone git://github.com/alexa/avs-device-sdk.git

2.2 ウェイクワードのエンジン(Sensory)もクローン
これをすると「アレクサ!」で反応してくれるようになります。

cd /home/pi/sdk-folder/third-party && git clone git://github.com/Sensory/alexa-rpi.git

で、それの利用規約に同意します。

cd /home/pi/sdk-folder/third-party/alexa-rpi/bin/ && ./license.sh 

2.3 日本語化対応
デフォルトは英語なので、これらのソースを変更し日本語に対応させます。

まずはエンドポイントを日本に変更

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/SampleApplication.cpp
//68行目
// Default AVS endpoint to connect to.
static const std::string DEFAULT_ENDPOINT("https://avs-alexa-fe.amazon.com");

アプリ実行中のメニューに日本語切り替えを追加

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/UIManager.cpp
//89行目のこのメニューに日本語を追加し、英語と入れ替える
static const std::string LOCALE_MESSAGE =
    "+----------------------------------------------------------------------------+n"
    "|                          Language Options:                                 |n"
    "|                                                                            |n"
    "| Press '1' followed by Enter to change the language to US English.          |n"
    "| Press '2' followed by Enter to change the language to UK English.          |n"
    "| Press '3' followed by Enter to change the language to German.              |n"
   "+----------------------------------------------------------------------------+n";
//を、次の内容に変更する
static const std::string LOCALE_MESSAGE =
    "+----------------------------------------------------------------------------+n"
    "|                          Language Options:                                 |n"
    "|                                                                            |n"
    "| Press '1' followed by Enter to change the language to Japan.               |n"
    "| Press '2' followed by Enter to change the language to UK English.          |n"
    "| Press '3' followed by Enter to change the language to German.              |n"
    "| Press '4' followed by Enter to change the language to US English.          |n"
    "+----------------------------------------------------------------------------+n";

メニューの変更をテーブルにも反映

/sdk-folder/sdk-source/avs-device-sdk/SampleApp/src/UIManager.cpp
//44行目 日本語を追加し、英語と入れ替える
static const std::unordered_map<char, std::string> LOCALE_VALUES({{'1', "ja-JP"}, {'2', "en-GB"}, {'3', "de-DE"},{'4', "en-US"}});

2.4 AVSのSDKをビルド
まずはcmakeを走らせます。
ウェイクワードをONやgstreamer許可なのをオプションとして設定しています。

cd /home/pi/sdk-folder/sdk-build && cmake /home/pi/sdk-folder/sdk-source/avs-device-sdk -DSENSORY_KEY_WORD_DETECTOR=ON -DSENSORY_KEY_WORD_DETECTOR_LIB_PATH=/home/pi/sdk-folder/third-party/alexa-rpi/lib/libsnsr.a -DSENSORY_KEY_WORD_DETECTOR_INCLUDE_DIR=/home/pi/sdk-folder/third-party/alexa-rpi/include -DGSTREAMER_MEDIA_PLAYER=ON -DPORTAUDIO=ON -DPORTAUDIO_LIB_PATH=/home/pi/sdk-folder/third-party/portaudio/lib/.libs/libportaudio.a -DPORTAUDIO_INCLUDE_DIR=/home/pi/sdk-folder/third-party/portaudio/include

そしてmake
make SampleApp -j3

makeのjオプションは並行処理の数で多い方がより高速になるがリスクもあるので、とりあえず3程度で。安全なのはもちろんjオプションなし。

3. AVSの認証
3.1 プロダクトの登録
amazonへサインインして今回のプロダクトを登録します。
入力方法はこちらを参考に(https://github.com/alexa/alexa-avs-sample-app/wiki/Create-Security-Profile)

この登録で設定したProductID,ClientID,ClientSecretの3つは認証時に必ず必要になるよ!

3.2 AlexaClientSDKConfig.jsonの設定
これは認証実行時に必要な情報を定義する設定ファイルです

場所:/home/pi/sdk-folder/sdk-build/Integration
ファイル名:AlexaClientSDKConfig.json

AlexaClientConfig.json
{
    "authDelegate":{
        "clientSecret”:”Amazonへ設定したClientSecret”,
        "deviceSerialNumber":"123456,(適当でいい)
        "refreshToken”:”{デフォルトの状態にしておく、認証後に自動的に追加されるため}”,
        "clientId”:”Amazonへ設定したclientID”,
        "productId”:”Amazonへ設定したproductID”
    },
    "alertsCapabilityAgent":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/alerts.db",
        "alarmSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_01._TTH_.mp3",
        "alarmShortSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_01_short._TTH_.wav",
        "timerSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_02._TTH_.mp3",
        "timerShortSoundFilePath":"/home/pi/sdk-folder/application-necessities/sound-files/med_system_alerts_melodic_02_short._TTH_.wav"
    },
    "settings":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/settings.db",
        "defaultAVSClientSettings":{
            "locale":"ja-JP"
        }
    },
    "certifiedSender":{
        "databaseFilePath":"/home/pi/sdk-folder/application-necessities/certifiedSender.db
    }
}

3.3 認証
認証用プログラムを起動、3000ポートで待ち受けます
cd /home/pi/sdk-folder/sdk-build && python AuthServer/AuthServer.py

ブラウザを立ち上げhttp://localhost:3000へアクセス
Developer用のアカウントを聞かれるのでログイン
ログインが成功するとWindowを閉じろというメッセージが出て認証完了

※AlexaClientSDKConfig.jsonのrefreshTokenに新たなトークンが追加されていることが確認できます。

4 実行
4.1 マイクのテスト
テストの前にpiのカレントに設定ファイルを用意します。

ファイル名:.asoundrc
場所:ユーザーカレントディレクトリ

.asoundrc
pcm.!default{
    type asym
    playback.pcm {
        type plug
        slave.pcm "hw:0,0"
    }
    capture.pcm {
        type plug
        slave.pcm "hw:1,0"
    }
}

※playbackは再生、captureは入力の設定ブロックになっている。
slave.pcmの値hw:はa,b a=カード b=デバイス番号となっている。
ラズパイに差したスピーカーとマイクの値を確認し設定すること。
aplay -lで確認できる。

4.2 soxのインストール
音声の加工や編集が出来るコマンドですー。
sudo apt-get install sox -y

4.3 マイクとスピーカーの入出力テスト
rec test.wav

実行したら何か話してみよう。
声に合わせて入力ボリュームが変化している様子がコマンドから分かると思う。
それが確認できたらControl+Cで終了

`aplay /usr/share/sounds/alsa/Front_Center.wav’

「フロントセンタ〜」っていう女性の声が聞こえたらOK
もし聞こえない場合はスピーカーの接続種別により次の設定を行って下さい

sudo amixer cset numid=3 x

xは接続の種類です。
0:自動判別
1:ライン入力(3.5mm)
2:HDMI
自動判別の場合はHDMIが優先されます。

4.4 いよいよAlexa起動
srcに移動
cd /home/pi/sdk-folder/sdk-build/SampleApp/src
SampleAPPを起動

TZ=UTC ./SampleApp /home/pi/sdk-folder/sdk-build/Integration/AlexaClientSDKConfig.json /home/pi/sdk-folder/third-party/alexa-rpi/models

SampleAPPの引数に設定ファイルであるAlexaClientSDKConfig.jsonとmodelsを指定

4.5 「Alexa! こんにちは!」と話しかけてみよう。
あとは、スマホにAlexa APPを追加するかここから好きなスキルを追加してみましょう。

カスタムスキルの作り方(Alexa Skills KitとLambda)はいろんな人が書いてるのでそちらを見てください。

続きを読む

AWS Lambda で Angular アプリを Server Side Rendering してみる

AWS Lambda Advent Calendar 2017 の8日目です。

前書き

Server Side Rendering (SSR) は、いわゆる SPA (Single Page Application) において、ブラウザー上(クライアントサイド)で動的に生成される DOM と同等の内容を持つ HTML をサーバーサイドで出力するための仕組みです。

React、Vue 等のモダンなフロントエンドフレームワークに軒並み搭載されつつあるこの機能ですが、 Angular にもバージョン2以降 Universal という SSR の仕組みがあります。

この仕組みを Lambda の上で動かして、 API Gateway 経由で見れるようにしてみる記事です。

SSR すると何がうれしいの?

以下のようなメリットがあるとされています

  • Web クローラーへの対応(SEO)

    • 検索エンジンのクローラーは HTML の内容を解釈してその内容をインデックスしますが、クライアントサイドで動的に変更された状態までは再現できません。そのため SSR に対応していない SPA では、コンテンツをクローラーに読み込ませるのが困難です。検索エンジンではないですが、 OGPTwitter Card もクローラーで読み込んだ情報を元に表示していますね
  • モバイル、低スペックデバイスでのパフォーマンス改善
    • いくつかのデバイスは JavaScript の実行パフォーマンスが非常に低かったり、そもそも実行できなかったりします。 SSR された HTML があれば、そのようなデバイスでもコンテンツを全く見られないという事態を避けられます
  • 最初のページを素早く表示する

どうやって Lambda で SSR するか

Angular Universal は各 Web サーバーフレームワーク用のミドルウェアとして実装されており、現時点では Express, Hapi, ASP.NET 用のエンジンがリリースされています。

他方 Lambda には AWS が開発した、既存の Express アプリを Lambda 上で動かすための aws-serverless-express があります。

今回は Angular Express Engineaws-serverless-express を組み合わせて Lambda 上で Angular アプリを SSR してみます。

やってみる

公式の Universal チュートリアルをなぞりつつ、 Lambda 対応に必要な部分をフォローしていきます。

Router を使っていないと面白くないので、公式の Angular チュートリアルである Tour of Heroes の完成段階 をいじって SSR 対応にしてみましょう。

チュートリアルのコードをまず動かす

コードを DL して展開、 yarn で依存物をインストールします。

ついでに git init して、 .gitignore も追加しておきましょう。

curl -LO https://angular.io/generated/zips/toh-pt6/toh-pt6.zip
unzip toh-pt6 -d toh-pt6-ssr-lambda
cd toh-pt6-ssr-lambda
yarn

ng serve で動くことを確認しておきます。

yarn run ng serve --open

SSR に必要なものをインストール

yarn add @angular/platform-server @nguniversal/express-engine @nguniversal/module-map-ngfactory-loader express
yarn add --dev @types/express

ルートモジュールを SSR 用に改変

src/app/app.module.ts
import { NgModule, Inject, PLATFORM_ID, APP_ID } from '@angular/core';
import { isPlatformBrowser } from '@angular/common';

// ...

@NgModule({
  imports: [
    BrowserModule.withServerTransition({appId: 'toh-pt6-ssr-lambda'}),

// ...

export class AppModule {
  constructor(
    @Inject(PLATFORM_ID) private platformId: Object,
    @Inject(APP_ID) private appId: string,
  ) {
    const platform = isPlatformBrowser(platformId) ? 'browser' : 'server';

    console.log({platform, appId});
  }
}

サーバー用ルートモジュールを追加

yarn run ng generate module app-server --flat true
src/app/app-server.module.ts
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ServerModule } from '@angular/platform-server';
import { ModuleMapLoaderModule } from '@nguniversal/module-map-ngfactory-loader';

import { AppModule } from './app.module';
import { AppComponent } from './app.component';

@NgModule({
  imports: [
    CommonModule,
    AppModule,
    ServerModule,
    ModuleMapLoaderModule,
  ],
  declarations: [],
  bootstrap: [AppComponent],
})
export class AppServerModule {}

サーバー用ブートストラップローダーを追加

src/main.server.ts
export { AppServerModule } from './app/app.server.module';

サーバーのコードを実装

server/index.ts
import 'zone.js/dist/zone-node';
import 'reflect-metadata';

import {enableProdMode} from '@angular/core';

import * as express from 'express';
import {join} from 'path';

// Faster server renders w/ Prod mode (dev mode never needed)
enableProdMode();

// Express server
export const app = express();

// * NOTE :: leave this as require() since this file is built Dynamically from webpack
const {AppServerModuleNgFactory, LAZY_MODULE_MAP} = require('../dist/serverApp/main.bundle');

// Express Engine
import {ngExpressEngine} from '@nguniversal/express-engine';
// Import module map for lazy loading
import {provideModuleMap} from '@nguniversal/module-map-ngfactory-loader';

app.engine('html', ngExpressEngine({
  bootstrap: AppServerModuleNgFactory,

  providers: [
    provideModuleMap(LAZY_MODULE_MAP)
  ],
}));

app.set('view engine', 'html');
app.set('views', join(process.cwd(), 'dist', 'browserApp'));

// Server static files from /browser
app.get('*.*', express.static(join(process.cwd(), 'dist', 'browserApp')));

// All regular routes use the Universal engine
app.get('*', (req, res) => {
  res.render(join(process.cwd(), 'dist', 'browserApp', 'index.html'), {req});
});
server/start.ts
import {app} from '.';

const PORT = process.env.PORT || 4000;

app.listen(PORT, () => {
  console.log(`Node server listening on http://localhost:${PORT}`);
});

チュートリアルからの変更点: あとで Lambda で使い回すのでこのコードでは app.listen() せずに単に export しておき、スタート用のファイルは別に用意します。ファイルの置き場所も若干変更しています。

サーバー用のビルド設定を追加

.angular-cli.json
// ...
  "apps": [
    {
      "platform": "browser",
      "root": "src",
      "outDir": "dist/browser",
// ...
    {
      "platform": "server",
      "root": "src",
      "outDir": "dist/server",
      "assets": [
        "assets",
        "favicon.ico"
      ],
      "index": "index.html",
      "main": "main.server.ts",
      "test": "test.ts",
      "tsconfig": "tsconfig.server.json",
      "testTsconfig": "tsconfig.spec.json",
      "prefix": "app",
      "styles": [
        "styles.css"
      ],
      "scripts": [],
      "environmentSource": "environments/environment.ts",
      "environments": {
        "dev": "environments/environment.ts",
        "prod": "environments/environment.prod.ts"
      }
    }
src/tsconfig.server.json
{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "baseUrl": "./",
    "module": "commonjs",
    "types": []
  },
  "exclude": [
    "test.ts",
    "**/*.spec.ts"
  ],
  "angularCompilerOptions": {
    "entryModule": "app/app-server.module#AppServerModule"
  }
}

.angular-cli.json にはクライアント用のビルド設定しか書かれていないので、ここにサーバーサイド用アプリのビルド設定を追加します。 outDir が被らないように変えておきます。

ng build では「サーバーサイド用の Angular アプリのビルド」までは面倒を見てくれますが、サーバー自体のコードのビルドは自力でやる必要があるので、もろもろ追加します。

yarn add --dev awesome-typescript-loader webpack copy-webpack-plugin concurrently
server/webpack.config.js
const {join} = require('path');
const {ContextReplacementPlugin} = require('webpack');
const CopyWebpackPlugin = require("copy-webpack-plugin");

module.exports = {
  entry: {
    server: join(__dirname, 'index.ts'),
    start: join(__dirname, 'start.ts'),
  },

  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  externals: [/(node_modules|main\..*\.js)/],
  output: {
    path: join(__dirname, '..', 'dist', 'server'),
    filename: '[name].js'
  },
  module: {
    rules: [{ test: /\.ts$/, loader: 'awesome-typescript-loader' }]
  },
  plugins: [
    new CopyWebpackPlugin([
      {from: "dist/browserApp/**/*"},
      {from: "dist/serverApp/**/*"},
    ]),

    // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
    // for 'WARNING Critical dependency: the request of a dependency is an expression'
    new ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      join(__dirname, '..', 'src'), // location of your src
      {} // a map of your routes
    ),
    new ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      join(__dirname, '..', 'src'),
      {}
    )
  ]
};

サーバーの実装上の都合で (クライアント用の Angular ビルド + サーバー用の Angular ビルド) → サーバーのビルド という手順を踏む必要があるので、一連のビルド用スクリプトを追加します。

package.json
    "build:prod": "concurrently --names 'browser,server' 'ng build --prod --progress false --base-href /prod/ --app 0' 'ng build --prod --progress false --base-href /prod/ --app 1 --output-hashing false'",
    "prebuild:server": "npm run build:prod",
    "build:server": "webpack --config server/webpack.config.js",
    "start:server": "node dist/server/start.js"

--base-href を指定しているのは、後で API Gateway の仕様との整合性をとるためです。

ローカルでサーバーを動かしてみる

yarn run build:server
yarn run start:server

http://localhost:4000/prod/detail/14 あたりにアクセスして、 SSR されているか確認してみます。

Tour_of_Heroes.png

最初のリクエストに対するレスポンスで、 Angular が生成した HTML が返ってきていることがわかります。チュートリアル第1章で pipe を使って大文字にしたところもちゃんと再現されていますね。

普通に ng serve した場合はこんな感じのはずです。

Tour_of_Heroes.png

Lambda にデプロイする

おなじみの Serverless Framework を使います。いつもありがとうございます。

ここで aws-serverless-express も追加しましょう。

yarn add aws-serverless-express
yarn add --dev @types/aws-serverless-express serverless serverless-webpack

Lambda 用のエントリーポイントを追加します。

lambda/index.ts

import {createServer, proxy} from 'aws-serverless-express';

import {app} from '../server';

export default (event, context) => proxy(createServer(app), event, context);

Lambda (serverless-webpack) 用のビルド設定を追加します。さっきのやつとほぼ同じですが、 libraryTarget: "commonjs" がないと動かないようです。

lambda/webpack.config.js
const {join} = require('path');
const {ContextReplacementPlugin} = require('webpack');
const CopyWebpackPlugin = require("copy-webpack-plugin");
const slsw = require('serverless-webpack');

module.exports = {
  entry: slsw.lib.entries,
  resolve: { extensions: ['.js', '.ts'] },
  target: 'node',
  externals: [/(node_modules|main\..*\.js)/],
  output: {
    libraryTarget: "commonjs",
    path: join(__dirname, '..', 'dist', 'lambda'),
    filename: '[name].js'
  },
  module: {
    rules: [{ test: /\.ts$/, loader: 'awesome-typescript-loader' }]
  },
  plugins: [
    new CopyWebpackPlugin([
      {from: "dist/browserApp/**/*"},
      {from: "dist/serverApp/**/*"},
    ]),

    // Temporary Fix for issue: https://github.com/angular/angular/issues/11580
    // for 'WARNING Critical dependency: the request of a dependency is an expression'
    new ContextReplacementPlugin(
      /(.+)?angular(\\|\/)core(.+)?/,
      join(__dirname, '..', 'src'), // location of your src
      {} // a map of your routes
    ),
    new ContextReplacementPlugin(
      /(.+)?express(\\|\/)(.+)?/,
      join(__dirname, '..', 'src'),
      {}
    )
  ]
};

Serverless の設定ファイルを追加します。あらゆるパスへの GET を Lambda にルーティングするように設定します。

serverless.yml
service: toh-pt6-ssr-lambda
provider:
  name: aws
  runtime: nodejs6.10
  region: ${env:AWS_REGION}
  memorySize: 128
plugins:
- serverless-webpack
custom:
  webpack: lambda/webpack.config.js
  webpackIncludeModules: true
functions:
  main:
    handler: lambda/index.default
    events:
    - http:
        path: /
        method: get
    - http:
        path: "{proxy+}"
        method: get

Serverless をインストールした状態でビルドしようとすると以下のようなエラーが出るので、とりあえず tsconfig.json を変更して回避しておきます。

ERROR in [at-loader] ./node_modules/@types/graphql/subscription/subscribe.d.ts:17:4
    TS2304: Cannot find name 'AsyncIterator'.

ERROR in [at-loader] ./node_modules/@types/graphql/subscription/subscribe.d.ts:29:4
    TS2304: Cannot find name 'AsyncIterable'.
tsconfig.json
    "lib": [
      "es2017",
      "dom",
      "esnext.asynciterable"
    ]

Lambda 用のデプロイスクリプトを追加します。

package.json
    "predeploy": "npm run build:prod",
    "deploy": "serverless deploy"

で、ようやく Lambda のデプロイです。

yarn run deploy

うまくいけば Serverless によって各種 AWS リソースが作られ、 API Gateway のエンドポイントが出力されるはずです。

API Gateway にアクセスして SSR されているか見てみる

先程と同様に確認してみます。

Tour_of_Heroes.png

よさげ。

今後の課題

Lambda + API Gateway で SSR することができました。しかし、いろいろと課題はまだありそうです。

HTTP ステータスコード問題

現状のコードでは固定的に 200 を返しているため、ありえないパスにリクエストが来てもクローラー的には OK と解釈されてしまいます。本来なら 404 などを適切に返すべきです。

HTML 以外をどうやって動的に返すか

一般的なサイトでは sitemap.xml など動的な内容かつ HTML ではないものも返す必要があります。これを Angular でできるかどうか、調べる必要がありそうです。

Express なくせるんじゃね?

今回は Angular -> Universal + Express Engine -> Express -> aws-serverless-express -> Lambda (API Gateway Proxy Integration) という繋ぎ方をしました。

これを Angular -> Universal + [何か] -> Lambda (API Gateway Proxy Integration) にできたら構成要素が減らせていい感じですよね。

renderModuleFactory などを自力で叩く実装ができれば、これが実現できるのかもしれません。

今回作ったコード

参考

続きを読む