20201218のAWSに関する記事は27件です。

privateのAmazonLinux2からyumとExtrasLibraryを利用する方法

AmazonLinux2からAWSが用意するAMIリポジトリにアクセスする方法です。

AMIリポジトリにVPC内からアクセス可能にする

インターネットアクセス可能な場合は特に考慮不要。
VPC内部からアクセスする場合は、VPCエンドポイントを利用しますが
リポジトリはS3に存在するのでS3へのアクセス許可が必要です。

必要な作業は以下の2つです。
1.AmazonLinux2が存在するVPCにVPCエンドポイントを作成する
2.VPCエンドポイントにS3へのアクセス許可を付与する。以下参照。

VPCエンドポイントポリシー例.json
{
"Statement": [
   {
      "Sid": "Amazon Linux AMI Repository Access",
      "Principal": "*",
      "Action": [
         "s3:GetObject"
      ],
      "Effect": "Allow",
      "Resource": [
         "arn:aws:s3:::packages.*.amazonaws.com/*",
         "arn:aws:s3:::repo.*.amazonaws.com/*"
      ]
   }
   ]
}

Extras Libraryの利用

AmazonLinux2ではさらに「Extras Library(amazon-linux-extras)」
と呼ばれる仕組みがあり、「トピック」という単位でパッケージが管理されています。
依存関係を考慮してバンドルされているのでトピック名を指定してインストールすることで
関連パッケージをまとめてインストルすることが可能です。

準備されているトピックを確認する.
amazon-linux-extras
トピックの情報を確認する.
amazon-linux-extras info トピック名
インストール.
sudo amazon-linux-extras install トピック名

EPELを有効にする

epelを有効にするには「amazon-linux-extras」を利用します。
利用するにはインターネットへのアクセスが必要です。

sudo amazon-linux-extras install epel
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AWS の EC2 Graviton2 インスタンスに MySQL をインストールしてみた(だけ)

これは MySQL Advent Calendar 2020 17 日目のエントリです(後から穴埋め)。

AWS では今年、EC2・RDS に 64-bit Arm ベースの Graviton2 プロセッサを採用したインスタンスを立てられるようになりましたので、EC2 の r6g.large インスタンス(2vCPU・メモリ 16GiB)に MySQL Server 8.0.22 をインストールしてみました。

※本当にインストールしただけです(続きは後日)。

EC2 インスタンス起動

まずは 64-bit Arm 向けの CentOS 8.3(あえて Stream ではない)を選んで、
r6g1.png
r6g.large インスタンスを選択して…
r6g2.png
ストレージは gp3 を選んでインスタンスを立ててみました。
r6g3.png

MySQL Community Server 8.0.22 インストール

x86_64 系プロセッサを使う場合とほぼ同様に MySQL Community Server 8.0.22 をインストールしていきます。

※適宜、言語・ロケールなどの設定を行ってください(ここでは省略)。

まずはwgetを入れる
$ sudo dnf install wget
Last metadata expiration check: 0:01:08 ago on Fri 18 Dec 2020 12:56:37 PM UTC.
Dependencies resolved.
================================================================================
 Package       Architecture     Version                Repository          Size
================================================================================
Installing:
 wget          aarch64          1.19.5-10.el8          appstream          716 k

Transaction Summary
================================================================================
Install  1 Package

Total download size: 716 k
Installed size: 2.8 M
Is this ok [y/N]: y
Downloading Packages:
wget-1.19.5-10.el8.aarch64.rpm                  1.0 MB/s | 716 kB     00:00
--------------------------------------------------------------------------------
Total                                           608 kB/s | 716 kB     00:01
warning: /var/cache/dnf/appstream-ad81255f8cddaa99/packages/wget-1.19.5-10.el8.aarch64.rpm: Header V3 RSA/SHA256 Signature, key ID 8483c65d: NOKEY
CentOS Linux 8 - AppStream                      1.6 MB/s | 1.6 kB     00:00
Importing GPG key 0x8483C65D:
 Userid     : "CentOS (CentOS Official Signing Key) <security@centos.org>"
 Fingerprint: 99DB 70FA E1D7 CE22 7FB6 4882 05B5 55B3 8483 C65D
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-centosofficial
Is this ok [y/N]: y
Key imported successfully
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1
  Installing       : wget-1.19.5-10.el8.aarch64                             1/1
  Running scriptlet: wget-1.19.5-10.el8.aarch64                             1/1
  Verifying        : wget-1.19.5-10.el8.aarch64                             1/1

Installed:
  wget-1.19.5-10.el8.aarch64

Complete!
MySQLのリポジトリを追加
$ wget https://dev.mysql.com/get/mysql80-community-release-el8-1.noarch.rpm
--2020-12-18 12:57:54--  https://dev.mysql.com/get/mysql80-community-release-el8-1.noarch.rpm
Resolving dev.mysql.com (dev.mysql.com)... 137.254.60.11
Connecting to dev.mysql.com (dev.mysql.com)|137.254.60.11|:443... connected.
HTTP request sent, awaiting response... 302 Found
Location: https://repo.mysql.com//mysql80-community-release-el8-1.noarch.rpm [following]
--2020-12-18 12:57:55--  https://repo.mysql.com//mysql80-community-release-el8-1.noarch.rpm
Resolving repo.mysql.com (repo.mysql.com)... 23.45.57.22
Connecting to repo.mysql.com (repo.mysql.com)|23.45.57.22|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 30388 (30K) [application/x-redhat-package-manager]
Saving to: ‘mysql80-community-release-el8-1.noarch.rpm’

mysql80-community-r 100%[===================>]  29.68K  --.-KB/s    in 0.001s

2020-12-18 12:57:55 (32.6 MB/s) - ‘mysql80-community-release-el8-1.noarch.rpm’ saved [30388/30388]

$ sudo dnf localinstall mysql80-community-release-el8-1.noarch.rpm
Last metadata expiration check: 0:01:37 ago on Fri 18 Dec 2020 12:56:37 PM UTC.
Dependencies resolved.
================================================================================
 Package                        Arch        Version     Repository         Size
================================================================================
Installing:
 mysql80-community-release      noarch      el8-1       @commandline       30 k

Transaction Summary
================================================================================
Install  1 Package

Total size: 30 k
Installed size: 29 k
Is this ok [y/N]: y
Downloading Packages:
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1
  Installing       : mysql80-community-release-el8-1.noarch                 1/1
  Verifying        : mysql80-community-release-el8-1.noarch                 1/1

Installed:
  mysql80-community-release-el8-1.noarch

Complete!
$ sudo dnf module disable mysql
MySQL 8.0 Community Server                      3.0 MB/s | 1.1 MB     00:00
MySQL Connectors Community                      127 kB/s |  43 kB     00:00
MySQL Tools Community                           246 kB/s |  79 kB     00:00
Dependencies resolved.
================================================================================
 Package           Architecture     Version             Repository         Size
================================================================================
Disabling modules:
 mysql

Transaction Summary
================================================================================

Is this ok [y/N]: y
Complete!
MySQLサーバをインストール
$ sudo dnf install mysql-community-server
Last metadata expiration check: 0:00:25 ago on Fri 18 Dec 2020 12:58:39 PM UTC.
Dependencies resolved.
================================================================================
 Package                   Arch    Version              Repository         Size
================================================================================
Installing:
 mysql-community-server    aarch64 8.0.22-1.el8         mysql80-community  60 M
Installing dependencies:
 libaio                    aarch64 0.3.112-1.el8        baseos             33 k
 mysql-community-client    aarch64 8.0.22-1.el8         mysql80-community  14 M
 mysql-community-client-plugins
                           aarch64 8.0.22-1.el8         mysql80-community 106 k
 mysql-community-common    aarch64 8.0.22-1.el8         mysql80-community 620 k
 mysql-community-libs      aarch64 8.0.22-1.el8         mysql80-community 1.4 M
 perl-Carp                 noarch  1.42-396.el8         baseos             30 k
 perl-Data-Dumper          aarch64 2.167-399.el8        baseos             57 k
 perl-Digest               noarch  1.17-395.el8         appstream          27 k
 perl-Digest-MD5           aarch64 2.55-396.el8         appstream          37 k
 perl-Encode               aarch64 4:2.97-3.el8         baseos            1.5 M
 perl-Errno                aarch64 1.28-416.el8         baseos             76 k
 perl-Exporter             noarch  5.72-396.el8         baseos             34 k
 perl-File-Path            noarch  2.15-2.el8           baseos             38 k
 perl-File-Temp            noarch  0.230.600-1.el8      baseos             63 k
 perl-Getopt-Long          noarch  1:2.50-4.el8         baseos             63 k
 perl-HTTP-Tiny            noarch  0.074-1.el8          baseos             58 k
 perl-IO                   aarch64 1.38-416.el8         baseos            141 k
 perl-MIME-Base64          aarch64 3.15-396.el8         baseos             31 k
 perl-Net-SSLeay           aarch64 1.88-1.module_el8.3.0+410+ff426aa3
                                                        appstream         372 k
 perl-PathTools            aarch64 3.74-1.el8           baseos             90 k
 perl-Pod-Escapes          noarch  1:1.07-395.el8       baseos             20 k
 perl-Pod-Perldoc          noarch  3.28-396.el8         baseos             86 k
 perl-Pod-Simple           noarch  1:3.35-395.el8       baseos            213 k
 perl-Pod-Usage            noarch  4:1.69-395.el8       baseos             34 k
 perl-Scalar-List-Utils    aarch64 3:1.49-2.el8         baseos             67 k
 perl-Socket               aarch64 4:2.027-3.el8        baseos             59 k
 perl-Storable             aarch64 1:3.11-3.el8         baseos             95 k
 perl-Term-ANSIColor       noarch  4.06-396.el8         baseos             46 k
 perl-Term-Cap             noarch  1.17-395.el8         baseos             23 k
 perl-Text-ParseWords      noarch  3.30-395.el8         baseos             18 k
 perl-Text-Tabs+Wrap       noarch  2013.0523-395.el8    baseos             24 k
 perl-Time-Local           noarch  1:1.280-1.el8        baseos             34 k
 perl-URI                  noarch  1.73-3.el8           appstream         116 k
 perl-Unicode-Normalize    aarch64 1.25-396.el8         baseos             78 k
 perl-constant             noarch  1.33-396.el8         baseos             25 k
 perl-interpreter          aarch64 4:5.26.3-416.el8     baseos            6.3 M
 perl-libnet               noarch  3.11-3.el8           appstream         121 k
 perl-libs                 aarch64 4:5.26.3-416.el8     baseos            1.5 M
 perl-macros               aarch64 4:5.26.3-416.el8     baseos             72 k
 perl-parent               noarch  1:0.237-1.el8        baseos             20 k
 perl-podlators            noarch  4.11-1.el8           baseos            118 k
 perl-threads              aarch64 1:2.21-2.el8         baseos             60 k
 perl-threads-shared       aarch64 1.58-2.el8           baseos             47 k
Installing weak dependencies:
 perl-IO-Socket-IP         noarch  0.39-5.el8           appstream          47 k
 perl-IO-Socket-SSL        noarch  2.066-4.module_el8.3.0+410+ff426aa3
                                                        appstream         298 k
 perl-Mozilla-CA           noarch  20160104-7.module_el8.3.0+416+dee7bcef
                                                        appstream          15 k
Enabling module streams:
 perl                              5.26
 perl-IO-Socket-SSL                2.066
 perl-libwww-perl                  6.34

Transaction Summary
================================================================================
Install  47 Packages

Total download size: 88 M
Installed size: 407 M
Is this ok [y/N]: y
Downloading Packages:
(1/47): perl-IO-Socket-IP-0.39-5.el8.noarch.rpm 229 kB/s |  47 kB     00:00
(2/47): perl-Digest-1.17-395.el8.noarch.rpm      78 kB/s |  27 kB     00:00
(3/47): perl-Digest-MD5-2.55-396.el8.aarch64.rp 104 kB/s |  37 kB     00:00
(4/47): perl-Mozilla-CA-20160104-7.module_el8.3 100 kB/s |  15 kB     00:00
(5/47): perl-IO-Socket-SSL-2.066-4.module_el8.3 527 kB/s | 298 kB     00:00
(6/47): perl-Net-SSLeay-1.88-1.module_el8.3.0+4 816 kB/s | 372 kB     00:00
(7/47): libaio-0.3.112-1.el8.aarch64.rpm        222 kB/s |  33 kB     00:00
(8/47): perl-URI-1.73-3.el8.noarch.rpm          243 kB/s | 116 kB     00:00
(9/47): perl-Data-Dumper-2.167-399.el8.aarch64. 400 kB/s |  57 kB     00:00
(10/47): perl-Carp-1.42-396.el8.noarch.rpm      131 kB/s |  30 kB     00:00
(11/47): perl-libnet-3.11-3.el8.noarch.rpm      206 kB/s | 121 kB     00:00
(12/47): perl-Exporter-5.72-396.el8.noarch.rpm  216 kB/s |  34 kB     00:00
(13/47): perl-Errno-1.28-416.el8.aarch64.rpm    158 kB/s |  76 kB     00:00
(14/47): perl-File-Path-2.15-2.el8.noarch.rpm   155 kB/s |  38 kB     00:00
(15/47): perl-File-Temp-0.230.600-1.el8.noarch. 177 kB/s |  63 kB     00:00
(16/47): perl-Encode-2.97-3.el8.aarch64.rpm     1.6 MB/s | 1.5 MB     00:00
(17/47): perl-Getopt-Long-2.50-4.el8.noarch.rpm 141 kB/s |  63 kB     00:00
(18/47): perl-HTTP-Tiny-0.074-1.el8.noarch.rpm  132 kB/s |  58 kB     00:00
(19/47): perl-IO-1.38-416.el8.aarch64.rpm       288 kB/s | 141 kB     00:00
(20/47): perl-MIME-Base64-3.15-396.el8.aarch64.  84 kB/s |  31 kB     00:00
(21/47): perl-Pod-Escapes-1.07-395.el8.noarch.r  80 kB/s |  20 kB     00:00
(22/47): perl-PathTools-3.74-1.el8.aarch64.rpm  189 kB/s |  90 kB     00:00
(23/47): perl-Pod-Usage-1.69-395.el8.noarch.rpm 246 kB/s |  34 kB     00:00
(24/47): perl-Pod-Perldoc-3.28-396.el8.noarch.r 166 kB/s |  86 kB     00:00
(25/47): perl-Pod-Simple-3.35-395.el8.noarch.rp 588 kB/s | 213 kB     00:00
(26/47): perl-Storable-3.11-3.el8.aarch64.rpm   245 kB/s |  95 kB     00:00
(27/47): perl-Scalar-List-Utils-1.49-2.el8.aarc 144 kB/s |  67 kB     00:00
(28/47): perl-Socket-2.027-3.el8.aarch64.rpm    130 kB/s |  59 kB     00:00
(29/47): perl-Term-ANSIColor-4.06-396.el8.noarc 137 kB/s |  46 kB     00:00
(30/47): perl-Text-ParseWords-3.30-395.el8.noar  52 kB/s |  18 kB     00:00
(31/47): perl-Term-Cap-1.17-395.el8.noarch.rpm   59 kB/s |  23 kB     00:00
(32/47): perl-Text-Tabs+Wrap-2013.0523-395.el8.  97 kB/s |  24 kB     00:00
(33/47): perl-Time-Local-1.280-1.el8.noarch.rpm 134 kB/s |  34 kB     00:00
(34/47): perl-Unicode-Normalize-1.25-396.el8.aa 293 kB/s |  78 kB     00:00
(35/47): perl-constant-1.33-396.el8.noarch.rpm   64 kB/s |  25 kB     00:00
(36/47): perl-macros-5.26.3-416.el8.aarch64.rpm 310 kB/s |  72 kB     00:00
(37/47): perl-parent-0.237-1.el8.noarch.rpm      77 kB/s |  20 kB     00:00
(38/47): perl-libs-5.26.3-416.el8.aarch64.rpm   1.6 MB/s | 1.5 MB     00:00
(39/47): perl-interpreter-5.26.3-416.el8.aarch6 5.6 MB/s | 6.3 MB     00:01
(40/47): perl-podlators-4.11-1.el8.noarch.rpm   303 kB/s | 118 kB     00:00
(41/47): perl-threads-2.21-2.el8.aarch64.rpm    132 kB/s |  60 kB     00:00
(42/47): perl-threads-shared-1.58-2.el8.aarch64 134 kB/s |  47 kB     00:00
(43/47): mysql-community-client-plugins-8.0.22-  88 kB/s | 106 kB     00:01
(44/47): mysql-community-common-8.0.22-1.el8.aa 451 kB/s | 620 kB     00:01
(45/47): mysql-community-client-8.0.22-1.el8.aa 5.2 MB/s |  14 MB     00:02
(46/47): mysql-community-libs-8.0.22-1.el8.aarc 914 kB/s | 1.4 MB     00:01
(47/47): mysql-community-server-8.0.22-1.el8.aa  10 MB/s |  60 MB     00:05
--------------------------------------------------------------------------------
Total                                           6.5 MB/s |  88 MB     00:13
warning: /var/cache/dnf/mysql80-community-4467c2595a927b0d/packages/mysql-community-client-8.0.22-1.el8.aarch64.rpm: Header V3 DSA/SHA1 Signature, key ID 5072e1f5: NOKEY
MySQL 8.0 Community Server                       27 MB/s |  27 kB     00:00
Importing GPG key 0x5072E1F5:
 Userid     : "MySQL Release Engineering <mysql-build@oss.oracle.com>"
 Fingerprint: A4A9 4068 76FC BD3C 4567 70C8 8C71 8D3B 5072 E1F5
 From       : /etc/pki/rpm-gpg/RPM-GPG-KEY-mysql
Is this ok [y/N]: y
Key imported successfully
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
  Preparing        :                                                        1/1
  Installing       : perl-Exporter-5.72-396.el8.noarch                     1/47
  Installing       : perl-libs-4:5.26.3-416.el8.aarch64                    2/47
  Installing       : perl-Carp-1.42-396.el8.noarch                         3/47
  Installing       : perl-Scalar-List-Utils-3:1.49-2.el8.aarch64           4/47
  Installing       : perl-parent-1:0.237-1.el8.noarch                      5/47
  Installing       : perl-Text-ParseWords-3.30-395.el8.noarch              6/47
  Installing       : mysql-community-common-8.0.22-1.el8.aarch64           7/47
  Installing       : mysql-community-client-plugins-8.0.22-1.el8.aarch6    8/47
  Installing       : mysql-community-libs-8.0.22-1.el8.aarch64             9/47
  Running scriptlet: mysql-community-libs-8.0.22-1.el8.aarch64             9/47
  Installing       : mysql-community-client-8.0.22-1.el8.aarch64          10/47
  Installing       : perl-Term-ANSIColor-4.06-396.el8.noarch              11/47
  Installing       : perl-macros-4:5.26.3-416.el8.aarch64                 12/47
  Installing       : perl-Errno-1.28-416.el8.aarch64                      13/47
  Installing       : perl-Socket-4:2.027-3.el8.aarch64                    14/47
  Installing       : perl-Text-Tabs+Wrap-2013.0523-395.el8.noarch         15/47
  Installing       : perl-Unicode-Normalize-1.25-396.el8.aarch64          16/47
  Installing       : perl-File-Path-2.15-2.el8.noarch                     17/47
  Installing       : perl-IO-1.38-416.el8.aarch64                         18/47
  Installing       : perl-PathTools-3.74-1.el8.aarch64                    19/47
  Installing       : perl-constant-1.33-396.el8.noarch                    20/47
  Installing       : perl-threads-1:2.21-2.el8.aarch64                    21/47
  Installing       : perl-threads-shared-1.58-2.el8.aarch64               22/47
  Installing       : perl-interpreter-4:5.26.3-416.el8.aarch64            23/47
  Installing       : perl-MIME-Base64-3.15-396.el8.aarch64                24/47
  Installing       : perl-IO-Socket-IP-0.39-5.el8.noarch                  25/47
  Installing       : perl-Time-Local-1:1.280-1.el8.noarch                 26/47
  Installing       : perl-Digest-1.17-395.el8.noarch                      27/47
  Installing       : perl-Digest-MD5-2.55-396.el8.aarch64                 28/47
  Installing       : perl-Net-SSLeay-1.88-1.module_el8.3.0+410+ff426aa3   29/47
  Installing       : perl-Data-Dumper-2.167-399.el8.aarch64               30/47
  Installing       : perl-File-Temp-0.230.600-1.el8.noarch                31/47
  Installing       : perl-Pod-Escapes-1:1.07-395.el8.noarch               32/47
  Installing       : perl-Storable-1:3.11-3.el8.aarch64                   33/47
  Installing       : perl-Term-Cap-1.17-395.el8.noarch                    34/47
  Installing       : perl-Mozilla-CA-20160104-7.module_el8.3.0+416+dee7   35/47
  Installing       : perl-Encode-4:2.97-3.el8.aarch64                     36/47
  Installing       : perl-Pod-Simple-1:3.35-395.el8.noarch                37/47
  Installing       : perl-Getopt-Long-1:2.50-4.el8.noarch                 38/47
  Installing       : perl-podlators-4.11-1.el8.noarch                     39/47
  Installing       : perl-Pod-Usage-4:1.69-395.el8.noarch                 40/47
  Installing       : perl-Pod-Perldoc-3.28-396.el8.noarch                 41/47
  Installing       : perl-HTTP-Tiny-0.074-1.el8.noarch                    42/47
  Installing       : perl-IO-Socket-SSL-2.066-4.module_el8.3.0+410+ff42   43/47
  Installing       : perl-libnet-3.11-3.el8.noarch                        44/47
  Installing       : perl-URI-1.73-3.el8.noarch                           45/47
  Installing       : libaio-0.3.112-1.el8.aarch64                         46/47
  Running scriptlet: mysql-community-server-8.0.22-1.el8.aarch64          47/47
  Installing       : mysql-community-server-8.0.22-1.el8.aarch64          47/47
  Running scriptlet: mysql-community-server-8.0.22-1.el8.aarch64          47/47
  Verifying        : perl-Digest-1.17-395.el8.noarch                       1/47
  Verifying        : perl-Digest-MD5-2.55-396.el8.aarch64                  2/47
  Verifying        : perl-IO-Socket-IP-0.39-5.el8.noarch                   3/47
  Verifying        : perl-IO-Socket-SSL-2.066-4.module_el8.3.0+410+ff42    4/47
  Verifying        : perl-Mozilla-CA-20160104-7.module_el8.3.0+416+dee7    5/47
  Verifying        : perl-Net-SSLeay-1.88-1.module_el8.3.0+410+ff426aa3    6/47
  Verifying        : perl-URI-1.73-3.el8.noarch                            7/47
  Verifying        : perl-libnet-3.11-3.el8.noarch                         8/47
  Verifying        : libaio-0.3.112-1.el8.aarch64                          9/47
  Verifying        : perl-Carp-1.42-396.el8.noarch                        10/47
  Verifying        : perl-Data-Dumper-2.167-399.el8.aarch64               11/47
  Verifying        : perl-Encode-4:2.97-3.el8.aarch64                     12/47
  Verifying        : perl-Errno-1.28-416.el8.aarch64                      13/47
  Verifying        : perl-Exporter-5.72-396.el8.noarch                    14/47
  Verifying        : perl-File-Path-2.15-2.el8.noarch                     15/47
  Verifying        : perl-File-Temp-0.230.600-1.el8.noarch                16/47
  Verifying        : perl-Getopt-Long-1:2.50-4.el8.noarch                 17/47
  Verifying        : perl-HTTP-Tiny-0.074-1.el8.noarch                    18/47
  Verifying        : perl-IO-1.38-416.el8.aarch64                         19/47
  Verifying        : perl-MIME-Base64-3.15-396.el8.aarch64                20/47
  Verifying        : perl-PathTools-3.74-1.el8.aarch64                    21/47
  Verifying        : perl-Pod-Escapes-1:1.07-395.el8.noarch               22/47
  Verifying        : perl-Pod-Perldoc-3.28-396.el8.noarch                 23/47
  Verifying        : perl-Pod-Simple-1:3.35-395.el8.noarch                24/47
  Verifying        : perl-Pod-Usage-4:1.69-395.el8.noarch                 25/47
  Verifying        : perl-Scalar-List-Utils-3:1.49-2.el8.aarch64          26/47
  Verifying        : perl-Socket-4:2.027-3.el8.aarch64                    27/47
  Verifying        : perl-Storable-1:3.11-3.el8.aarch64                   28/47
  Verifying        : perl-Term-ANSIColor-4.06-396.el8.noarch              29/47
  Verifying        : perl-Term-Cap-1.17-395.el8.noarch                    30/47
  Verifying        : perl-Text-ParseWords-3.30-395.el8.noarch             31/47
  Verifying        : perl-Text-Tabs+Wrap-2013.0523-395.el8.noarch         32/47
  Verifying        : perl-Time-Local-1:1.280-1.el8.noarch                 33/47
  Verifying        : perl-Unicode-Normalize-1.25-396.el8.aarch64          34/47
  Verifying        : perl-constant-1.33-396.el8.noarch                    35/47
  Verifying        : perl-interpreter-4:5.26.3-416.el8.aarch64            36/47
  Verifying        : perl-libs-4:5.26.3-416.el8.aarch64                   37/47
  Verifying        : perl-macros-4:5.26.3-416.el8.aarch64                 38/47
  Verifying        : perl-parent-1:0.237-1.el8.noarch                     39/47
  Verifying        : perl-podlators-4.11-1.el8.noarch                     40/47
  Verifying        : perl-threads-1:2.21-2.el8.aarch64                    41/47
  Verifying        : perl-threads-shared-1.58-2.el8.aarch64               42/47
  Verifying        : mysql-community-client-8.0.22-1.el8.aarch64          43/47
  Verifying        : mysql-community-client-plugins-8.0.22-1.el8.aarch6   44/47
  Verifying        : mysql-community-common-8.0.22-1.el8.aarch64          45/47
  Verifying        : mysql-community-libs-8.0.22-1.el8.aarch64            46/47
  Verifying        : mysql-community-server-8.0.22-1.el8.aarch64          47/47

Installed:
  libaio-0.3.112-1.el8.aarch64
  mysql-community-client-8.0.22-1.el8.aarch64
  mysql-community-client-plugins-8.0.22-1.el8.aarch64
  mysql-community-common-8.0.22-1.el8.aarch64
  mysql-community-libs-8.0.22-1.el8.aarch64
  mysql-community-server-8.0.22-1.el8.aarch64
  perl-Carp-1.42-396.el8.noarch
  perl-Data-Dumper-2.167-399.el8.aarch64
  perl-Digest-1.17-395.el8.noarch
  perl-Digest-MD5-2.55-396.el8.aarch64
  perl-Encode-4:2.97-3.el8.aarch64
  perl-Errno-1.28-416.el8.aarch64
  perl-Exporter-5.72-396.el8.noarch
  perl-File-Path-2.15-2.el8.noarch
  perl-File-Temp-0.230.600-1.el8.noarch
  perl-Getopt-Long-1:2.50-4.el8.noarch
  perl-HTTP-Tiny-0.074-1.el8.noarch
  perl-IO-1.38-416.el8.aarch64
  perl-IO-Socket-IP-0.39-5.el8.noarch
  perl-IO-Socket-SSL-2.066-4.module_el8.3.0+410+ff426aa3.noarch
  perl-MIME-Base64-3.15-396.el8.aarch64
  perl-Mozilla-CA-20160104-7.module_el8.3.0+416+dee7bcef.noarch
  perl-Net-SSLeay-1.88-1.module_el8.3.0+410+ff426aa3.aarch64
  perl-PathTools-3.74-1.el8.aarch64
  perl-Pod-Escapes-1:1.07-395.el8.noarch
  perl-Pod-Perldoc-3.28-396.el8.noarch
  perl-Pod-Simple-1:3.35-395.el8.noarch
  perl-Pod-Usage-4:1.69-395.el8.noarch
  perl-Scalar-List-Utils-3:1.49-2.el8.aarch64
  perl-Socket-4:2.027-3.el8.aarch64
  perl-Storable-1:3.11-3.el8.aarch64
  perl-Term-ANSIColor-4.06-396.el8.noarch
  perl-Term-Cap-1.17-395.el8.noarch
  perl-Text-ParseWords-3.30-395.el8.noarch
  perl-Text-Tabs+Wrap-2013.0523-395.el8.noarch
  perl-Time-Local-1:1.280-1.el8.noarch
  perl-URI-1.73-3.el8.noarch
  perl-Unicode-Normalize-1.25-396.el8.aarch64
  perl-constant-1.33-396.el8.noarch
  perl-interpreter-4:5.26.3-416.el8.aarch64
  perl-libnet-3.11-3.el8.noarch
  perl-libs-4:5.26.3-416.el8.aarch64
  perl-macros-4:5.26.3-416.el8.aarch64
  perl-parent-1:0.237-1.el8.noarch
  perl-podlators-4.11-1.el8.noarch
  perl-threads-1:2.21-2.el8.aarch64
  perl-threads-shared-1.58-2.el8.aarch64

Complete!

起動・初期設定

MySQLサーバ起動・初期設定
$ sudo systemctl enable mysqld.service
$ sudo systemctl start mysqld.service
$ sudo fgrep assword /var/log/mysqld.log
2020-12-18T13:08:42.554021Z 6 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: 【初期パスワード】
$ sudo mysql_secure_installation

Securing the MySQL server deployment.

Enter password for user root:(初期パスワードを入力)

The existing password for the user account root has expired. Please set a new password.

New password:(新しいパスワードを入力)

Re-enter new password:(同じパスワードを入力)
The 'validate_password' component is installed on the server.
The subsequent steps will run with the existing configuration
of the component.
Using existing password for root.

Estimated strength of the password: 100
Change the password for root ? ((Press y|Y for Yes, any other key for No) : n

 ... skipping.
By default, a MySQL installation has an anonymous user,
allowing anyone to log into MySQL without having to have
a user account created for them. This is intended only for
testing, and to make the installation go a bit smoother.
You should remove them before moving into a production
environment.

Remove anonymous users? (Press y|Y for Yes, any other key for No) : y
Success.


Normally, root should only be allowed to connect from
'localhost'. This ensures that someone cannot guess at
the root password from the network.

Disallow root login remotely? (Press y|Y for Yes, any other key for No) : y
Success.

By default, MySQL comes with a database named 'test' that
anyone can access. This is also intended only for testing,
and should be removed before moving into a production
environment.


Remove test database and access to it? (Press y|Y for Yes, any other key for No) : y
 - Dropping test database...
Success.

 - Removing privileges on test database...
Success.

Reloading the privilege tables will ensure that all changes
made so far will take effect immediately.

Reload privilege tables now? (Press y|Y for Yes, any other key for No) : y
Success.

All done!
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 11
Server version: 8.0.22 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select * from performance_schema.global_variables where variable_name in ("innodb_buffer_pool_size", "innodb_log_file_size", "innodb_flush_method");
+-------------------------+----------------+
| VARIABLE_NAME           | VARIABLE_VALUE |
+-------------------------+----------------+
| innodb_buffer_pool_size | 134217728      |
| innodb_flush_method     | fsync          |
| innodb_log_file_size    | 50331648       |
+-------------------------+----------------+
3 rows in set (0.00 sec)

mysql> quit
Bye

innodb_dedicated_server=ON で起動してみます(参考:MySQL 8.0 の innodb_dedicated_server について)。

innodb_dedicated_server=ONで起動
$ sudo systemctl stop mysqld.service
$ vi /etc/my.cnf
※末尾に「innodb_dedicated_server=ON」を追記して保存
$ systemctl start mysqld.service
$ mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 8
Server version: 8.0.22 MySQL Community Server - GPL

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> select * from performance_schema.global_variables where variable_name in ("innodb_buffer_pool_size", "innodb_log_file_size", "innodb_flush_method");
+-------------------------+-------------------+
| VARIABLE_NAME           | VARIABLE_VALUE    |
+-------------------------+-------------------+
| innodb_buffer_pool_size | 12884901888       |
| innodb_flush_method     | O_DIRECT_NO_FSYNC |
| innodb_log_file_size    | 1073741824        |
+-------------------------+-------------------+
3 rows in set (0.01 sec)

以上です。

次回(MySQL Advent Calendar 2020 21 日目)はこの環境を r5.large インスタンスと比較してみます。

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

Docker+Rails+AWS(EC2+RDS)を使ってデプロイした

概要

AWSを使ってアプリケーションを公開している人は多く、私も実際に体験してみたくタイトル通りDockerで環境構築し、Railsでアプリケーションを開発し、AWSで実際にデプロイすることにチャレンジしました。
AWSは無料枠内にてデプロイまで目指します。

前提

  • アプリケーションはGithubを使って管理する。
  • 各サービスのアカウント登録は済ませている。(AWS、Github等)
  • AWSはIAMユーザーを作成し、必要なセキュリティ、ポリシーを割り当てている。
  • アプリケーションはDockerを使用して環境構築している。

AWSの設定

AWSのサービスの設定は極力IAMユーザーにて行います。まず、AWSにIAMユーザーでログインする。

  • VPCの作成

まずはVPCの作成を行います。VPCサービスのページを開き、メニューからVPCを選択。VPCの作成を選択します。
VPC名、CIDRブロックを設定し、VPCを作成します。

  • サブネットの作成

サブネットの作成を行います。
サブネットはEC2用のサブネットを1つ、RDS用のサブネットを2つ用意します。
メニューからサブネットを選択し、サブネットの作成を選択します。
先ほど作成したVPCを選択し、サブネット名、アベイラビリティゾーン、CIDRブロックを設定し、サブネットを作成を選択します。
※RDS用のサブネットはアベイラビリティゾーンを別々にします。

  • インターネットゲートウェイの作成

インターネットに接続するためにインターネットゲートウェイを設定します。
メニューからインタネットゲートウェイを選択し、インターネットゲートウェイの作成を選択します。
名前を設定し、インターネットゲートウェイの作成を選択します。
アクションからVPCにアタッチを選択し、作成したVPCにアタッチします。

  • EC2インスタンスの作成

EC2ダッシュボードに開き、インスタンスを起動を選択し、EC2インスタンスを作成します。
EC2インスタンスは無料枠で使えるマシンイメージとインスタンスタイプを選択します。
セキュリティグループは新しく作成します。
SSH接続を行いたいのでSSHの設定を行います。

タイプ プロトコル ポート範囲 ソース
HTTP TCP 80 0.0.0.0/0
SSH TCP 22 マイIP

設定に問題がなければ起動を選択します。
インスタンスを作成するときにキーペアを作成します。
キーペアはEC2にSSH接続する際必要となります。新しいキーペアの作成を選択し、キーペア名を設定しローカルにダウンロードしておきます。

インスタンスメニューの[ステータスチェック]が終了し、[インスタンスの状態]が[実行中]になれば、無事インスタンスが起動したこととなります。

続いてElastic IPを設定します。
メニューよりElastic IPを選択します。Elastic IP アドレスの割り当てを選択し、新しいアドレスの割り当てを選択し、割り当てを選択します。
作成したElastic IPを選択し、アクションからElastic IPアドレスの関連付けを選択します。
作成したEC2インスタンスを設定してElastic IPアドレスをEC2に関連付けます。

  • RDSの作成

RDSを作成する前に、RDS用のサブネットグループの作成を行います。
RDSメニューよりサブネットグループを選択し、DBサブネットグループの作成を選択します。

サブネットグループ名、VPCを選択し、RDS用に作成したサブネット2つを設定し、作成をします。
RDSサービスを開き、データベースの作成を選択します。
データベースはMySQLを選択します。基本的には既存の設定で大丈夫ですが、無料利用枠という箇所は忘れずに選択します。バージョン等は自身の使用するバージョンを設定します。

データベース名、マスターユーザー名、マスターパスワードは適宜設定します。
後に、Railsの設定に必要となります。(作成後、確認することができます)
作成したVPC、サブネットグループを選択します。セキュリティグループは新規作成を選択し、新しいセキュリティグループを作成します。
データベースの作成を選択し、データベースを作成します。

データベース用のセキュリティグループのインバウンドルールを変更します。

タイプ プロトコル ポート範囲 ソース
MYSQL/Aurora TCP 3306 EC2のセキュリティグループのID

Railsの設定

  • RDSの設定

RDSの情報をcredentials.yml.encに書き込みます。

docker-compose run -e EDITOR="vim" app rails credentials:edit
credentials.yml.enc
rds:
  host: RDSのエンドポイント
  database: RDSのデータベース名
  username: RDSのマスターユーザの名前
  password: RDSのマスターパスワード

config/database.ymlのproductionの箇所にRDSの情報を挿入します。

database.yml
#省略
production:
  <<: *default
  host: <%= Rails.application.credentials.rds[:host] %>
  database: <%= Rails.application.credentials.rds[:database] %>
  username: <%= Rails.application.credentials.rds[:username] %>
  password: <%= Rails.application.credentials.rds[:password] %>
  • Docker-compose.ymlの編集

データベースはRDSを使用するためdb:の箇所は全てコメントアウトします。volumes:のmysql-data:もコメントアウトします。
アプリケーションを本番環境で立ち上げるため、app:のcommand:に-e productionを追加します。

docker-compose.yml
version: '3'

services:
  # db:
  #   image: mysql:5.7
  #   command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
  #   env_file:
  #     - ./.env
  #   volumes:
  #     - mysql-data:/var/lib/mysql
  #   ports:
  #     - "4306:3306"

  app:
    build: .
    env_file:
      - ./.env
    command: bundle exec puma -C config/puma.rb -e production
    init: true
    volumes:
      - .:/myproject
      - public-data:/myproject/public
      - tmp-data:/myproject/tmp
      - log-data:/myproject/log
    # depends_on:
    #   - db

  web:
    build:
      context: containers/nginx
    init: true
    volumes:
      - public-data:/myproject/public
      - tmp-data:/myproject/tmp
    ports:
      - 80:80
    # depends_on:
    #   - app

volumes:
  # mysql-data:
  public-data:
  tmp-data:
  log-data:
  • nginx.comfの編集

server_nameのxx.xx.xx.xxの箇所にElastic IPアドレスを入力します。

nginx.conf
upstream myproject {
  server unix:///myproject/tmp/sockets/puma.sock;
}

server {
  listen 80;
  server_name xx.xx.xx.xx [or localhost];

  #省略
  }
}
  • その他の設定

その他Github等外部に知られたくないKEYや環境変数などは.envファイルを作成し、そちらに書き込みます。dotenv-railsを使うとGitを使用する際管理をし易くなります。

gemfile
gem 'dotenv-rails'
$ bundle install

.gitignoreファイルに.envファイルを記述することで、githubにpushされなくなります。

.gitignore
/.env

config/environmentsのproduction.rbを編集します。
下記内容を編集すると、本番環境で立ち上げ時に便利です。

production.rb
config.assets.js_compressor = Uglifier.new(harmony: true)
config.assets.compile = true

以上、完了したらアプリケーションをgithubへpushします。

AWSでアプリケーションをデプロイ

  • EC2のSSH接続する。

EC2にSSH接続するための設定を行います。

$ mkdir ~/.ssh
$ mv ~/Downloads/myapp.pem ~/.ssh/
$ chmod 600 ~/.ssh/myapp.pem
$ ssh -i ~/.ssh/myapp.pem ec2-user@xxx.xxx.xxx.xxx

myapp.pemはダウンロードしたキーペアの名前です。chmodで権限を変更後、キーペアを使用してEC2にログインします。ec2-userはAWS linuxのデフォルトユーザーです。xxx.xxx.xxx.xxxはEC2インスタンスのElastic IPを入力します。

まず初めにyumをアップデートします。

[ec2-user@ip-xxx-xxx-xxx-xxx ~]$ sudo yum update -y
  • EC2にDocker環境を作る

EC2にDockerとdocker-composeをインストールします。

# Dockerをインストール
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo yum install -y docker
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo service docker start
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo usermod -G docker ec2-user
[ec2-user@xxx.xxx.xxx.xxx ~]$ exit
$ ssh -i ~/.ssh/myapp.pem ec2-user@xxx.xxx.xxx.xxx
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo chkconfig docker on
# docker-composeをインストール
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo curl -L "https://github.com/docker/compose/releases/download/インストールするバージョン/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo chmod +x /usr/local/bin/docker-compose
  • gitをインストール

Githubからアプリケーションをクローンするためにgitをインストールします。

[ec2-user@xxx.xxx.xxx.xxx ~]$ sudo yum install -y git
[ec2-user@xxx.xxx.xxx.xxx ~]$ git clone [Githubのパス]
  • ローカルから必要ファイルをコピー

credentialsの解読するためのmaster.keyと.envファイルをローカルからコピーします。

[ec2-user@xxx.xxx.xxx.xxx ~]$ exit
$ scp -i ~/.ssh/myapp.pem master.key ec2-user@xx.xxx.xxx.xx:myapp/config/
$ scp -i ~/.ssh/myapp.pem .env ec2-user@xx.xxx.xxx.xx:myapp/
  • コンテナの起動
$ ssh -i ~/.ssh/myapp.pem ec2-user@xxx.xxx.xxx.xxx
[ec2-user@xxx.xxx.xxx.xxx ~]$ cd myapp
[ec2-user@xxx.xxx.xxx.xxx ~/myapp]$ docker-compose build
[ec2-user@xxx.xxx.xxx.xxx ~/myapp]$ docker-compose up -d
[ec2-user@xxx.xxx.xxx.xxx ~/myapp]$ docker-compose exec app bin/rails db:create db:migrate RAILS_ENV=production

Elastic IPアドレスにアクセスして正しく表示されれば完了です。

まとめ

AWSを使ってデプロイをすることは、初学者にはかなりハードルが高い内容だと思っておりましたが、素晴らしい文献等が豊富にあったため乗り越えることができました。
所々修正したり、試行錯誤しながらデプロイまで行ったので、全く同じやり方でできるかはわかりませんが、現場私ができる最適な方法だと考えております。

気になった点
参考記事によっては「この方法では開発環境で動いてしまうのでは?」「本番環境でないためRDSが使用されていないのではないか?」と思う部分もあり、いくつかの記事を参考にさせて頂きました。database.ymlのproduction:をRDSに指定したのであれば、本番環境でアプリケーションを立ち上げなければRDSは機能しないと考えております。(勘違いであれば申し訳ありません)
docker-composeの設定次第でなんとかできるのかもしれませんが、もう少しリサーチをしてみようと思いました。

課題
せっかくDockerを使用したのでAWSのECSやFargateを駆使してデプロイすることを考えるべきだったと思いました。
次の機会があれば挑戦したいと思います。

その他
私はS3も取り入れました。そちらは別記事にてまとめております。
AWS_S3+Ruby on Rails_Active Storageで画像データを保存

参考記事

無料!かつ最短?で Ruby on Rails on Docker on AWS のアプリを公開するぞ。
EC2上でRailsアプリケーションにDockerを導入する(Rails、Nginx、RDS)
見ながらやろう! AWSを始めよう -VPC構築編-
Rails5.2から追加された credentials.yml.enc のキホン

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

Amazon ECRにプライベートリポジトリを作成してイメージのpush/pullを実行する

1. はじめに

AWS re:Invent 2020で発表されましたが、Amazon ECRでパブリックレジストリが利用できるようになりました。
しかし、今回はそれとは関係なく、プライベートレジストリを使用します。
これまでECRを触ってこなかったので、使ってみたという内容です。
ecr005.png

2. 構成

2台のLinuxサーバを使用します。1台目からイメージをpushし、2台目はイメージをpullします。
ECRはリージョンサービスで、VPCエンドポイントも利用できます。(今回は使用しません)
ecr006.png

3. ECRの設定

リポジトリの作成

ECRのリポジトリを以下の設定で作成します。
可視性設定:プライベート
リポジトリ名:test-app-repo
タグのイミュータビリティ:無効
プッシュ時にスキャン:有効
KMS暗号化:有効
ecr003.png

4. EC2(1台目)からのpush

まず、ECRレジストリに対してDockerを認証します。「Login Succeeded」と表示されたことを確認します。
WARNINGが出ていますが、そのまま進めます。これについては本記事の「8. 警告&エラー集」で説明します。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

Login Succeeded

今回は「test-app」というイメージをpushしたいと思います。

$ docker images
REPOSITORY      TAG                 IMAGE ID            CREATED             SIZE
test-app        v1.0                72b199328340        10 days ago         461MB

docker tagコマンドでpushする対象にタグを設定します。
指定する形式は、「AWSアカウントID.dkr.ecr.リージョン.amazonaws.com/リポジトリ名:タグ」です。
タグを省略した場合は、自動でlatestのタグが付きます。

$ docker tag test-app:v1.0 \
> 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo:v1.0

タグ付けした結果を確認します。

$ docker images
REPOSITORY                                                        TAG                 IMAGE ID            CREATED             SIZE
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo   v1.0                72b199328340        10 days ago         461MB
test-app                                                          v1.0                72b199328340        10 days ago         461MB

では、イメージをpushします。

$ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo:v1.0

コンソールから確認すると、イメージタグにv1.0が追加されたことが確認できます。
ecr007.png

5. EC2(2台目)からのpull

5-1. IAMポリシーの作成とアタッチ

2台目のEC2はaws configureで認証情報(アクセスキー、シークレットアクセスキー)を渡していないので、
そのままではECRにアクセスできません。
以下のIAMポリシーを作成してEC2のIAMロールにアタッチし、ECRにアクセスできるようにします。
IAMポリシー名:AmazonECRFullAccess

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

5-2. EC2からのpull

まずは、1台目と同じようにECRレジストリに対してDockerを認証します。

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com

イメージをpullします。

$ docker pull 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo:v1.0

結果を確認します。2台目のLinuxサーバがECRからイメージをpullすることができました。

$ docker images
REPOSITORY                                                        TAG                 IMAGE ID            CREATED             SIZE
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo   v1.0                72b199328340        10 days ago         461MB

6. v2.0をpushする

次に、タグ「v2.0」を付けたイメージもpushしてみたいと思います。

$ docker images
REPOSITORY                                                        TAG                 IMAGE ID            CREATED             SIZE
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo   v1.0                72b199328340        10 days ago         461MB
123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo   v2.0                72b199328340        10 days ago         461MB

イメージをpushします。

$ docker push 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo:v2.0

コンソールから確認すると、v2.0が追加されています。
rapture_20201218204702.png

7. イメージタグを削除する

v2.0が不要になったと仮定して、イメージタグを削除したいと思います。
(aws configureでregionを指定してある場合は、コマンドでは指定不要です)

$ aws ecr batch-delete-image --repository-name test-app-repo --image-ids imageTag=v2.0 --region ap-northeast-1
{
    "failures": [],
    "imageIds": [
        {
            "imageTag": "v2.0",
            "imageDigest": "sha256:58d3c26bee377e039c0ce5c2ef92ed2ce10b956bf3dc0cf5dba4b4d6f56aaf94"
        }
    ]
}

再度コンソールから確認すると、v2.0が削除されています。
ecr007.png
イメージを削除する場合は、イメージのダイジェストを指定すれば削除できます。
参考:https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/delete_image.html

8. 警告&エラー集

警告①

$ aws ecr get-login --region ap-northeast-1 --no-include-email
$ docker login -u AWS -p {認証トークン} https://123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Using --password via the CLI is insecure. Use --password-stdin.

→AWS CLI バージョン 1.17.10 より前のバージョンを使用している場合はget-loginコマンドで認証しますが、
セキュリティリスクがあり非推奨です。AWS CLIのバージョンを上げて、get-login-passwordを使用することが推奨されます。
参考:https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/Registries.html

警告②

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
WARNING! Your password will be stored unencrypted in /home/ec2-user/.docker/config.json.
Configure a credential helper to remove this warning. See
https://docs.docker.com/engine/reference/commandline/login/#credentials-store

→Dockerのデフォルトの動作として、ログインパスワードを暗号化せずにconfig.jsonに保存します。
外部のクレデンシャルストアに保存する方がより安全という警告です。
参考:https://docs.docker.com/engine/reference/commandline/login/#credentials-store

エラー①

$ docker pull 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/test-app-repo:v1.0
Error response from daemon: Get https://123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/v2/test-app-repo/manifests/v1.0: no basic auth credentials

→ECRレジストリに対してDockerを認証していないと出るエラーです。

エラー②

$ aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin 123456789012.dkr.ecr.ap-northeast-1.amazonaws.com
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:sts::123456789012:assumed-role/IAMロール名/インスタンスID is not authorized to perform: ecr:GetAuthorizationToken on resource: *
Error: Cannot perform an interactive login from a non TTY device

→aws configureで認証情報を指定していない、あるいはEC2のIAMロールにECRへのアクセスに必要な
IAMポリシー(本記事ではAmazonECRFullAccess)がアタッチされていない場合に出るエラーです。

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

Next.jsでWeb VitalsをAmazon CloudWatchに送信してみる

この記事は、ニフティグループ Advent Calendar 2020の19日目の記事です。
昨日は@shin27さんで「Amazon SageMaker Feature StoreにKaggleのTitanicデータを登録してみた」でした。

はじめに

今年も早いものでもう2週間を切ってしまいましたね。今年11月に水樹奈々さんがおめでたを公表したり、近頃は寒い日が続いたりで皆さんいかがお過ごしでしょうか。

私は、今年11月にdアニメストアで配信が開始されたアニメ「頭文字D」に今更ながらハマったり、イベント「五等分の花嫁展 MAKEOVER」に参加したりして、コロナ禍でイベントが軒並み中止になる状況でもプライベートはそれなりに充実していたかなと思っています。

そして2021年1月から放映される予定のアニメ「五等分の花嫁∬」が待ち遠しいです。推しキャラは中野三玖です。

弊社内の話に移すと、ニフティではAWSやスクラム開発、フロントエンドフレームワークなどの勉強会が定期的に開催されていて、私も運営メンバー兼勉強メンバーとして精進しています。難しさもありますが、自分の中の知識をアップデートできる楽しさもあります!
ちなみに勉強会について少しでも気になった方や詳しい話を聞きたい方はぜひ説明会に来てみると良いかと!
ニフティ株式会社 採用情報

さて前置きが長くなりすぎたのでそろそろ本題に入りますが、今回はNext.jsの勉強も兼ねつつ、Next.jsでWeb VitalsをAmazon CloudWatchに送信してみた話を書きます。

どうしてこのお題で執筆しようと思ったのかについて語っておくと、それはNext.jsでWeb VitalsをGoogle Analyticsに送信する記事をたまたま見つけたので、仮に送信先をAmazon CloudWatchに変更してみたらどうなるのだろうかと興味を持ったためです。

用語の説明

ここではこの記事のタイトルに登場する用語について簡単に説明します。

Next.jsとは

オープンソースのReactフロントエンド開発Webフレームワークで、主に以下の機能を持ち合わせています。

  • サーバーサイドレンダリング
  • ReactベースのWebアプリケーション用の静的Webサイトの生成

公式サイトはこちらで、執筆当時の最新はバージョン10.0.3です。

Web Vitalsとは

WebサイトやWebアプリケーションのパフォーマンスを計測するための仕組みで、Next.jsではバージョン9.4から導入されています。
Blog - Next.js 9.4 | Next.js

なおWeb Vitalsについて詳細を書くと記事が長くなってしまうのでここでは書きませんが、FE Study #2というイベントでも話に上がっていましたので、気になる方は以下の資料を見てください。
メタ・パフォーマンスチューニング

Amazon CloudWatchとは

DevOpsエンジニア、開発者、SRE、および ITマネージャーのために構築されたモニタリング/オブザーバビリティサービスで、公式サイトはこちらです。

それでは早速チャレンジしてみましょう

今回はGetting Startedを参考にしながらアプリケーションを作成するところから始めます。

まずは適当な内容のページpages/index.jsを用意します。自己紹介を行うページでも何でも良いです。
次にWeb Vitalsを使うコードSDKを組み込むためのコード例、そしてSDKドキュメントとにらめっこしながら、以下の内容をpages/_app.jsに書きます。

pages/_app.js
// import App from 'next/app'
var AWS = require('aws-sdk');

AWS.config.update({
  credentials: new AWS.Credentials(
    "AKIAIOSFODNN7EXAMPLE",
    "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
  ),
  region: 'us-west-2'
});

// Create CloudWatch service object
var cw = new AWS.CloudWatch({apiVersion: '2010-08-01'});

export function reportWebVitals(metric) {
  const name = "mypage"
  var params = {
    MetricData: [
      {
        MetricName: 'PageAnalytics',
        Dimensions: [
          {
            Name: 'NextJSAPP',
            Value: 'Initial'
          },
        ],
        Unit: 'None',
        Value: 0.0
      },
    ],
    Namespace: 'Page/Analytics'
  };
  console.log("=======CW Before=======");
  console.log(params);

  switch (metric.name) {
    case 'FCP':
      console.log("=======FCP=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'LCP':
      console.log("=======LCP=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'CLS':
      console.log("=======CLS=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'FID':
      console.log("=======FID=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    case 'TTFB':
      console.log("=======TTFB=======");
      console.log(metric);
      params["MetricData"][0]["Dimensions"][0]["Value"] = metric.name;
      params["MetricData"][0]["Dimensions"]["Value"] = name;
      params["MetricData"][0]["Value"] = metric["value"];
      break
    default:
      break
  }

  console.log("=======CW After=======");
  console.log(params);
  cw.putMetricData(params, function(err, data) {
    if (err) {
      console.log("Error", err);
    } else {
      console.log("Success", JSON.stringify(data));
    }
  });
}

function MyApp({ Component, pageProps }) {
  return <Component {...pageProps} />
}

export default MyApp

最終的なディレクトリは以下のようになります。

.
├── pages
│   ├── _app.js
│   └── index.js
├── .gitignore
├── package.json
├── README.md
└── yarn.lock

準備が整ったので実際に動かしてみましょう。

$ yarn add react react-dom next aws-sdk
$ yarn build
$ yarn start

実際に動かしてみましたが、Webブラウザに搭載されている開発者ツールにあるコンソールにも
image.png

Amazon CloudWatchにも記録されていました。
image03.png

なお、今回は実装を簡単にするため、AWS認証情報をソースコード内に直接記述する方式にしていますが、GitHubのパブリックリポジトリに上げてしまうと攻撃されるのであまり望ましくはありません。あらかじめ.gitignoreファイルに記載した.envファイルを使用するなどしてよりセキュアにしましょう。
また、認証情報を設定する際には、AWS.config.loadFromPath('./credentials.json');を記述する方法もありますが、こちらのマニュアルにも記載されている通り、対応していないケースがあるのでご注意ください。
さらに、こちらの記事にも記載されている通り、ソースコード内に認証情報を直接記述する方式には非推奨のものが存在しています。ご注意ください。

おわりに

今回はNext.jsでWeb VitalsをAmazon CloudWatchに送信してみた話を書きました。

パフォーマンスメトリクスを追っていくことはサービスやシステムの運用を行う上で欠かせないことの一つなので、この記事が参考になれば幸いです。

また送信したメトリクスをもとにして、Slack通知など何らかのアクションを行うLambda関数を発火させるのもきっと面白いかもしれません。

そしてこの記事を執筆していたときに似たようなことをやっている方を見つけました。ただこちらはサーバレスでしたね。
https://github.com/serverless-nextjs/serverless-next.js/issues/762

ここまで読んでいただきありがとうございました。明日は@D_Wさんの記事です。GitHub ActionsでStoryBookをデプロイする方法について執筆してくれるそうです。楽しみにお待ちください!

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

AWS LambdaとAPI GatewayでQiita検索botを作ってみた

完成物

■Qiita検索
検索したいタグを入れると、そのタグの新着記事が返ってくるLINE bot
qiita検索.gif

今回使うもの

  • LINE Messaging API
  • Amazon API Gateway
  • AWS Lambda(言語:node.js)
  • Qiita API

LINE Messaging API, Amazon API Gateway, AWS Lambdaの準備は以下の記事に書きましたので、
こちらをご参照ください。
[過去記事]AWS(Lambda, API Gateway)でLINEの天気予報botを作ってみる

この記事では、実際のコードについてのみ書いています。

Lambda関数を書く

ローカルで、以下の通りにモジュールをインストールします。

$ npm install @line/bot-sdk
$ npm install axios

index.jsは以下の通り。

index.js
const line = require('@line/bot-sdk');
const axios = require('axios');
axios.defaults.baseURL = 'https://qiita.com/api/v2';

const client = new line.Client({ channelAccessToken: process.env.ACCESSTOKEN });

let event;
let callback;
let userMessage = '';
let message = {};

exports.handler = (_event, _context, _callback) => {
    event = _event;
    callback = _callback;

    userMessage = JSON.parse(event.body).events[0].message.text;
    const url = '/tags/' + userMessage +'/items?page=1&per_page=5';

    main(url);
};


function main(url) {

    axios.get(url)
    .then(function (response) {

        if (response.data.length !== 0) {
            let columns = [];

            for (let i = 0; i < 5; i++) {
                let column = {};
                let tags = "";
                for (let j = 0; j < response.data[i].tags.length; j++) {
                    tags += response.data[i].tags[j].name + ", ";
                }
                tags = tags.slice(0, -2);

                column = {
                    "thumbnailImageUrl": response.data[i].user.profile_image_url,
                    "title": response.data[i].title.length > 40 ? response.data[i].title.slice(0, 40) : response.data[i].title, //最大40文字
                    "text": tags.length > 55 ? "tag: " + tags.slice(0, 55) : "tag: " + tags, //最大60文字
                    "actions": [
                        {
                            "type": "uri",
                            "label": "記事を読む",
                            "uri": response.data[i].url
                        }
                    ]
                }
                columns.push(column);
            }

            message = {
                "type": "template",
                "altText": "#" + userMessage + "の新着記事",
                "template": {
                    "type": "carousel",
                    "columns": columns
                }
            }
        } else { //検索結果が0件だった場合
            message = {
                type: "text",
                text: "検索結果は0件でした。"
            };
        }
    })
    .catch(function (error) { //該当するタブが存在しない場合
        console.log(error);
        message = {
            type: "text",
            text: "#" + userMessage + "は存在しません。"
        };
    })
    .finally(function () {
        //メッセージ返信
        console.log(JSON.stringify(message));
        client.replyMessage(JSON.parse(event.body).events[0].replyToken, message)
            .then(() => {
                callback(null, {});
            })
            .catch((err) => {
                callback(null, {});
            });
    });
};

環境変数には、LINE botのチャネルアクセストークンを設定します。

これで完成!:laughing:

まとめ

今回は、簡単なカルーセルテンプレートを使用したので記事は5つのみの表示です。

ここに色々なメッセージタイプが載っているので、是非色々試してみてください。

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

EC2の脆弱性診断をInspectorで自動化してみた

本記事はサムザップ #1 Advent Calendar 2020の 20 日目の記事です。

Amazon Inspectorとは

InspectorについてはAWSの公式ドキュメントでご確認ください。
https://aws.amazon.com/jp/inspector/

実現したいこと

定期的に自動でEC2インスタンスを起動してInspectorで脆弱性をチェックしてSlackへ通知する。起動中のインスタンスを接直チェックはしたくない。
上記要件を踏まえた上で以下構成で定期的に脆弱性チェックを行うことにしました。
スクリーンショット 2020-12-18 17.33.45.png

  1. Amazon EventBridgeでLambdaを実行
  2. Lambdaから脆弱性チェックしたいEC2インスタンスのAMIからインスタンスを起動
  3. 起動したインスタンスをAmazon Inspectorで脆弱性チェック
  4. Inspectorの診断が完了したらSNS経由で診断結果を取得してSlackに通知するLambdaを実行

AWSリソースの作成は全てTerraformでコード管理できるようにしました。

Inspectorの設定

inspector.tf

######################################
# 評価ターゲット作成
######################################
resource "aws_inspector_resource_group" "resource" {
  tags = {
    Inspector = "true"
  }
}

resource "aws_inspector_assessment_target" "target" {
  name               = "test-target"
  resource_group_arn = aws_inspector_resource_group.resource.arn
}

######################################
# Rule Package 取得
######################################
data "aws_inspector_rules_packages" "rules" {}

######################################
# 評価テンプレート作成
######################################
resource "aws_inspector_assessment_template" "template" {
  name               = "test-template"
  target_arn         = aws_inspector_assessment_target.target.arn
  duration           = 3600
  rules_package_arns = data.aws_inspector_rules_packages.rules.arns
}

・評価ターゲットの作成
Tag:Inspector:trueが設定されているEC2インスタンスをターゲットにします。

・評価Rule Packageの取得
以下全てのRule Packageで評価テンプレートを取得します。
Common Vulnerabilities and Exposures: 共通脆弱性識別子
Security Best Practices: Amazon Inspector のセキュリティのベストプラクティス
Center for Internet Security (CIS) Benchmarks: Center for Internet Security (CIS) ベンチマーク
Network Reachability: ネットワーク到達可能性

・評価テンプレート作成
作成した評価ターゲットを設定
実行時間を1時間に設定
取得した評価Rule Packageを設定

Lambdaの設定

Lambda関数は以下の2つの関数を作成します。
・AMIを起動してInspector評価の実行を行うLambda
・Inspector評価の結果をSlackに通知するLambda

AMIを起動してInspector評価の実行を行うLambda
function_inspector_run.tf

######################################
# Lambda IAM Role作成
######################################
resource "aws_iam_role" "lambda_role" {
  name = "lambda-inspector-role"
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
EOF
}

######################################
# Lambda IAM Policy作成
######################################
resource "aws_iam_policy" "lambda_policy" {
  name        = "lamda-inspector-policy"
  description = "LambdaFunction function_inspector_run/function_inspector_report Use Policy"
  policy = <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "inspector:GetAssessmentReport",
                "inspector:ListAssessmentTemplates",
                "inspector:ListAssessmentTargets",
                "inspector:ListAssessmentRuns",
                "inspector:StartAssessmentRun",
                "inspector:PreviewAgents"
            ],
            "Resource": "*"
        },
        {
            "Sid": "VisualEditor1",
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
}
EOF
}

######################################
# RoleにPolicyをアタッチ
######################################
resource "aws_iam_role_policy_attachment" "lambda_role_policy_attach" {
  role       = aws_iam_role.lambda_role.name
  policy_arn = aws_iam_policy.lambda_policy.arn
}

######################################
# Lambda関数の作成
######################################
resource "aws_lambda_function" "inspector_run" {
  filename         = "./function_inspector_run.zip"
  function_name    = "function_inspector_run"
  role             = aws_iam_role.lambda_role.arn
  handler          = "lambda_function.lambda_handler"
  source_code_hash = filebase64sha256("./function_inspector_run.zip")
  runtime          = "python3.8"
  timeout          = 300
  environment {
    variables = {
      TZ               = "Asia/Tokyo"
      SECURITYGROUP_ID = "xxxxxx"
      SUBNET_ID        = "xxxxxx"
    }
  }
}

function_inspector_run.py

import boto3
import os
from operator import itemgetter
import logging
import datetime
import time

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):
    create_ec2_instance()
    start_inspector_assessment_run()

def create_ec2_instance():
    """
    Inspectorで分析するため最新のAMIを取得しAMIからEC2Instanceを起動します
    起動設定でawsagentをinstallします
    """
    # web AMI一覧取得
    ec2 = boto3.client('ec2')
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_images
    response = ec2.describe_images(
        Filters=[
            {
                "Name": "tag:Name",
                "Values": [
                    "test-web*",
                ]
            }
        ],
        Owners=[
            "self"
        ]
    )
    # 最新のAMIを取得
    image_details = sorted(response['Images'], key=itemgetter('CreationDate'), reverse=True)
    image_id = image_details[0]['ImageId']
    user_data = """#!/bin/sh
    sudo su -
    wget https://inspector-agent.amazonaws.com/linux/latest/install
    bash install -u false
    /etc/init.d/awsagent restart
    """
    # Instance作成/起動
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.run_instances
    response = ec2.run_instances(
        ImageId=image_id,
        InstanceType="t2.micro",
        MinCount=1,
        MaxCount=1,
        KeyName="test_key_pair",
        SecurityGroupIds=[
            os.environ['SECURITYGROUP_ID']
        ],
        SubnetId=os.environ['SUBNET_ID'],
        UserData=user_data,
        TagSpecifications=[
            {
                'ResourceType': 'instance',
                'Tags': [
                    {
                        'Key': 'Inspector',
                        'Value': 'true'
                    },
                    {
                        'Key': 'Name',
                        'Value': 'inspector-target'
                    }
                ]
            }
        ]
    )
    # EC2Instanceがstatus:runningになるまで待ち
    instance_id = response.get('Instances')[0]['InstanceId']
    while 1:
        time.sleep(30)
        instances = ec2.describe_instances(
            InstanceIds=[
                instance_id
            ]
        )
        logger.info(instances.get('Reservations')[0].get('Instances')[0].get('State').get('Name'))
        if instances.get('Reservations')[0].get('Instances')[0].get('State').get('Name') == 'running':
            break

def start_inspector_assessment_run():
    """
    Inspector評価実行を行います
    """
    # 評価テンプレート一覧取得
    inspector = boto3.client('inspector')
    # 評価ターゲットのEC2Instance awsagentのステータスがHEALTHYになるまで待機
    assessment_target = inspector.list_assessment_targets(
        filter={
            'assessmentTargetNamePattern': os.environ['ENV'] + '-target'
        }
    )
    while 1:
        time.sleep(30)
        awsagents_status = inspector.preview_agents(previewAgentsArn=assessment_target.get('assessmentTargetArns')[0])
        logger.info(awsagents_status.get('agentPreviews')[0])
        if awsagents_status.get('agentPreviews')[0].get('agentHealth') == 'HEALTHY':
            break
    """
    list_assessment_templates Response Syntax
    {
        'assessmentTemplateArns': [
            'string',
        ],
        'nextToken': 'string'
    }
    https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/inspector.html#Inspector.Client.list_assessment_templates
    """
    assessment_templates = inspector.list_assessment_templates(
        filter={
            'namePattern': 'test-template'
        }
    )
    # 評価テンプレートARN一覧取得
    if not assessment_templates.get('assessmentTemplateArns'):
        return
    logger.info(assessment_templates.get('assessmentTemplateArns'))
    # 評価の実行
    for template_arn in assessment_templates.get('assessmentTemplateArns'):
        inspector.start_assessment_run(
            assessmentTemplateArn=template_arn,
            assessmentRunName='RunAssessment_' + 'test-template' + datetime.date.today().strftime("%Y-%m-%d")
        )

Inspector評価の結果をSlackに通知するLambda
function_inspector_report.tf

resource "aws_lambda_function" "inspector_report" {
  filename         = "./function_inspector_report.zip"
  function_name    = "function_inspector_report"
  role             = aws_iam_role.lambda_role.arn
  handler          = "lambda_function.lambda_handler"
  source_code_hash = filebase64sha256("./function_inspector_report.zip")
  runtime          = "python3.8"
  timeout          = 300
  environment {
    variables = {
      TZ       = "Asia/Tokyo"
      SLACK_HOOK_URL = "https://xxxxxxxxxxxxx"
    }
  }
}

resource "aws_lambda_permission" "inspector_report_permission" {
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.inspector_report.function_name
  principal     = "sns.amazonaws.com"
}

function_inspector_report.py

import json
import boto3
import os
import logging
import datetime
from urllib.request import Request, urlopen
from urllib.error import URLError, HTTPError
import time

logger = logging.getLogger()
logger.setLevel(logging.INFO)

def lambda_handler(event, context):

    assessment_run_arn = get_assessment_run_arn(event)
    if not assessment_run_arn:
        logger.info('AssessmentTarget Not Found')
        logger.info(event)
        return

    send_inspector_assessment_report(assessment_run_arn)
    terminate_ec2_instance()

def get_assessment_run_arn(event):
    """
      評価実行のARN取得処理
      SNS, Lambdaテスト実行の判定を行いInspector評価実行のARNを返却します
      Args:
          event : Lambda実行Parameter
      Return:
          assessment_run_arn: Inspector 評価実行ARN
    """
    inspector = boto3.client('inspector')
    # SNSからのLambda実行
    if event.get('Records'):
        logger.info('SNS execute')
        message = event['Records'][0]['Sns']['Message']
        message = json.loads(message)
        logger.info(message)
        return message.get('run')
    # Lambda テスト実行パラメータ有
    elif event.get('target_date'):
        logger.info('Lambda execute Parameter ON target_date =' + event.get('target_date'))
        """
        list_assessment_runs Response Syntax
        {
            'assessmentRunArns': [
                'string',
            ],
            'nextToken': 'string'
        }
        https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/inspector.html#Inspector.Client.list_assessment_runs
        """
        assessment_runs = inspector.list_assessment_runs(
            filter={
                'namePattern':'RunAssessment_' + 'test-template' + event.get('target_date')
            }
        )
        return assessment_runs.get('assessmentRunArns')[0]
    # Lambda テスト実行パラメータ無
    else:
        logger.info('Lambda execute Parameter OFF')
        assessment_runs = inspector.list_assessment_runs(
            filter={
                'namePattern':'RunAssessment_' + 'test-template' + datetime.date.today().strftime("%Y-%m-%d")
            }
        )
        return assessment_runs.get('assessmentRunArns')[0]

def send_inspector_assessment_report(assessment_run_arn):
    """
    Inspector評価実行結果をSlackに送信します
    """
    inspector = boto3.client('inspector')
    report = {}
    """
    get_assessment_report Response Syntax
    {
        'status': 'WORK_IN_PROGRESS'|'FAILED'|'COMPLETED',
        'url': 'string'
    }
    https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/inspector.html#Inspector.Client.get_assessment_report
    """
    # すぐ結果を取得しても取得できない時があるのでStatusがCOMPLETEDになるまで待機
    while 1:
        time.sleep(30);
        # HTML形式でReport出力
        assessment_report_html = inspector.get_assessment_report(
            assessmentRunArn=assessment_run_arn,
            reportFileFormat='HTML',
            reportType='FULL'
        )
        if assessment_report_html.get('status') == 'COMPLETED':
            break

    # すぐ結果を取得しても取得できない時があるのでStatusがCOMPLETEDになるまで待機
    while 1:
        time.sleep(30);
        # PDF形式でReport出力
        assessment_report_pdf = inspector.get_assessment_report(
            assessmentRunArn=assessment_run_arn,
            reportFileFormat='PDF',
            reportType='FULL'
        )
        logger.info(assessment_report_pdf.get('status'))
        if assessment_report_pdf.get('status') == 'COMPLETED':
            break

    report['arn'] = assessment_run_arn
    report['html'] = assessment_report_html.get('url')
    report['pdf'] = assessment_report_pdf.get('url')

    channel = '#' + 'inspector-report'
    message = "*Inspector 評価実行結果*\n*Arn*:```{}```\n\n*HTML結果*:```{}```\n\n*PDF結果*:```{}```\n\n※ページの有効期限が900sで切れます。有効期限が切れた時はLambda関数:function_inspector_reportでテスト実行またはawscliから実行を行なってください\n```テストイベントのパラメータ:{}\naws inspector get-assessment-report --assessment-run-arn {} --report-file-format PDF --report-type FULL```"
    slack_message = {
        'channel': channel,
        'text': message.format(
            report.get('arn'),
            report.get('html'),
            report.get('pdf'),
            '{"target_date": YYYYMMDD}',
            report.get('arn')
        )
    }
    HOOK_URL = os.environ['SLACK_HOOK_URL']

    req = Request(HOOK_URL, json.dumps(slack_message).encode('UTF-8'))
    try:
        response = urlopen(req)
        response.read()
    except HTTPError as e:
        logger.error("Request failed: %d %s", e.code, e.reason)
    except URLError as e:
        logger.error("Server connection failed: %s", e.reason)

def terminate_ec2_instance():
    """
    Inspectorで分析したEC2InstanceをTerminateします
    """
    ec2 = boto3.client('ec2')
    # tag:Inspector:trueのEC2Instance一覧取得
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_instances
    instances = ec2.describe_instances(Filters=[{
        'Name': 'tag:Inspector',
        'Values': ['true']
    }])

    if not instances:
        return

    # InstanceIDの配列生成
    instance_ids = []
    for reservation in instances.get('Reservations'):
        for instance in reservation.get('Instances'):
            logger.info(instance.get('InstanceId'))
            instance_ids.append(instance.get('InstanceId'))

    if not instance_ids:
        return

    # 対象EC2InstanceのTerminate
    # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.terminate_instances
    ec2.terminate_instances(
        InstanceIds=instance_ids
    )

CloudWatchEventsの設定

最後にAmazon EventBridgeでInspector実行Lambdaをスケジュール設定して完了です。

実行してみた結果

Slackに以下メッセージが表示されたことが確認できました!!

Inspector 評価実行結果
Arn:
arn:aws:inspector:xxxx:xxxx:target/xxxx/template/xxxx/run/xxxx
HTML結果:
https://inspector-html-report
PDF結果:
https://inspector-pdf-report

※ページの有効期限が900sで切れます。有効期限が切れた時はLambda関数:function_inspector_reportでテスト実行またはawscliから実行を行なってください
テストイベントのパラメータ:{"target_date": YYYYMMDD}
aws inspector get-assessment-report --assessment-run-arn xxx --report-file-format PDF or HTML --report-type FULL
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Amazon Transcribeで日本語会話を文字起こししてみた

インタビュー動画の記事作成をする機会があり、動画を再生して、少し戻って。。。というのが面倒だったので、音声をなんとか一発で文字起こしできないかを調べていたところ、いくつかツールが存在していることを知りました。

その中でも普段から馴染みのあるAWSの「Amazon Transcribe」を試しに使ってみたので、その感想をメモとして残します。

Amazon Transcribeとは

音声ファイルの文字起こしをしてくれるAWSのサービスです。

Amazon Transcribe は、自動音声認識 (ASR、automatic speech recognition) と呼ばれる深層学習プロセスを使って迅速かつ高精度に音声をテキストに変換します。
引用:https://aws.amazon.com/jp/transcribe/

料金も15秒の音声の文字起こしで0.006 USDで、1時間でも1.44 USDととてもリーズナブルです。他の文字起こしサービスでは1時間で2000円を超えることも多いので、値段設定はさすがAWSですね。登録後12ヶ月は1ヶ月あたり60分の無料利用枠もあるので、気になる方は是非試してみてください。

使ってみた

使い方もとても簡単です。

  • s3にファイルをアップロードする

  • Amazon Transcribeでjobを作成する

以上です。

操作手順等は以下の記事を参考にさせていただきました。ありがとうございます。
https://qiita.com/shimizu-nowhere/items/2508f2e87daee8cb1083
https://qiita.com/kooohei/items/2580addd6c1bbc8f1c34

以下が出力結果のサンプルです。内容は編集しています。
単語ごとに出力されて、それぞれの精度まで数値で出力されます。

{
    "jobName": "amazon_transcribe",
    "accountId": "hogehogehoge",
    "results": {
        "transcripts": [
            {
                "transcript": "hoge fuga piyp"
            }
        ],
        "items": [
            {
                "start_time": "0.04",
                "end_time": "0.41",
                "alternatives": [
                    {
                        "confidence": "0.9699",
                        "content": "hoge"
                    }
                ],
                "type": "pronunciation"
            },
            {
                "start_time": "0.42",
                "end_time": "0.79",
                "alternatives": [
                    {
                        "confidence": "0.5512",
                        "content": "fuga"
                    }
                ],
                "type": "pronunciation"
            },
            {
                "start_time": "0.79",
                "end_time": "1.45",
                "alternatives": [
                    {
                        "confidence": "0.9987",
                        "content": "piyo"
                    }
                ],
                "type": "pronunciation"
            }
        ]
    },
    "status": "COMPLETED"
}

感想

以下使ってみての感想です。

精度に関しては予想よりもよかった

英語ならまだしも日本語の文字起こしは難しいんじゃないかと思っていて、正直あまり期待していませんでした。ところが、実際に使ってみると単語単位では予想以上に高精度で文字起こしができており、文章単位でもところによっては精度が高かったです。

文字起こしまでの操作が簡単

AWSを少しでも触ったことのある人ならば、文字起こし完了までのハードルはとても低いと思います。

対話の音声データでは利用ハードルがグッと上がる

今回使用したデータはインタビューの音声データで、登場人物が二人いました。そのため、文章や単語の文字起こしができても、どちらが発言したのかはインタビューの流れから確認する必要があります。また、同時に発言しているシーンでは精度が極端に低下していました。利用シーンは考える必要がありそうです。

終わりに

実際にAmazon Transcribeを使ってみると、想像以上に精度は高かったです。ただ、所々でミスがあったりするので、あくまでサポート的な役割でこのサービスは使ってみるのがいいと思いました。AWSのアップデート速度は凄まじいので、日本語の識別精度もどんどん良くなっていくと思います。

参考

https://aws.amazon.com/jp/transcribe/
https://qiita.com/shimizu-nowhere/items/2508f2e87daee8cb1083
https://qiita.com/kooohei/items/2580addd6c1bbc8f1c34

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

AWS Protonのサンプルプロジェクトを試す

AWS Protonがパブリックプレビューで公開されているので、
サンプルプロジェクトを実施して具体的にどんなことが出来るのかを確かめてみました。
この記事ではサンプルプロジェクトで何ができたのか、またサンプルプロジェクトで実際に何が起きたのかを紹介したいと思います。

AWS Proton とは

  • 公式より
コンテナおよびサーバーレスアプリケーションのための初の完全マネージド型アプリケーションデプロイサービスです。
Proton を使用すれば、プラットフォームエンジニアリングチームは、インフラストラクチャのプロビジョニング、
コードデプロイ、モニタリング、更新の際に必要になるさまざまなツールをすべて接続し、協調させることできます。
数百、場合によっては数千のマイクロサービスを、絶えず変化するインフラストラクチャリソースと
継続的インテグレーション/継続的デリバリー (CI/CD) 構成で維持することは、
どんなに有能なプラットフォームチームにとってもほぼ不可能なタスクです。
AWS Proton を利用することで、この煩雑なタスクを管理して一貫した標準を適用するために必要となるツールをプラットフォームチームが
手にすることとなり、デベロッパーはコンテナとサーバーレステクノロジーを利用してコードを簡単にデプロイできるようになるため、前述の問題を解決できます。
  • 私が触った感覚ではざっくりですが
    • AWS上でのサーバレス環境とそのCICD環境をセットで管理できるサービス
    • システムのリソースおよびCICD環境のリソース構築のためのテンプレートを作成し、バージョン管理できる
      • 開発者はテンプレートからアプリケーションをデプロイ、運用できる
      • アプリの更新、削除などもProtonのコマンドで実行可能
    • テンプレートは現状はCloudFormationテンプレートを利用したものになります。またデプロイもCodePipelineを利用しています
      • ロードマップによると今後terraformや他のサービスも利用できるようになるようです

環境テンプレートとサービステンプレート

Protonでは2種類のテンプレートを作成、管理することができます。

  • 環境テンプレート
    • 主にアプリケーションのインフラ周りのリソースを定義するテンプレートになります
    • 例)VPC,DB,SG,Role,ECSClusterなど
  • サービステンプレート
    • アプリケーション本体のリソースを定義するテンプレート
      • 例)APIGateway,Lambda、ECSService、ALBなど
    • CICDパイプライン用のリソースもこちらで定義します
      • 例)CodePipeline,CodeBuild,CodeDeployなど

ただし、テンプレートの記述自体は自由なので、環境テンプレート、サービステンプレートにどのリソースを定義するかは開発者で判断する必要があります。

チュートリアル実施

公式で用意されているサンプルテンプレートを試してみました。サンプルはAPI Gateway+LambdaでCRUD処理をするAPIをデプロイするサンプルとなっています。

サンプルのソースやサンプルを利用したチュートリアル手順はgithubで公開されていますので、ぜひ試してみてください。

1 事前準備

1.1 サンプルリポジトリのクローン

まずはワークスペース上にサンプルテンプレート用のリポジトリをクローンしてください。
その後、カレントディレクトリを「{ワークスペース}/aws-proton-sample-templates/lambda-crud-svc」まで移動してください。
基本的には今後の操作はそのディレクトリ上で実行していきます。

git clone https://github.com/aws-samples/aws-proton-sample-templates.git
cd ./aws-proton-sample-templates/lambda-crud-svc/

1.2 アカウントIDを環境変数に設定

チュートリアルのCLIコマンドでAWSアカウントIDが必要になるため予め環境変数に登録しておきます。
この手順は自分でコマンド内の${account_id}をAWSアカウントIDに置き換えるのであれば実施不要です。

account_id=`aws sts get-caller-identity|jq -r ".Account"`

1.3 CLIでProtonのコマンドを実行できるようにする

Protonはまだプレビュー版なので、aws cliでコマンドを実行できるように設定が必要になります。

aws s3 cp s3://aws-proton-preview-public-files/model/proton-2020-07-20.normal.json .
aws s3 cp s3://aws-proton-preview-public-files/model/waiters2.json .
aws configure add-model --service-model file://proton-2020-07-20.normal.json --service-name proton-preview
mv waiters2.json ~/.aws/models/proton-preview/2020-07-20/waiters-2.json
rm proton-2020-07-20.normal.json

1.4 Protonのテンプレート置き場用のS3バケット作成

Protonの環境、サービステンプレートはS3に保存されるため、テンプレート保存用のバケットを作成します。

aws s3api create-bucket \
  --region us-west-2 \
  --bucket "proton-cli-templates-${account_id}" \
  --create-bucket-configuration LocationConstraint=us-west-2

1.5 IAMロール作成

Protonがサービス実行のために必要なIAMロールを作成します。

aws iam create-role \
  --role-name ProtonServiceRole \
  --assume-role-policy-document file://./policies/proton-service-assume-policy.json
aws iam attach-role-policy \
  --role-name ProtonServiceRole \
  --policy-arn arn:aws:iam::aws:policy/AdministratorAccess
aws proton-preview update-account-roles \
  --region us-west-2 \
  --account-role-details "pipelineServiceRoleArn=arn:aws:iam::${account_id}:role/ProtonServiceRole"

1.6 アプリのソースコード用リポジトリの設定

サンプルではGithubのリポジトリからソースコードをpullしてデプロイする構成になっているので、
ソースコードをGithub上のリポジトリに置き、さらにAWSからそのリポジトリを参照できるようにするための接続設定が必要になります。

  1. 以下のリポジトリを自分のGithubアカウントにフォークする
  2. CodeStarで自分のGithubアカウントの接続設定を作成する

2 環境テンプレート作成

まずは環境テンプレートの作成から行っていきます。

2.1 テンプレート作成

まずはテンプレートを作成します。

aws proton-preview create-environment-template \
  --region us-west-2 \
  --template-name "crud-api" \
  --display-name "CRUD Environment" \
  --description "Environment with DDB Table"

コンソール上では環境テンプレート「crud-api」が作成されていることが確認できました。
ただし、この時点ではまだ何もない状態となっています。

image.png

2.2 メジャーバージョン作成

メジャーバージョンを新規登録します。

aws proton-preview create-environment-template-major-version \
  --region us-west-2 \
  --template-name "crud-api" \
  --description "Version 1"

コンソール上では新たにバージョン「1」が登録されたことが確認できます

image.png

2.3 マイナーバージョン作成

さらにマイナーバージョンの登録を行います。
環境テンプレート群を圧縮してS3にアップロードし、マイナーバージョン登録時のコマンドで
そのアップロードされた環境テンプレートのS3パスを指定しています。

tar -zcvf env-template.tar.gz environment/

aws s3 cp env-template.tar.gz s3://proton-cli-templates-${account_id}/env-template.tar.gz --region us-west-2

rm env-template.tar.gz

aws proton-preview create-environment-template-minor-version \
  --region us-west-2 \
  --template-name "crud-api" \
  --description "Version 1" \
  --major-version-id "1" \
  --source-s3-bucket proton-cli-templates-${account_id} \
  --source-s3-key env-template.tar.gz

コンソールでもマイナーバージョン「1.0」が登録されていますね。

image.png

2.4 登録したバージョンの公開

コンソールの忠告にもあるようにまだバージョンの状態が「Draft」なので、この状態ではこのテンプレートはまだ利用できない状態になっています。
利用できるようにするためにステータスを「公開」状態に更新しましょう。

aws proton-preview update-environment-template-minor-version \
  --region us-west-2 \
  --template-name "crud-api" \
  --major-version-id "1" \
  --minor-version-id "0" \
  --status "PUBLISHED"

ステータスが「Published」に更新されました。
ちなみに一度公開状態にしたバージョンはDraft状態には戻せないようです。
やり直す場合は対象のバージョンを削除して作り直す、または新しいバージョンを発行することになるようです。

image.png

3 サービステンプレート作成

3.1 テンプレート作成

環境テンプレートと同様にまずはテンプレートを作成します。

aws proton-preview create-service-template \
  --region us-west-2 \
  --template-name "crud-api-service" \
  --display-name "CRUD API Service" \
  --description "CRUD API Service backed by AWS Lambda"

サービス.png

3.2 メジャーバージョン作成

メジャーバージョンを新規登録します。

aws proton-preview create-service-template-major-version \
  --region us-west-2 \
  --template-name "crud-api-service" \
  --description "Version 1" \
  --compatible-environment-template-major-version-arns arn:aws:proton:us-west-2:${account_id}:environment-template/crud-api:1

コンソール上では新たにバージョン「1」が登録されたことが確認できます

image.png

3.3 マイナーバージョン作成

さらにマイナーバージョンの登録を行います。

tar -zcvf svc-template.tar.gz service/

aws s3 cp svc-template.tar.gz s3://proton-cli-templates-${account_id}/svc-template.tar.gz --region us-west-2

rm svc-template.tar.gz

aws proton-preview create-service-template-minor-version \
  --region us-west-2 \
  --template-name "crud-api-service" \
  --description "Version 1" \
  --major-version-id "1" \
  --source-s3-bucket proton-cli-templates-${account_id} \
  --source-s3-key svc-template.tar.gz

コンソールでもマイナーバージョン「1.0」が登録されていますね。

image.png

2.4 登録したバージョンの公開

コンソールの忠告にもあるようにまだバージョンの状態が「Draft」なので、この状態ではこのテンプレートはまだ利用できない状態になっています。
利用できるようにするためにステータスを「公開」状態に更新しましょう。

aws proton-preview update-service-template-minor-version \
  --region us-west-2 \
  --template-name "crud-api-service" \
  --major-version-id "1" \
  --minor-version-id "0" \
  --status "PUBLISHED"

ステータスが「Published」に更新されました。

image.png

4 環境デプロイ

まずは環境側のリソースをデプロイします。

aws proton-preview create-environment \
  --region us-west-2 \
  --environment-name "crud-api-beta" \
  --environment-template-arn arn:aws:proton:us-west-2:${account_id}:environment-template/crud-api \
  --template-major-version-id 1 \
  --proton-service-role-arn arn:aws:iam::${account_id}:role/ProtonServiceRole \
  --spec file://specs/env-spec.yaml

ここで指定しているspecs/env-spec.yamlは環境テンプレートに対する入力パラメータが定義されているyamlになります。

このサンプルの環境テンプレートで作成されるテンプレートは以下になります。

  • DynamoDBテーブル

コンソールで確認するとProtonコンソールでは環境が作成されていることが確認できます

環境作成.png

CloudFormationコンソールでは環境リソース用のスタックが作成されています。
CloudFormationでDynamoDBテーブルが作成されたことが確認できますね。

image.png

5 サービスデプロイ

デプロイコマンド実行

次にサービスのデプロイを行います。
デプロイコマンド時にアプリのソースコードがあるリポジトリおよびリポジトリ接続のための接続情報を入力する必要があります。

ここでも環境デプロイ時と同様に入力パラメータはfile://specs/svc-spec.yamlで定義されています。
サービスがどの環境リソースを利用するかは入力パラメータで指定されています。

aws proton-preview create-service \
  --region us-west-2 \
  --service-name "tasks-front-end" \
  --repository-connection-arn arn:aws:codestar-connections:us-west-2:${account_id}:connection/<your-codestar-connection-id> \
  --repository-id "<your-source-repo-account>/<your-repository-name>" \
  --branch "main" \
  --template-major-version-id 1 \
  --service-template-arn arn:aws:proton:us-west-2:${account_id}:service-template/crud-api-service \
  --spec file://specs/svc-spec.yaml

入力パラメータは以下の形式で定義されています。
すこしややこしい&私もまだ理解しきれていないですが、入力パラメータinstancesは配列のため、name以下のパラメータを複数定義できます。
配列が複数定義されている場合、その要素の数だけサービス用のリソースがデプロイされるような設計になっています。

svc-spec.yaml
proton: ServiceSpec

# cicd用リソースで利用する入力パラメータ
pipeline:
  unit_test_command: make test
  packaging_commands: make publish

# サービスリソースで利用する入力パラメータ
instances:
  - name: "front-end"
    # どの環境を利用するか
    environment: "crud-api-beta"
    spec:
      resource_name: task
      resource_handler: src/api
      lambda_runtime: ruby2.7

デプロイコマンド実行直後

デプロイコマンド実行直後ではまずアプリケーションリソース用のテンプレート(service/infrastructure/cloudformation.yaml)のデプロイが実施されます。

アプリケーションリソース用のスタックが作成されていますね。
デプロイすぐのサービススタック.png

CICD用パイプラインリソースのデプロイ

アプリケーションリソース用のテンプレートデプロイ完了後、CICDパイプライン用のテンプレート(service/pipeline/cloudformation.yaml)がデプロイされます。
CodePipelineやCodBuildが作成されていることが確認できます。

image.png

CodePipeline実行

CICDパイプライン用のテンプレートのデプロイ完了後、作成されたCodePipelineが実行し、
CodeBuild上でアプリケーションのデプロイ処理が実行されます。

pipeline.png

パイプライン実行後、アプリケーションリソース用のスタックを再度確認すると新たにリソースが作成されていることが確認できます。

pipeline実行後のサービススタック.png

サンプルのチュートリアル実施はこれで以上となります。
テンプレートが用意されていれば、
コマンド一つでCICD環境の構築からアプリケーションデプロイまで実施できますね。

サービスデプロイ時の実施フロー

実際に触ってみてわかったのですが、サービステンプレートからサービスをデプロイする際、アプリケーションリソース用のスタックがデプロイコマンド実行時とPipeline実行後でスタックに含まれるリソースが異なっていました。
これ、サンプルソースを確認すると一つのテンプレートを使いまわしていて、入力パラメータによって、デプロイするリソースを切り替えていることがわかりました。ソースを見るだけでは少し分かりづらいので、実際にどのようなフローでリソースが作成されていったのか整理しました。

  1. デプロイコマンド実行
  2. アプリケーション用テンプレート(`service/infrastructure/cloudformation.yaml)からスタック作成
    • この時点に作成されるリソース
      • CrudHttpApi(AWS::Serverless::HttpApi) - CRUD処理用API Gateway
  3. CICDパイプライン用のテンプレート(service/pipeline/cloudformation.yaml)からスタック作成
    • 作成されるリソース(IAMロール、ポリシーについては割愛しています)
      • FunctionBucket(AWS::S3::Bucket) - Lambdaリソースアップロード用バケット
      • BuildProject(AWS::CodeBuild::Project) - Buildステージ用CodeBuildプロジェクト
      • PipelineArtifactsBucketEncryptionKey(AWS::KMS::Key) - アーティファクトアップロード時の暗号化キー
      • PipelineArtifactsBucketEncryptionKeyAlias(AWS::KMS::Alias) - PipelineArtifactsBucketEncryptionKeyのエイリアス
      • PipelineArtifactsBucket(AWS::S3::Bucket) - アーティファクト用S3バケット
      • Pipeline(AWS::CodePipeline::Pipeline) - CICD用CodePipeline
        • 入力パラメータのservice_instancesの要素数分デプロイ用のActionが追加されます
      • 以下のリソースは入力パラメータのservice_instancesの要素数分作成されます
        • Deploy{{loop.index}}Project(AWS::CodeBuild::Project) - Deployステージ用CodeBuildプロジェクト
  4. CodePipeline実行
  5. CodePipelineのBuildステージでCodeBuildが実行
    • CodeBuild内でProtonコマンド用の入力パラメータファイル(svc-spec.yamlのような)を作成
    • 具体的にはsvc-spec.yamlにinstances[*].spec.code_uriパラメータが追加された状態のファイルになります。
  6. CodePipelineのDeployステージでCodeBuild実行
    • CodeBuild上でprotonのupdate-service-instanceコマンド実行
      • aws proton-preview --endpoint-url https://proton.$AWS_DEFAULT_REGION.amazonaws.com --region $AWS_DEFAULT_REGION update-service-instance --version-update-type UPDATE_SPEC --service-instance-name $service_instance_name --service-name $service_name --spec file://rendered_service.yaml
  7. update-service-instanceで再度アプリケーション用テンプレート(`service/infrastructure/cloudformation.yaml)のスタック更新
    • このとき入力パラメータにinstances[*].spec.code_uriが含まれているため、追加で以下のリソースが作成される
      • ListFunction(AWS::Serverless::Function) - データ一覧取得リクエスト用Lambda
      • CreateFunction(AWS::Serverless::Function) - データ作成リクエスト用Lambda
      • GetFunction(AWS::Serverless::Function) - データ取得リクエスト用Lambda
      • DeleteFunction(AWS::Serverless::Function) - データ削除リクエスト用Lambda
      • UpdateFunction(AWS::Serverless::Function) - データ更新リクエスト用Lambda

このように同じテンプレートファイルを利用しているのにも関わらず、入力パラメータなどによって動的にCfnテンプレートを作成し、実行しています。これはサンプルソースを見ればわかりますが、テンプレートファイルにテンプレートエンジン「jinja」を利用しているためです。
jinjaを利用して入力パラメータの参照はif文などでCfnテンプレートを動的に作成できるようになっています。

以下のコードのように入力パラメータで条件分岐していたため、実行タイミングによって作成されるリソースが切り替わっていました。

service/infrastructure/cloudformation.yaml
Resources:
  CrudHttpApi:
    Type: AWS::Serverless::HttpApi
    Properties:
      FailOnWarnings: false
{% if service_instance.code_uri|length %} # <-入力パラメータ「code_uri」の有無で条件分岐
  ListFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: "{{service_instance.code_uri}}"
      Handler: "{{service_instance.resource_handler}}.list_{{service_instance.resource_name}}"
      Runtime: "{{service_instance.lambda_runtime}}"
      Environment:
        Variables:
          TABLE_NAME: "{{environment.TableName}}"
      Policies:
        - DynamoDBCrudPolicy:
            TableName: "{{environment.TableName}}"
...

まとめ

チュートリアル実施しただけになりますが、触った感想としては、

  • アプリケーションリソースとCICDリソースをまとめて管理できるのは便利だと思いました
    • プロトンでバージョンも管理できるのもいいですね
  • 環境テンプレートとサービステンプレートそれぞれ用意できる
    • 一つの環境に複数のサービステンプレートのデプロイ等もできるので色々応用できそうです
    • ただしどちらのテンプレートにも任意のリソースを定義できてしまうので、どのリソースを環境、サービスどちらテンプレートに置くかは開発者で検討する必要があります
    • 個人的には環境テンプレートにはDB等のデータストアや基本的に変更しないリソースを定義、サービステンプレートには全削除しても問題ないリソース群を定義するのが一つの切り分け方になるのかなと感じました
  • まだプレビュー版なので仕方がない部分もあるのですが、現状気になる点としては
    • テンプレートの記述がProtonの仕様に制限される
      • service_instancesの取り扱いの都合上必ず、{% if service_instance.code_uri|length %}などの記載が必要になっている状態です
    • また当然といえば当然ですが学習コストが高いですね。CloudFormation、CodePipeline、CodeBuildに加えてProtonの仕様も理解する必要があります
    • 現時点ではアプリケーションのソースコードリポジトリがGithubかBitbucketのみ対応
      • CodeCommit対応してほしいですね。(ロードマップにはCodeCommit対応が載っています)
    • 既存のシステムからProtonに移行するのは大変そう

プレビュー版なのでまだまだ機能は限定的ですが、ロードマップでCloudFormation以外のテンプレートなどにも対応していくようなので、より汎用的になっていくようです。GAされるが楽しみですね。

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

AssumeRoleをECS&Digdag環境で利用する

やりたいこと

  • AWS アカウント A

    • S3 に日次でファイルが置かれる
  • AWS アカウント B

    • アカウント A の S3 のファイルを取得したい
    • 実行環境は ECS 上で動いている Digdag サーバー

AssumeRole は一時的なクレデンシャルを生成して、他の AWS アカウントからのアクセスを許可できるので、以下のようなセキュリティ的に避けたい設定を行わなくて済みます

  • IAM ユーザーを作成し、AWS アクセスキー発行
  • S3 の公開設定

AWS アカウント A - 参照先

ロールを作成

  1. AWS コンソールからロールを作成
  2. 信頼されたエンティティの種類を「別の AWS アカウント」にして、アカウント B のアカウント ID を設定
    • この段階では、アカウント B の root に権限が付与される(ここは後で変えます)
  3. アカウント B 用のポリシーを作成して、ロールにアタッチ
    以下は今回のやりたいことを実現する最低限のポリシーです
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "VisualEditor0",
         "Effect": "Allow",
         "Action": "s3:GetObject",
         "Resource": ["arn:aws:s3:::[バケット名]/*"]
       }
     ]
   }

AWS アカウント B - 参照元

AssumeRole のポリシーを作成

  1. AWS コンソールからポリシーを作成
   {
     "Version": "2012-10-17",
     "Statement": [
       {
         "Sid": "VisualEditor0",
         "Effect": "Allow",
         "Action": "sts:AssumeRole",
         "Resource": "arn:aws:iam::[アカウントAのID]:role/[さっき作ったロール名]"
       }
     ]
   }
  1. ECS タスクのロールにこのポリシーをアタッチ
    ECS タスクで使われているロールは、コンソールの ECS タスク定義から、「タスク実行ロール」で確認できます

AWS アカウント A - 参照先

作成したロールの信頼関係タブから、以下のように編集

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::[アカウントBのID]:role/ECSタスクロール名"
      },
      "Action": "sts:AssumeRole",
      "Condition": {}
    }
  ]
}

さきほどは、アカウント B の root に権限を与えていましたが、ECS タスクロールに権限を与えるように変更しました

動作確認

AWS CLI

Digdag のタスクが sh で、AWS CLI を使う場合

ECS タスクで使う Docker などで以下を用意する

~/.aws/configを作成

[profile assume]
role_arn = arn:aws:iam::[アカウントAのID]:role/[ロール名]
credential_source = EcsContainer

または

echo "[profile assume]" > ~/.aws/config
echo "role_arn = arn:aws:iam::[アカウントAのID]:role/[ロール名]" >> ~/.aws/config
echo "credential_source = EcsContainer" >> ~/.aws/config
aws s3 cp [コピー元] [コピー先] --recursive --profile assume

Python

Digdag のタスクが Python の場合

from sts import STS

# role_arn: arn:aws:iam::[アカウントAのID]:role/[ロール名]
# bucket: 参照先のバケット名
# key: 参照先のオブジェクトキー
# save_file_path: 保存先(Digdagコンテナー内)
class S3(object):
    def get_by_sts(self, role_arn, bucket, key, save_file_path):
        resource = STS().get_resource(role_arn, "s3")

        resource.Bucket(bucket).download_file(
            key,
            save_file_path,
        )
import boto3


class STS(object):
    sts_client = boto3.client("sts")

    def get_resource(self, role_arn, resource_name):
        role_obj = self.sts_client.assume_role(
            RoleArn=role_arn,
            RoleSessionName="AssumeRoleSession",
        )

        resource = boto3.resource(
            resource_name,
            aws_access_key_id=role_obj["Credentials"]["AccessKeyId"],
            aws_secret_access_key=role_obj["Credentials"]["SecretAccessKey"],
            aws_session_token=role_obj["Credentials"]["SessionToken"],
        )

        return resource

boto3.client("sts").assume_roleに RoleArn と任意のセッション名を渡して、一時的なクレデンシャルを発行しています

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

機械学習を利用した不正検知サービス Amazon Fraud Detector を触ってみる

こんにちは。
ナレッジコミュニケーション 繁松です。

この記事は株式会社ナレッジコミュニケーションが運営する Amazon AI by ナレコム Advent Calendar 2020 の 18日目にあたる記事になります。

本日は、機械学習を利用した不正検知サービスの[Amazon Fraud Detector]について書きたいと思います。

Amazon Fraud Detectorとは

AWSより引用

Amazon Fraud Detector は完全マネージドサービスであり、オンライン支払い詐欺や偽のアカウントの作成など、潜在的に不正なオンラインアクティビティを簡単に識別できます。
Amazon Fraud Detector は、機械学習 (ML) と AWS およびAmazon.com の 20 年にわたる不正検出の専門知識を使用して、不正行為の可能性を自動的に特定し、より多くの不正行為を迅速に発見できるようにします。
Amazon Fraud Detector は、数回クリックするだけで、ML の経験がまったくない不正検出モデルを作成できます。
これは、Amazon Fraud Detector がすべての ML の手間のかかる作業を処理するためです。

機械学習の知識が無い人でも簡単に詐欺、偽のアカウント作成等の不正検知を取り入れることができるフルマネージドサービスです。

Amazon Fraud Detectorを使ってみた

実際に使ってみたいと思います。
手順は以下の手順になります。

AWSブログより引用

ステップ 1: 不正行為について評価したいイベントを定義する。
ステップ 2: 履歴的なイベントデータセットを Amazon S3 にアップロードして、不正検出モデルタイプを選択する。
ステップ 3: Amazon Fraud Detector が履歴データを入力として使用し、カスタムモデルを構築する。
           このサービスは、自動的にデータを調べてリッチ化し、特徴量エンジニアリングを実行して、アルゴリズムの選択、およびモデルのトレーニングと調整を行い、モデルをホストします。
ステップ 4: モデルの予測に基づいて受け入れる、レビューする、またはより多くの情報を収集するかどうかのルールを作成する。
ステップ 5: オンラインアプリケーションから Amazon Fraud Detector API を呼び出して、リアルタイムの不正行為予測を受け取り、設定された検出ルールに基づいて措置を講じる。 
           (例: e コマースアプリケーションは、E メールと IP アドレスを送信し、不正行為スコアとルールからの出力 (例: レビュー) を受け取ることができます)

数クリックで簡単に?とは行かなそうですが、実際にやってみたいと思います。
今回は公式のチュートリアルの「パートA:Amazon Fraud Detectorモデルを構築」を参考に進めて行きます。

1. サンプルのトレーニングデータの取得、アップロード

AWS公式の架空のトレーニングデータを以下のURLよりダウンロードします。
ダウンロードはこちら

次にS3バケットにトレーニングデータをアップロードします。
※今回amazon-fraud-detector-testというバケットを作成しています。
※Zipファイルには、[registration_data_20K_full.csv]と[registration_data_20K_minimum.csv]が入っています。
S3とAmazon Fraud Detectorのリージョンは同じリージョンにする必要があります。
画像2.jpg

今回は[registration_data_20K_minimum.csv]をアップロードします。
画像1.jpg

ファイルの中身はこのような感じです。
画像3.jpg

2. イベント作成

実際にAmazon Fraud Detectorにイベントを作成していきます。
東京リージョンは2020年12月18日現在、未対応ですので、、バージニア北部リージョンに作成します。
画像4.jpg

エンティティを作成し、選択します。
画像5.jpg

[トレーニングデータセットから変数を選択する]を選択し、IAMロールの作成を行います。
IAMロール名は先程作成したS3バケット名と同じ名前にします。
画像6.jpg

先程アップロードしたcsvファイルのS3 URLを入力しアップロードを行い、
表示された変数の変数型を指定します。
画像7.jpg

ラベルを少なくとも2つ定義する必要があるので、
[fraud]と[legit]を定義し、イベントタイプの作成を行います。
画像8.jpg

3. モデル作成

イベントタイプ作成が完了したら、モデルの作成を行います。
画像9.jpg

イベントタイプは先程作成したものを選びます
画像10.jpg
画像11.jpg

トレーニングの設定不正ラベル正当ラベルを選択します。
画像12.jpg

モデルはデータのサイズに応じて30~40分から数時間かかるそうです。
画像13.jpg

生成後はパフォーマンスメトリクスの確認と、モデルのデプロイができるようになります。
2020-12-18_18h14_58.jpg
2020-12-18_18h14_21.jpg

Amazon Fraud Detectorを取り入れた運用を考え、不正アクセスなどを防ぎたいですね。
以上 Amazon Fraud Detectorについてでした。

参考文献
https://aws.amazon.com/jp/fraud-detector/features/
https://aws.amazon.com/jp/blogs/news/amazon-fraud-detector-is-now-generally-available/

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

AWS Tools for PowerShellで不要なMFAデバイス登録したプロフィールでのロール切替えが失敗する

概要

AWS Tools for PowerShellでロール切替え用のプロフィールを作る際、MFA不要のロールに対してはMFAデバイスを登録してはいけない。不要に登録すると、このプロファイルでの操作が Error calling AssumeRole for role (ロールのarn) で失敗する。

詳細

AWS Tools for PowerShellでロール切替えをしたいときは、ロール切替え用のプロファイルを作成するのが便利。作成するプロファイルが MyRole で使用する認証情報のプロファイルが MyCredProfile だとして、こんな感じ。 ^ はコマンドが複数行にわたるときのWindows PowerShell環境でのシンボルなので、Linux環境では \ に置き換えてください。

PS> Set-AWSCredential -StoreAs MyRoleProfile -SourceProfile MyCredProfile ^
    -RoleArn arn:aws:iam::123456789012:role/MyRole

登録したプロファイルが使用されるように Set-AWSCredential で指定しておく。

PS> Set-AWSCredential -ProfileName MyRoleProfile

ここでもし使用するロールがMFA(多要素認証)必須の場合、次のようにしてMFAデバイスも登録しておく。

PS> Set-AWSCredential -StoreAs MyRoleProfile -SourceProfile MyCredProfile ^
    -RoleArn arn:aws:iam::123456789012:role/MyRole
    -MfaSerial arn:aws:iam::123456789012:mfa/MyName

こうすると、コマンドレット実行時に適宜MFAコードを問合わせてくれる。

PS> Get-IAMUserList
Enter MFA code:*******

さて、もしここでロール arn:aws:iam::123456789012:role/MyRole がMFAを要求しないロールの場合どうなるか?もちろん、MFAコードを聞かれないとか、聞かれても無視されて、操作は成功すると思ってた。でも実際には、エラーになる。

PS> Get-IAMUserList
Enter MFA code:*******
Get-IAMUserList: Error calling AssumeRole for role arn:aws:iam::123456789012:role/MyRole

結論としてAWS Tools for PowerShellでロール切替え用のプロフィールを作る際、MFA不要のロールに対してはMFAデバイスを登録してはいけない。不要に登録すると、このプロファイルでの操作が Error calling AssumeRole for role (ロールのarn) で失敗する。このエラー内容でこれがいけないのだとは気づきにくいと思うのだけど、気づけないとMFAコード入力し間違えたかなとか認証用プロファイルおかしいだろうかとか無駄な調査をすることになる。

参照

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

Amazon Braket を使って量子コンピュータの実機を動かしました

はじめに

今回は、Amazon Braketを使って代表的な量子アルゴリズムを幾つか実装してみることにしました。量子アルゴリズムを学んでも、それが正しいことを机上の計算で確かめるだけでは、それが実際の量子デバイス上でワークするかどうか実感がわきません。その点、Amazon Braketでは後述するようにRegettiIonQD-waveなど複数の量子コンピュータへのアクセスが可能です。同じアルゴリズムを異なるNISQな量子デバイスで計算することによって、リアルに量子アルゴリズムの正しさを実感できるのは魅力的です。

この記事では、量子アルゴリズムとして有名なDeutsch-JozsaのアルゴリズムShorのアルゴリズムを取り上げます。後者はRSA暗号を解読できるアルゴリズムとして有名です。この記事が、量子コンピューティングを体験するきっかけとなれば幸いです。

Amazon Braket について

Amazon Braket は AWS が提供している量子コンピューティングサービスです。量子回路の設計を行い、ローカルシミュレーターまたはクラウドベースのマネージドシミュレーター SV1 で回路を実行できます。さらにこれらの古典シミュレータで設計された回路を、量子マシンで実行するような操作を Python の SDK を通して行うことができます。今回はこの Amazon Braket を活用して、量子アルゴリズムを構築、実行してみたいと思います。本記事は Braket が提供している Jupyter notebook 形式の環境にて実行した内容をご紹介しています。この環境へは Braket SDK がすでにインストールされていますので、すぐに始めることができます。回路の構築については、Braket ではいくつかの量子ゲート操作を提供しており、組み合わせて回路を構築します。追加できるゲートを確認してみましょう。

import string
from braket.circuits import Gate

gate_set = [attr for attr in dir(Gate) if attr[0] in string.ascii_uppercase]
print('Gate set supported by SDK:\n', gate_set)

出力結果は下記のようになります。なんとなく、馴染みのあるゲート操作が出力されているのではないでしょうか。

Gate set supported by SDK:
['CCNot', 'CNot', 'CPhaseShift', 'CPhaseShift00', 'CPhaseShift01', 'CPhaseShift10', 'CSwap', 'CY', 'CZ', 'H', 'I', 'ISwap', 'PSwap', 'PhaseShift', 'Rx', 'Ry', 'Rz', 'S', 'Si', 'Swap', 'T', 'Ti', 'Unitary', 'V', 'Vi', 'X', 'XX', 'XY', 'Y', 'YY', 'Z', 'ZZ']

回路の作成は、空の回路を作成し、ゲートを追加して構築していきます。試しにベル状態を構築してみました。

# ベル状態の回路を構築
bell = Circuit().h(0).cnot(0, 1)
print(bell)
T  : |0|1|

q0 : -H-C-
        | 
q1 : ---X-

T  : |0|1|

Deutsch-Jozsa のアルゴリズム

Deutsh-Jozsa のアルゴリズムは、古典アルゴリズムよりも、実行時間が短くなるとされる量子アルゴリズムの中で、一番最初に発見されたものの一つです。与えられた関数のオラクルの判定に使います。解くべき問題のサイズ $n$ に対し、古典アルゴリズムでは指数関数的に処理時間が増えるような問題に対して、量子計算を活用すると、多項式時間で収まることを示しました。

Braket を使った実装

例えば、$n=2$ とした $f_1(x_1, x_2)=x_1$ のような関数は $x_2$ の値によらず、$x_1$ の入力がそのまま出力されます。$x_1$ が 0 になるか 1 になるかは半々なので、この関数は均等なオラクルと言えます。オラクルの量子回路のイメージは下図のようになります。

オラクルは関数 $f$ の引数となる $2$ 量子ビットと制御ビットに対するユニタリ変換 $U_f$ として定義されます。オラクルで判定を行う際には、入力される量子ビットにそれぞれアダマール変換を行って重ね合わせ状態にした上でオラクルに入力します。オラクルの出力を再びアダマール変換した結果、制御ビット以外の出力が $\left|00\right>$ ならば、関数 $f$ は一定であり、それ以外の $\left|10\right>$, $\left|01\right>$, $\left|11\right>$ の何れかであれば、関数 $f$ は均等と判定できます。

それでは、均等な関数 $f_1(x_1,x_2) = x_1$ について、実際に量子回路を設計し、測定してみましょう。まずは、回路の設計は下記のようになります。

my_circuit1 = Circuit()\
        .x(target=2)\
        .h(range(3))\
        .cnot(control=0, target=2)\
        .h(range(3))

print(my_circuit1)

このように表記されました。

T  : |0|1|2|3|

q0 : -H---C-H-
          |   
q1 : -H-H-|---
          |   
q2 : -X-H-X-H-

T  : |0|1|2|3|

続いて、この回路を使った量子計算の実行です。今回はシミュレータである SV1 を活用しています。

device = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")

my_bucket = f"amazon-braket-Your-Bucket-Name"
my_prefix = "simulation-output" # the name of the folder in the bucket
s3_folder = (my_bucket, my_prefix)

# タスクの定義と実行
task = device.run(my_circuit1, s3_folder, 
                  poll_timeout_seconds = 100, 
                  shots=1000)


result = task.result()
counts = result.measurement_counts

plt.bar(counts.keys(), counts.values());
plt.xlabel('bitstrings');
plt.ylabel('counts');

dj.png

Deutsch-Jozsaのアルゴリズムでは、関数$f(x_1,x_2)$が一定な関数の場合、必ず$\left|00\right>$を返します。今回は$\left|10\right>$が返ってきていることが観測され、関数が均等なオラクルだと判定されます。

Shorのアルゴリズム

素因数分解を少ない計算量で解く、という量子計算では非常に有名なアルゴリズムです。

ここでは簡単な例についてBraketを使った実装を行います。
以下、簡単にアルゴリズムについて説明を行いますが、こちらについてはIBMQのShor's algorithmと次の教科書を参考にさせていただきました。

周期発見問題

周期発見問題と呼ばれる問題が解ければ、因数分解が効率的に行えることは1970年代から数学者の間で知られていました。周期発見問題は次のように定義されています。

2つの整数 $N, a$ が与えられたとき、 $a^r -1$ が $N$ の倍数となるような最小の整数 $r>0$ を求める

$r$ は $N$ を法としたときの $a$ の周期と呼ばれます。合同式を使うと、周期 $r$ は $a^r \equiv 1 (\mbox{mod} N)$ を満たします。量子計算によって、この周期 $r$ を少ない計算量で求めることができる、というのが利点となります。

素因数分解問題

素因数分解問題はその名の通り自然数 $N$ に対し、 $N=pq$ を満たす素数の組 $(p,q)$ を求める問題です。$N$ が大きくなるにつれて、この問題を解くための計算量が爆発的に増える、というのは有名な話だと思います。
詳細は何も説明しないので恐縮ですが、 $N$ と互いに素な整数 $x$ について $x^r \equiv 1~ (\mbox{mod}~ N)$ を満たす最小の $r$ を求めることができれば、古典アルゴリズムでも素因数分解問題を効率よく解けることが知られています。これはまさに $x$ の $N$ を法とした周期を求める問題です。

Shorのアルゴリズム

周期発見問題の部分を量子計算に行わせて、残りは古典計算で素因数をみつける、というのが Shorのアルゴリズムになります(以下の手順は量子情報科学入門3章からの引用です)。

  • $x\in { 1,\ldots,N-1 }$ を一様ランダムに選ぶ
  • 最大公約数 GCD($x,N$) を計算する。その値が $1$ でなければ GCD($x,N$) を出力して終了する
  • $x^r \equiv 1~ \mbox{mod}~ N$ となる最小の $r$ を求める (量子計算部分)
  • $r$ が偶数かつ $x^{r/2}\not\equiv -1 ~ \mbox{mod}~ N$ ならば、GCD($x^{r/2}+1,N$) と GCD($x^{r/2}-1,N$) が $N$ を割り切るか検査し、割り切るならそれを出力して終了する。そうでなければ最初の工程からやり直す

Braketでの実装

Shorのアルゴリズムと言っていますが、量子計算部分は周期発見問題なので、簡単な周期発見問題に対応する回路を組んでみようと思います。例として、IBMQのページと同様に $N=15, a=7$ を扱ってみます。このとき

7^2 \equiv 4,~ 7^3 \equiv 13,~ 7^4 \equiv 1 ~ (\mbox{mod} 15)

なので、周期 $r=4$ となります。
この問題を量子回路で表現する際に、 modular multiplication function $f(x)$ と呼ばれる関数を考えます。今の例では、具体的には $f(x)=7x~ (\mbox{mod} 15)$ です。この関数をユニタリー演算子 $U_f$ で表現し、これを回路で実現する、というのが目標になります。

ビット列で15を法とした整数を表現したベクトルを $\left|x\right>$ とすると、$U_f$ の作用は

U_f : \left|x\right> ~ \rightarrow ~ \left|7x~ (\mbox{mod} 15)\right>

なので、具体的に $\left|1\right>$ に作用させてみると

\left|1\right> ~ \rightarrow ~ \left|7\right> ~ \rightarrow ~ \left|4\right> ~ \rightarrow ~ \left|13\right> ~ \rightarrow ~ \left|1\right>

となり、$(U_f)^4$ で元の状態に戻っています。
$U_f$ の量子回路における実装は次の形になります[Markov, Saeedi]。

量子回路.001.png

アルゴリズムの関係上、これは $f^{-1}$ に対応するものになっていて、上で見せたものと逆向きに変換されます(なので本当は$U_{f^{-1}}$の回路です)。
また、各整数に対応する状態は次のようになります。Braketでは $\left|q_0q_1q_2q_3\right>$ という順で状態が表記されるようです。

$x$ 状態
$1$ $1000$
$7$ $1110$
$4$ $0010$
$13$ $1011$

前準備

やっと本題ですが、$7\times 1 ~(\mbox{mod} 15)$の回路をBraketで実装したコードは次のようになります。ほとんどチュートリアルとして用意されているノート(/Braket examples/getting_started/以下にあります)のコピペで実装できます。

# general imports
import matplotlib.pyplot as plt
# magic word for producing visualizations in notebook
%matplotlib inline
import string
import time
import numpy as np

# AWS imports: Import Braket SDK modules
from braket.circuits import Circuit, Gate, Instruction, circuit, Observable
from braket.devices import LocalSimulator
from braket.aws import AwsDevice, AwsQuantumTask

量子コンピュータの実機や、SV1と呼ばれるシミュレーターを使うためには、S3のbucketを指定して上げる必要があります。

# Please enter the S3 bucket you created during onboarding in the code below
my_bucket = "amazon-braket-Your-Bucket-Name" # the name of the bucket
my_prefix = "Your-Folder-Name" # the name of the folder in the bucket
s3_folder = (my_bucket, my_prefix)

# set up device
device = LocalSimulator()
device_sv1 = AwsDevice("arn:aws:braket:::device/quantum-simulator/amazon/sv1")

7×1 (mod 15)

回路図としては、まず1に対応する状態$\left|1000\right>$を$X$演算子で作成し、それに$U_{f^{-1}}$の回路をかけるものになります。

量子回路.002.png

# Multi 7 \times 1 Mod 15
circ = Circuit()
# circ.x([0,1,2,3])
circ.x([0,1,2,3])
circ.x(0)
circ.cnot(control=1, target=2)
circ.cnot(control=2, target=1)
circ.cnot(control=1, target=2)
circ.cnot(control=0, target=1)
circ.cnot(control=1, target=0)
circ.cnot(control=0, target=1)
circ.cnot(control=0, target=3)
circ.cnot(control=3, target=0)
circ.cnot(control=0, target=3)
print(circ)

print(circ) とすることで組んだ回路の模式図を出してくれます。

T  : |0|1|2|3|4|5|6|7|8|9|                         
q0 : -X-X-----C-X-C-C-X-C-
              | | | | | | 
q1 : -X-C-X-C-X-C-X-|-|-|-
        | | |       | | | 
q2 : -X-X-C-X-------|-|-|-
                    | | | 
q3 : -X-------------X-C-X-
T  : |0|1|2|3|4|5|6|7|8|9|

ローカルシミュレーター

ローカルシミュレーターを使って、100回測定を実行してみます。

# run circuit
result = device.run(circ,  shots=100).result()
# get measurement shots
counts = result.measurement_counts
# print counts
print(counts) # Counter({'1011': 100})

# plot using Counter
plt.bar(counts.keys(), counts.values());
plt.xlabel('bitstrings');
plt.ylabel('counts');

7_times_1.png

結果は100回とも$\left|1011\right> = \left|13\right>$となりました。$U_f\left|13\right>=\left|1\right>$なので正しいのですが、1つの状態だけが観測されるので少し面白みに欠けます。

SV1

では、SV1シミュレーターを使って同じことをやると次のようなコードになります。run の引数が少し違うだけでほぼ同じです。

# run circuit on SV1
result_sv1 = device_sv1.run(circ, s3_folder, shots=100, poll_timeout_seconds=24*60*60).result()
counts_sv1 = result_sv1.measurement_counts
print(counts_sv1) # Counter({'1011': 100})

# plot using Counter
plt.bar(counts_sv1.keys(), counts_sv1.values());
plt.xlabel('bitstrings');
plt.ylabel('counts'); 

7_times_1_sv1.png

そして結果も全く同じとなりました。もう少し実機のような結果(他の状態も観測される)を期待したのですが、100回程度ではあんまりぶれないのでしょうか?ちなみに実機ほどではないですがSV1を使うとBraketで立てているSagemakerインスタンスとは別に料金が発生します。

IonQ

では、実機を回してみます。今回はシミュレーターとほぼ同じ書き方で実行が可能なIonQで試してみました。

# set up device
ionq = AwsDevice("arn:aws:braket:::device/qpu/ionq/ionQdevice")

# run circuit with a polling time of 2 days
ionq_task = ionq.run(circ, s3_folder, shots=100, poll_timeout_seconds=2*24*60*60)

# get id and status of submitted task
ionq_task_id = ionq_task.id
ionq_status = ionq_task.state()
# print('ID of task:', ionq_task_id)
print('Status of task:', ionq_status)

IonQとRigettiは実機が利用できる時間が限られているため、上記を実行すると Status of task: CREATED とタスクが生成され、実機が動いてくれるまで待ちになります。自分が投げたタスク一覧は、Braketの Task というタブから確認可能です。

tasks_mod.PNG

タスクが完了したら、タスク一覧で COMPLETED と表示され、タスク詳細にARNと呼ばれる実行結果に対応するラベルが発行されています。

task_details_mod.PNG

ARNを使って、実行結果を確認することが可能です。

# recover task
task_load = AwsQuantumTask(arn=ionq_task_id) # Task details からコピーしたARNを使う

# print status
status = task_load.state()
print('Status of (reconstructed) task:', status)

# terminal_states = ['COMPLETED', 'FAILED', 'CANCELLED']

# get results
results = task_load.result()

# get all metadata of submitted task
metadata = task_load.metadata()
# example for metadata
shots = metadata['shots']
machine = metadata['deviceArn']
# print example metadata
print("{} shots taken on machine {}.".format(shots, machine))

# get measurement counts
counts = results.measurement_counts
print('Measurement counts:', counts)

# plot results: see effects of noise
plt.bar(counts.keys(), counts.values());
plt.xlabel('bitstrings');
plt.ylabel('counts');
plt.tight_layout();
plt.savefig('bell_ionq.png', dpi=700);

今回実行した結果は以下の通りです。

# print結果
Status of (reconstructed) task: COMPLETED
100 shots taken on machine arn:aws:braket:::device/qpu/ionq/ionQdevice.
Measurement counts: Counter({'1011': 73, '1010': 10, '1000': 5, '0010': 3, '1110': 2, '1001': 2, '0011': 2, '1111': 2, '0110': 1})

7_times_1_ionq.png

ピークは$\left|1011\right>$ですが、他の状態も観測されていることがわかります。

Shorのアルゴリズム、というセクションですが、周期発見問題に関係する回路1つを Amazon Braket 使ってやってみた、というところで終わりです。ここでは$\left|1\right>$に対応する入力を行いましたが、$X$演算子を使って2進数表示させた$0\sim 15$の整数値を作れば他の値でも実行可能です。

最後に

Amazon Braket が出てきたことで、AWSのアカウントがあればすぐに量子コンピュータの実機が動かせるようになったのは感動的でした。実機の値段がわからず恐る恐る回したのですが、IonQでは100ショット1USDでしたので、思ったよりも金額が掛からなかったのも良かったです。
ブレインパッドでは、量子コンピュータに興味がある人が集まって勉強会を行っています。BrainPad Advent Calendar 2020 1日目のohtamanさんの記事でも Amazon Braket で D-Wave を使っていたようです。実機が触れることでチャレンジできることの幅が広がったので、これからも色々試してみたいと思っています。

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

AWSでプッシュ通知の送る方法

AWSでプッシュ送りたいであれば、サービスは2件あります:

  • SNS (Simple Notification Service)
  • Pinpoint

SNS

SNSの中でも2パターンが存在してます、ユーザーを一人一人にプッシュを送信するかユーザーをTOPICに登録して、そのTOPICに送信する。

Screenshot 2020-12-18 at 14.39.19.png

Snsの流れ

SNSでアプリをApplicationを作成して、そのApplicationにエンドポイント(ユーザーのDeviceToken)を登録して、そのエンドポイントを送信する。送信後はSNSにそのユーザーに送信出来たかどうかの確認する。一人一人のユーザーに送信したいならSNSのソフトリミットは1秒1500件を送ります。

でも1<>1の送信だけではなくてTopicにも送信出来ます。

TOPICとは?

Topicは送信先のエンドポイントです、120万エンドポイントまで登録出来るエンドポイントです。エンドポイントはSNSの中で「送信出来る場所」になります。
たとえば、このアプリは100ユーザーを使ってます、プッシュ通知を送りたいならユーザーをSNSのApplicationに登録して、一人一人に送信するですが、もっと早い方法欲しいであれば、Applicationに登録する時はその1エンドポイントをTopicにsubscribeすることです。100ユーザーをsubscribeするなら、Topicに送信する時は1リクエストで100ユーザーに送信出来ます。

それは100ユーザーでも、100万ユーザーでも変わらないです!

Screenshot 2020-12-18 at 16.58.41.png

Topicのいいところは送信速度早いとAWSのApiを1回のみです。

pinpoint

Pinpointはお話題になったサービスで、今年に日本で使えるようになりました!SNSと違って、一気に100ユーザーを登録して、一気にプッシュ出来ます!

pinpointのプッシュ流れはシンプル、セグメントを作成して、セグメント作成する時はユーザーのファイルをインポートする
Screenshot 2020-12-18 at 16.09.33.png

ファイルのサンプル

import.csv :
ChannelType,Address,Location.Country,Demographic.Platform,Demographic.Make,User.UserId
APNS,482aba02e7da338707541bb4c4a570b0ec090b8b0001b28ae1634ee680f2cbc4,JP,iOS,Apple,255830951

ユーザーのdeviceTokenとプラットフォームと他の情報で組み合わせのCSVやJsonです。

セグメント作成したあとはそのセグメントに対してキャンペーンを作成して、プッシュの設定をして(タイトルとメッセージ)と送信の設定(繰り返し送信などは出来ます)をして終わりです!

Screenshot 2020-12-18 at 16.23.25.png

送信速度も早い、1秒2万件送信されてます。

だが、

Screenshot 2020-12-18 at 16.26.04.png

送信率をもらえますが、誰に送信出来たか出来てないは出来ません。どこのユーザーをアンインストールしたどかも出来ません。Pinpointと言う名前なのにユーザーを特定出来ないは微妙ですね...その上、安いSNSに対しては大きなユーザー数あればPinpointは高いです。
https://aws.amazon.com/jp/pinpoint/pricing/

通知 100 万件までは無料、その後は通知 100 万件あたり 1 USD。
MTA の 5,000 エンドポイントまでは無料、その後は 1,000 エンドポイントあたり 1.20 USD。

なので1プッシュで200万ユーザーなら1ヶ月2400ドルになります。自分の使い方としてならPinpointは合わないです、地裁ユーザーベースでたくさんのプッシュ送りたいなら出来そうです。

SNSーPinpoint

判断する為に200万ユーザーを送りたい場合はどれぐらいかかる(送信時間と値段):

Screenshot 2020-12-18 at 16.40.07.png

SNSとpinpointは両方いいところありますが今回の要望としては大きくな対象ユーザーがあれば、Topicは一番使いやすいです。

以上

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

AWS Route53 で DNSSEC を使う

こんにちは。re:Invent 2020 最終日の本日(JST基準)、Route53がDNSSECのサポートを始めるという発表がありました。AWSはDNSSECはやらない方針だという噂を聞いたことがあるので、アメリカで何か流れが変わってきたのかな?と推測しつつ、早速試してみました。Route53のDNSSEC設定方法と検証結果をみなさまと共有します。

目次

  • 前提
  • 設定
  • 検証
  • RRSIGについて解説
  • 参考資料

前提

  • ドメイン: uu1.jp (検証用に持っている本物のドメインです)
  • ドメインレジストラー: JPRS

設定

1. AWS Route53でDNSSEC署名の有効化

  1. Route53のホストゾーンをクリックすると「DNSSEC署名」という新しいタブメニューが出来ていました。それをクリックします。
    1.png

  2. 右上の「DNSSEC署名を有効化する」をクリックします。
    2.png

  3. キー署名キー(KSK)を作ります。まず名前を付けて、
    3.png

  4. AWS KMSのCMKを作成し有効化します。
    4.png

  5. KSKがアクティブになったことを確認します。数分かかります。
    5.png

  6. アクティブになったら、「DSレコードを作成するための情報を表示」をクリックします。
    6.png

  7. 信頼チェーンを確立情報 > 別のドメインレジストラでDSレコードを確認し、コピーしておきます。AWS側での作業はこれで終わりです。
    8.png

2. ドメインレジストラーでDSを登録

ドメインレジストラーでドメインのNameserver設定メニューに、上記でコピーしたDSレコードを追加します。
ここは、利用しているレジストラー毎にメニューやUIで差があるので省略します。

検証

1. digで引いてみる

# dig +dnssec uu1.jp ns

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> +dnssec uu1.jp ns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3994
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;uu1.jp.                                IN      NS

;; ANSWER SECTION:
uu1.jp.                 172707  IN      NS      ns-48.awsdns-06.com.
uu1.jp.                 172707  IN      NS      ns-852.awsdns-42.net.
uu1.jp.                 172707  IN      NS      ns-1510.awsdns-60.org.
uu1.jp.                 172707  IN      NS      ns-1857.awsdns-40.co.uk.
uu1.jp.                 172707  IN      RRSIG   NS 13 2 172800 20201220041808 20201218021808 11110 uu1.jp. CZrd12BqXetmHKZBNBwH7vqrQQ5VNGqTvm47mgeXTFr6CwqZOVRtI1Jw 9vcpUnmEf7lNMgoV8Tn/tz/9XUPE+Q==

2. drillで引いてみる

# drill -r named.root -k root.key -S uu1.jp soa
;; Number of trusted keys: 1
;; Chasing: uu1.jp. SOA

DNSSEC Trust tree:
uu1.jp. (SOA)
|---uu1.jp. (DNSKEY keytag: 11110 alg: 13 flags: 256)
    |---uu1.jp. (DNSKEY keytag: 7052 alg: 13 flags: 257)
    |---uu1.jp. (DS keytag: 7052 digest type: 2)
        |---jp. (DNSKEY keytag: 18321 alg: 8 flags: 256)
            |---jp. (DNSKEY keytag: 46369 alg: 8 flags: 257)
            |---jp. (DS keytag: 46369 digest type: 2)
                |---. (DNSKEY keytag: 26116 alg: 8 flags: 256)
                    |---. (DNSKEY keytag: 20326 alg: 8 flags: 257)
;; Chase successful

3. dnsvizでAnalyzeしてみる

uu1.jp-2020-12-18-07_24_34-UTC.png

ちゃんとチェーンが出来ています。素晴らしい。

RRSIG署名について解説

有効期間

ふと、RRSIGの有効期間を見ると、かなり短いですね。一般的には1週間とか2週間で運用するのが多いのかと思っていましたが、2時間くらいになっています。

# dig @ns-48.awsdns-06.com. +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 864
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218062631 20201218041131 11110 uu1.jp. Qzkzm...(略)

有効期間が2020年12月18日 04:11:31 ~ 2020年12月18日 06:26:31 です。期間が2時間15分です。
TTLが15分と短いのでいいですが、長いとどうなるのでしょうか。1日にしてやってみましょう。

# dig @ns-48.awsdns-06.com. +dnssec ttl-86400.uu1.jp a
...
;; ANSWER SECTION:
ttl-86400.uu1.jp.       86400   IN      A       1.1.1.1
ttl-86400.uu1.jp.       86400   IN      RRSIG   A 13 3 86400 20201219061446 20201218041446 11110 uu1.jp. B9RoA...(略)

2020年12月18日 04:14:46 ~ 2020年12月19日 06:14:46 になりました。期間が1日と2時間になりました。
なるほど。TTL + 2時間 がRRSIGの有効期間ということのようです。

署名タイミング

署名の有効期間開始日時もちょっと気になります。現在時刻になっているような気がします。
試してみましょう。

# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218063622 20201218042122 11110 uu1.jp. +3v2yqQSUmPANtvqEmLma5dtrauehT2zEP+5nR5H5Z2P0YUf5jodAE+I PSt8RRTN96BqE4gyxwPrJEAh0EAq8Q==


```console
# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218063624 20201218042124 11110 uu1.jp. YH5Q+JGAK9KkXiQCt1Wio7AlvSPG2iKu9Yx5jB3sJtd3Qq4CSj25O+34 obVpzeuSaACuckcyDOWLntaln8iywA==

やはり、現在時刻ですね。
あらかじめ署名したZoneを用意するのではなく、Queryが来るたびに署名しているようです。
たくさんあるDNSサーバーにZSKのPrivate Keyを使えるようにするのが大変そうですが、さすがAWSですね。

DNSKEYの署名

レコードセットの中でも、DNSKEYだけは、ZSKではなくKSKで署名されます。
こちらはどうでしょうか。

# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp dnskey
...
;; ANSWER SECTION:
uu1.jp.                 3600    IN      DNSKEY  256 3 13 4rIKNvbTPUfKYk1nJEQvtChbIheFCjpukQlt35iKoKwMag32hEhc124h h6qxQRbrXG7JC4HE0AJyo8+m5SIAYA==
uu1.jp.                 3600    IN      DNSKEY  257 3 13 /FcqSpmG5z0DXzFOBnm5vGGweJUZ1BewWNZ7zLf5amARaEU1HTcKjGLT n6hmBND92WkjI4tkfSkFeRcVJuB4BQ==
uu1.jp.                 3600    IN      RRSIG   DNSKEY 13 2 3600 20201218090000 20201217230000 7052 uu1.jp. bUofVksYDT3Z5/H5OwS3vP7bb8X3KFq3vgEq5C/GwS8zlIVeGOvBDiy4 te+Ah1x2j3FbJ15M/kM3Zc5SN0gneA==

こちらは毎回署名するわけではなさそうですね。

参考資料

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

AWS Route53でDNSSECを使う

こんにちは。re:Invent 2020 最終日の本日(JST基準)、Route53がDNSSECのサポートを始めるという発表がありました。AWSはDNSSECはやらない方針だという噂を聞いたことがあるので、アメリカで何か流れが変わってきたのかな?と推測しつつ、早速試してみました。Route53のDNSSEC設定方法と検証結果をみなさまと共有します。

目次

  • 前提
  • 設定
  • 検証
  • RRSIGについて解説
  • 参考資料

前提

  • ドメイン: uu1.jp (検証用に持っている本物のドメインです)
  • ドメインレジストラー: JPRS

設定

1. AWS Route53でDNSSEC署名の有効化

  1. Route53のホストゾーンをクリックすると「DNSSEC署名」という新しいタブメニューが出来ていました。それをクリックします。
    1.png

  2. 右上の「DNSSEC署名を有効化する」をクリックします。
    2.png

  3. キー署名キー(KSK)を作ります。まず名前を付けて、
    3.png

  4. AWS KMSのCMKを作成し有効化します。
    4.png

  5. KSKがアクティブになったことを確認します。数分かかります。
    5.png

  6. アクティブになったら、「DSレコードを作成するための情報を表示」をクリックします。
    6.png

  7. 信頼チェーンを確立情報 > 別のドメインレジストラでDSレコードを確認し、コピーしておきます。AWS側での作業はこれで終わりです。
    8.png

2. ドメインレジストラーでDSを登録

ドメインレジストラーでドメインのNameserver設定メニューに、上記でコピーしたDSレコードを追加します。
ここは、利用しているレジストラー毎にメニューやUIで差があるので省略します。

検証

1. digで引いてみる

# dig +dnssec uu1.jp ns

; <<>> DiG 9.11.4-P2-RedHat-9.11.4-9.P2.el7 <<>> +dnssec uu1.jp ns
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 3994
;; flags: qr rd ra ad; QUERY: 1, ANSWER: 5, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags: do; udp: 4096
;; QUESTION SECTION:
;uu1.jp.                                IN      NS

;; ANSWER SECTION:
uu1.jp.                 172707  IN      NS      ns-48.awsdns-06.com.
uu1.jp.                 172707  IN      NS      ns-852.awsdns-42.net.
uu1.jp.                 172707  IN      NS      ns-1510.awsdns-60.org.
uu1.jp.                 172707  IN      NS      ns-1857.awsdns-40.co.uk.
uu1.jp.                 172707  IN      RRSIG   NS 13 2 172800 20201220041808 20201218021808 11110 uu1.jp. CZrd12BqXetmHKZBNBwH7vqrQQ5VNGqTvm47mgeXTFr6CwqZOVRtI1Jw 9vcpUnmEf7lNMgoV8Tn/tz/9XUPE+Q==

2. drillで引いてみる

# drill -r named.root -k root.key -S uu1.jp soa
;; Number of trusted keys: 1
;; Chasing: uu1.jp. SOA

DNSSEC Trust tree:
uu1.jp. (SOA)
|---uu1.jp. (DNSKEY keytag: 11110 alg: 13 flags: 256)
    |---uu1.jp. (DNSKEY keytag: 7052 alg: 13 flags: 257)
    |---uu1.jp. (DS keytag: 7052 digest type: 2)
        |---jp. (DNSKEY keytag: 18321 alg: 8 flags: 256)
            |---jp. (DNSKEY keytag: 46369 alg: 8 flags: 257)
            |---jp. (DS keytag: 46369 digest type: 2)
                |---. (DNSKEY keytag: 26116 alg: 8 flags: 256)
                    |---. (DNSKEY keytag: 20326 alg: 8 flags: 257)
;; Chase successful

3. dnsvizでAnalyzeしてみる

uu1.jp-2020-12-18-07_24_34-UTC.png

ちゃんとチェーンが出来ています。素晴らしい。

RRSIG署名について解説

有効期間

ふと、RRSIGの有効期間を見ると、かなり短いですね。一般的には1週間とか2週間で運用するのが多いのかと思っていましたが、2時間くらいになっています。

# dig @ns-48.awsdns-06.com. +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 864
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218062631 20201218041131 11110 uu1.jp. Qzkzm...(略)

有効期間が2020年12月18日 04:11:31 ~ 2020年12月18日 06:26:31 です。期間が2時間15分です。
TTLが15分と短いのでいいですが、長いとどうなるのでしょうか。1日にしてやってみましょう。

# dig @ns-48.awsdns-06.com. +dnssec ttl-86400.uu1.jp a
...
;; ANSWER SECTION:
ttl-86400.uu1.jp.       86400   IN      A       1.1.1.1
ttl-86400.uu1.jp.       86400   IN      RRSIG   A 13 3 86400 20201219061446 20201218041446 11110 uu1.jp. B9RoA...(略)

2020年12月18日 04:14:46 ~ 2020年12月19日 06:14:46 になりました。期間が1日と2時間になりました。
なるほど。TTL + 2時間 がRRSIGの有効期間ということのようです。

署名タイミング

署名の有効期間開始日時もちょっと気になります。現在時刻になっているような気がします。
試してみましょう。

# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218063622 20201218042122 11110 uu1.jp. +3v2yqQSUmPANtvqEmLma5dtrauehT2zEP+5nR5H5Z2P0YUf5jodAE+I PSt8RRTN96BqE4gyxwPrJEAh0EAq8Q==


```console
# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp soa
...
;; ANSWER SECTION:
uu1.jp.                 900     IN      SOA     ns-852.awsdns-42.net. awsdns-hostmaster.amazon.com. 1 7200 900 1209600 86400
uu1.jp.                 900     IN      RRSIG   SOA 13 2 900 20201218063624 20201218042124 11110 uu1.jp. YH5Q+JGAK9KkXiQCt1Wio7AlvSPG2iKu9Yx5jB3sJtd3Qq4CSj25O+34 obVpzeuSaACuckcyDOWLntaln8iywA==

やはり、現在時刻ですね。
あらかじめ署名したZoneを用意するのではなく、Queryが来るたびに署名しているようです。
たくさんあるDNSサーバーにZSKのPrivate Keyを使えるようにするのが大変そうですが、さすがAWSですね。

DNSKEYの署名

レコードセットの中でも、DNSKEYだけは、ZSKではなくKSKで署名されます。
こちらはどうでしょうか。

# dig @ns-48.awsdns-06.com. +norec +dnssec uu1.jp dnskey
...
;; ANSWER SECTION:
uu1.jp.                 3600    IN      DNSKEY  256 3 13 4rIKNvbTPUfKYk1nJEQvtChbIheFCjpukQlt35iKoKwMag32hEhc124h h6qxQRbrXG7JC4HE0AJyo8+m5SIAYA==
uu1.jp.                 3600    IN      DNSKEY  257 3 13 /FcqSpmG5z0DXzFOBnm5vGGweJUZ1BewWNZ7zLf5amARaEU1HTcKjGLT n6hmBND92WkjI4tkfSkFeRcVJuB4BQ==
uu1.jp.                 3600    IN      RRSIG   DNSKEY 13 2 3600 20201218090000 20201217230000 7052 uu1.jp. bUofVksYDT3Z5/H5OwS3vP7bb8X3KFq3vgEq5C/GwS8zlIVeGOvBDiy4 te+Ah1x2j3FbJ15M/kM3Zc5SN0gneA==

こちらは毎回署名するわけではなさそうですね。

参考資料

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

【AWS】SystensManager AutomationでリソースID以外でターゲットを指定する方法

SystemsManager Automationのドキュメントでリソース指定にIDを使いたくない

AWSではSystemsManager Automation(以下、SSM Automation)という機能があります。その機能を利用したEC2バックアップ方法について記事を書きました。
実際はこのドキュメントをメンテナンスウィンドウを利用して定期的に実行しています。

https://qiita.com/chittai/items/dbe9185ae552fd54fac5

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/systems-manager-maintenance.html

ここでの運用はバックアップ対象を直接ドキュメントに記述しています。ですが、これだと困ることがあります。例えば、EC2/Volumeのリストアを行い、リソースIDが変わるケースです。
リストアする毎にリソースIDを修正しなければなりません。それは耐えられないので、直接リソースIDを記入する運用を変えたいと思います。

ここで、ポイントとして上げられるのは下記になります。
1. 操作対象のリソースにTagをつける
1. Tagベースでリソースグループを作成する
1. メンテナンスウィンドウのターゲットでリソースグループを指定する
1. メンテナンスウィンドウのタスクで疑似パラメータを使用する
1. 1タスクで操作する対象は1種類のリソースとする(EC2,EBSなどちゃんと分けて管理する)

構成について

まず、構成について紐解きます。Automationのタスクを定期的に実行するには、メンテナンスウィンドウを利用する必要があります。このメンテナンスウィンドウでは
タスクという単位で操作を登録することができます。そして、操作対象をターゲットという単位で登録することができます。このターゲットはリソースグループ単位でもEC2インスタンス単位でも登録できます

そして、リソースグループはTagベースでの登録が可能です。なので、操作したい対象に同じタグをつけ、リソースグループでまとめることでリソースIDを直書きせずに
対象を指定することができます。

対象リソースにTagをつける

今回は、linux, windows2019というインスタンスにAutomation:Yesというタグを追加しました。このタグベースでリソースグループを作成します。

Tagベースでリソースグループを作成する

リソースグループ作成画面からTagベースでリソースグループを作成します。

メンテナンスウィンドウのターゲットでリソースグループを指定する

メンテナンスウィンドウのターゲットで先ほど作成したリソースグループを登録します。

メンテナンスウィンドウのタスクで疑似パラメータを使用する

ここからも大事なポイントとなります。次に、タスクを登録します。今回はAWS-StopEC2Instanceを実行します。想定される動作は、先程作成したリソースグループに登録されているEC2インスタンスすべてが停止されることです。

先程登録したリソースグループのターゲットを登録します。

リソースグループを操作するのに必要な権限をロールに付与します。
それと、EC2インスタンスを停止できる権限があるロールを選択します。(今回はAdministrator権限がついたロールを使用しています)

}
"resource-groups":"GetGroupQuery",
"resource-groups":"GetGroup",
"resource-groups":"GetTags"
}

ここで一番大事なのは、InstanceIdのボックスに{{RESOURCE_ID}}を記述することです。これは疑似パラメータといいます。この疑似パラメータを
記述することで、リソースグループに登録したリソースのID情報がAWS-StopEC2Instanceに自動で渡されて実行されます。動作として、各リソースに対して
順番に子タスクが作成されそれぞれ実行されます。

https://docs.aws.amazon.com/ja_jp/systems-manager/latest/userguide/mw-cli-register-tasks-parameters.html

以上で完了です。

感想

{{RESOURCE_ID}}が登録されたリソースを順に置き換わるので、複数のリソースタイプが登録されていると、ドキュメントの引数と型があわずに基本失敗します。
なので、リソースごとにタスクとリソースグループを登録するのが良いかと思います。

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

[小ネタ] AWS SAM で Parameter Store 見るときはポリシーテンプレートがいいよ

ポリシーテンプレートとは

SAM にはポリシーテンプレートという、パラメータを渡すことで自動的に決められた IAM ポリシーを適用してくれる便利機能があります。

https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/serverless-policy-templates.html

Lambda 関数は ECS タスクと違って、Parameter Store を環境変数に設定する機能が(まだ)ないので、基本的にコード内で ssm API を使ってシークレット値を取得する必要があります。

Parametrer Store を読めるマネージドポリシー(arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccessとか)をアタッチしてもいいけど、読める範囲が大きすぎるのでポリシーテンプレートを使ったほうがいいです。

  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: sample-function
      CodeUri: .
      Handler: index.handler
      Runtime: nodejs12.x
      Environment:
        Variables:
          PARAMETER_PATH: /my/secret/parameter_path
      Policies:
        # ポリシーテンプレートを使う
        # ParameterName は先頭にスラッシュをつけてはいけないので注意
        - SSMParameterReadPolicy:
            ParameterName: "my/secret/parameter_path"

公式ドキュメント見ると、ParameterName ではなく parameterName になってるんですけど、先頭大文字が正解です。(AWSにフィードバック済み)

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

AWS SNSからAndroidにプッシュ通知するためにやったこと、ハマったこと

今関わっているプロジェクトのバックエンドはAWSです
そんなAWSからユーザが操作するAndroidにプッシュ通知がしたいと思いました
しかしFirebase(FCM)が便利すぎて逆にハマってしまったのでメモしておきます

プッシュ通知なんてどうせみんなオフするからいらん!という漢気も私は好きです

参考にした素晴らしい記事様はこちら
Android から Amazon SNS を使ってみる - Qiita
Android端末で、FCM経由でAWSSNSを受け取るまで - Qiita

目指すもの

スクリーンショット 2020-12-18 9.40.06.png
まずは最小限、プッシュ通知をすることだけを目標にします
細かい設定や他の人の方が詳しい!

用意するもの

  • 適当なAndroidプロジェクト
  • Firebaseアカウント ※無料プランでOK
  • AWSアカウント

FirebaseからAndroidに通知するまで

適当にAndroidプロジェクトを作成する

スクリーンショット 2020-12-18 9.00.37.png
パッケージ名は後ほど必要です

Firebaseにアカウントを作成、適当なプロジェクトを作る

スクリーンショット 2020-12-18 8.53.42.png
「プロジェクトを追加」から適当な名前でプロジェクトを作成します
ちなみにFCM(Firebase Cloud Messaging)はだけなら無料プランでもよさそうです。素敵。

Firebase Cloud MessagingにAndroidアプリを登録する

公式がとても丁寧に案内してくれるのでそれに従います

Androidパッケージを登録する

スクリーンショット 2020-12-18 9.09.23.png
Androidパッケージ名に、先ほど作成したAndroidプロジェクトのパッケージ名を入力します
署名はよりセキュリティを高めるなら入れた方がいいんでしょうね。今回は省略!

appディレクトリにJSONファイルを追加

スクリーンショット 2020-12-18 9.14.08.png
firebaseからダウンロードしたgoogle-services.jsonをapp以下に追加します

Androidプロジェクトに依存関係を追加する

/build.gradle
buildscript {
    ext.kotlin_version = "1.4.21"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath "com.android.tools.build:gradle:4.1.1"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "com.google.gms:google-services:4.3.4"  // <<<<< added
        ~
/app/build.gradle
plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'com.google.gms.google-services' // <<< added
}
// ~略~
dependencies {
    // ~略~
    implementation platform('com.google.firebase:firebase-bom:26.1.1')  // <<< added
    implementation 'com.google.firebase:firebase-analytics-ktx'         // <<< added
    implementation 'com.google.firebase:firebase-messaging-ktx'         // <<< added
}

FCMにAndroidデバイスを登録する

通知用のデバイストークンを取得する

プッシュ通知するためには、Androidデバイスを一意に特定するためのトークンが必要になります
トークンの発行はAndroidプロジェクトに組み込まれたFirebaseがやってくれます
例えば下記のようにtokenをログ出力するようにしてみます

MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        // ↓added
        FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener {
            if (!it.isSuccessful) {
                Log.e("Firebase", "Fetching FCM registration token failed", it.exception)
                return@OnCompleteListener
            }

            val token = it.result
            Log.d("Firebase Token", token.toString())
        })

    }
}

この状態でアプリを動かすと、ログにトークンが吐かれますので、これをメモしておきます

D/Firebase Token: c3ilo ~~~

FCMからプッシュ通知を送信してみる

スクリーンショット 2020-12-18 9.25.20.png
FCMのダッシュボードから「通知の作成」へ行き、早速メッセージを送ってみます

スクリーンショット 2020-12-18 9.38.33.png
FCM登録トークンに、先ほどAndroidから生成されたトークンを登録して、テストメッセージを送信します

スクリーンショット 2020-12-18 9.40.06.png
すると無事届きました!簡単ですねー
さあFirebase > Android間の連携ができたので、次はAWS SNSとの連携だ

注意:アプリがバックグラウンドでないと通知が届かない

デフォルトの状態だと、アプリがフォアグラウンド中(前面表示)には届かないようです
「君もうアプリ開いてるから通知せんでええやろ?」ってことでしょうか

AWS SNSからAndroidに通知するまで

AWS SNSとFirebaseを紐付ける

AWS公式が非常に親切に手順を説明してくれていますので、基本はこの通りに

Firebaseのサーバーキーを取得する

スクリーンショット 2020-12-18 10.54.14.png
プロジェクトの設定→「Cloud Messaging」からサーバーキーを取得します

FirebaseサーバーキーをAWS SNSに登録する

image.png

  • AWSコンソールからSimple Notification Serviceを開く
  • 左バーの「プッシュ通知」からモバイルプッシュ通知画面を開き、「プラットフォームアプリケーションの作成」
  • プッシュ通知プラットフォームは「FCM」
  • APIキーに先ほど取得したFirebaseサーバーキーを入力する
  • そのほかは空欄でOK

アプリケーションエンドポイントを作成する

image (1).png

  • デバイストークンにAndroidから取得したトークンを入力する
  • そのほかは空欄

いざSNSメッセージ送信!しかし全く反応なし

image (2).png

なにせFirebaseからAndroidへのプッシュ通知は成功しているので、AWS SNSとFirebase間の紐付けがうまくいってない?
と思ってサーバーキー周りをめちゃくちゃ確認しましたが、原因は別のところにありました
それでは、トラブルシュート編に続きます

AWS SNSからAndroidに通知するために追加でやるべきこと

送信するメッセージにはフォーマットがある

AWS SNSをちょっと知っている人には当たり前だと思いますが...
Firebaseで受け取れるようなメッセージを送るにはフォーマットがあります
しかもAWS側がテンプレートを用意してくれています(カスタムペイロード)

image (3).png

しかし、これでもAndroidにはプッシュ通知されませんでした

Android側でレシーバを実装する必要がある

Firebase公式にはしっかりとこんな説明があります

フォアグラウンド アプリで通知メッセージまたはデータ メッセージを受信する場合は、onMessageReceived コールバックを処理するコードを記述する必要があります。

FirebaseからAndroidへの通知は成功していたので、動くと思っていましたが甘かったようです

Androidマニフェストに通知受信用のサービスを追加

マニフェストファイルのタグ以下にサービスを追加します

AndroidManifest.xml
<service
     android:name=".MyFirebaseMessagingService"
     android:exported="false">
     <intent-filter>
          <action android:name="com.google.firebase.MESSAGING_EVENT" />
     </intent-filter>
</service>
MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }
    }
}

しかしこれでもプッシュ通知が飛んでこないなーと、何気なくログを見てたら
log_image.png
来てたァ!!

カスタム通知を作成する

公式のFCMメッセージについてを参照する限り、AWS SNSからのメッセージはデータメッセージとして扱われていて、
その場合はクライアント側で処理をする必要があるとのこと
(SNSから送信するメッセージにdataって付けてますしね)

MyFirebaseMessagingService.kt
class MyFirebaseMessagingService : FirebaseMessagingService() {
    val CHANNEL_ID = "MyNotification"

    override fun onMessageReceived(message: RemoteMessage) {
        super.onMessageReceived(message)

        Log.d("onMessageReceived", "From: ${message.from}")
        if (message.data.isNotEmpty()) {
            Log.d("onMessageReceived", "payload: ${message.data}")
        }

        createNotificationChannel()
        val notificationBuilder = NotificationCompat.Builder(this, CHANNEL_ID)
            .setContentTitle("通知がきたで")
            .setContentText("内容は「${message.data}」やで")
            .setSmallIcon(R.drawable.ic_launcher_background)
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        with(NotificationManagerCompat.from(this)) {
            notify(UUID.randomUUID().hashCode(), notificationBuilder.build())
        }
    }

    private fun createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val name = "MyNotificationChannelName"
            val descriptionText = "MyNotificationChannelDescription"
            val importance = NotificationManager.IMPORTANCE_DEFAULT
            val channel = NotificationChannel(CHANNEL_ID, name, importance).apply {
                description = descriptionText
            }
            // Register the channel with the system
            val notificationManager: NotificationManager =
                getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
            notificationManager.createNotificationChannel(channel)
        }
    }
}

正直ここの実装はかなりみようみまねで適当なので、ご使用の際は十分に注意してください
ちなみにこのとき、アイコンなど一部の設定を省略するとランタイムでクラッシュしたりします

無事プッシュ通知ができました! :tada: :tada:

スクリーンショット 2020-12-18 12.01.21.png

ということで無事通知が動きました!
まだ通知周りは詰められてないところもありますが、なんとかなりましたね

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

割れ窓を放置した人の末路

この記事は、本番環境でやらかしちゃった人 Advent Calendar 2020の18日目の記事になります。

自己紹介

バックエンドのエンジニアとしてそろそろ8年が経ちます、星光輝と申します。
守備範囲はサーバーサイドですが、アプリ・インフラ・テスト・開発環境改善あたりの経験もある、広く浅い系エンジニアです。
実務で経験した言語は、Java/C#/Ruby/PHP/Python/TypeScript/swift/kotlin...と様々です。

昨年度もこちらで参加させて頂きました。
顧客のコンテンツデータを消失させた話

案件概要

APIサーバー+スマートフォンアプリの構築案件で、私は APIサーバーの実装を担当しています。
このプロジェクトでは、サーバーレスアプリケーションで作られることが決まっており、
chalice という Python フレームワークを使って実装をしています。

私の参画時点での経験値は、

  • Python: それほど実装経験なし(バッチのスクリプトで記載したことがある程度)
  • サーバーレス: 全くなし
  • AWS APIGateway: 初めて使う
  • DynamoDB: 見たことあるー

俗にいうド素人だったわけですが、当案件における私の役割はリードエンジニアでした。
チーム構成としては、インフラ、アプリ、サーバー、テスト、PMO のチームがあり、合計15名程度の中程度の案件です。

経緯

あの日あの時あの場所でパスを間違わなかったら...

chalice は、規則に沿ってプログラムを書けば、APIGateway, Lambda などを自動で作ってくれるという、
インフラ側を詳しく知らない人にはとてもありがたいフレームワークです。

プロジェクト初期の頃、検証のために chalice を使っていて、
Authorizer で認証しない場合、どんなメッセージが返ってくるんだろう...と思い、
下記のように、authorizer をつけた状態でリクエストしてみました。

from chalice import Chalice, CognitoUserPoolAuthorizer

app = Chalice(app_name='sample')
authorizer = CognitoUserPoolAuthorizer(
    'SamplePool',
    header='Authorization',
    provider_arns=['arn:aws:cognito-idp:ap-northeast-1....']
)

@app.route('/', authorizer=authorizer)
def index():
    return {'hello': 'world'}
$ chalice deploy
Creating deployment package.
Reusing existing deployment package.
Creating IAM role: sample-dev
Creating lambda function: sample-dev
Creating Rest API
Resources deployed:
  - Lambda ARN: arn:aws:lambda:ap-northeast-1:XXXXXXXXXXX:function:sample-dev
  - Rest API URL: https://q13wuyd6hi.execute-api.ap-northeast-1.amazonaws.com/api/
$ curl https://q13wuyd6hi.execute-api.ap-northeast-1.amazonaws.com/ -w '%{http_code}\n' -s
{"message":"Forbidden"}403

なるほど 403 なのか。(※注意: 正しくは https://q13wuyd6hi.execute-api.ap-northeast-1.amazonaws.com/api/)

そして、ログインユーザを認識するために、トークンからログインユーザを取る処理を書く。
Authorizer で認証情報が正しいのはわかるが、何かのエラーで取れないことはある。
そこで、デフォルト chalice の標準エラーが 403 だから...403にしよう。(※正しいパスを叩けば401)

import boto3
from chalice import Chalice, CognitoUserPoolAuthorizer, ForbiddenError

app = Chalice(app_name='sample')

authorizer = CognitoUserPoolAuthorizer(
    'SamplePool',
    header='Authorization',
    provider_arns=['arn:aws:cognito-idp:ap-northeast-1....']
)

@app.route('/', authorizer=authorizer)
def index():
    token = app.current_request.headers['Authorization']
    try:
        user = boto3.client('cognito-idp').get_user(AccessToken=token)
    except Exception as e:
        raise ForbiddenError('Forbidden')
    return {'hello': 'world'}

次第になくなるコミュニケーション

chalice はプログラムコードからインフラ環境を含めて作られるため、色んな学習コストを抑えることができます。
しかし、それは同時にプログラムコードが、インフラと密に連携したシステムと言えると思います。

つまり、基本的に、chalice に従うということが暗黙の了解になりました。
その過程で、APIGateway, Lambda は chalice で作られるからAPIサーバー側で。
Cognito, DynamoDB と言ったデータストアは、業務要件が関わるためAPIサーバー側で開発の過程で増やして構わない。
...と言った感じになり、アプリ側とインフラ側が互いに関わることが少なくなりました。

———— そして、先ほどのコードが生み出された後、5ヶ月後のお話。

そして時は動き出す...

デプロイを改良すべく、CloudFormation を使ったデプロイをすることになりました。
chalice では、chalice package コマンドで CloudFormation のファイルに変換することができます。

そして、一通り動くことを完了して AWS コンソールを確認していたときのこと。

....? なんだこれは

スクリーンショット 2020-12-18 4.25.23.png

作っていない、ステージ(Stage)ができている!
どういうことだろうと思うと、既存のバグらしい。

https://medium.com/veltra-engineering/avoid-aws-sam-stage-stage-45f7331b7b5d

解決策は、下記を OpenAPIバージョンを 3.0.2 にすること!簡単!
消えた!一応、処理確認。うん、何個か動かして動いているから問題なさそう!

(しばらくして)

「なんかアプリ使っていて、しばらくした後にアプリが全く動かなくなったんだけど??」

:scream: !!!!

原因

  • chalice が作る CloudFormation テンプレートファイルは Open API 2.0 相当の記法だった。
  • OpenAPI 2.0/ 3.0 で Authorizer 定義キーが異なる(securityDefinitions/securitySchemes)
  • Authorizer の定義が無視されてしまった
  • アプリ側は、トークンの有効期限切れをステータス401で検知している
  • Authorizer でトークン期限切れを検知できず、ユーザ取得部分でエラーが出るようになり 403 が常に返ってくるようになった

惨劇はなぜおこってしまったのか

割れ窓の放置

潜在的なバグを作っていたにも関わらず、検証せず放ったらかしにしたこと。
ここがバグっていなければ、このようなケースにはならなかったはず...。
言い訳をするならば、Authorizer を外さない限りは該当部分のテストはできず、Authorizer を外すことはなさそうだったので、正常系の確認のみで確認したつもりになって放置されたのだと思います。

思い込み

動作検証時に気づけなかったこと、検証できていなかったこと。
それを生んでしまったことは、下記のような思い込みが原因だと思います。

  • CloudFormation の定義が問題になるなら、エラーが出るはずと思っていた
  • 仮に Authorizer がなくても、その後のユーザ取得で同じようになるから影響ないと思っていた

確認内容の把握不足

確認すべき項目をインフラ側とコミュニケーションを取り、確認すべき項目を詰めていなかったことも
今回の問題点だと思っています。これができていれば、検証段階で気づけたかもしれません。

なお、自身では直接 API を実行して、挙動確認をしていましたが、
Authorizerの設定は下記を見れば確認ができました。これは後で知りました。。。

スクリーンショット 2020-12-18 4.25.10.png

二度と惨劇を起こさないためにどうしたのか

割れ窓を直す

ユーザの取得のエラーを正しい内容(401, Unauthorized) に変更。
念の為、Authorizer を外して動作確認して問題ないことを確認しました。

テスト仕様書の作成

今回の思い込みの部分というのは、他人から指摘されない限り変えることはできないはずです。
...なので、そのあたりを明文化して他の人に確認してもらうしかなさそうです。

今回の件を踏まえて、デプロイ実行に関する確認点をテスト仕様書として作成しました。
そして、動作確認時の確認項目に関してインフラ側にも確認して頂きました。

まとめ

今回は昔のバグを放置し続けて、別項目のバグと複合してサービスを止めてしまった話を記載しました。

CloudFormationによるデプロイ化は、自身でも経験のないタスクという認識をしており、
なるべく気をつけて作業をしていましたが、やはり、一人の力ではその精度には限界があるように思います。
また、関連しそうな人を巻き込みコミュニケーションを取れば、自身の負担も多少は軽減されていたように思います。

今回の件は、気づいた時点で即対応して、PMO側でも迅速に対応したため、顧客から厳重注意はありましたが、大ごとにはならずにすみました。
こんな風に助けられて成り立っているのだなぁということを改めて思い知らされました。
やってしまったことに関しては取り戻せませんが、それを上回るほどの貢献をして取り返そうと改めて思いました。

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

AWS AmplifyのWhat's Newに載らないアップデート情報の紹介

はじめに

本エントリは「AWS Amplify Advent Calendar 2020」の22日目です。
いやー今年もAmplifyはAdmin UIなど激アツでしたねw
カレンダー初日にも書きました通り、SSRが個人的には一番熱かったです!
でも本当にこんなものしかAmplifyって進化してないのでしょうか?
もちろん違います、では他にはどんな進化をしてきたのか、少し皆さんが知らないような機能まで紹介していこうと思います!
ちなみにこの記事が出た12/22時点でのAmplifyCLIの最新バージョンは4.40.0(2020/12/16リリース)です

アップデート①:AppSyncの@keyディレクティブで複数同時更新が可能になりました

これはamplify-cliの4.38.0(2020/12/8リリース)から利用可能になったオプションです。
今まではGSIなどの@keyディレクティブは一つずつamplify pushする必要があったのですが、一気に同時変更できるようになりました。ただしデフォルトではこの機能は有効になっていないので以下のような作業が必要です。
といっても難しく考える必要はなく、features.graphqltransformer.enableiterativegsiupdatestrueにしてあげるだけなのでお手軽ですね!

amplify/cli.jsonのデフォルト
% cat amplify/cli.json
{
  "features": {
    "graphqltransformer": {
      "addmissingownerfields": true,
      "validatetypenamereservedwords": true,
      "useexperimentalpipelinedtransformer": false,
      "enableiterativegsiupdates": false // ここをtrueにしてあげる
    },
    "frontend-ios": {
      "enablexcodeintegration": true
    },
    "auth": {
      "enablecaseinsensitivity": true
    },
    "codegen": {
      "useappsyncmodelgenplugin": true
    }
  }
}

つまり(こんなGSIに意味があるのかは置いておいて)、このような複数の@keyが一発で作成・更新が可能になります。

amplify/backend/api/apiname/schema.graphql
> cat amplify/backend/api/<apiname>/schema.graphql
type Todo @model
  @key(name: "todosByName", fields: ["name"], queryField: "todosByName")
  @key(name: "todosByStatus", fields: ["status"], queryField: "todosByStatus") {
  id: ID!
  name: String!
  status: String!
}

今まで本番環境をAmplifyで構築するのに段階リリースをしなければならなかった手順を簡素化できるなどのメリットがあるかなと思います!

アップデート②:upgrade、uninstallコマンドが追加されました

こちらは4.35.0(2020/11/24リリース)からの機能になります。
amplify upgradeamplify uninstallと2つのコマンドが実行できるようになりました。
npmでAplifyCLIを利用してる人には代わりのnpmコマンドを教えてくれるだけなのですが、MacやLinuxにシェルでインストールしている方やWindowsの方はこちらのコマンドで実行できるようになったようです。
私はnpmでインストールしてるので、あまり恩恵はないのですがアップデートコマンドを忘れてしまった場合などにぜひ利用してみてくださいw
npmでインストールしている人には実行すると以下のようなメッセージが出ます。

AmplifyCLI
% amplify uninstall
Initializing new Amplify CLI version...
Done initializing new version.
Scanning for plugins...
Plugin scan successful
"uninstall" is not available in this installation of Amplify.
Use npm uninstall -g @aws-amplify/cli instead.

% amplify upgrade
"upgrade" is not supported in this installation of Amplify.
Use npm i -g @aws-amplify/cli instead.

アップデート③:AppSyncの予約語制約をオフにする機能フラグが追加されました

こちらは4.32.1(2020/11/8リリース)からの機能になります。
アップデート①の対応と同様に機能フラグをオフにすることによって例えば「Subscription」などの予約語をモデル名に命名することが可能になりました。
AppSyncのスキーマ更新後のamplify push時に [:reservedWord] is a reserved type name and currently in use within the default schema element. というエラーが出た際に思い出してください!
features.graphqltransformer.validatetypenamereservedwordsfalseにするだけです!

amplify/cli.jsonのデフォルト
% cat amplify/cli.json
{
  "features": {
    "graphqltransformer": {
      "addmissingownerfields": true,
      "validatetypenamereservedwords": true, // ここをfalseにしてあげる
      "useexperimentalpipelinedtransformer": false,
      "enableiterativegsiupdates": false
    },
    "frontend-ios": {
      "enablexcodeintegration": true
    },
    "auth": {
      "enablecaseinsensitivity": true
    },
    "codegen": {
      "useappsyncmodelgenplugin": true
    }
  }
}

一行で紹介するアップデート

  • 4.32.0(2020/10/31リリース)でLambdaリゾルバの中からCognitoユーザープールのグループリストを参照できるようにIAMの変更が入りました
  • 4.32.0(2020/10/31リリース)でAppSyncの@searchableディレクティブからクエリを投げる際にfromパラメータのサポートが追加されました。これによりたくさんのデータを表示する際にページ遷移などを画面で表現する際にnextTokenを今まで通り使うことも出来ますし、fromパラメータでオフセットすることも選択出来るようになりました。

まとめ

機能追加系の説明しやすいアップデートをチョイスしてお届けしていますが、他にもユーザーフレンドリーにするための細やかな文言変更や、amplify deleteコマンド実行時にいきなり削除せずに確認を挟むようになった、など細やかなアップデートもたくさんあった一年でした!
主に10月-12月のアップデートを中心に紹介していますが実際の今年のアップデート数はこんなものじゃないですので、興味がある方はGitHubのコミット履歴なども見て下さいね!

ではまた!

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

Serverless FrameworkでLambda関数をデプロイ&VPC内に入れる方法

Serverless Frameworkとは

image.png

Serverlessはサーバーレスなアプリケーションを簡単に開発、デプロイするためのNode.js製のツールです。
AWS、Azure、GCP等のクラウドサービスによらず利用することができ、ランタイムの言語もクラウドサービス側で許されているものであれば利用することができます。

下記サイトから引用。
https://www.wantedly.com/companies/forstartups/post_articles/279817

インストールからデプロイまで

ServerlessFrameworkのインストール

$ npm install -g serverless

Node.jsがインストールしていない場合はインストールが必要。

インストールしたサーバーレスのバージョンを確認するには、次のコマンドを実行します。

$ serverless --version

AWSのセットアップ

IAMユーザーとアクセスキーを作成する
1. AWSアカウントにログインし、Identity&Access Management(IAM)ページに移動します。

  1. [ユーザー]、[ユーザーの追加]の順にクリックします。最初のフィールドに名前を入力して、このユーザーがサーバーレスフレームワークに関連していることを思い出させますserverless-admin。チェックボックスをクリックして、プログラムによるアクセスを有効にします。[次へ]をクリックして、[権限]ページに移動します。[既存のポリシーを直接添付]をクリックします。AdministratorAccessを検索して選択し、[次へ:レビュー]をクリックします。すべてが良好に見えることを確認し、[ユーザーの作成]をクリックします。

  2. API Key&Secretを表示して一時的な場所にコピーします。

AWSアクセスキーの使用

AWS API Key&Secretを使用するようにServerlessFrameworkを設定するには次の2つの方法があります。

クイックセットアップ
開始するための簡単なセットアップとして、それらを環境変数としてエクスポートして、サーバーレスとシェル内のAWSSDKにアクセスできるようにすることができます。

export AWS_ACCESS_KEY_ID=<your-key-here>
export AWS_SECRET_ACCESS_KEY=<your-secret-key-here>
# AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are now available for serverless to use
serverless deploy

# 'export' command is valid only for unix shells. In Windows - use 'set' instead of 'export'

注意: 自己署名証明書を使用している場合は、次のいずれかを実行する必要があります。

# String example:
# if using the 'ca' variable, your certificate contents should replace the newline character with '\n'
export ca="-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"
# or multiple, comma separated
export ca="-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----,-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----"

# File example:
# if using the 'cafile' variable, your certificate contents should not contain '\n'
export cafile="/path/to/cafile.pem"
# or multiple, comma separated
export cafile="/path/to/cafile1.pem,/path/to/cafile2.pem"

# 'export' command is valid only for unix shells. In Windows - use 'set' instead of 'export'

1.サービスを作成します

serverlessコマンドは slsと略すことができるため以降slsで行っています。
下記コマンドでサービスが作成できます。

$ sls create --template aws-python --path myServerlessService

createコマンドを使用して、使用可能なテンプレートの1つを指定できます。この例では、--templateまたは短縮-tフラグを指定してaws-pythonを使用します。--pathまたは速記は、-pテンプレートのサービスファイルを作成する場所です。ディレクトリをこの新しいフォルダに変更します。

2.デプロイ

$ sls deploy

これにより、の設定に基づいて関数がAWSLambdaにデプロイされます。

デプロイ先のstageはdevになっており、リージョンはデフォルトでes-east-1バージニア北部に指定されているので、regionキーの値をap-northeast-1にすることで東京に指定することができる。Lambda関数をVPCの中に入れたい場合は

serverless.yml
# 使用するクラウドサービス(AWS)と言語(Pytho3.6)を指定
provider:
  name: aws
  runtime: python3.6

  # ステージを指定。開発と本番環境を切り分けることができます。
  stage: dev
  # デプロイするリージョンを指定
  region: ap-northeast-1
  # roleの設定
 iamRoleStatements:
    - Effect: "Allow"
      Action:
        - "s3:*"
      Resource:
        - "*"
  #VPCの中に入れる場合
 vpc:
    securityGroupIds:
      - sg-xxxxx
    subnetIds:
      - subnet-xxxxx

3.デプロイされた関数を呼び出す

$ sls invoke -f hello

呼び出しは、コマンドと機能を展開invokeし、--functionまたは速記-f。
ターミナルウィンドウに、AWSLambdaからの応答が表示されます。

{
    "statusCode": 200,
    "body": "{\"message\":\"Go Serverless v1.0! Your function executed successfully!\",\"input\":{}}"
}

Hello World関数をデプロイして実行できました。

実際のLambdaコンソール

詰まったこと

チュートリアルのサイトに沿って進めて、lgwan側のAWSにセットアップしてデプロイしたはずですが関数を呼び出してレスポンスも正常に返ってきているのに実際にLambdaコンソールを見ても作成した関数がありませんでした。
リージョンも合っているはずなのに無し。神隠し?
結果的には他のIAMユーザーの環境にデプロイされてました。

原因

元々~/.aws/credentialsにAWSプロファイルが設定されており、そこが他のIAMユーザーのアクセスキーが設定してあったためそちらを参照されていた。

~/.aws/credentials
[default]
aws_access_key_id=***************
aws_secret_access_key=***************

解決法

~/.aws/credentialsに新たにAWSプロファイルを設定して下記コマンドでプロジェクトごと(/ API)を切り替えることができるため切り替えた後、再度デプロイしたことで指定のAWS Lambdaコンソールにデプロイされました!

~/.aws/credentials
[default]
aws_access_key_id=***************
aws_secret_access_key=***************

[profileName1]
aws_access_key_id=***************
aws_secret_access_key=***************
$ export AWS_PROFILE="profileName1"

これで、ターミナルでsls deployなどを実行できるようにサーバーレスCLIオプションが設定されます。

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

【AWS】“Aws::S3::Errors::AccessDenied in 〇〇sController#create”エラー(AWSのS3に保存できない)

エラー内容

AWSのS3を使い、画像データを保存できるように実装していたが、
1. ローカル環境においてエラー
2. 本番環境においてエラー
3. AWSのS3上のバケットにはデータなし(保存されていない)
という事象が発生。

ローカル環境↓
20201218error(ローカル環境).png

本番環境↓
デプロイエラー.png

エラーが起こったときのターミナルのログ↓
20201218error(ターミナル).png

エラーが起こったときの設定の状況

  • macOS Catalina バージョン10.15.7
  • Rails Rails 6.0.3.4
  • Herokuを使ってデプロイ
  • AWSのアカウント・IAMユーザー・S3のバケットの3つを新規作成したばかり

解決した方法(結論)

「AWSのアカウント削除→作成し直し」を行ったところ、エラー解決。
(保存できるようになった)

解決方法の補足
  • AWSアカウント作成時、Googleのシステム障害が発生していた。
  • メンターさんにも見てもらいタイポや記述もれが無いのにも関わらず、保存ができなかった。
  • アカウントの新規作成から1日以上、保存できない状況が続いた。(時間をおいても反映されない)
  • AWSアカウントを削除して、新規登録し直す際、前回登録していたメールアドレスが90日間以内であれば再開できる対象になるからか、使えない状況になる。(前回のメールアドレス以外を選択しないといけない)
  • こちらの方法を試すも、今回は上手くいかなかった。

AWSアカウント作成し直す前の確認作業

 (1)AWS上のバケットポリシーは適切か?

結果:①②あたる部分は、ともに適切だった。

{
   "Version": "2012-10-17",
   "Id": "Policy1544152951996",
   "Statement": [
       {
           "Sid": "Stmt1544152948221",
           "Effect": "Allow",
           "Principal": {
               "AWS": "①今回のIAMユーザーのARNをここに記述"
           },
           "Action": "s3:*",
           "Resource": "arn:aws:s3:::②今回のバケット名をここに記述"
       }
   ]
}

 (2)ブロックパブリックアクセスのバケット設定は適切か?

結果:適切だった。

20201218バケットポリシーの確認.png

 (3)VSコード上の、保存先は適切か?

結果:適切だった。

:amazonになっているのでOK。(開発環境)

config/environments/development.rb
  # Store uploaded files on the amazon file system (see config/storage.yml for options).
  config.active_storage.service = :amazon

:amazonになっているのでOK。(本番環境)

config/environments/production.rb
  # Store uploaded files on the amazon file system (see config/storage.yml for options).
  config.active_storage.service = :amazon

”今回のバケット名”にあたる部分について、合っていたためOK。

config/storage.yml
amazon:
  service: S3
  region: ap-northeast-1
  bucket: 今回のバケット名
  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>

 (4)環境変数は適切か?

結果:適切だった。

環境変数の確認方法(MacOSがCatalina以降の場合)
terminal
vim ~/.zshrc
  • :wqで閉じる。
  • もし誤っていた場合にはiを押して編集し、escを押してから、:wqで閉じる。
コンソール確認
  • rails cで立ち上げ。
  • storage.ymlに記載の変数名を入力。 20201218コンソール確認.png

確認した内容は以上。

AWSアカウント(ルートユーザー)を作り直し

上記の通り、確認したが誤りはない様子。
思い当たる仮説としては、「Googleの調子が悪い頃、ちょうどアカウント登録していた」だったため、アカウントを作り直すことにした。

  1. 請求がゼロか確認
  2. バケットの削除
  3. IAMユーザーの削除(今回はルートユーザーに紐づけて作成したため、無し)
  4. AWSアカウント(ルートユーザー)の削除

AWSを作り直したあとは、
- 環境変数の設定し直し
- バケットポリシーの設定し直し
が必要となる。

感想

  • 今回のケースは、因果関係が定かではないが、「システム障害が起きている前後は、アカウントの作成をしないほうがよい」ことを学んだ。
  • 今回はアカウントを新規作成したばかりで、作り直しても手間はかかるが他に消えると困る設定をしていなかった他の登録がなかったためラッキーだった。
  • 2回作成したので、AWS作成の復習になった。

以上です。
同じように困った人の解決になれば幸いです。
(間違いあった時は、教えてください!)
 

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

【AWS】“Aws::S3::Errors::AccessDenied in 〇〇sController#create”エラー(AWSのS3に保存されない)

エラー内容

AWSのS3を使い、画像データを保存できるように実装していたが、
1. ローカル環境においてエラー
2. 本番環境においてエラー
3. AWSのS3上のバケットにはデータなし(保存されていない)
という事象が発生。

ローカル環境↓
20201218error(ローカル環境).png

本番環境↓
デプロイエラー.png

エラーが起こったときのターミナルのログ↓
20201218error(ターミナル).png

エラーが起こったときの設定の状況

  • macOS Catalina バージョン10.15.7
  • Rails Rails 6.0.3.4
  • Herokuを使ってデプロイ
  • AWSのアカウント・IAMユーザー・S3のバケットの3つを新規作成したばかり

解決した方法(結論)

「AWSのアカウント削除→作成し直し」を行ったところ、エラー解決。
(保存できるようになった)

解決方法の補足
  • AWSアカウント作成時、Googleのシステム障害が発生していた。
  • メンターさんにも見てもらいタイポや記述もれが無いのにも関わらず、保存ができなかった。
  • アカウントの新規作成から1日以上、保存できない状況が続いた。(時間をおいても反映されない)
  • AWSアカウントを削除して、新規登録し直す際、前回登録していたメールアドレスが90日間以内であれば再開できる対象になるからか、使えない状況になる。(前回のメールアドレス以外を選択しないといけない)
  • こちらの方法を試すも、今回は上手くいかなかった。

AWSアカウント作成し直す前の確認作業

 (1)AWS上のバケットポリシーは適切か?

結果:①②あたる部分は、ともに適切だった。

{
   "Version": "2012-10-17",
   "Id": "Policy1544152951996",
   "Statement": [
       {
           "Sid": "Stmt1544152948221",
           "Effect": "Allow",
           "Principal": {
               "AWS": "①今回のIAMユーザーのARNをここに記述"
           },
           "Action": "s3:*",
           "Resource": "arn:aws:s3:::②今回のバケット名をここに記述"
       }
   ]
}

 (2)ブロックパブリックアクセスのバケット設定は適切か?

結果:適切だった。

20201218バケットポリシーの確認.png

 (3)VSコード上の、保存先は適切か?

結果:適切だった。

:amazonになっているのでOK。(開発環境)

config/environments/development.rb
  # Store uploaded files on the amazon file system (see config/storage.yml for options).
  config.active_storage.service = :amazon

:amazonになっているのでOK。(本番環境)

config/environments/production.rb
  # Store uploaded files on the amazon file system (see config/storage.yml for options).
  config.active_storage.service = :amazon

”今回のバケット名”にあたる部分について、合っていたためOK。

config/storage.yml
amazon:
  service: S3
  region: ap-northeast-1
  bucket: 今回のバケット名
  access_key_id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
  secret_access_key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>

 (4)環境変数は適切か?

結果:適切だった。

環境変数の確認方法(MacOSがCatalina以降の場合)
terminal
vim ~/.zshrc
  • :wqで閉じる。
  • もし誤っていた場合にはiを押して編集し、escを押してから、:wqで閉じる。
コンソール確認
  • rails cで立ち上げ。
  • storage.ymlに記載の変数名を入力。 20201218コンソール確認.png

確認した内容は以上。

AWSアカウント(ルートユーザー)を作り直し

上記の通り、確認したが誤りはない様子。
思い当たる仮説としては、「Googleの調子が悪い頃、ちょうどアカウント登録していた」だったため、アカウントを作り直すことにした。

  1. 請求がゼロか確認
  2. バケットの削除
  3. IAMユーザーの削除(今回はルートユーザーに紐づけて作成したため、無し)
  4. AWSアカウント(ルートユーザー)の削除 (補足)リンク先は、1と同じ。


AWSを作り直したあとは、

  • 環境変数の設定し直し
  • バケットポリシーの設定し直し が必要となる。

感想

  • 今回のケースは、因果関係が定かではないが、「システム障害が起きている前後は、アカウントの作成をしないほうがよい」ことを学んだ。
  • 今回はアカウントを新規作成したばかりで、作り直しの手間はかかるが、他に消えると困る設定をしていなかったのでラッキーだった。
  • 2回作成したので、AWS作成の復習になった。

以上です。
同じように困った人の解決になれば幸いです。
(間違いあった時は、教えてください!)
 

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

Amazon Managed Service for Prometheus 触ってみた

皆さん AWS re:Invent は楽しんでおりますか!
12 月は 12/18 まで開催中ですので、まだの人は見てみましょう!

https://reinvent.awsevents.com/

自分は今年の夏に EKS へ移行するのに合わせて、Kubernetes 上に Airflow, Grafana, Prometheus を苦しみながら導入したのですが、全部 Managed Service としてあっけなく発表されてしまいました (Airflow は re:Invent の時期ではなく、予選落ち組)。
ただ、これらのサービスを AWS Managed のサービスとしてではなく構築・運用したことで、それぞれのサービスについての概念・知見を習得することができたので後悔はしておりません。

さて EKS 上で管理している Prometheus についてですが、

  • メモリを食いまくって落ちる
  • インターネット経由して一定のセキュリティを担保した上でメトリクスを格納 & クエリしたい

といった困り・要望があり、ちょうどなんとかしないとなぁと思っていたところでした。
今回発表された Prometheus のマネージドサービスである Amazon Managed Service for Prometheus ですべて解決しそうだったのでどんなものかと触ってみたので共有します。

話は脱線しますが、EKS 移行劇については下記 note にまとめてありますので、ご興味ある方はどうぞ!

https://note.com/tyrwzl/n/n14703cfca236

Amazon Managed Service for Prometheus について

Prometheus について詳しくは説明しません、他の記事をあたってください。

Amazon Managed Service for Prometheus は Prometheus 互換のメトリクスデータを格納したり、格納したデータをクエリしたりできるサービスみたいです。
Prometheus Exporter などを利用して、Amazon Managed Service for Prometheus にメトリクスを格納し、Grafana から Amazon Managed Service for Prometheus に対してメトリクスをクエリしてグラフとして確認する、といったように利用できます。
Prometheus のマネージドサービスといいましたが、「Prometheus で収集したメトリクスデータの格納庫」がイメージとしては近いのでしょうか。
そもそものスタンスとして Prometheus はメトリクスを永続保存しません。
永続保存したい場合、Thanos や Cortex といった別のサービスを使ってメトリクスデータを保管しておく必要がありました (弊社では Thanos を採用、Amazon Managed Service for Prometheus は Cortex を利用しているみたい)。
したがって、Amazon Managed Service for Prometheus を利用しても、コンテナからメトリクスをスクレイプしたりする Exporter などは自前でデプロイする必要がありますし、格納したデータをグラフなどで確認するには Grafana なども自前で用意する必要があります。
Grafana に関しては Amazon Managed Service for Grafana がありますね!(絶賛プレビュー申し込み中)。

誰が使うべき?

さて、Amazon Managed Service for Grafana のターゲットユーザー、使って得する人は誰でしょう?
そこは流石 AWS、きちんと FAQ に記載されていました。

Q: Why should I use Amazon Managed Service for Prometheus?
You should use AMP if you have adopted an open source-based monitoring strategy, have already deployed or plan to adopt Prometheus for container monitoring, and prefer a fully managed experience where AWS provides enhanced security, scalability, and availability.

Q: How does Amazon Managed Service for Prometheus relate to Amazon CloudWatch? Which one should I use?
You should use Amazon CloudWatch if you are looking for a comprehensive observability service that brings together logs, metrics, tracing, dashboarding, and alerting in a unified experience that encompasses AWS services, EC2, containers, and serverless.
You should use AMP if you are running containers and want a service that is fully compatible with the Prometheus open source project. You should also choose AMP if you are already running Prometheus and are looking to eliminate that ongoing operational cost while also improving security.

(https://aws.amazon.com/jp/grafana/faqs/ から抜粋)

既に Prometheus を利用してモニタリングシステムを構築していて、運用コストに嫌気が指している人をターゲットにしていそうです。
Amazon Managed Service for Prometheus を利用することで、収集したメトリクスデータの管理などについて気にかける必要がなくなります。

主要な機能

下記ドキュメントから読み取れたことを記載します。

https://docs.aws.amazon.com/prometheus/index.html

セキュリティ

Amazon Managed Service for Prometheus ではメトリクスデータの格納と取得を実施するのに AWS IAM の認証情報が格納された AWS Signature Version 4 の署名が必要です。
したがって、AWS IAM によるセキュリティ機能の上に Prometheus を利用することができます。
更にインターネット経由でメトリクスデータのやり取りをしたくないユースケースのために、VPC Endpoint (Interface 型) を利用することができます。

可用性

メトリクスデータについてはドキュメントを確認する限り、Multi-AZ での保管をしており、3 AZ への展開をしています
データはまず EBS に保管し、その後 S3 に保存しているとのことです。

データ保管期間

現時点では 150 日の保管期間のみしか選択できません。

Prometheus の機能

現時点では下記機能は提供されていません。

  • Prometheus alert manager
  • Prometheus alerting rules and recording rules

リージョン

現時点では下記リージョンでしか利用できません

  • Europe (Ireland)
  • Europe (Frankfurt)
  • US East (N. Virginia)
  • US East (Ohio)
  • US West (Oregon)

東京リージョンにも期待してます!

Amazon Managed Service for Prometheus 触ってみた with EKS

下記ブログを参照したので、ブログを読んだ人は下記を読まないでください。
ただ、リージョンをまたいで使えるか検証したかったので、そこが気になる場合や日本語で読みたい場合は読んでください。

https://aws.amazon.com/jp/blogs/mt/getting-started-amazon-managed-service-for-prometheus/

下記のようにリージョンをまたいでいます。

リソース リージョン
Amazon Managed Service for Prometheus us-east-1
EKS ap-northeast-1

なお、下記では AWS CLI や eksctl、kubectl, helm を利用するのでそれらが利用可能、かつ、それぞれの認証情報が揃っている shell 環境が必要です、

Amazon Managed Service for Prometheus の作成

image.png

AWS マネジメントコンソールにログインして、Amazon Managed Service for Prometheus のページ (https://console.aws.amazon.com/prometheus/home) に行きましょう。
Amazon Managed Service for Prometheus はプレビュー状態ですが、プレビュー申請は不要です。
上記画像のようにサクッと workspace を作成します。

image.png

すると数秒後に Status が ACTIVE となってリソースが作成されます。
リソースが作成されると 2 種類の HTTP Endpoint (Endpoint - remote write URL, Endpoint - query URL) が発行され、それぞれメトリクスデータを格納するものと格納したメトリクスデータをクエリするものとなります。
なお、これらの URL はパブリックにアクセス可能な模様です。

$ dig aps-workspaces.us-east-1.amazonaws.com +short
3.223.166.82
100.24.159.220
52.70.212.28
54.210.4.8
35.169.187.85
54.243.211.98

IAM ロール、プロバイダーの作成

次に Amazon Managed Service for Prometheus へメトリクスデータを格納、格納したメトリクスデータを取得するのに利用する IAM リソースを作成します。
ブログにて用意されている下記 bash スクリプトを実行します。
書き換えなければならないのは 1 行目の YOUR_EKS_CLUSTER_NAME と、今回 EKS が ap-northeast-1 にあるので最後の行の eksctl に --region パラメータを渡しているところです。

上記スクリプトを実行すると下記のようになります
エラー文が出力されているように見えますが、問題ありません

✦ ❯ bash init.sh 
Creating a new trust policy

An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name EKS-AMP-ServiceAccount-Role cannot be found.
Appending to the existing trust policy

An error occurred (NoSuchEntity) when calling the GetPolicy operation: Policy arn:aws:iam::XXXXXXXXXX:policy/AWSManagedPrometheusWriteAccessPolicy was not found.
Creating a new permission policy AWSManagedPrometheusWriteAccessPolicy
{
    "Policy": {
        "PolicyName": "AWSManagedPrometheusWriteAccessPolicy",
        "PolicyId": "ANPARVSLQ63UWTO7GOCOY",
        "Arn": "arn:aws:iam::XXXXXXXXXX:policy/AWSManagedPrometheusWriteAccessPolicy",
        "Path": "/",
        "DefaultVersionId": "v1",
        "AttachmentCount": 0,
        "PermissionsBoundaryUsageCount": 0,
        "IsAttachable": true,
        "CreateDate": "2020-12-17T12:50:08+00:00",
        "UpdateDate": "2020-12-17T12:50:08+00:00"
    }
}

An error occurred (NoSuchEntity) when calling the GetRole operation: The role with name EKS-AMP-ServiceAccount-Role cannot be found.
EKS-AMP-ServiceAccount-Role role does not exist. Creating a new role with a trust and permission policy
arn:aws:iam::XXXXXXXXXX:role/EKS-AMP-ServiceAccount-Role
[ℹ]  eksctl version 0.30.0
[ℹ]  using region ap-northeast-1
[ℹ]  will create IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1"
[✔]  created IAM Open ID Connect provider for cluster "eks-cluster" in "ap-northeast-1"

IAM のページに行くと、下記のように IAM ロールが作成されていることが確認できます。
image.png

Prometheusのインストール

Amazon Managed Service for Prometheus を触るために、なんか適当なデータを投入しなければなりません。
ということで、Prometheus を EKS にインストールします。
Helm を使ってコマンドを実行するだけです。

$ helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
"prometheus-community" has been added to your repositories

$ kubectl create ns prometheus
namespace/prometheus created

$ helm install prometheus-for-amp prometheus-community/prometheus -n prometheus
NAME: prometheus-for-amp
LAST DEPLOYED: Thu Dec 17 12:56:30 2020
NAMESPACE: prometheus
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-for-amp-server.prometheus.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9090


The Prometheus alertmanager can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-for-amp-alertmanager.prometheus.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=alertmanager" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9093
#################################################################################
######   WARNING: Pod Security Policy has been moved to a global property.  #####
######            use .Values.podSecurityPolicy.enabled with pod-based      #####
######            annotations                                               #####
######            (e.g. .Values.nodeExporter.podSecurityPolicy.annotations) #####
#################################################################################


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prometheus-for-amp-pushgateway.prometheus.svc.cluster.local


Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

下記のように prometheus ネームスペースに Prometheus 関連の POD が作成されていることがわかります。

$ k get po -n prometheus
NAME                                                    READY   STATUS    RESTARTS   AGE
prometheus-for-amp-alertmanager-5cb9f4478c-km4ht        2/2     Running   0          6m26s
prometheus-for-amp-kube-state-metrics-bc9cb958f-l7p7k   1/1     Running   0          6m26s
prometheus-for-amp-node-exporter-69qsw                  1/1     Running   0          6m27s
prometheus-for-amp-node-exporter-bg4ss                  1/1     Running   0          6m26s
prometheus-for-amp-node-exporter-fs74x                  1/1     Running   0          6m26s
prometheus-for-amp-pushgateway-56ff9d9d99-4z2sf         1/1     Running   0          6m26s
prometheus-for-amp-server-7f6d6fcf59-kpl5m              2/2     Running   0          6m26s

AWS signing proxy のデプロイ

先述したとおり、Amazon Managed Service for Prometheus にメトリクスデータを投入・取得するには AWS Signature Version 4 の署名が必要です。
先の Helm テンプレートは Amazon Managed Service for Prometheus のための Prometheus ではなく、AWS Signature Version 4 の署名を含める仕組みが用意されていません。
これを実現してくれるのが AWS signing proxy というもので、Prometheus のサイドカーコンテナとしてデプロイします。

ブログのに記載のある YAML をコピー & 自分の環境に合わせて上書きしてもいいのですが、Amazon Managed Service for Prometheus のマネジメントコンソールにも同じものが用意されています。

image.png

マネジメントコンソールの YAML の場合は annotationseks.amazonaws.com/role-arn の値を「IAM ロール、プロバイダーの作成」で作成した IAM ロール (arn:aws:iam:::role/EKS-AMP-ServiceAccount-Role)の ARN に書き換えるだけで OK です。

YAML が用意できたら Helm テンプレートの Value として渡して再度 Prometheus をデプロイします。

$ helm upgrade --install prometheus-for-amp prometheus-community/prometheus -n prometheus -f ./amp_ingest_override_values.yaml

Release "prometheus-for-amp" has been upgraded. Happy Helming!
NAME: prometheus-for-amp
LAST DEPLOYED: Thu Dec 17 13:03:23 2020
NAMESPACE: prometheus
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
The Prometheus server can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-for-amp-server.prometheus.svc.cluster.local


Get the Prometheus server URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=server" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9090


The Prometheus alertmanager can be accessed via port 80 on the following DNS name from within your cluster:
prometheus-for-amp-alertmanager.prometheus.svc.cluster.local


Get the Alertmanager URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=alertmanager" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9093
#################################################################################
######   WARNING: Pod Security Policy has been moved to a global property.  #####
######            use .Values.podSecurityPolicy.enabled with pod-based      #####
######            annotations                                               #####
######            (e.g. .Values.nodeExporter.podSecurityPolicy.annotations) #####
#################################################################################


The Prometheus PushGateway can be accessed via port 9091 on the following DNS name from within your cluster:
prometheus-for-amp-pushgateway.prometheus.svc.cluster.local


Get the PushGateway URL by running these commands in the same shell:
  export POD_NAME=$(kubectl get pods --namespace prometheus -l "app=prometheus,component=pushgateway" -o jsonpath="{.items[0].metadata.name}")
  kubectl --namespace prometheus port-forward $POD_NAME 9091

For more information on running Prometheus, visit:
https://prometheus.io/

デプロイされたものを確認すると、Prometheus Server のみ再デプロイされています。

$ k get po -n prometheus
NAME                                                    READY   STATUS              RESTARTS   AGE
prometheus-for-amp-alertmanager-5cb9f4478c-km4ht        2/2     Running             0          7m10s
prometheus-for-amp-kube-state-metrics-bc9cb958f-l7p7k   1/1     Running             0          7m10s
prometheus-for-amp-node-exporter-69qsw                  1/1     Running             0          7m11s
prometheus-for-amp-node-exporter-bg4ss                  1/1     Running             0          7m10s
prometheus-for-amp-node-exporter-fs74x                  1/1     Running             0          7m10s
prometheus-for-amp-pushgateway-56ff9d9d99-4z2sf         1/1     Running             0          7m10s
prometheus-for-amp-server-0                             0/3     ContainerCreating   0          17s

詳細を確認してみるとサイドカーコンテナがついていることがわかります。

$ k describe po prometheus-for-amp-server-0  -n prometheus
Name:         prometheus-for-amp-server-0

...

Containers:
  prometheus-server-configmap-reload:
    Container ID:  docker://51f6c62b778acb9b6c895b7dc6f14efe54f9747ed07936206be487fe4227a2ef
    Image:         jimmidyson/configmap-reload:v0.4.0

...

  prometheus-server:
    Container ID:  docker://35840c35fcad0428b5568f469cd2e28209cda1eab52dc135bee1a1d8693546d7
    Image:         quay.io/prometheus/prometheus:v2.22.1

...

  aws-sigv4-proxy-sidecar:
    Container ID:  docker://6897c52c5114bee6886e254c8c526c80ce2f94fcd807ae202f4b5491bbbc6bdb
    Image:         public.ecr.aws/aws-observability/aws-sigv4-proxy:1.0

...


Events:
  Type    Reason                  Age   From                                                    Message
  ----    ------                  ----  ----                                                    -------
  Normal  Scheduled               50s   default-scheduler                                       Successfully assigned prometheus/prometheus-for-amp-server-0 to ip-10-0-44-92.ap-northeast-1.compute.internal
  Normal  SuccessfulAttachVolume  47s   attachdetach-controller                                 AttachVolume.Attach succeeded for volume "pvc-c1a0ad74-fe75-4646-86d8-61c51e320f87"
  Normal  Pulling                 40s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Pulling image "jimmidyson/configmap-reload:v0.4.0"
  Normal  Pulled                  35s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Successfully pulled image "jimmidyson/configmap-reload:v0.4.0"
  Normal  Created                 35s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Created container prometheus-server-configmap-reload
  Normal  Started                 35s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Started container prometheus-server-configmap-reload
  Normal  Pulling                 35s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Pulling image "quay.io/prometheus/prometheus:v2.22.1"
  Normal  Pulled                  24s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Successfully pulled image "quay.io/prometheus/prometheus:v2.22.1"
  Normal  Created                 23s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Created container prometheus-server
  Normal  Started                 23s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Started container prometheus-server
  Normal  Pulling                 23s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Pulling image "public.ecr.aws/aws-observability/aws-sigv4-proxy:1.0"
  Normal  Pulled                  19s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Successfully pulled image "public.ecr.aws/aws-observability/aws-sigv4-proxy:1.0"
  Normal  Created                 19s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Created container aws-sigv4-proxy-sidecar
  Normal  Started                 19s   kubelet, ip-10-0-44-92.ap-northeast-1.compute.internal  Started container aws-sigv4-proxy-sidecar

Grafana のデプロイ

さて、Amazon Managed Service for Prometheus にメトリクスデータを投入できたか確認してみましょう。
Amazon Managed Service for Grafana のプレビュー申請が通ってないので、Grafana をデプロイします。

$helm repo add grafana https://grafana.github.io/helm-charts
"grafana" has been added to your repositories

$kubectl create ns grafana
namespace/grafana created

$helm install grafana-for-amp grafana/grafana -n grafana
NAME: grafana-for-amp
LAST DEPLOYED: Thu Dec 17 13:08:08 2020
NAMESPACE: grafana
STATUS: deployed
REVISION: 1
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace grafana grafana-for-amp -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana-for-amp.grafana.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:

     export POD_NAME=$(kubectl get pods --namespace grafana -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana-for-amp" -o jsonpath="{.items[0].metadata.name}")
     kubectl --namespace grafana port-forward $POD_NAME 3000

3. Login with the password from step 1 and the username: admin
#################################################################################
######   WARNING: Persistence is disabled!!! You will lose your data when   #####
######            the Grafana pod is terminated.                            #####
#################################################################################

Prometheus の場合と同様に AWS signing proxy をデプロイします (必要な YAML は Amazon Managed Service for Prometheusのマネジメントコンソールにはないのでブログを参照してください)。

$ helm upgrade --install grafana-for-amp grafana/grafana -n grafana -f ./amp_query_override_values.yaml
Release "grafana-for-amp" has been upgraded. Happy Helming!
NAME: grafana-for-amp
LAST DEPLOYED: Thu Dec 17 13:08:47 2020
NAMESPACE: grafana
STATUS: deployed
REVISION: 2
NOTES:
1. Get your 'admin' user password by running:

   kubectl get secret --namespace grafana grafana-for-amp -o jsonpath="{.data.admin-password}" | base64 --decode ; echo

2. The Grafana server can be accessed via port 80 on the following DNS name from within your cluster:

   grafana-for-amp.grafana.svc.cluster.local

   Get the Grafana URL to visit by running these commands in the same shell:

     export POD_NAME=$(kubectl get pods --namespace grafana -l "app.kubernetes.io/name=grafana,app.kubernetes.io/instance=grafana-for-amp" -o jsonpath="{.items[0].metadata.name}")
     kubectl --namespace grafana port-forward $POD_NAME 3000

3. Login with the password from step 1 and the username: admin
#################################################################################
######   WARNING: Persistence is disabled!!! You will lose your data when   #####
######            the Grafana pod is terminated.                            #####
#################################################################################

デプロイが完了したら、まず Grafana POD の名前を確認してから kubectl port-forward を利用してローカルのブラウザで開けるようにします。
(自分は Cloud9 上で実行したので 8080 番にフォワードしています。)

$ k get po -n grafana
NAME                               READY   STATUS    RESTARTS   AGE
grafana-for-amp-79d5454dbc-ghhp8   1/1     Running   0          3h42m
$ kubectl port-forward -n grafana pods/grafana-for-amp-79d5454dbc-ghhp8 8080:3000

image.png

うまくいくと上記のようにログイン画面が出ます。
パスワードは下記コマンドで確認します

$ kubectl get secrets grafana-for-amp -n grafana -o jsonpath='{.data.admin-password}'|base64 --decode

image.png

ログインできたら Grafana に Prometheus の設定をします。
Grafana のページ左側、歯車マークをクリックして "Data Sources" をクリックして、"Prometheus" を選択します。
すると上記のような画面が出るので、

  • HTTP > URL: Amazon Managed Service for Prometheus のマネジメントコンソールの Endpoint - query URL
  • Auth > SigV4 auth: ON
  • SigV4 Auth Details > Authentication Provider: AWS SDK Default
  • SigV4 Auth Details > Default Region: us-east-1

image.png

設定終わったら上記画像のように Grafana のページの下の方に "Save & Test" というボタンがあるのでクリックして、"Data source is working" と出れば設定完了です。

image.png

実際にメトリクスデータをクエリしてみます。
Grafana のページ左側、コンパスマークをクリックします。
"Metrics" の右脇にクエリしたいメトリクス名を入力して右上のボタンをクリックするとグラフが表示されます。

今後試したいこと

今回はサラッと触っただけなので、メトリクスの数を増やして自前の Prometheus 環境とクエリ速度を比べたり、料金コストの比較をしてみたいと思います。

また、文頭の方で自前の Prometheus に対して「インターネット経由して一定のセキュリティを担保した上でメトリクスを格納 & クエリしたい」という要望を出していたのですが、これはカナリアリリースのために Spinnaker のカナリア分析ツール Kayenta で Prometheus のメトリクスを利用したいために出たものとなります。
なので、今後 kayenta で素直に Amazon Managed Service for Prometheus が利用できるか、Istio を利用しているので Istio Proxy と AWS signing proxy が共存できるのかなどを検証していきたいと思います。

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

Amazon DLMとは?

勉強前イメージ

EBSのスナップショットをスケジュールに組み込むことが出来る・・・?
EC2の中にあるのか、そもそも別サービスかは知らない

調査

Amazon DLMとは?

Amazon Data Lifecycle Manager の略で
EBSのスナップショットのスケジューリングや世代管理をすることが出来る
HDD自体のバックアップを取るようなイメージ

そもそもEBSってなに?

Amazon Elastic Block Store の略で
EC2にアタッチするブロックレbwるのストレージサービス
仮想ディスクのようなもの。

Amazon DLMを実際触ってみよう!

ボリュームにタグ付けを行う

対象となるEC2のボリュームにタグ付を行います。

  • インスタンスから辿る

対象のEC2インスタンスにチェックを入れ、 ストレージ タブに移ります。
ストレージタブの下にブロックデバイスとしてアタッチされているボリュームが表示されています。
今回はこちらのボリュームを対象としていきます。
ボリュームIDをクリックして、ボリュームに移ります。

1EC2無題 - ペイント 2020-12-15 21.30.17.png

  • ボリュームににタグ付を行う

どのボリュームかわかりやすいようにタグ付を行います。
タグのタブへ移ります

2無題 - ペイント 2020-12-15 21.34.11.png

ここで、 タグの追加/編集 を行います。

3無題 - ペイント 2020-12-15 21.35.19.png

今回は以下の文字列でタグとします。

キー : DLM_tag
値 : test_storage

記入を行ったら、保存をします。

4ボリューム _ EC2 Management Console - Google Chrome 202.png

先程保存したキーが表示されていることを確認します

5無題 - ペイント 2020-12-15 21.34.11.png - ペイント 2020-12-.png

ライフサイクルポリシーを作成

  • ライフサイクルポリシーの作成

EC2 > Elastic Block Store へ移動して、ライフサイクルポリシーを作成します

1ライフサイクルマネージャー _ EC2 Management Console - Google Ch.png

  • ライフサイクルポリシーを設定

今回は以下の選択肢で作成

ポリシータイプ  : EBSスナップショットポリシー
リソースタイプ  : ボリューム (インスタンスごとでも出来るらしい)
説明           :  ライフサイクルポリシーの説明を記載
これらのタグを持つターゲット  : 先程↑で設定したタグを記載

1ライフサイクルポリシーを作成 _ EC2 Management Console - Google C.png

IAMロールはデフォルトで作成します。

2ライフサイクルポリシーを作成 _ EC2 Management Console - Google C.png

ポリシースケジュールに関しては、頻度や時間を設定します。

スケジュール名 : スケジュールの名前を設定します
頻度 : どれくらいの頻度で設定するかを決めます
毎 : ここは、↑の頻度によって変わるのですが、毎日に設定したので何時間ごとに取るか決めます
開始時間 : UTCの時間で何時から開始するかを決めます
保持タイプ : 保持タイプとしてカウント方式と期間方式があります
保持する : 今回は7回分保持します

3ライフサイクルポリシーを作成 _ EC2 Management Console - Google C.png

追加のタグに名前を入れておくことで、出来たスナップショットに名前(Nameの値)が追加されます。

00イフサイクルポリシーを作成 _ EC2 Management Console - Google C.png

概要が記載されていますので確認します。
作成してすぐに有効にするのか、無効のままにするのか選んで作成を行います。
記載していない部分はデフォルトで設定しております。

4ライフサイクルポリシーを作成 _ EC2 Management Console - Google C.png

  • 新しいポリシーが追加されました

22無題 - ペイント 2020-12-15 21.34.11.png - ペイント 2020-12-.png

作成されたスナップショットの確認

ちゃんと作られていました!

スナップショット - EC2 Management Console 2020-12-16 22-58-09.png

勉強後イメージ

結構思ったよりいろいろ選択項目があった。
でもバックアップとしてはめっちゃ楽だなー

参考

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

AWS Lambda で作る無償の脅威インテリジェンスリレーモジュール

はじめに

この記事はシスコの同志による Cisco Systems Japan Advent Calendar 201818 日目として投稿しました。今年はカレンダーが2つあります!!

2020年版(1枚目): https://qiita.com/advent-calendar/2020/cisco
2020年版(2枚目): https://qiita.com/advent-calendar/2020/cisco2

2017年版: https://qiita.com/advent-calendar/2017/cisco
2018年版: https://qiita.com/advent-calendar/2018/cisco
2019年版: https://qiita.com/advent-calendar/2019/cisco
2020年版: https://qiita.com/advent-calendar/2020/cisco

このエントリで取り上げるテーマは、「AWS Lambda で作る無償の脅威インテリジェンスリレーモジュール」です。Cisco SecureXという、こちらも無償のCiscoのプラットフォームを利用し、数多くの利用可能な脅威インテリジェンスプラットフォームを組み合わせて、AWS Lambda 上に情報中継用のアプリケーションをデプロイします。以下のステップでまとめます。

  • 無償で使えるセキュリティプラットフォーム:Cisco SecureX
  • リレーモジュールの導入ステップ
  • AWS Lambda 環境セットアップ
  • AWS Lambda にリレーモジュールデプロイ
  • Cisco SecureX へのリレーモジュール組み込み

無償で使えるセキュリティプラットフォーム:Cisco SecureX

Ciscoのセキュリティ製品を利用している誰もが、すぐに、簡単に、サインナップして使える無償のプラットフォームとして、Cisco SecureX が今年の7月1日にリリースされました!Cisco SecureXは日々のセキュリティオペレーションについて、運用の簡素化、可視化の改善、運用の効率化を目指して開発された、「クラウドから提供されるセキュリティ製品のAPIアグリゲータ」です。
01.png
フリートライアルも含む、例えば Umbrella、AMP、Firepower、Stealthwatch など、どれかを使っていれば Cisco SecureX アカウントによりこれらの製品と連携でき、専用の統合化されたダッシュボードを構築することができます。さらに Cisco SecureX Orchestrator と、ビルドインも含む SecureX 上で新しく設計・設定できるオーケストレーションワークフローを使うことによって、インシデント発生時に行う作業の自動化、インシデントに関わる Observable (怪しいドメイン、IPアドレス、ファイルハッシュなどの観測情報)の調査を劇的に簡素化することが可能になります。

Cisco SecureX はシスコ製品だけでなく、シスコ製品以外のメーカーのセキュリティ製品や、データセンターインフラ、ネットワーク・インフラ、クラウドセキュリティサービスに対してオーケストレーションワークフローを組み込むことが可能です。

Cisco SecureX (http://security.cisco.com)


のリンクから、ログイン/サインナップできます!

無償の脅威インテリジェンスと SecureX リレーモジュール

Cisco SecureX で連携可能な脅威インテリジェンス提供サービス、連携可能な他ベンダー製品は以下より参照可能です。

Cisco SecureX threat response: Integrations and Partners

VirusTotal は非常に有名で良く使われる方も多いのではないでしょうか?他にも Shodan.ioAbuseIPDBGoogle Safe BrowsingMicrosoft Graph Security なども有名です。Cisco SecureX では、このような各インテリジェンス提供サービスと連携させるための「リレーモジュール」が用意されています。

Cisco Security APIs

これにより、各インテリジェンスソースをアプリケーションとして展開された AWS Lambda 関数および、 API Gateway により Cisco SecureX に対応した STIX 2.0 ベースの CTIM (Cisco Threat Intel Model) に変換し、SecureX Threat Response 機能に情報をエンリッチできます。
02.png

リレーモジュールの導入ステップ

今回はサンプルとして Shodan.io のリレーモジュールを AWS Lambda 上に展開します。基本的にはその他の AWS 上でリレーモジュールを利用するインテリジェンス連携も、作業ステップは同じです。

SecureX Shodan Relay
https://github.com/CiscoSecurity/tr-05-serverless-shodan#shodan-relay-api

  • デプロイ用ローカル環境の整備 (python/pip/zappaのインストールと環境設定)(AWS Lambdaを展開する準備)
  • AWS Lambda (IAM) 環境セットアップ
  • AWS Lambda にリレーモジュールデプロイ
  • Cisco SecureX へのリレーモジュール組み込み

以下、導入用の環境を使いました。

CentoOS 6.10

デプロイ用ローカル環境の整備 (python/pip/zappaのインストールと環境設定)(AWS Lambdaを展開する準備)

python 3.7 インストール

アプリケーション導入に必要な要求事項として、 python は Version 3.7 以上である必要があります。
https://github.com/CiscoSecurity/tr-05-serverless-shodan#shodan-relay-api

The application has been implemented and tested using Python 3.7. You may try to use any higher versions if you wish as they should be backward-compatible.

python が旧バージョンだったため、 バージョン3.7 をインストールします。

pyhon3.7
# yum install gcc openssl-devel bzip2-devel libffi-devel zlib-devel
# cd /usr/src
# wget https://www.python.org/ftp/python/3.7.9/Python-3.7.9.tgz

# tar xzf Python-3.7.9.tgz
# cd Python-3.7.9
# ./configure --enable-optimizations
# make altinstall

シンボリックリンクの貼り直し等の調整を行いました

シンボリックリンク調整
# ln -s /usr/local/bin/python3.7 /usr/bin/python3
# ln -s /usr/local/bin/pip3.7 /usr/bin/pip3

Zappaイントール

AWS の PaaS である AWS Lambda + API Gateway として Cisco SecureX リレーモジュールをデプロイします。イベントドリブンであるこの Python ベースのアプリケーションは Zaapa を使って導入ができます。Cisco SecureX リレーモジュールは Python アプリのためのサーバレス Web アプリケーションとなります。もちろんすでに AWS Lambda を利用している方はこのステップは必要ありません。ここでは Zappa をインストールします。

Zappa
# pip install --upgrade pip
# pip install flask
# pip install zappa

# zappa init
# zappa deploy

awscli インストール

Zappa で導入する AWS Lambda IAM ユーザ環境をセットアップするため、awscliをインストールします。

awscli
# pip install awscli

デプロイ用ローカル環境のリレーモジュールの導入準備

リレーモジュールを導入用の環境へコピー

導入用環境 (CentOS) の任意のディレクトリに、リレーモジュールソースコードをコピーします。

git
# git clone https://github.com/CiscoSecurity/tr-05-serverless-shodan

リレーモジュール導入用環境ファイルの編集

zappa_settings.json
# cp zappa_settings.json zappa_settings.json.bak
# vi zappa_settings.json

"aws_region" は導入するべき正しい AWS リージョンに変更、"s3_bucket" は AWS Accoun ID 等任意の情報を追加するのが望ましいです。S3 バケットの情報は AWS アカウントに対してユニークである必要があります。同一 AWS アカウント内で展開する AWS Lambda 関数の名前が重複することはできません。

zappa_settings.json
{
    "dev": {
        "app_function": "app.app",
        "aws_region": "ap-northeast-1",
        "exclude": [".*", "*.json", "*.md", "*.txt"],
        "keep_warm": false,
        "log_level": "INFO",
        "manage_roles": false,
        "profile_name": "serverless",
        "project_name": "tr-shodan-relay",
        "role_name": "tr-serverless-relay-ZappaLambdaExecutionRole",
        "runtime": "python3.7",
        "s3_bucket": "zappa-tr-shodan-relay-xxxxxxxx"
    }
}

リレーモジュール導入用 IAM ユーザ Deployment ポリシーファイルの編集

以降のステップで適用する、AWS IAM ユーザの Deployment Policy のテンプレートファイルを編集します。以下のように の項目を "< >" を削除した、前のステップで設定をした AWS IAM ユーザを利用する AWS Account ID に書き換えます。

ZappaLambdaDeploymentPolicy.json
# cd aws
# vi ZappaLambdaDeploymentPolicy.json
ZappaLambdaDeploymentPolicy.json(編集前)
:
            "Resource": [
                "arn:aws:iam::<ACCOUNT_ID>:role/*ZappaLambdaExecutionRole"
            ]
:
ZappaLambdaDeploymentPolicy.json(編集後)
:
            "Resource": [
                "arn:aws:iam::9xxxxxxxxxx2:role/*ZappaLambdaExecutionRole"
            ]
:

AWS Lambda 環境セットアップ

AWS IAM ユーザ作成

以下に従って、AWS IAM ユーザの導入ポリシーとロールのセットアップを行います。2つ目以降のリレーモジュールを展開する場合も含め、この作業は一回のみで終了します。2つ目以降の新規モジュール導入では、このステップを省略できます。
https://github.com/CiscoSecurity/tr-05-serverless-shodan/blob/develop/aws/HOWTO.md

  • AWS コンソールにログイン
  • コンソールホームから ”IAM” を検索
  • 左のPaneの ”アクセス管理” から ”ユーザー” タブを選択
  • ”ユーザーを追加”(青ボタン)をクリック
    • ユーザ名:serverless
    • アクセスの種類
    • プログラムによるアクセスチェック
    • ”次のステップ:アクセス権限” をクリック(次へ)
  • ”ユーザーを追加” ”次のステップ:タグ” をクリック(何も選択せず次へ)
  • ”ユーザーを追加” "タグの追加オプション" : ”次のステップ:確認” をクリック(何も選択せず次へ)
  • ”ユーザーを追加”
    • "このユーザにはアクセス権限がありません" と警告が出ますが、問題ありません。
    • ”ユーザの作成” をクリック(次へ)
  • ”ユーザーを追加”

    • IAMユーザ作成が成功したステータスが確認できます
    • ".csvのダウンロード" を選択しローカルファイルに保存 03.png
  • 保存した .csv ファイルを確認し、作成したserverlessユーザの以下情報を保存します

    • アクセスキー (AWS Access Key ID)
    • シークレットキー (AWS Secret Access Key)

Deployment ポリシー (ZappaLambdaDeploymentPolicy) の作成とIAMユーザへのアタッチ

  • IAM コンソール
    • アクセス管理 ポリシー 選択
  • ポリシー作成 選択
  • JSON タグ選択
    • 既存の設定を削除し、前のステップで編集した ZappaLambdaDeploymentPolicy.json の内容をコピー&ペーストします。 04.png
  • ポリシーの確認 選択
  • ポリシー名を "ZappaLambdaDeploymentPolicy" に指定
  • IAM コンソール アクセス管理 ユーザ 選択
  • serverless 選択
  • アクセス権限の追加 選択
  • 既存のポリシーを直接アタッチ 選択
  • "ZappaLambdaDeploymentPolicy" 選択
  • アクセス権限の追加 選択

IAMロール : tr-serverless-relay-ZappaLambdaExecutionRole 作成

  • IAM コンソール
    • アクセス管理 ポリシー 選択
  • ポリシーの作成 選択
  • JSON タグ選択
    • ローカルにクローンした aws/ZappaLambdaExecutionPolicy.json をコピー&ペースト
ZappaLambdaExecutionPolicy.json
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:*"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "lambda:InvokeFunction"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
  • ポリシーの選択 選択
  • ポリシーの作成 選択
  • IAM コンソール
    • アクセス管理 ロール 選択
  • ロールの作成 選択
  • AWS サービス Lambda 選択
  • 次のステップ:アクセス権限 選択
  • ZappaLambdaExecutionPolicy を選択
    • 検索ボックスに "Zappa" を入力、表示された
    • ZappaLambdaExecutionPolicy を選択し、チェックボックスにチェック
  • 次のステップ:タグ 次のステップ:確認
  • ロール名を tr-serverless-relay-ZappaLambdaExecutionRole としてロール作成
  • ロールから tr-serverless-relay-ZappaLambdaExecutionRole 選択
  • 信頼関係 タブ選択
  • 信頼関係の編集 選択
    • JSONファイルの編集
    • "apigateway.amazonaws.com", を追加
(編集前)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
(編集後)
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "apigateway.amazonaws.com",
          "lambda.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
  • 信頼ポリシーの更新

AWS Lambda にリレーモジュールデプロイ

リレーモジュール導入用ローカル環境のセットアップ (CentOS)

これでリレーモジュールを AWS Lambda に導入する準備が整いました。このステップにてリレーモジュール本体を導入できます。この作業のために Linux root 権限は必要としません。念の為、これまで行った各ステップを再度入念に確認しましょう。

Zappa コマンドにて AWS Lambda にアプリケーションを展開するための AWS IAM アカウント ID 環境を awscli を利用して設定します。

このステップは awscli を利用せず、以下のように指定ディレクトリに環境ファイルをマニュアルで作成することでも対応可能です。初期ステップにて作成した IAM ユーザのユーザ情報ファイル (.csv) から AWS アクセスキー、AWS シークレットアクセスキーを確認し aws コマンドで指定ます。

awscli
% aws configure
AWS Access Key ID [None]: XXXXXXXXXXXXXXXXXXXX
AWS Secret Access Key [None]: XXXXXXXXXXAi5VxZyEwK+G+E/+qTjuXXXXXXXXXX
Default region name [None]: ap-northeast-1
Default output format [None]: json

この結果、~/.aws ディレクトリ内に credentials ファイルと config ファイルが作成されます。以下はそのフォーマットです。

credential
~/.aws/redentials
 [default]
 aws_access_key_id=XXXXXXXXXXXXXXXXXXXX
 aws_secret_access_key=XXXXXXXXXXAi5VxZyEwK+G+E/+qTjuXXXXXXXXXX
config
~/.aws/config
 [profile serverless]
 region=ap-northeast-1
 output=json

リレーモジュールを導入

再度 python 3.7 以上であることを確認します。

python
% python3 --version

python 仮想環境をセットアップします。このコマンドを実行したディレクトリ配下に仮想環境用のテンポラリディレクトリが作成されます。このコマンドの実行は展開用リレーモジュールユーザのユーザディレクトリや、クローンしたモジュールのソースコード配下のディレクトリでも構いません。

python
% python3 -m venv venv

仮想環境をアクティベートします

python
% source venv/bin/activate

仮想環境にてpipをアップグレードします

pip
% pip install --upgrade pip

本体アプリケーションに必要なライブラリをインストールします。必要とするライブラリは導入するリレーモジュールによって異なります。このコマンドはソースコードをクローンしたディレクトリ内の requirements.txt を正しく指定する必要があります。

pip
% pip install --upgrade --requirement requirements.txt

いよいよリレーモジュールを導入します。これまでのステップを入念に再度確認します。以下のコマンドは、クローンしたソースコードのディレクトリ上で行う必要があります。

zappa
% zappa deploy dev

Calling deploy for stage dev..
Downloading and installing dependencies..
 - markupsafe==1.1.1: Downloading
100%|██████████████████████████████████████| 27.5k/27.5k [00:00<00:00, 12.9MB/s]
 - cryptography==3.3.1: Downloading
100%|██████████████████████████████████████| 2.66M/2.66M [00:00<00:00, 11.5MB/s]
 - cffi==1.14.4: Using locally cached manylinux wheel
Packaging project as zip.
Uploading tr-shodan-relay-dev-1607574935.zip (10.6MiB)..
100%|██████████████████████████████████████| 11.1M/11.1M [00:01<00:00, 9.00MB/s]
Uploading tr-shodan-relay-dev-template-1607574945.json (1.6KiB)..
100%|██████████████████████████████████████| 1.66k/1.66k [00:00<00:00, 19.2kB/s]
Waiting for stack tr-shodan-relay-dev to create (this can take a bit)..
100%|████████████████████████████████████████████| 4/4 [00:15<00:00,  3.90s/res]
Deploying API Gateway..
Deployment complete!: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/dev

エラー無くデプロイが完了すると、最後のzappaコマンドのステータスにAWA Lambda API GatewayのURLが出力されますので、これを保存します。

上記 Zappa でのアプリケーションデプロイは、以下リンクの動画のような流れで行われます。
Zappa - Serverless Python

デプロイエラーが出る場合は、これまでの導入ステップを再度確認し、トラブルシューティングを行います。以下リンクの情報を是非確認ください。

リレーモジュールのステータス確認

AWS Lambda に導入されたリレーモジュールのステータスを確認します。

  • AWS コンソールにログイン
  • コンソールホームから ”Lambda” を検索

デプロイに成功したリレーモジュール "tr-shodan-relay-dev" のLambda関数が確認できます。

05.png

Cisco SecureX へのリレーモジュール組み込み

Cisco SecureX へのリレーモジュール組み込み

最終フェーズです。Shodan.io のリレーモジュールは Cisco SecureX Integration ページから API Gateway のURL 指定のみで統合が完了します。
06.png

2020/12/12 に Cisco SecureX はVersion 1.6.2 にバージョンアップされ、連携できる製品が大幅に増えました。Shodan.io と同様の連携ステップでその他のリレーモジュールを展開してみます。

動作確認

Cisco SecureX Ribbon から Threat Response にて調査用のIOCを投入します。JP CERT、Cisco Talos Blog 等、ランサムウェアなどの任意のIOC情報をいくつか投入して確認します。

07.png

統合に成功したリレーモジュールからのエンリッチメントが確認できます。

おまけ

この機会に現時点で手軽に導入できるだけのリレーモジュールを展開してみました。
08.png
合計10以上のアプリケーションを AWS Lambda 上に組み込み、毎日リクエストして使いながら時間が経ちました。AWS Lambda の利用は、EC2で稼働させる定常的にCPUリソースを消費するインスタンスと異なり、実行時(リクエスト時)のみのリソース消費で課金がされます。

AWS Lambda 料金

100 万件リクエストに対して 0.20USD の課金となるとのこと、10個以上のモジュールをデプロイした私の今月の AWS への請求額は 4円程度となりました。非常に格安ですね!

参照

[0] Cisco SecureX
http://security.cisco.com
[1] SecureX Shodan Relay
https://github.com/CiscoSecurity/tr-05-serverless-shodan#shodan-relay-api
[2] Zappa
https://github.com/Miserlou/Zappa
[3] Cisco Security API
https://github.com/CiscoSecurity
[4] Cisco Threat Intel Model (CTIM)
https://github.com/threatgrid/ctim
[5] Cisco SecureX threat response: Integrations and Partners
https://www.cisco.com/c/en/us/products/security/threat-response/partners-integrations.html#~technology-partner-ecosystem
[6] AWS HOWTO
https://github.com/CiscoSecurity/tr-05-serverless-shodan/blob/develop/aws/HOWTO.md
[7] untitled (required permission for deploying AWS Lambda)
https://github.com/Miserlou/Zappa/blob/master/example/policy/deploy.json
[8] Zappa throws AccessDenied exception while attempting to upload zip to S3 while AWS CLI can upload files with same profile
https://github.com/Miserlou/Zappa/issues/1972
[9] JSON Web Token (JWT)
https://en.wikipedia.org/wiki/JSON_Web_Token
[10] AWS Lambda 料金
https://aws.amazon.com/jp/lambda/pricing/

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