20190627のAWSに関する記事は15件です。

EC2の削除保護を無視してterminatedなる状況を発生させてサービスダウンさせてしまった

ECS on EC2での話です。

結論

  • ECSのスケールインはマルチAZを優先にスケールインさせる
  • ECSのスケールインは条件によってはEC2の削除保護を無視してスケールインされる
  • インスタンスタイプ選択時は立てれるアベイラビリティゾーンは確認しよう

再現方法

簡略化させて発生させる再現手順を記載します。

  1. アベイラビリティゾーンに偏りがある状態でECSのEC2インスタンスを管理する
  2. 片側のアベイラビリティゾーンだけを対象にEC2の削除保護を設定する
  3. ECS(CloudformationもしくはAuto Scalingで)の最大台数を減らす
  4. 削除保護されたEC2がterminatedになる

手順はわかったところで、スクショを交えて手順の説明の実施と再現させます。

ECSインスタンスの状況

まずは前提となる状況を作り出します。

ECSのアベイラビリティゾーンの設定

ECSのアベイラビリティゾーンを2つとします。今回はcとd。

image url.jpg

ECSインスタンス達

アベイラビリティゾーンを2ゾーンしか設定していなければ、奇数台数のインスタンスを立てれば偏りが出ます。
このインスタンス達の 削除保護 の状態はこんな感じに片側だけ保護するようにする。

image url (1).jpg

image url (2).jpg

スケールインの実施

ここはCloudformationを変更してもいいし、Auto Scalingグループの変更でもどっちでもいいですが、最大台数を落とします。
今回は3台→2台への変更とします。

image url (3).jpg

すると、あら不思議!削除保護したインスタンスがterminatedになる。

image url (4).jpg

実業務で発生した状況

グダグダ書きましたが、私自身が実際に起きた状況は以下です。

前提条件

  1. 該当のECSの アベイラビリティゾーンはaおよびc しか設定されていない

状況

  1. EC2インスタンスタイプを変更するために最大台数(2→4)とEC2インスタンスタイプを R5a に変更する
  2. R5aは現状アベイラビリティゾーンaとdしか対応していない ため今回はaに偏って立つ
    →この時点で状況を理解していたが、 気にせず次のオペレーションを実施した が最大のミス
  3. 作成された新インスタンスの削除保護を実行
  4. 旧インスタンスに乗っているコンテナのdrainingを開始
  5. 旧インスタンスに乗っているコンテナがないことを確認後、Cloudformationで最大台数を戻す(4→2)
  6. 旧インスタンスの1台はdrainingにも関わらず、アベイラビリティゾーンcであるがゆえに残されて、新インスタンス1台がterminatedになる
  7. 晴れてterminatedとなった新インスタンスで動いていたコンテナ達は無情にも落とされて、サービスダウンとなる?

正直ここからサービス復旧には30分もかかってないけど(長いと見るか短いと見るかは人それぞれだが、私に長い)、まぁ落ちたのは凹んだ。
最悪なのはECSのCloudformationのアベイラビリティゾーンの設定にdを足しても復旧しなかった。理由はアベイラビリティゾーンdに立ったインスンタンスでコンテナが立ち上がってもALBからのヘルスチェックが通らなかったため。
なぜならECSだけでなく、ALBのアベイラビリティゾーンもdの設定がなかったためです。
急遽ALBにもアベイラビリティゾーンdを足してコンテナを立ち上げたけど、時すでに遅し。

最後に

ECSのすべての動作を理解できてなかった検証不足と今後このような悲劇を生み出さないようにと自分への戒めとしてこの記事を書きました。

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

Amazon Linux2にyumでMeCabが入らなかった

はじめに

https://github.com/neologd/mecab-ipadic-neologd
の手順に従って、 Amazon Linux2にMeCabをインストールしようとしたところ、以下のエラーが発生しました。

実行したコマンド
$ sudo rpm -ivh http://packages.groonga.org/centos/groonga-release-1.1.0-1.noarch.rpm
$ sudo yum install mecab mecab-devel mecab-ipadic git make curl xz patch
エラーメッセージ
http://packages.groonga.org/centos/2/x86_64/repodata/repomd.xml: [Errno 14] HTTP Error 404 - Not Found

解決策

エラーメッセージ中のURLにAmazon Linuxのバージョン「2」が入っているっぽいのでCentOSの最新バージョンである「7」に変えてみます。

具体的には、/etc/yum.repos.d/groonga.repoの以下の部分を

/etc/yum.repos.d/groonga.repo
baseurl=http://packages.groonga.org/centos/$releasever/$basearch/

以下のように変えました。

/etc/yum.repos.d/groonga.repo(更新後)
baseurl=http://packages.groonga.org/centos/7/$basearch/

その後以下をもう一度実行するとエラーは出ず

$ sudo yum install mecab mecab-devel mecab-ipadic git make curl xz patch

MeCabも実行できました。

$ mecab
アデリーペンギンかわいい
アデリーペンギン        名詞,一般,*,*,*,*,*
かわいい        形容詞,自立,*,*,形容詞・イ段,基本形,かわいい,カワイイ,カワイイ
EOS

おわりに

これが正しい方法なのかはわかりません。
ソースからビルドするのが無難だと思います。

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

Amazon Linux2とLet's EncryptでSSL対応サーバを0から爆速構築

どんな話?

以前Amazon Linux2でLet's Encrypt使おうとしたらコケた話という記事で
certbotを書き換えて認証なんてことをしちゃてたわけなのですが、これをする必要はもうありません。

EPEL7を有効化してcertbotと一緒にpython2-certbot-apache もしくは python2-certbot-nginxをインストール
あとはcertbotコマンドを実行するだけです。
すっごい楽ですね!
実際、新規環境で試した際は5分もかかりませんでした。

また、python-certbot-○○は他のディストリビューションでも利用可能ですので参考にしていただけると思います。

ということでご紹介します。

実際にやってみる

Amazon linux2インスタンスの立ち上げとドメイン割当までは完了しているものとします。
また、Apacheでの設定方法についてはAWSのドキュメントに記載がありますので、今回はnginxにします。

nginxのインストール

今回はサクッとextrasレポジトリからインストールします。

コマンドを実行してnginxのバージョンを確認
今回は1.12.1。

$ amazon-linux-extras list
0  ansible2                 available    [ =2.4.2  =2.4.6 ]
2  httpd_modules            available    [ =1.0 ]
3  memcached1.5             available    [ =1.5.1  =1.5.16 ]
4  nginx1.12                available    [ =1.12.2 ]
5  postgresql9.6            available    [ =9.6.6  =9.6.8 ]

nginx1.12をインストール。

$ sudo amazon-linux-extras install nginx1.12

nginxを起動して終了。

$ sudo systemctl enable nginx
$ sudo systemctl start nginx

EPEL7の有効化

EPEL7をダウンロードして、

$ sudo wget -r --no-parent -A 'epel-release-*.rpm' http://dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/

リポジトリパッケージをインストール。

$ sudo rpm -Uvh dl.fedoraproject.org/pub/epel/7/x86_64/Packages/e/epel-release-*.rpm

最後にEPEL7を有効化。

$ sudo yum-config-manager --enable epel*

Let's Encryptの証明書を取得と設定

以下のコマンドで証明書取得を開始。

$ sudo certbot --nginx

アドレスを聞かれるので入力。
このアドレスに重要な連絡が来るようになります。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Plugins selected: Authenticator nginx, Installer nginx
Enter email address (used for urgent renewal and security notices) (Enter 'c' to
cancel): example@example.com

ライセンスへの同意を求められるので従うことができる場合はaを入力。
当然ですが同意しないと使えません。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Please read the Terms of Service at
https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf. You must
agree in order to register with the ACME server at
https://acme-v02.api.letsencrypt.org/directory
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(A)gree/(C)ancel: a

キャンペーンメール等を送るためEFFとアドレスを共有してもいいか聞かれる。
同意できればy、同意できなければn。
これはどちらでもオッケーです。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Would you be willing to share your email address with the Electronic Frontier
Foundation, a founding partner of the Let's Encrypt project and the non-profit
organization that develops Certbot? We'd like to send you email about our work
encrypting the web, EFF news, campaigns, and ways to support digital freedom.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
(Y)es/(N)o: n

nginxをインストールしたてのまっさらな状態だとドメイン名は設定されていないのでここで聞かれる。
利用するドメイン名を入力。

No names were found in your configuration files. Please enter in your domain
name(s) (comma and/or space separated)  (Enter 'c' to cancel): example.com

ドメイン認証、鍵取得、nginxへ鍵設定までが実施されるので待機。
設定はnginx.confに書き込まれる。

Obtaining a new certificate
Performing the following challenges:
http-01 challenge for example.com
Waiting for verification...
Cleaning up challenges
Deploying Certificate to VirtualHost /etc/nginx/nginx.conf

HTTPアクセスをHTTPSにリダイレクト設定をするか聞かれる。
設定しない場合は1、設定しない場合2を入力。
今回は2を選択してみましたが、ドメイン直下のリダイレクトがうまくいかなかったので
個人的には1を選んで自分で設定する方法がおすすめです。

Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
1: No redirect - Make no further changes to the webserver configuration.
2: Redirect - Make all requests redirect to secure HTTPS access. Choose this for
new sites, or if you're confident your site works on HTTPS. You can undo this
change by editing your web server's configuration.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Select the appropriate number [1-2] then [enter] (press 'c' to cancel): 2
Redirecting all traffic on port 80 to ssl in /etc/nginx/nginx.conf

以下のように成功メッセージが出れば完了。

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Congratulations! You have successfully enabled https://example.com

You should test your configuration at:
https://www.ssllabs.com/ssltest/analyze.html?d=example.com
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

IMPORTANT NOTES:
 - Congratulations! Your certificate and chain have been saved at:
   /etc/letsencrypt/live/example.com/fullchain.pem
   Your key file has been saved at:
   /etc/letsencrypt/live/example.com/privkey.pem
   Your cert will expire on 2019-09-25. To obtain a new or tweaked
   version of this certificate in the future, simply run certbot again
   with the "certonly" option. To non-interactively renew *all* of
   your certificates, run "certbot renew"
 - Your account credentials have been saved in your Certbot
   configuration directory at /etc/letsencrypt. You should make a
   secure backup of this folder now. This configuration directory will
   also contain certificates and private keys obtained by Certbot so
   making regular backups of this folder is ideal.
 - If you like Certbot, please consider supporting our work by:

   Donating to ISRG / Let's Encrypt:   https://letsencrypt.org/donate
   Donating to EFF:                    https://eff.org/donate-le

自動更新設定

メッセージにもあるようにcertbot renewコマンドで証明書更新ができます。
AWSのドキュメントでは--no-self-upgradeをつけるようになっているので安定性を考えるとそちらがベストでしょう。

これをCronにでも突っこんでおけば何もしなくても更新されるのでやっておきましょう。

crontabコマンドを実行して

$ sudo crontab -u root -e

以下のように書き加えればOKです。この例では毎日0:00と12:00にroot権限で実行されます。
(AWSでは1:39と13:39でした。golangの"2009-11-10 23:00:00 UTC"のようなおもしろい理由があるんでしょうか…?)

0 0,12 * * * root certbot renew --no-self-upgrade

最後に設定されているか確認すれば完了です。

$ sudo crontab -u root -l
0 0,12 * * * root certbot renew --no-self-upgrade

最後に

簡単、爆速ですね。
extpectコマンドも併せて使ってやれば、ほとんどすることなくなっちゃうんじゃないでしょうか?
これが無料でできるなんていい時代だなー!

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

CircleCIでAWS CLIを利用する時に環境変数(or context)が効かない

概要

CircleCIで環境変数やContextsに値を設定しても、AWS CLIが以下のようなエラーで落ちることがあります。

#!/bin/bash -eo pipefail
aws configure set aws_access_key_id \
$AWS_ACCESS_KEY_ID \
--profile default
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:

  aws help
  aws <command> help
  aws <command> <subcommand> help
aws: error: the following arguments are required: value
Exited with code 2

原因

  • GitHubのフォークしたリポジトリからPRを出している
    • そしてCircleCIのAdvanced SettingsPass secrets to builds from forked pull requestsOffになっている

対策

上記の設定をOnにすれば解決するが、脆弱性の懸念が大きいためおすすめできません。(詳しくは設定画面の説明を参照)

Pass secrets to builds from forked pull requests
Run builds for fork pull request changes with this project's configuration, environment variables, and secrets.

There are serious security concerns with this setting (see the documentation for details.) If you have SSH keys, sensitive env vars or AWS credentials stored in your project settings and untrusted forks can make pull requests against your repo, then this option isn't for you!

各自でビルドを行わないのであればフォークしない運用とするのが良いと思われます。

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

EC2にgit、docker、docker-compose、pip、pythonコマンドをインストールする方法

EC2を立ち上げた際にやることを忘れがち&チームに共有としてメモしておきます。

準備まではQiitaの「(下準備編)世界一丁寧なAWS解説。EC2を利用して、RailsアプリをAWSにあげるまで」という記事がわかりやすかった。

コマンドのインストール

git

$ sudo yum install git

gitの連携方法は以下

# gotconfigを作成、編集
$ vi .gitconfig

[user]
  name = your_name
  email = hoge@hoge.com

# githubに公開鍵を登録するために公開鍵作成
$ chmod 700 ~/.ssh
$ cd ~/.ssh
$ ssh-keygen -t rsa

あとは、Github/Gitlabに公開鍵を登録してcloneするだけ。

docker

# yum の更新
$ sudo yum update -y

# yum から docker をインストール
$ sudo yum install -y docker

# docker サービスの起動
$ sudo service docker start

# ec2-user を docker グループに追加する
$ sudo usermod -a -G docker ec2-user

# ログインしなおして以下を実行しインストールされていることを確認
$ docker info

docekrコマンドをsudo無しで実行する場合は以下を実行

# dockerグループがなければ作る
$ sudo groupadd docker

# 現行ユーザをdockerグループに所属させる
$ sudo gpasswd -a $USER docker

# dockerデーモンを再起動する (CentOS7の場合)
$ sudo systemctl restart docker

# exitして再ログインすると反映される。
$ exit

docker-compose

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.23.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose
$ docker-compose --version 

他のリンクにしたい場合以下から探す

https://github.com/docker/compose/releases/

byobu

$ sudo yum update -y
$ wget https://launchpad.net/byobu/trunk/5.119/+download/byobu_5.119.orig.tar.gz
$ tar xzf byobu*.tar.gz
$ cd byobu-* && ./configure
$ sudo make && sudo make install

pip

$ curl -O https://bootstrap.pypa.io/get-pip.py
$ python get-pip.py --user # or python3

python

# 依存関係インストール
$ sudo yum install gcc zlib-devel bzip2 bzip2-devel readline readline-devel sqlite sqlite-devel openssl openssl-devel -y

# 本体インストール
$ pyenv install 3.6.5

# このOSで使用するPythonのバージョンを宣言
$ pyenv global 3.6.5

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

awscli を pip でインストールするときは Python 3 を使ってください

Python 2 は 2020-01-01 に EOL を迎えます。
しかし、いまだに PyPI からのダウンロードの50%強は Python 2 からのものです。

image.png
from PyPI Stats

この膨大なPython 2からのダウンロード数は、 Python のライブラリをメンテナンスしている人たちに Python 2 のサポートを続けさせるプレッシャーになっています。 Python 2 の EOL までにこのダウンロード数をなるべく下げたいところです。

だれがこんなに Python 2 で pip install をしているのでしょうか。OS別ダウンロード数統計にヒントがあります。

image.png

過半数の Python ユーザーが macOS か Windows を使っているのに対して、ダウンロードの90%くらいが Linux からのものです。 CI, CD, サーバーのプロビジョニングで実行される pip install が多いのでしょう。

ではどのパッケージが多くダウンロードされているのでしょうか。 PyPI Stats のランキング を見てみましょう。

image.png

トップ20のうち、pip とその依存ライブラリである setuptools を除いた 18 パッケージは、 awscli かその依存パッケージです!

image.png

そして awscli パッケージの 80% 以上が Python 2 から pip install されています!!

もし多くの人に読まれているドキュメントや Dockerfile のサンプルなどで awscli を Python 2 から pip install しているのを見かけたら、この記事か 英語版 のURLを紹介して Python 3 に移行するように提案してみてください。

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

WSL 上に AWS CLI をインストールする

概要

WSL上に AWS CLI をインストールする方法 with pip3

環境

Windows10 Pro
バージョン 1803
WSL ... Ubuntu 18.04 LTS

WSLのインストール方法
WSL への pip3 インストール方法

インストール

公式を参考に順番にやります

--upgrade オプションは pip 自体のアップデート
--upgrade オプションについて

--user オプションは linux のユーザーディレクトリにインストトールするためのオプション。
AWS CLI の場合は /home/username/.local/bin/aws ディレクトリにインストールされる。

# --upgrade 
$ pip3 install awscli --upgrade --user
Collecting awscli
  Downloading https://files.pythonhosted.org/packages/1c/fc/6de0d0e616db20fbb0bffbed73b4b575b6c5d6632503720760d21b5ec354/awscli-1.16.188-py2.py3-none-any.whl (1.7MB)
    100% |████████████████████████████████| 1.7MB 577kB/s
Collecting colorama<=0.3.9,>=0.2.5 (from awscli)
  Downloading https://files.pythonhosted.org/packages/db/c8/7dcf9dbcb22429512708fe3a547f8b6101c0d02137acbd892505aee57adf/colorama-0.3.9-py2.py3-none-any.whl
Collecting PyYAML<=5.1,>=3.10; python_version != "2.6" (from awscli)
Collecting rsa<=3.5.0,>=3.1.2 (from awscli)
  Downloading https://files.pythonhosted.org/packages/e1/ae/baedc9cb175552e95f3395c43055a6a5e125ae4d48a1d7a924baca83e92e/rsa-3.4.2-py2.py3-none-any.whl (46kB)
    100% |████████████████████████████████| 51kB 1.4MB/s
Collecting s3transfer<0.3.0,>=0.2.0 (from awscli)
  Downloading https://files.pythonhosted.org/packages/16/8a/1fc3dba0c4923c2a76e1ff0d52b305c44606da63f718d14d3231e21c51b0/s3transfer-0.2.1-py2.py3-none-any.whl (70kB)
    100% |████████████████████████████████| 71kB 1.5MB/s
Collecting docutils>=0.10 (from awscli)
  Downloading https://files.pythonhosted.org/packages/36/fa/08e9e6e0e3cbd1d362c3bbee8d01d0aedb2155c4ac112b19ef3cae8eed8d/docutils-0.14-py3-none-any.whl (543kB)
    100% |████████████████████████████████| 552kB 1.0MB/s
Collecting botocore==1.12.178 (from awscli)
  Downloading https://files.pythonhosted.org/packages/98/8f/ca7e67d37570e0dc0c5a495096a9a7428fb01a97fa908f57dcd0739f956b/botocore-1.12.178-py2.py3-none-any.whl (5.6MB)
    100% |████████████████████████████████| 5.6MB 261kB/s
Collecting pyasn1>=0.1.3 (from rsa<=3.5.0,>=3.1.2->awscli)
  Using cached https://files.pythonhosted.org/packages/7b/7c/c9386b82a25115cccf1903441bba3cbadcfae7b678a20167347fa8ded34c/pyasn1-0.4.5-py2.py3-none-any.whl
Collecting python-dateutil<3.0.0,>=2.1; python_version >= "2.7" (from botocore==1.12.178->awscli)
  Downloading https://files.pythonhosted.org/packages/41/17/c62faccbfbd163c7f57f3844689e3a78bae1f403648a6afb1d0866d87fbb/python_dateutil-2.8.0-py2.py3-none-any.whl (226kB)
    100% |████████████████████████████████| 235kB 1.2MB/s
Collecting urllib3<1.26,>=1.20; python_version >= "3.4" (from botocore==1.12.178->awscli)
  Downloading https://files.pythonhosted.org/packages/e6/60/247f23a7121ae632d62811ba7f273d0e58972d75e58a94d329d51550a47d/urllib3-1.25.3-py2.py3-none-any.whl (150kB)
    100% |████████████████████████████████| 153kB 1.1MB/s
Collecting jmespath<1.0.0,>=0.7.1 (from botocore==1.12.178->awscli)
  Downloading https://files.pythonhosted.org/packages/83/94/7179c3832a6d45b266ddb2aac329e101367fbdb11f425f13771d27f225bb/jmespath-0.9.4-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1; python_version >= "2.7"->botocore==1.12.178->awscli)
  Using cached https://files.pythonhosted.org/packages/73/fb/00a976f728d0d1fecfe898238ce23f502a721c0ac0ecfedb80e0d88c64e9/six-1.12.0-py2.py3-none-any.whl
Installing collected packages: colorama, PyYAML, pyasn1, rsa, six, python-dateutil, urllib3, jmespath, docutils, botocore, s3transfer, awscli
Successfully installed PyYAML-5.1 awscli-1.16.188 botocore-1.12.178 colorama-0.3.9 docutils-0.14 jmespath-0.9.4 pyasn1-0.4.5 python-dateutil-2.8.0 rsa-3.4.2 s3transfer-0.2.1 six-1.12.0 urllib3-1.25.3

インストール確認

PATH は通ってました。
もし aws なんてコマンドねーよって言われたら which コマンドでインストールされたディレクトリを確認後 PATH を通してみましょう。

$ aws --version
aws-cli/1.16.188 Python/3.6.7 Linux/4.4.0-17763-Microsoft botocore/1.12.178

$ which aws
/home/unix_user/.local/bin/aws

認証情報設定

インストール後そのまま何かコマンド実行すると認証情報がないよって怒られます。

# iam ユーザーのリスト取得
$ aws iam list-users
Unable to locate credentials. You can configure credentials by running "aws configure".

設定方法

とりあえず公式

$ aws configure
# 以下は対話式で入力していく
AWS Access Key ID [None]: accesskey1
AWS Secret Access Key [None]: secret1
Default region name [None]: ap-northeast-1
Default output format [None]: json

設定ファイル確認

~/.aws ディレクトリが作成されています。
~/.aws/credentials は認証情報、~/.aws/config は認証情報以外の情報(デフォルトのリージョンとかデフォルトの出力形式とか)が格納されています。

ls -la ~/.aws/
total 0
drwxrwxrwx 1 unix_user unix_user 4096 Jun 27 13:27 .
drwxr-xr-x 1 unix_user unix_user 4096 Jun 27 13:27 ..
-rw------- 1 unix_user unix_user   48 Jun 27 13:27 config
-rw------- 1 unix_user unix_user  116 Jun 27 13:27 credentials

$ cat ~/.aws/config
[default]
region = ap-northeast-1
output = json

$ cat ~/.aws/credentials
[default]
aws_access_key_id = accesskey1
aws_secret_access_key = secret1

環境変数とかでも設定できます。
ほかの設定方法と優先順位

設定されたか確認

設定したアクセスキーで取得できるはずの AWS のリソースへアクセスしてみます。

# 無事取れてます
$ aws iam list-users
{
    "Users": [
        {
            "Path": "/",
            "UserName": "some.one",
            "UserId": "user1",
            "Arn": "arn:aws:iam::123456789:user/some.one",
            "CreateDate": "2019-06-25T11:43:32Z",
            "PasswordLastUsed": "2019-06-26T00:38:36Z"
        },
        {
            "Path": "/",
            "UserName": "some.two",
            "UserId": "user2",
            "Arn": "arn:aws:iam::123456789:user/some.two",
            "CreateDate": "2019-06-25T11:45:32Z",
            "PasswordLastUsed": "2019-06-26T00:40:36Z"
        }
    ]
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RDS Notification Message について

What is RDS Notification Message?

Amazon RDSでは, Amazon RDSのイベントが発生した時に, Amazon Simple Notification Service (Amazon SNS) を使用して通知を送る.
通知にはAmazon SNSでサポートされている全ての通知形式が使用可能.
e.g. Email, Text message, Calling HTTP endpoint.

サブスクライブ可能なカテゴリにイベントが分類されており, サブスクラブしたカテゴリのイベントの通知を受け取ることができる.
イベント通知は, サブスクリプションを作成する時に指定したアドレスに送信されるが, 複数のサブスクリプションを作成することで 必要なイベントを必要なアドレスに対して通知することができる.
例えば, 全ての通知を受信するサブスクリプション (all-infra@hirrot.com) と, 本番か同様のDBインスタンスに関する重要なイベントのみを含むサブスクリプション (production-infra@hirrot.com) など分類して作成することができる.

Event Categories and Event Message of Amazon RDS

各カテゴリとイベントIDは公式サイトから参照できる.
例えば, 以下の形式で通知が送信される.

Event Source : db-instance

Identifier Link: https://console.aws.amazon.com/rds/home?region=ap-asia-9999#dbinstance:id=source_id

SourceId: source_id

Notification time : 2019-06-26 16:12:05.604

Message : Finished DB Instance backup

Event ID : http://docs.amazonwebservices.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0002
  • Event Source: イベントの発生元
  • Identifier Link: RDS DB Instance ID
  • SourceId: 発生元ID
  • Notification time: 通知時刻
  • Message: 通知の内容
  • Event ID: Amazon RDS イベント ID

つまり, 上記の通知内容は 2019-06-26 16:12:05.604db-instance:source_id でDBインスタンスをバックアップする(EventID: http://docs.amazonwebservices.com/AmazonRDS/latest/UserGuide/USER_Events.html#RDS-EVENT-0002) ことを表している.

Subscribe to Amazon RDS Events Notification

Amazon RDSイベント通知サブスクリプションを作成して特定の通知を受け取る.
最も簡単な方法はRDS Consoleを使用する方法. CLIまたはAPIを使用してイベント通知をサブスクリプションすることも可能.

How to

  1. AWS マネジメントコンソールにサインインし, Amazon RDS コンソール (https://console.aws.amazon.com/rds/) を開く.
  2. ナビゲーションペインで, [イベントサブスクリプション] を選択する.
  3. [イベントサブスクリプション] ページで, [イベントサブスクリプションの作成] を選択する.
  4. [イベントサブスクリプションの作成] ダイアログボックスで, 次の操作を行う.
    1. [名前] に, イベント通知サブスクリプションの名前を入力する.
    2. [通知の送信先] で既存の Amazon SNS トピックの Amazon SNS ARN を選択するか, [トピックを作成] を選択してトピックの名前と受取人のリストを入力する.
    3. [ソースタイプ] で、ソースタイプを選択する.
    4. [はい] を選択して, サブスクリプションを有効にする. サブスクリプションは作成するが, 通知はまだ送信しない場合は, [いいえ] を選択する.
    5. 選択したソースタイプに応じて, イベント通知を受け取る対象のイベントカテゴリとソースを選択する.
    6. [Create] を選択する.

Amazon RDS コンソールでは、サブスクリプションが作成されることが示される.
rds-screen-shot.png

hirrot.

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

S3 へのデータアップロードをトリガーにデータを推論し、推論結果を S3 に保存する AWS Lambda 関数の作成

はじめに

今回は S3 へのデータアップロードをトリガーに、アップロードされたデータを推論し、結果を S3 に保存する関数を作成しました。
その内容を記載します。

前提

  • SageMakerでデプロイできるモデルがある。
    ※今回作成した Lambda 関数を使う際には、事前にモデルをデプロイしておく必要があります。
    ※私は画像検出のモデルを使用します。

関数の作成/設定

  • まず、AWS のコンソールから Lambda と検索し、 Lambda を選択します。
    001.png

  • 左の一覧から関数を選択し、右上の関数の作成を選択します。
    002.png

  • 一から作成にチェックをつけ下へスクロール。
    003.png

  • 関数名に任意の名前を入力し、ランタイムは Python3.7 を選択してください。
    004.png

  • 実行ロールは基本的な Lambda アクセス権限で新しいロールを作成を選択し、関数の作成を選択します。
    005.png

  • 作成が完了すると、次のような画面が表示されます。
    006.png

  • 少し下にスクロールし左のトリガーの追加から S3 を選択してください。
    007.png

  • 選択したら、更に下にスクロールし各種設定を行います。

    • バケット名 : データをアップロードするバケットを選択します。
    • イベントタイプ : PUT を選択します。
    • プレフィックス : データのアップロードを検知するフォルダを指定します。
    • サフィックス : 検知するデータの末尾の形式を指定します。
      私は画像検出のモデルを使用するので、.jpgと入力しています。
    • トリガーの有効化 : チェックを付けておきます。 008.png
  • 下にスクロールして、右下の追加を選択します。
    009.png

  • 追加したら、右上の保存ボタンを選択して、設定を保存します。
    010.png

  • 保存したら、少し上にスクロールして、赤枠で囲まれた部分を選択し、下にスクロール。
    011.png

  • すると、以下のような画面がでてきます。
    赤枠内に、実際のプログラムを記述します。
    012.png

  • 今回私が作成したプログラムは以下の通りです。

import json
import boto3

def lambda_handler(event, context):
    s3 = boto3.client("s3")
    sagemaker = boto3.client("sagemaker-runtime", region_name="ap-northeast-1")

    #S3にアップロードされた画像データ情報を取得
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']
    input_data = s3.get_object(Bucket=bucket, Key=key)

    #画像データをバイナリデータに変換
    body = input_data['Body'].read()
    b = bytearray(body)

    #推論
    endpoint_response = sagemaker.invoke_endpoint(
        #エンドポイント名を設定
        EndpointName='auto-endpoint',
        Body=b,
        ContentType='image/jpeg'
    )
    results = endpoint_response['Body'].read()
    print(endpoint_response)
    print(results)


    split_s3_path = key.split('/')
    for i in split_s3_path:
        if 'jpg' in i:
            full_file_name = i
            file_name = i.split('.')[0]

    #output_keyに推論結果の保存パスを設定
    output_key = 'dog-face/lambda/推論結果/'+file_name+'.json'
    #S3に推論結果をjsonファイルで保存
    s3.put_object(Body=results,Bucket=bucket,Key=output_key)

    #copy_keyに推論後画像を格納するパスを設定
    copy_key = 'dog-face/lambda/推論済み画像/'+full_file_name
    #S3のアップロードされたデータを別フォルダにコピーして元データは削除
    s3.copy_object(Bucket=bucket,Key=copy_key,CopySource={'Bucket': bucket, 'Key': key})
    s3.delete_object(Bucket=bucket,Key=key)

プログラムは下記の流れになっています。
1. S3にアップロードされたデータを推論。
2. 推論結果を保存。
3. アップロードされたデータを、別フォルダにコピーし、アップロードされたデータを削除。

  • プログラムを書き終えたら、もう一度右上の保存を選択します。
    013.png

実行ロール の設定

さきほど何もいじらずに作成した、実行ロールに、S3とSageMakerのフルアクセスをアタッチします。
※今回はテストのため、フルアクセスをアタッチしますが、本番で利用する際は適切なIAMロールを作成してください。

  • IAMを開き、左の欄からロールを選択し、検索欄にLambda関数を作成した時に付けた名前を入力してください。
    015.png

  • 入力し、出てきたロールを選択します。
    016.png

  • ポリシーのアタッチを選択します。

    017.png

  • 検索バーに S3 と入力し、S3 のフルアクセスにチェックを付けて、ポリシーのアタッチを選択します。
    018.png

  • 同様に、検索バーに SageMaker と入力し、SageMaker のフルアクセスにチェックを付けて、ポリシーのアタッチを選択します。
    019.png

  • すると、2つのポリシーがアタッチされたことが確認できます。
    020.png

実際にS3にデータをアップロードし、Lambda 関数を起動してみる。

この手順に入る前に使用するモデルをデプロイしてください。

  • Lambda関数作成時に設定した画像アップロードフォルダに画像をアップロードします。
    021.png
    022.png

  • アップロードしたら、作成したLambda関数を開いて、モニタリングタブを選択し、CloudWatch のログ表示を選択します。
    023.png

  • するとこのような形で、Lambda 関数の起動ごとにログが記録されています。
    ログを開くと Lambda 関数が正常に動作しているか確認できます。
    024.png

  • 正常に動作している場合、S3に、推論結果が保存され、アップロードしたデータは別フォルダに格納され、削除されます。
    025.png
    026.png
    027.png

  • ちなみに推論結果を可視化した画像はこちらになります。
    028.png

最後に

今回は、S3へのデータアップロードをトリガーに、推論を行いました。
トリガーを変更することで、他にもいろいろな処理ができますので、ぜひ試してみてください。
[undefined]()

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

Baby Step Terraform for AWS [EC2]

お題

英語的におかしいであろう表題のことは置いといて、ある(小さな)アプリをAWS上で動かそうとした時にTerraformを使って少しずつ目的のものに近づけていくことを試みる。
Terraformのテンプレートファイルの記述も、最初は愚直に、問題に直面しつつ徐々に改修していく想定。
世の中の記事や書籍では最初からベストプラクティスを踏まえた設計をすることが多い。
でも、理解のためには、まず目的とする最低限の書き方で書いて、そこから「今、こうなってるからこうした方がいい」、「次にこれらを追加する想定だから、こうしておいた方がいい」といった改善をしてベストプラクティスに近づいた方がいいと思う。

以下、構築予定。ただし、今回は1のEC2上でWebアプリを動かすとこのみ。

  1. アプリ(※)をEC2上で動かしてみる。
  2. マネージドなDBに接続しにいく。
  3. デフォルトのネットワークを使うのではなくvpcsubnetを定義する。
  4. マルチAZ(アベイラビリティゾーン)化とロードバランサーを導入する。 (5. 画像ファイルをS3に置いてCloudFront経由でアクセスさせる。) ※
  5. アプリ(※)をDocker化してECSで動かすようにする。
  6. 最後はCI/CD。GitHubにアプリのソースをプッシュしたら自動でビルド・デプロイが走るようにする。
  7. Route 53使って独自ドメインでアクセスできるようにする。

※Goで適当に作ったアプリ(http://【デプロイ先ホスト】/helloにアクセスすると「hello」と吐くだけ)
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Terraformのテンプレートファイル(*.tf)の書き方やModule化など、最初は愚直なやり方で行い、ちょっとずつベストプラクティス要素を取り入れていく。

極力、無料枠内でやりくりしたいところだけど、立てるリソース如何ではなんとも。。。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%2312monthsfree%7Ccategories%23alwaysfree

お断り

  • AWS自体の説明は薄いので「AWS触ったこともない」人に理解してもらえる内容にはなっていないです。
  • 上記に関連して、AWSのアカウントは保持済み、AWSのCredentialはローカルにある前提でterraformコマンド叩いています。
  • この記事の内容で実際のサービスを本番運用しているわけではないので、このまま真似してプロダクションレディになる保証はないです。

開発環境

# OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

# IDE - Visual Studio Code

Version: 1.35.1
Commit: c7d83e57cd18f18026a8162d042843bda1bcf21f
Date: 2019-06-12T14:27:31.086Z
Electron: 3.1.8
Chrome: 66.0.3359.181
Node.js: 10.2.0
V8: 6.6.346.32
OS: Linux x64 4.15.0-47-generic
vscode-terraform Plugin
Name: Terraform
Id: mauve.terraform
Description: Syntax highlighting, linting, formatting, and validation for Hashicorp's Terraform
Version: 1.3.12
Publisher: Mikael Olenfalk
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=mauve.terraform

# Terraform

$ terraform version
Terraform v0.12.2

# tfenv

$ tfenv
tfenv 0.6.0

実践

1. ミニマムアプリのEC2デプロイ

アプリはGolang製でLinux(Ubuntu)環境でビルドしたバイナリ(go-experiment)がGitHub(※)に上がっている前提。
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Projectのディレクトリ構成

EC2インスタンス1つ立てるだけということもあり、Projectルート直下に1ファイルのみ。

$ tree 
.
└── main.tf

0 directories, 1 file

テンプレートファイルの中身チェック

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

aws_instance

EC2のインスタンス生成を行う。
https://www.terraform.io/docs/providers/aws/r/instance.html

ami

Amazonマシンイメージは、Amazon Linux2を採用。

instance_type

インスタンスタイプの種類はさまざま。
https://aws.amazon.com/jp/ec2/instance-types/
今回は無料枠の対象であるt2.microを採用。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%23featured

user_data

ここでGitHub経由でgo-experimentアプリをEC2インスタンス内に格納して起動。

ただ、ここでエラーが起きた場合、どうやって検知できるんだろう・・・。

AWS環境へ反映

フォーマッターにかけて、

$ terraform fmt
main.tf

バリデーションチェックして、

$ terraform validate
Success! The configuration is valid.

プランチェックして、

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


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

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

Terraform will perform the following actions:

  # aws_instance.name will be created
  + resource "aws_instance" "name" {
      + ami                          = "ami-0f9ae750e8274075b"
      + arn                          = (known after apply)
         〜〜 省略 〜〜
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
         〜〜 省略 〜〜
      + tenancy                      = (known after apply)
      + user_data                    = "3a280d56e8e7e3aec19bad9a8f5b696867b8dd39"
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

 〜〜 省略 〜〜

いざ、実行。

$ terraform apply

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

 〜〜 省略 〜〜

  Enter a value: yes

aws_instance.name: Creating...
aws_instance.name: Still creating... [10s elapsed]
aws_instance.name: Still creating... [20s elapsed]
aws_instance.name: Still creating... [30s elapsed]
aws_instance.name: Creation complete after 32s [id=i-0018f388ca8a7774f]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

EC2インスタンスは?

screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-23-11-24-22-157.png

出来てる。
じゃあ、もうアプリにつながるかというと、

Screenshot from 2019-06-23 19-08-09.png

つながらない。

セキュリティグループの定義

EC2インスタンス立てただけでは80番ポートが開いてないので、穴あけ作業が必要。

テンプレートファイルに追記

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.go-app-security-group.id]

 〜〜 省略 〜〜
}

resource "aws_security_group" "go-app-security-group" {
  name = "go-app-security-group"

  ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
  }
}

output "go-app-public-dns" {
  value = aws_instance.go-app-server.public_dns
}

vpc_security_group_ids

作成したセキュリティグループとこのEC2インスタンスを紐付ける。
https://www.terraform.io/docs/providers/aws/r/instance.html#vpc_security_group_ids

aws_security_group

紐付けたインスタンスへの”入り”と”出”を制御する。
https://www.terraform.io/docs/providers/aws/r/security_group.html

ingress

紐付けるサーバへの”IN"を制御。今回はtcp:80番ポートを開ける。特にアクセス元は絞らない。

egress

紐付けるサーバの”OUT"を制御。protocol-1ないしallにすると全ポート開放になるらしい。
https://www.terraform.io/docs/providers/aws/r/security_group_rule.html

再度、AWS環境へ反映

Terraformコマンド使うくだりは同じなので省略。
applyが終わり、再度、ブラウザで見てみると、

Screenshot from 2019-06-24 07-58-08.png

セキュリティグループの設定でポート「80」番を開けたので、今度は表示された。

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/blob/821511843b67305945f52491a81c26a7cc1e5e6a/v0.12/aws/step01/main.tf

1の後のStepupフェーズ

おさらい

現在のTerraformテンプレートファイルは下記。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.web-server-security-group.id]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "web-server-public-dns" {
  value = aws_instance.web-server.public_dns
}

プラクティス

この分量のテンプレートファイルなら一読して理解不可能な複雑さはない。
ただ、少なくともこの後、ネットワークを構成したり、マネージドなDB、S3、ECSというようにAWSのリソースをふんだんに使い出すことになる。
そうなると、main.tfだけでは読み解きづらくなるので、現時点でファイルを分けておく。

outputs

本家HashiCorpのGitHub上の事例でも、outputvariable(当記事ではまだ使ってない)は専用のファイルを設けている。
Subnetの事例

上記にならって以下のようにファイル分けしておく。
以下の通り。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

※ちなみに、これまで単に「aws_instance.web-server.public_dns」と書いていた部分を今回「"${aws_instance.web-server.public_dns}"」というように修正した。
修正前の時点でも動作はしていたものの、Visual Studio CodeのTerraformプラグイン上では警告が出ていた。
今回の修正により警告がなくなった。

AWSリソース別のファイル化

これも本家HashiCorpの事例や他の記事でもよく見るように、AWSリソース別にファイル化することが多い様子。
あわせて、何度も使われる(であろう)リソースはModule化も行うらしいけど、今回はまだそこまではしない。

今回やるのは、EC2インスタンスリソース分とセキュリティグループ分の別ファイル化。
以下の通り。

ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}
security_group.tf
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

user_dataの別ファイル化

EC2インスタンス起動時に実行するコマンドをuser_dataに指定しているが、これはいわゆるセットアップスクリプトなので別ファイルとして管理したい。
Terraformでは、ファイル操作や文字列操作ができる(テンプレートファイル内に書ける)組み込み関数が用意されているので、それを利用。

web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── security_group.tf
├── terraform.tfstate
└── web-server-setup.sh

terraform.tfstateはAWS適用後の状態を保持するファイル(terraform apply実行により自動作成される)

Providerの設定

リージョン指定の謎

これまでのテンプレートファイルで作られるのはAWSの各種リソース。
ただ、Terraformが扱うのはAWSに限ったことではない。GCPだってAzureだって扱える。
じゃあ、何をもってAWSのリソースを使うと判断できるのか。
テンプレートファイルで「aws_instance」といった、AWSを示すリソースを指定しているのだから、当然と言えば当然なんだけど。
ここで1つ疑問。
今のところ、テンプレートファイルでリージョンの指定はしていない。
なのに、EC2インスタンスは下記のように東京リージョンに作られている。
screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-25-16-32-25-813.png
これは、実はAWSのクライアントツールをセットアップした時に各種クレデンシャル情報に加え、デフォルトのリージョンを下記のように環境変数にセットしたためと思われる。

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

試しに、以下のようにリージョンを「us-east-2(=オハイオ)」に変えてterraform applyを実行すると、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

Screenshot from 2019-06-26 06-38-49.png

となる。

リージョンの明示的な定義

上記のようにTerraformコマンドの実行環境に依存するリージョンを明示的に指定するには「provider」を定義する。
https://www.terraform.io/docs/configuration/providers.html
これも、専用のテンプレートファイルとする。
ちなみに、前述の「outputs.tf」もそうだったけど、「variables.tf」や今回のproviderが「providers.tf」になるようにファイル名には複数形を用いられる(ことが多い?)。
ただし、「vpc.tf」や「security_group.tf」のように各サービスの場合は単数形を用いている様子。

providers.tf
provider "aws" {
  region = "ap-northeast-1"
}

上記のようにしておくと、環境変数として下記のように「us-east-2」がセットされていても、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

下のように「ap-northeast-1」リージョンでインスタンスが作られる。

Screenshot from 2019-06-26 08-18-20.png

その他プロバイダー設定

プロバイダーバージョン

プロバイダーとしてはAWSを用いる。そのプロバイダーにはバージョンがある。
今どんなバージョンが存在するかは下記参照。
https://github.com/terraform-providers/terraform-provider-aws/blob/master/CHANGELOG.md

セマンティックバージョニングを採用しているので、現時点の安定版バージョンの中でバグフィックス起因のアップデートのみ許容する指定にする。
https://www.terraform.io/docs/configuration/providers.html#version-provider-versions

provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}

これ以外に指定可能な設定については下記参照。
https://www.terraform.io/docs/providers/aws/index.html#argument-reference

Terraform自体の設定

プロバイダーのバージョンも大事だけど、そもそもTerraform自体のバージョンも明示的に固定すべき。
これは以下のように、また別ファイル化しておく。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── providers.tf
├── security_group.tf
├── terraform.tf
└── web-server-setup.sh

※あえて *.tfstate ファイルは表示上、外した。

上記以外の設定については下記参照。
https://www.terraform.io/docs/configuration/terraform.html

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/tree/595afc446d7b4c9f72fb81331187a52358175f2e/v0.12/aws/step01n

今回は、まだ6ファイルだけなので、中身を全て再掲しておく。

以下はEC2インスタンスを立てて外部からアクセスするのに必要な定義。

ec2.tf
# https://www.terraform.io/docs/providers/aws/r/instance.html
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}
web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
security_group.tf
# https://www.terraform.io/docs/providers/aws/r/security_group.html
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

↓は、単にterraform applyした時にコンソールに出力してほしい内容。

outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

以降は、Terraform自体やAWSプロバイダーの定義。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}
providers.tf
provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Baby Step Terraform for AWS [No.01: EC2]

お題

英語的におかしいであろう表題のことは置いといて、ある(小さな)アプリをAWS上で動かそうとした時にTerraform使って少しずつ目的のものに近づけていくことを試みる。
(Terraformのテンプレートファイルの記述も、最初は愚直に、問題に直面しつつ徐々に改修していく想定)

以下、構築予定。ただし、今回は1のEC2上でWebアプリを動かすとこのみ。

  1. アプリ(※)をEC2上で動かしてみる。
  2. マネージドなDBに接続しにいく。
  3. デフォルトのネットワークを使うのではなくvpcsubnetを定義する。
  4. マルチAZ(アベイラビリティゾーン)化とロードバランサーを導入する。 (5. 画像ファイルをS3に置いてCloudFront経由でアクセスさせる。) ※
  5. アプリ(※)をDocker化してECSで動かすようにする。
  6. 最後はCI/CD。GitHubにアプリのソースをプッシュしたら自動でビルド・デプロイが走るようにする。
  7. Route 53使って独自ドメインでアクセスできるようにする。

※Goで適当に作ったアプリ(http://【デプロイ先ホスト】/helloにアクセスすると「hello」と吐くだけ)
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Terraformのテンプレートファイル(*.tf)の書き方やModule化など、最初は愚直なやり方で行い、ちょっとずつベストプラクティス要素を取り入れていく。

極力、無料枠内でやりくりしたいところだけど、立てるリソース如何ではなんとも。。。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%2312monthsfree%7Ccategories%23alwaysfree

お断り

  • AWS自体の説明は薄いので「AWS触ったこともない」人に理解してもらえる内容にはなっていないです。
  • 上記に関連して、AWSのアカウントは保持済み、AWSのCredentialはローカルにある前提でterraformコマンド叩いています。
  • この記事の内容で実際のサービスを本番運用しているわけではないので、このまま真似してプロダクションレディになる保証はないです。

開発環境

# OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

# IDE - Visual Studio Code

Version: 1.35.1
Commit: c7d83e57cd18f18026a8162d042843bda1bcf21f
Date: 2019-06-12T14:27:31.086Z
Electron: 3.1.8
Chrome: 66.0.3359.181
Node.js: 10.2.0
V8: 6.6.346.32
OS: Linux x64 4.15.0-47-generic
vscode-terraform Plugin
Name: Terraform
Id: mauve.terraform
Description: Syntax highlighting, linting, formatting, and validation for Hashicorp's Terraform
Version: 1.3.12
Publisher: Mikael Olenfalk
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=mauve.terraform

# Terraform

$ terraform version
Terraform v0.12.2

# tfenv

$ tfenv
tfenv 0.6.0

実践

1. ミニマムアプリのEC2デプロイ

アプリはGolang製でLinux(Ubuntu)環境でビルドしたバイナリ(go-experiment)がGitHub(※)に上がっている前提。
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Projectのディレクトリ構成

EC2インスタンス1つ立てるだけということもあり、Projectルート直下に1ファイルのみ。

$ tree 
.
└── main.tf

0 directories, 1 file

テンプレートファイルの中身チェック

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

aws_instance

EC2のインスタンス生成を行う。
https://www.terraform.io/docs/providers/aws/r/instance.html

ami

Amazonマシンイメージは、Amazon Linux2を採用。

instance_type

インスタンスタイプの種類はさまざま。
https://aws.amazon.com/jp/ec2/instance-types/
今回は無料枠の対象であるt2.microを採用。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%23featured

user_data

ここでGitHub経由でgo-experimentアプリをEC2インスタンス内に格納して起動。

ただ、ここでエラーが起きた場合、どうやって検知できるんだろう・・・。

AWS環境へ反映

フォーマッターにかけて、

$ terraform fmt
main.tf

バリデーションチェックして、

$ terraform validate
Success! The configuration is valid.

プランチェックして、

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


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

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

Terraform will perform the following actions:

  # aws_instance.name will be created
  + resource "aws_instance" "name" {
      + ami                          = "ami-0f9ae750e8274075b"
      + arn                          = (known after apply)
      + associate_public_ip_address  = (known after apply)
      + availability_zone            = (known after apply)
      + cpu_core_count               = (known after apply)
      + cpu_threads_per_core         = (known after apply)
      + get_password_data            = false
      + host_id                      = (known after apply)
      + id                           = (known after apply)
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
      + ipv6_addresses               = (known after apply)
      + key_name                     = (known after apply)
      + network_interface_id         = (known after apply)
      + password_data                = (known after apply)
      + placement_group              = (known after apply)
      + primary_network_interface_id = (known after apply)
      + private_dns                  = (known after apply)
      + private_ip                   = (known after apply)
      + public_dns                   = (known after apply)
      + public_ip                    = (known after apply)
      + security_groups              = (known after apply)
      + source_dest_check            = true
      + subnet_id                    = (known after apply)
      + tenancy                      = (known after apply)
      + user_data                    = "3a280d56e8e7e3aec19bad9a8f5b696867b8dd39"
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

 〜〜 省略 〜〜

いざ、実行。

$ terraform apply

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

 〜〜 省略 〜〜

  Enter a value: yes

aws_instance.name: Creating...
aws_instance.name: Still creating... [10s elapsed]
aws_instance.name: Still creating... [20s elapsed]
aws_instance.name: Still creating... [30s elapsed]
aws_instance.name: Creation complete after 32s [id=i-0018f388ca8a7774f]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

EC2インスタンスは?

screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-23-11-24-22-157.png

出来てる。
じゃあ、もうアプリにつながるかというと、

Screenshot from 2019-06-23 19-08-09.png

つながらない。

セキュリティグループの定義

EC2インスタンス立てただけでは80番ポートが開いてないので、穴あけ作業が必要。

テンプレートファイルに追記

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.go-app-security-group.id]

 〜〜 省略 〜〜
}

resource "aws_security_group" "go-app-security-group" {
  name = "go-app-security-group"

  ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
  }
}

output "go-app-public-dns" {
  value = aws_instance.go-app-server.public_dns
}

vpc_security_group_ids

作成したセキュリティグループとこのEC2インスタンスを紐付ける。
https://www.terraform.io/docs/providers/aws/r/instance.html#vpc_security_group_ids

aws_security_group

紐付けたインスタンスへの”入り”と”出”を制御する。
https://www.terraform.io/docs/providers/aws/r/security_group.html

ingress

紐付けるサーバへの”IN"を制御。今回はtcp:80番ポートを開ける。特にアクセス元は絞らない。

egress

紐付けるサーバの”OUT"を制御。protocol-1ないしallにすると全ポート開放になるらしい。
https://www.terraform.io/docs/providers/aws/r/security_group_rule.html

再度、AWS環境へ反映

Terraformコマンド使うくだりは同じなので省略。
applyが終わり、再度、ブラウザで見てみると、

Screenshot from 2019-06-24 07-58-08.png

セキュリティグループの設定でポート「80」番を開けたので、今度は表示された。

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/blob/821511843b67305945f52491a81c26a7cc1e5e6a/v0.12/aws/step01/main.tf

1の後のStepupフェーズ

おさらい

現在のTerraformテンプレートファイルは下記。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.web-server-security-group.id]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "web-server-public-dns" {
  value = aws_instance.web-server.public_dns
}

プラクティス

この分量のテンプレートファイルなら一読して理解不可能な複雑さはない。
ただ、少なくともこの後、ネットワークを構成したり、マネージドなDB、S3、ECSというようにAWSのリソースをふんだんに使い出すことになる。
そうなると、main.tfだけでは読み解きづらくなるので、現時点でファイルを分けておく。

outputs

本家HashiCorpのGitHub上の事例でも、outputvariable(当記事ではまだ使ってない)は専用のファイルを設けている。
Subnetの事例

上記にならって以下のようにファイル分けしておく。
以下の通り。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

※ちなみに、これまで単に「aws_instance.web-server.public_dns」と書いていた部分を今回「"${aws_instance.web-server.public_dns}"」というように修正した。
修正前の時点でも動作はしていたものの、Visual Studio CodeのTerraformプラグイン上では警告が出ていた。
今回の修正により警告がなくなった。

AWSリソース別のファイル化

これも本家HashiCorpの事例や他の記事でもよく見るように、AWSリソース別にファイル化することが多い様子。
あわせて、何度も使われる(であろう)リソースはModule化も行うらしいけど、今回はまだそこまではしない。

今回やるのは、EC2インスタンスリソース分とセキュリティグループ分の別ファイル化。
以下の通り。

ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}
security_group.tf
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

user_dataの別ファイル化

EC2インスタンス起動時に実行するコマンドをuser_dataに指定しているが、これはいわゆるセットアップスクリプトなので別ファイルとして管理したい。
Terraformでは、ファイル操作や文字列操作ができる(テンプレートファイル内に書ける)組み込み関数が用意されているので、それを利用。

web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── security_group.tf
├── terraform.tfstate
└── web-server-setup.sh

terraform.tfstateはAWS適用後の状態を保持するファイル(terraform apply実行により自動作成される)

Providerの設定

リージョン指定の謎

これまでのテンプレートファイルで作られるのはAWSの各種リソース。
ただ、Terraformが扱うのはAWSに限ったことではない。GCPだってAzureだって扱える。
じゃあ、何をもってAWSのリソースを使うと判断できるのか。
テンプレートファイルで「aws_instance」といった、AWSを示すリソースを指定しているのだから、当然と言えば当然なんだけど。
ここで1つ疑問。
今のところ、テンプレートファイルでリージョンの指定はしていない。
なのに、EC2インスタンスは下記のように東京リージョンに作られている。
screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-25-16-32-25-813.png
これは、実はAWSのクライアントツールをセットアップした時に各種クレデンシャル情報に加え、デフォルトのリージョンを下記のように環境変数にセットしたためと思われる。

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

試しに、以下のようにリージョンを「us-east-2(=オハイオ)」に変えてterraform applyを実行すると、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

Screenshot from 2019-06-26 06-38-49.png

となる。

リージョンの明示的な定義

上記のようにTerraformコマンドの実行環境に依存するリージョンを明示的に指定するには「provider」を定義する。
https://www.terraform.io/docs/configuration/providers.html
これも、専用のテンプレートファイルとする。
ちなみに、前述の「outputs.tf」もそうだったけど、「variables.tf」や今回のproviderが「providers.tf」になるようにファイル名には複数形を用いられる(ことが多い?)。
ただし、「vpc.tf」や「security_group.tf」のように各サービスの場合は単数形を用いている様子。

providers.tf
provider "aws" {
  region = "ap-northeast-1"
}

上記のようにしておくと、環境変数として下記のように「us-east-2」がセットされていても、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

下のように「ap-northeast-1」リージョンでインスタンスが作られる。

Screenshot from 2019-06-26 08-18-20.png

その他プロバイダー設定

プロバイダーバージョン

プロバイダーとしてはAWSを用いる。そのプロバイダーにはバージョンがある。
今どんなバージョンが存在するかは下記参照。
https://github.com/terraform-providers/terraform-provider-aws/blob/master/CHANGELOG.md

セマンティックバージョニングを採用しているので、現時点の安定版バージョンの中でバグフィックス起因のアップデートのみ許容する指定にする。
https://www.terraform.io/docs/configuration/providers.html#version-provider-versions

provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}

これ以外に指定可能な設定については下記参照。
https://www.terraform.io/docs/providers/aws/index.html#argument-reference

Terraform自体の設定

プロバイダーのバージョンも大事だけど、そもそもTerraform自体のバージョンも明示的に固定すべき。
これは以下のように、また別ファイル化しておく。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── providers.tf
├── security_group.tf
├── terraform.tf
└── web-server-setup.sh

※あえて *.tfstate ファイルは表示上、外した。

上記以外の設定については下記参照。
https://www.terraform.io/docs/configuration/terraform.html

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/tree/595afc446d7b4c9f72fb81331187a52358175f2e/v0.12/aws/step01n

今回は、まだ6ファイルだけなので、中身を全て公開しておく。

以下はEC2インスタンスを立てて外部からアクセスするのに必要な定義。

ec2.tf
# https://www.terraform.io/docs/providers/aws/r/instance.html
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}
web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
security_group.tf
# https://www.terraform.io/docs/providers/aws/r/security_group.html
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

↓は、単にterraform applyした時にコンソールに出力してほしい内容。

outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

以降は、Terraform自体やAWSプロバイダーの定義。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}
providers.tf
provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

第1回「EC2」@Baby Step Terraform for AWS

お題

英語的におかしいであろう表題のことは置いといて、ある(小さな)アプリをAWS上で動かそうとした時にTerraformを使って少しずつ目的のものに近づけていくことを試みる。
Terraformのテンプレートファイルの記述も、最初は愚直に、問題に直面しつつ徐々に改修していく想定。
世の中の記事や書籍では最初からベストプラクティスを踏まえた設計をすることが多い。
でも、理解のためには、まず目的とする最低限の書き方で書いて、そこから「今、こうなってるからこうした方がいい」、「次にこれらを追加する想定だから、こうしておいた方がいい」といった改善をしてベストプラクティスに近づいた方がいいと思う。

以下、構築予定。ただし、今回は1のEC2上でWebアプリを動かすとこのみ。

  1. アプリ(※)をEC2上で動かしてみる。
  2. マネージドなDBに接続しにいく。
  3. デフォルトのネットワークを使うのではなくvpcsubnetを定義する。
  4. マルチAZ(アベイラビリティゾーン)化とロードバランサーを導入する。 (5. 画像ファイルをS3に置いてCloudFront経由でアクセスさせる。) ※
  5. アプリ(※)をDocker化してECSで動かすようにする。
  6. 最後はCI/CD。GitHubにアプリのソースをプッシュしたら自動でビルド・デプロイが走るようにする。
  7. Route 53使って独自ドメインでアクセスできるようにする。

※Goで適当に作ったアプリ(http://【デプロイ先ホスト】/helloにアクセスすると「hello」と吐くだけ)
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Terraformのテンプレートファイル(*.tf)の書き方やModule化など、最初は愚直なやり方で行い、ちょっとずつベストプラクティス要素を取り入れていく。

極力、無料枠内でやりくりしたいところだけど、立てるリソース如何ではなんとも。。。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%2312monthsfree%7Ccategories%23alwaysfree

お断り

  • AWS自体の説明は薄いので「AWS触ったこともない」人に理解してもらえる内容にはなっていないです。
  • 上記に関連して、AWSのアカウントは保持済み、AWSのCredentialはローカルにある前提でterraformコマンド叩いています。
  • この記事の内容で実際のサービスを本番運用しているわけではないので、このまま真似してプロダクションレディになる保証はないです。

開発環境

# OS - Linux(Ubuntu)

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.2 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.2 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

# IDE - Visual Studio Code

Version: 1.35.1
Commit: c7d83e57cd18f18026a8162d042843bda1bcf21f
Date: 2019-06-12T14:27:31.086Z
Electron: 3.1.8
Chrome: 66.0.3359.181
Node.js: 10.2.0
V8: 6.6.346.32
OS: Linux x64 4.15.0-47-generic
vscode-terraform Plugin
Name: Terraform
Id: mauve.terraform
Description: Syntax highlighting, linting, formatting, and validation for Hashicorp's Terraform
Version: 1.3.12
Publisher: Mikael Olenfalk
VS Marketplace Link: https://marketplace.visualstudio.com/items?itemName=mauve.terraform

# Terraform

$ terraform version
Terraform v0.12.2

# tfenv

$ tfenv
tfenv 0.6.0

実践

1. ミニマムアプリのEC2デプロイ

アプリはGolang製でLinux(Ubuntu)環境でビルドしたバイナリ(go-experiment)がGitHub(※)に上がっている前提。
https://github.com/sky0621/go-experiment/tree/af129be808476b56e06073775a92fb5d4cec3980

Projectのディレクトリ構成

EC2インスタンス1つ立てるだけということもあり、Projectルート直下に1ファイルのみ。

$ tree 
.
└── main.tf

0 directories, 1 file

テンプレートファイルの中身チェック

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

aws_instance

EC2のインスタンス生成を行う。
https://www.terraform.io/docs/providers/aws/r/instance.html

ami

Amazonマシンイメージは、Amazon Linux2を採用。

instance_type

インスタンスタイプの種類はさまざま。
https://aws.amazon.com/jp/ec2/instance-types/
今回は無料枠の対象であるt2.microを採用。
https://aws.amazon.com/jp/free/?all-free-tier.sort-by=item.additionalFields.SortRank&all-free-tier.sort-order=asc&awsf.Free%20Tier%20Types=categories%23featured

user_data

ここでGitHub経由でgo-experimentアプリをEC2インスタンス内に格納して起動。

ただ、ここでエラーが起きた場合、どうやって検知できるんだろう・・・。

AWS環境へ反映

フォーマッターにかけて、

$ terraform fmt
main.tf

バリデーションチェックして、

$ terraform validate
Success! The configuration is valid.

プランチェックして、

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


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

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

Terraform will perform the following actions:

  # aws_instance.name will be created
  + resource "aws_instance" "name" {
      + ami                          = "ami-0f9ae750e8274075b"
      + arn                          = (known after apply)
         〜〜 省略 〜〜
      + instance_state               = (known after apply)
      + instance_type                = "t2.micro"
      + ipv6_address_count           = (known after apply)
         〜〜 省略 〜〜
      + tenancy                      = (known after apply)
      + user_data                    = "3a280d56e8e7e3aec19bad9a8f5b696867b8dd39"
      + volume_tags                  = (known after apply)
      + vpc_security_group_ids       = (known after apply)

 〜〜 省略 〜〜

いざ、実行。

$ terraform apply

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

 〜〜 省略 〜〜

  Enter a value: yes

aws_instance.name: Creating...
aws_instance.name: Still creating... [10s elapsed]
aws_instance.name: Still creating... [20s elapsed]
aws_instance.name: Still creating... [30s elapsed]
aws_instance.name: Creation complete after 32s [id=i-0018f388ca8a7774f]

Apply complete! Resources: 1 added, 0 changed, 0 destroyed.

EC2インスタンスは?

screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-23-11-24-22-157.png

出来てる。
じゃあ、もうアプリにつながるかというと、

Screenshot from 2019-06-23 19-08-09.png

つながらない。

セキュリティグループの定義

EC2インスタンス立てただけでは80番ポートが開いてないので、穴あけ作業が必要。

テンプレートファイルに追記

main.tf
resource "aws_instance" "go-app-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.go-app-security-group.id]

 〜〜 省略 〜〜
}

resource "aws_security_group" "go-app-security-group" {
  name = "go-app-security-group"

  ingress {
      from_port = 80
      to_port = 80
      protocol = "tcp"
      cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
      from_port = 0
      to_port = 0
      protocol = "-1"
      cidr_blocks = ["0.0.0.0/0"]
  }
}

output "go-app-public-dns" {
  value = aws_instance.go-app-server.public_dns
}

vpc_security_group_ids

作成したセキュリティグループとこのEC2インスタンスを紐付ける。
https://www.terraform.io/docs/providers/aws/r/instance.html#vpc_security_group_ids

aws_security_group

紐付けたインスタンスへの”入り”と”出”を制御する。
https://www.terraform.io/docs/providers/aws/r/security_group.html

ingress

紐付けるサーバへの”IN"を制御。今回はtcp:80番ポートを開ける。特にアクセス元は絞らない。

egress

紐付けるサーバの”OUT"を制御。protocol-1ないしallにすると全ポート開放になるらしい。
https://www.terraform.io/docs/providers/aws/r/security_group_rule.html

再度、AWS環境へ反映

Terraformコマンド使うくだりは同じなので省略。
applyが終わり、再度、ブラウザで見てみると、

Screenshot from 2019-06-24 07-58-08.png

セキュリティグループの設定でポート「80」番を開けたので、今度は表示された。

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/blob/821511843b67305945f52491a81c26a7cc1e5e6a/v0.12/aws/step01/main.tf

1の後のStepupフェーズ

おさらい

現在のTerraformテンプレートファイルは下記。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = [aws_security_group.web-server-security-group.id]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "web-server-public-dns" {
  value = aws_instance.web-server.public_dns
}

プラクティス

この分量のテンプレートファイルなら一読して理解不可能な複雑さはない。
ただ、少なくともこの後、ネットワークを構成したり、マネージドなDB、S3、ECSというようにAWSのリソースをふんだんに使い出すことになる。
そうなると、main.tfだけでは読み解きづらくなるので、現時点でファイルを分けておく。

outputs

本家HashiCorpのGitHub上の事例でも、outputvariable(当記事ではまだ使ってない)は専用のファイルを設けている。
Subnetの事例

上記にならって以下のようにファイル分けしておく。
以下の通り。

main.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}

resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}
outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

※ちなみに、これまで単に「aws_instance.web-server.public_dns」と書いていた部分を今回「"${aws_instance.web-server.public_dns}"」というように修正した。
修正前の時点でも動作はしていたものの、Visual Studio CodeのTerraformプラグイン上では警告が出ていた。
今回の修正により警告がなくなった。

AWSリソース別のファイル化

これも本家HashiCorpの事例や他の記事でもよく見るように、AWSリソース別にファイル化することが多い様子。
あわせて、何度も使われる(であろう)リソースはModule化も行うらしいけど、今回はまだそこまではしない。

今回やるのは、EC2インスタンスリソース分とセキュリティグループ分の別ファイル化。
以下の通り。

ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = <<EOF
    #!/bin/bash
    sudo yum -y install git
    git clone https://github.com/sky0621/go-experiment.git
    cd go-experiment
    ./go-experiment
EOF
}
security_group.tf
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

user_dataの別ファイル化

EC2インスタンス起動時に実行するコマンドをuser_dataに指定しているが、これはいわゆるセットアップスクリプトなので別ファイルとして管理したい。
Terraformでは、ファイル操作や文字列操作ができる(テンプレートファイル内に書ける)組み込み関数が用意されているので、それを利用。

web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
ec2.tf
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── security_group.tf
├── terraform.tfstate
└── web-server-setup.sh

terraform.tfstateはAWS適用後の状態を保持するファイル(terraform apply実行により自動作成される)

Providerの設定

リージョン指定の謎

これまでのテンプレートファイルで作られるのはAWSの各種リソース。
ただ、Terraformが扱うのはAWSに限ったことではない。GCPだってAzureだって扱える。
じゃあ、何をもってAWSのリソースを使うと判断できるのか。
テンプレートファイルで「aws_instance」といった、AWSを示すリソースを指定しているのだから、当然と言えば当然なんだけど。
ここで1つ疑問。
今のところ、テンプレートファイルでリージョンの指定はしていない。
なのに、EC2インスタンスは下記のように東京リージョンに作られている。
screenshot-ap-northeast-1.console.aws.amazon.com-2019-06-25-16-32-25-813.png
これは、実はAWSのクライアントツールをセットアップした時に各種クレデンシャル情報に加え、デフォルトのリージョンを下記のように環境変数にセットしたためと思われる。

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=ap-northeast-1
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

試しに、以下のようにリージョンを「us-east-2(=オハイオ)」に変えてterraform applyを実行すると、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

Screenshot from 2019-06-26 06-38-49.png

となる。

リージョンの明示的な定義

上記のようにTerraformコマンドの実行環境に依存するリージョンを明示的に指定するには「provider」を定義する。
https://www.terraform.io/docs/configuration/providers.html
これも、専用のテンプレートファイルとする。
ちなみに、前述の「outputs.tf」もそうだったけど、「variables.tf」や今回のproviderが「providers.tf」になるようにファイル名には複数形を用いられる(ことが多い?)。
ただし、「vpc.tf」や「security_group.tf」のように各サービスの場合は単数形を用いている様子。

providers.tf
provider "aws" {
  region = "ap-northeast-1"
}

上記のようにしておくと、環境変数として下記のように「us-east-2」がセットされていても、

$ env | grep AWS
AWS_SECRET_ACCESS_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
AWS_DEFAULT_REGION=us-east-2
AWS_ACCESS_KEY_ID=xxxxxxxxxxxxxxxxxxxx

下のように「ap-northeast-1」リージョンでインスタンスが作られる。

Screenshot from 2019-06-26 08-18-20.png

その他プロバイダー設定

プロバイダーバージョン

プロバイダーとしてはAWSを用いる。そのプロバイダーにはバージョンがある。
今どんなバージョンが存在するかは下記参照。
https://github.com/terraform-providers/terraform-provider-aws/blob/master/CHANGELOG.md

セマンティックバージョニングを採用しているので、現時点の安定版バージョンの中でバグフィックス起因のアップデートのみ許容する指定にする。
https://www.terraform.io/docs/configuration/providers.html#version-provider-versions

provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}

これ以外に指定可能な設定については下記参照。
https://www.terraform.io/docs/providers/aws/index.html#argument-reference

Terraform自体の設定

プロバイダーのバージョンも大事だけど、そもそもTerraform自体のバージョンも明示的に固定すべき。
これは以下のように、また別ファイル化しておく。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}

ファイル分割後の構成

$ tree
.
├── ec2.tf
├── outputs.tf
├── providers.tf
├── security_group.tf
├── terraform.tf
└── web-server-setup.sh

※あえて *.tfstate ファイルは表示上、外した。

上記以外の設定については下記参照。
https://www.terraform.io/docs/configuration/terraform.html

この段階でのTerraform関連のソース

https://github.com/sky0621/stepup-terraform/tree/595afc446d7b4c9f72fb81331187a52358175f2e/v0.12/aws/step01n

今回は、まだ6ファイルだけなので、中身を全て再掲しておく。

以下はEC2インスタンスを立てて外部からアクセスするのに必要な定義。

ec2.tf
# https://www.terraform.io/docs/providers/aws/r/instance.html
resource "aws_instance" "web-server" {
  ami                    = "ami-0f9ae750e8274075b" # https://aws.amazon.com/jp/amazon-linux-2/
  instance_type          = "t2.micro"              # https://aws.amazon.com/jp/ec2/instance-types/
  vpc_security_group_ids = ["${aws_security_group.web-server-security-group.id}"]

  user_data = file("./web-server-setup.sh")
}
web-server-setup.sh
#!/bin/bash
sudo yum -y install git
git clone https://github.com/sky0621/go-experiment.git
cd go-experiment
./go-experiment
security_group.tf
# https://www.terraform.io/docs/providers/aws/r/security_group.html
resource "aws_security_group" "web-server-security-group" {
  name = "web-server-security-group"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

↓は、単にterraform applyした時にコンソールに出力してほしい内容。

outputs.tf
output "web-server-public-dns" {
  value = "${aws_instance.web-server.public_dns}"
}

以降は、Terraform自体やAWSプロバイダーの定義。

terraform.tf
terraform {
  required_version = "~> 0.12.0"
}
providers.tf
provider "aws" {
  version = "~> 2.16.0"
  region = "ap-northeast-1"
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JAWSUG HPC: ParallelCluster Workshop

JAWSUG HPC支部 第16回勉強会 ParallelCluster Workshop
https://jawsug-hpc.connpass.com/event/132565/
https://github.com/porcaro33/aws-parallelcluster-workshop

ParallelCluster Workshop Environment

image.png

1. What is ParallelCluster?

  • AWS ParallelCluster は、AWS がサポートするオープンソースのクラスター管理ツールです。このツールは、AWS クラウドでハイパフォーマンスコンピューティング (HPC) クラスターを簡単にデプロイおよび管理するのに役立ちます。オープンソース CfnCluster プロジェクト上に構築されている AWS ParallelCluster を使用すると、AWS にすばやく HPC コンピューティング環境を構築できます。必要なコンピューティングリソースと共有ファイルシステムが自動的に設定されます。AWS ParallelCluster は、さまざまなバッチスケジューラ (AWS Batch、SGE、Torque、Slurm) で使用できます。AWS ParallelCluster では、クイックスタート PoC (概念実証) および本番稼働用デプロイのいずれも可能です。また、AWS ParallelCluster には、DNA シーケンスワークフロー全体を自動化するゲノミクスポータルなど、高度なワークフローを構築することもできます。
  • https://docs.aws.amazon.com/ja_jp/parallelcluster/latest/ug/what-is-aws-parallelcluster.html

2. Infrastructure as Code x GitOps

3. Build Infrastructure

Prerequisites

  • まずはAWSにログイン https://console.aws.amazon.com/
  • Oregonリージョンに移動、ログインしたユーザがAdmin権限であることを確認してください。

Create KeyPair

  • Bastion Serverにアクセスするのに使用するKeyPairを作成します。
  • 後ほどCloudFormationを実行するときにKeyNameを入力するのでメモしといてください。
    AWS Console -> EC2 -> Key Pairs -> Create Key Pair -> Enter key name -> Create image

Run CloudFormation

  • ParallelClusterで構築するクラスターの管理ノード兼踏み台ノードとして、CentOS7をPublic Subnetに構築します。このワークショップではCloudFormationで必要な環境を構築してしまいます。

    • VPC
    • Subnets
    • SecurityGroup
    • Bastion EC2 (CentOS7)
    • Bastion Instance Role
    • S3 Bucket
    • CodeCommit repo
    • ...etc
  • CloudFormationテンプレートをGithubからダウンロード
    https://raw.githubusercontent.com/porcaro33/aws-parallelcluster-workshop/master/pcluster_infrastructure.yml

  • CloudFormation -> Create New Stack -> Choose File -> Next
    image.png

  • Enter Stack Name, Bucket Name, KeyPair Name -> Next
    image.png

  • Next -> checkin at "I acknowledge that AWS CloudFormation..." -> Create
    image.png

  • CloudFormaitonが収束したら、Outputsを見てください。ここの情報をあとでスクリプト編集をするときに使います。このBrowser Tabは残しておいてください。
    image.png

4. Build Pipeline for ParallelCluster

Login to the bastion server

  • CloudFormationで作成したCentOS7サーバにログインします。お好きなssh clientでログインしてください。ユーザ名は"centos"です。
ssh -i <path_to_keypair> centos@<IP_from_CF_OUTPUT>
  • parallelclusterはインストール済みです。確認してみましょう。またhomeに".parallelcluster"というフォルダがあるのを確認しましょう。
pcluster version
ls -la ~/

version.png

Create SSH key

  • CodeCommitにアクセスするためのsshkeyを作成します。public keyを表示して内容をコピーしましょう。
ssh-keygen -f ~/.ssh/id_rsa -q -N ""
cat ~/.ssh/id_rsa.pub
  • AWS Console -> IAM -> Users -> pcluster-admin -> Upload SSH public key -> public key貼り付け -> Upload SSH public key -> copy "SSH key ID" sshkey.png

SSH Config

  • CodeCommitを使うための設定をしていきます。configファイルに先ほどコピーしたSSH key IDをセットします。
cd
git clone https://github.com/porcaro33/aws-parallelcluster-workshop.git
cd aws-parallelcluster-workshop/
vi config
  • SSH key IDをセットしたconfigファイルを.sshフォルダにおいて、パーミッションを変更します。最後にCodeCommitへのsshを確認します。
cp ~/aws-parallelcluster-workshop/config ~/.ssh/config
chmod 600 ~/.ssh/config
ssh git-codecommit.us-west-2.amazonaws.com

ssh_codecommit.png

Working on CodeCommit

  • CodeCommitからgit cloneして空のローカルリポジトリを作っておきます。
cd
git clone ssh://git-codecommit.us-west-2.amazonaws.com/v1/repos/pcluster-git
  • いくつかのファイルとフォルダをgithubからコピーして、CodeCommitにpushします。
cp -r ~/aws-parallelcluster-workshop/configs ~/pcluster-git
cp -r ~/aws-parallelcluster-workshop/projects ~/pcluster-git
cp -r ~/aws-parallelcluster-workshop/appspec.yml ~/pcluster-git/appspec.yml
cd ~/pcluster-git
git add .
git commit -m "initial commit"
git push origin master
  • push完了したら、実際にCodeCommitにアップされているか確認しましょう。
    image.png

  • ここまでで、CodeCommitの準備はできました。ここからはCodePileline, CodeDeployを設定して、Pipelineを完成させます。

Create Pipeline to S3

AWS Cosole -> CodePipeline -> Create pipeline

  • Pipeline name : pcluster-pipeline
  • Service role : Existing service role
  • Role name : -CodePipelineServiceRole-XXXXXX
  • click "Next"
    image.png

  • Source provider : AWS CodeCommit

  • Repository name: pcluster-git

  • Branch name : master

  • click "Next"

image.png

  • click "Skip build stage"

  • Deploy providor : Amazon S3

  • Region : US West - (Oregon)

  • Bucket :

  • Check-in : Extract file before deploy

  • click "Next"
    image.png

  • click "create pipeline"

  • これでParallelclusterで実行するpost_installスクリプトをCodeCommitからS3に送るPipelineができました。Pipelineを作成した時点で一回実行されるのでSucceededになっていることを確認してください。
    image.png

Create Pipeline to EC2

  • CodeDeploy -> Applications -> Create application image.png
  • Application name : deploy2ec2
  • Compute platform : EC2/On-premises
  • Click "Create application"

  • Create deployment group
    image.png

  • Enter a deployment group name : pcluster-mgr

  • Choose a service role : -CodeDeployServiceRole-XXXXXXXX

  • Deploy type : In-plane

  • Environment configuration : Amazon EC2 Instances

  • Key : Name

  • Value : CentOS7 Bastion

  • Uncheck : Enable load balancing

  • Click "Create deployment group"
    image.png

  • Add pipeline

  • pcluster-pipeline -> Edit

  • Edit deploy stage -> Add action

  • Action name : deploy2ec2

  • Action provider : AWS CodeDeploy

  • Input artifact : SourceArtifact

  • Application name : deploy2ec2

  • Deployment group : pcluster-mgr

  • Click "Done"
    image.png

  • Click "Save"

  • Click "Release change"

  • これでParallelClusterのクラスターConfigファイルを、クラスター管理ノードに送るPipelineができました。2つのデプロイがともにSucceededになっていることを確認してください。

  • image.png

5. Launch ParallelCluster

  • pipelineが完成したので、cluster configファイルにbucket nameやsubnet idをセットしてクラスターを起動します。/home/pcluster-gitに移動して下記ファイルを編集してください。
  • パラメータはCloudFormationのOutputから取ってきてください。

Edit cluster config and post_install scripts

  • projects/cpu/scripts/00-cluster-init.sh

    • S3BUCKET
  • projects/cpu/scripts/00-cluster-init.sh

    • S3BUCKET
  • config/cpu

    • master_subnet_id
    • compute_subnet_id
    • vpc_id
    • additional_sg
    • key_name
    • ec2_iam_role
    • s3_read_resource
    • s3_read_write_resource
    • tags
    • post_install
  • ファイルの修正が完了したら、CodeCommitのPush

git add .
git commit -m "first cluster"
git push origin master

Launch a cluster via ParallelCluster

  • Pipelineが流れるのを確認したら、実際の起動コマンドを打つ
cd ~/.parallelcluster
pcluster list -r us-west-2
pcluster create cpu -c cpu
  • 10分くらい待つと完了します。コンソールにIPが表示される。
  • pem keyを踏み台ノードに転送して、踏み台ノードからCluster Masterノードにssh
  • あとはご自由にアプリケーションをインストールして、ジョブスケジューラ使ってあそんでください。
  • configファイルを修正して、git push, pcluster updateってやるとクラスターの設定が更新されます。

6. Terminate workshop environment

delete cluster

cd ~/.parallelcluster
pcluster delete cpu -c cpu

clean-up environment

  • delete s3 objects
  • remove policies from roles of codepipeline, codedeploy and cluster
  • delete pipeline
  • deleter deployment group
  • delete deployment application
  • delete cloudformation stack

fin!

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

AWS Route53 DNS設定メモ

EC2にDNSを割り当ててみる

  1. EC2インスタンスを作成する
    AWSチュートリアルWordPress ウェブサイトの起動をやっておくと簡単に確認できる

  2. EC2ダッシュボードからElastic IPを作成する
    IPをメモしておく

  3. Route53の画面にアクセスする

  4. メニューから「ホストゾーン」を選ぶ

  5. 「ホストゾーンの作成」ボタンをクリックする

  6. ドメイン名の入力欄にドメインを入力する

  7. 作成ボタンを押す

  8. レコードが作成される
    NSレコードとSOAレコードが自動で作成される

  9. 作成したレコードを選択する

  10. 「レコードセットの作成」ボタンをクリックする

  11. Nameのテキストボックスに「www」、Type:「A」、Value:2で設定したIPを入力

  12. 作成ボタンを押す

  13. ブラウザで「www.ドメイン名」にアクセスする
    WordPress ウェブサイトの起動をやっていると、ウェブサイトが表示される

設定完了

その他DNS知識メモ

FQDN(完全修飾ドメイン)

Fully Qualified Domain Nameの略。
ホスト名などを省略せずに指定した形式のこと。

例えばgoogleのドメインは「www.google.co.jp.」
www:第4レベルドメイン
google:第3レベルドメイン
co:第2レベルドメイン
jp:第1レベルドメイン
.:トップドメイン
となる。

レコード

A レコード

AはAdressの略。
ドメインに対応するIPアドレス。

CNAME レコード

CはCanonicalの略。
ドメイン名につけられた別名(FQDN)。
あだ名のようなもの。

MX レコード

MXはMailExchangerの略。
ドメインに対するメールのサーバー(FQDN)。

TXT レコード

TXTはTEXTの略。
ドメインに対するコメント。

NS レコード

NSはNameServerの略。
ドメインに対するゾーンファイル中身で、DNSサーバー名。

SOA レコード

SOAはStart Of Authorityの略。
ドメインに対するゾーンファイルの中身で、ゾーンの管理のための情報。

参考

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

Nuxt.jsとAWSで招待状webページを作ったまとめ

概要

2018年に結婚しました!ので!
ここはエンジニアらしくパーティ招待状Webページをつくってみることにしたのが始まりです。

とりあえず無事にパーティも終わったので、開発時の記憶をさかのぼりながら残す備忘録ですが
申請から開発まで一通り殴り書くので、何かしら参考になれば幸いです。┗(^o^)┛
…ちょっと前の記憶を掘り起こしながらやるので、間違って書いてそうなところもある気がしますが温かい目で御覧ください :bow:

開発環境はMacなので、Winの方は適宜読み替えていただけると幸いです :pray:
AWS上で日本語表示できている部分は、日本語の画面で説明してる…はずです

成果物

webページ

ざっくり3つの画面構成です。(3つ目はフリー素材やOSSの情報なので割愛)
GoogleMapや開催日を記載したホーム画面と、実際に参加者の情報を登録してもらう登録画面の2つです。

ホーム画面 登録画面
スクリーンショット 2019-03-06 13.52.42.png スクリーンショット 2019-03-06 15.38.07.png

システム構成

今回のwebページのシステム概要はこんな感じ

Route53でドメインをとったうえで、「必要なときに必要な程度稼働してくれる」実運用を考えながら、今回そこまでやる必要はないシステムを作ってます。先人たちの知恵借りまくりです。

Github

書いたコードは、以下の2つ。

システム リンク
webページ https://github.com/tyabata/web-invite
api(lambda) https://github.com/tyabata/lambda-api-invite

個人情報とかcommitに入れちゃったのでgitのcommitログだけ消し去ってます :innocent:

採用した技術

関連キーワードの羅列。詳細は次項から説明します。

Server Side

AWS
Lambda DynamoDB API Gateway CloudFront Route53 S3

理由
ちょっと前に自作IoTでGCP使ったので今回はAWS。
業務でスマートスピーカー開発してたときに触ったけど雰囲気でやってたので
復習でもしようかなという気持ちで選択しました。

Client Side

Nuxt.js
Vue.js Vuex Vuetify TypeScript axios PWA PostCSS

直近の業務で、React + Reduxを使っていたので今回はVue。
上記にいろいろ陳列してますがnuxt-tsでほぼ全て用意してるので、PostCSS以外はだいたいコマンド一発。それが今年の1月末の頃…
nuxt-tsは2019/4/5ぐらいにnuxtに統合され 導入方法が変わっています
https://github.com/nuxt/nuxt.js/releases/tag/v2.6.0

「nuxtのconfigにtsの設定いれるのつら」
と思いながら格闘して環境構築完了した後日(2019/1末頃)、Nuxt公式が上をツイートしててnuxt-tsを知り、結果的にはほぼ何もせず 「TypeScript」で「Vue+Vuex」を作る環境ができました。

型はいいぞぉ
型により構造の把握が楽になるし、ちゃんと書けてれば静的に問題に気付けるし以下略

開発手順概要

とりあえずAWSで登録をすませます
https://aws.amazon.com/jp/register-flow/

複数人で開発するならIAMとかで管理アカウントと分けましょう。と言いたいとこですが
今回は一人で かつ お仕事ではないので端折ります。

Server Side

まずは「参加者情報を登録するAPI」「Webサーバの代わりにs3を使う」という流れ

  1. Route53でドメイン取得 -> Certificate ManagerでSSL証明書取得
  2. s3準備 (linkのみ紹介)
  3. aws-cliの導入
  4. Lambda準備からDynamoDBにデータ登録まで
  5. API GatewayLambdaを接続
  6. CloudFrontS3API Gatewayのマルチオリジンにバックポストする際の設定

必要なとき以外、見る必要も見られることもない招待ページなので
コンピューティング時間を減らして省エネ運用の構成をとって…いるように見せかけてやたら色々準備したのは勉強がてら実際に使うことを考えた構成を目指してみたという具合です。

Client Side

Nuxt.jsを利用してページを開発 -> index.htmlを出力してS3にアップロードするまで
を以下の手順で説明していきます。

  1. nuxt-tsで開発できる下準備
  2. ページを作る
  3. index.htmlを出力する
  4. S3にアップロードする

開発詳細 : Server Side

ここからが本題です

ドメイン取得と証明書作成まで

Route53でドメイン取得

まずはドメインを取得します。
新しいドメインの登録 - Amazon Route 53

画像は省きますが、基本は同じ。
リンク先の説明にならって作業をすれば、自分のドメインがつくれます。
トップドメインによってお値段が異なるので、今回は安価でよくみる .netを選択

  • トップドメイン以降の自分でほしい名前を入力
  • チェックの結果がOKであれば、それをカートに入れる (Add to cart)
  • 連絡先などを入力して進むと登録完了
  • 登録したドメインが [Domain registration in progress]の状態からしばらく待つと[All Contacts]になれば完了。SOAレコードとかも同時に作成済みの状態になります。

ドメイン作成はここまでですが、これだけは登録したタイミングで課金が発生します。

Certificate Managerで証明書作成

さすがにwebエンジニアとしてhttpのリンクで友人だけでなく、嫁の知人含めて招待ページ登録してねー。と公開するのは社会の窓全開でご挨拶してる気がするのでサボらずちゃんと作成します。

初回は
[Provision certificates(証明書のプロビジョニング)] => [Get Started(今すぐ始める)] => [Request a Certificated(署名書のリクエスト)]
と選択していくと以下のような画面になると思います。

image.png

ドメイン名の入力欄に先程登録したドメインを入れましょう。
ここではワイルドカード証明書のリクエストもできるので、私は *.hoge.netのような名前で証明書を作成しました。
この後、進めていくとドメイン所有者(つまり自分)に下記のようなメールが飛びます。
image.png

DomainやAccountIDや取得したRegionなどに問題がなければ、メールに記載されている
To approve this request, go to~と書いてあるあたりのリンクから遷移して承認完了させます。
下記のような画面まで行けば、証明書の作成まで完了です。
image.png

実際に証明書を設定したりするのはCloudFrontあたりを扱う項へ。

S3の準備

ほか項目含めて全部書くと、やたら長いドキュメントになるので備忘録としてリンクだけ。
S3 バケットを作成する方法 - Amazon Simple Storage Service

バケット作成後に追加で設定したものは

  • バージョニングの有効化 + ライフサイクルから旧バージョンに対する削除の設定
    • 本番リリース後に問題発覚して戻すことがある場合、バージョン指定で戻せる
    • しかし一度動いてしまえば1日以上たっても変わらない
  • cliコマンドからのデプロイするために対象アカウントのみ書き込みを有効化
  • それ以外の全ユーザーに対してはReadのみ有効

ライフサイクルの設定については、作成したバケットの上部にある「管理」タブから
[ライフサイクルルールの追加]という項目からできます。
上記通り、一時的なロールバックを考慮してバージョニングの有効化をしたので
一日以上たった過去バージョンは削除する。というライフサイクルを設定しました。
(結果的に不要でした)

image.png

アクセス権限については、CloudFrontからアクセスが前提なので
本当は全ユーザー有効設定ではなくCloudFrontからのアクセスに対してReadを与えるような設定が良いと思います。
…色々調べながらやってたので、全ユーザーがreadできる方が都合がよかったのです… :bow: w
(といってもさすがにURLは公開してないです)

とりあえずこのタイミングでは、表示確認のためのindex.htmlに適当になんか書いたものをバケットのrootにおいといてください。

設定についてはこちらを参考にしてみてください。
CloudFront ディストリビューションからのみ S3 バケットへのアクセスを許可する

アクセス権限は適切にね!!

aws-cliの準備

Lambdaやs3にデプロイをするために、AWS用のコマンドラインツールであるaws-cliからデプロイする準備をします。

IAMでユーザーの作成

aws-cliでアクセスする際に使うユーザー設定を行います。
admin使ってもできるんですが、お勉強とお作法的に分けます。

下記リンク等を参考に必要な情報を作成します。
最初の IAM 管理者のユーザーおよびグループの作成 - AWS Identity and Access Management

IAMから、左カラムのナビゲーションメニューから ユーザーを選択し、上部にあるユーザーを追加を選択します。
すると下の画像のように、ユーザーを追加するための設定画面が出てくるので、下の表のように設定していってください。
image.png

項目 設定 補足
ユーザー名 cli ※なんでもOK
アクセスの種類 プログラムによるアクセス
グループの作成 あとで説明 既存のポリシーを直接アタッチすることもできます
タグ なし IAMの管理用です。個人開発で特にいらないので今回は省略

ここまで入力すると、確認の表示が出てくるので問題なければ次に進むと以下のようにアクセスキーとシークレットアクセスキーが取得できます。
どこかにメモっておきましょう。あとのcli設定に使います

image.png

IAMの概念(雑まとめ

グループ作成について説明をしていきます。
AWSに初めて触ると ロール ユーザー グループ ポリシーといろんな言葉がでてきて
チンプンカンプンになる(な気もしてる)ので、私の雑まとめです。

  • ポリシーは 「AというポリシーはDynamoへのRead権限をもつ」といったルール的なもの
  • ユーザー、グループ、ロールはそれぞれポリシーをアタッチして使う
  • ユーザーやグループは人の管理に使う
  • ロールはシステムで使う

という雰囲気理解です。とりあえず関係性が雰囲気でもわかればOKかと。

上の画像で線を足し忘れましたが、ユーザーにポリシーをアタッチできます。
が、複数で開発する場合は「開発」というグループをつくってユーザーをそこに紐づけていくことで、わざわざ個別にポリシーをアタッチする手間を一つにまとめられる利点等があると思います。

  • Lambdaにデプロイするには AWSLambdaFullAccessポリシー
  • S3にデプロイするには AmazonS3FullAccessポリシー

といった具合で今回は

項目 設定値
ユーザー名 cli
グループ名 deployment
ポリシー(グループに対して) AWSLambdaFullAccess,AmazonS3FullAccess

を設定しています。

「勉強だしとりあえず全権限ふっておけ」といった場合は
AdministratorAccessを設定すればOKです。
仕事でやるなら用法用量はまもりまs(ry

aws-cli導入

brewで入れました。brew update行ってinstallから始めていきます。

brew install awscli

[~] aws --version                                            13:29:19
aws-cli/1.16.80 Python/3.7.1 Darwin/18.0.0 botocore/1.12.70

次にアクセスキーの設定をします。

[~] aws configure                                            14:52:40
AWS Access Key ID [****************XXXX]:
AWS Secret Access Key [****************XXXX]:
Default region name [us-east-1]:
Default output format [None]:

Access Key IDAWS Secret Access Keyは先程取得した値を設定してください。
regionはとりあえずTokyo(ap-northeast-1)とかでもいいと思います。
Outputは jsonTextとかありますが、これはご随意に(説明略)

[~] aws s3 ls                                                14:52:50
2018-12-24 20:36:54  hoge.xxxx.net

といったように確認コマンドで結果が返ってくればOKです。

LambdaへのdeployとdynamoDBの設定

とうとうコードがでてきますが、API側はだいぶシンプルに書いたつもりなので概要だけ。
https://github.com/tyabata/lambda-api-invite

APIのロジック

  • Lambdaに対してリクエスト
    • /invitees/userへのGET と /inviteesへのPUTを処理する
    • 実行例外は拾って、500を返すようにしている
  • /invitees/user : GET
    • ページを初めて開いたときにアクセスされる。サーバ側でもつパスワードでcryptoした文字列をUserIDとして返す。UUIDはcrypto-jsを利用して時間から生成してる
    • ページ側がlocalstorageで保存しているので、消さない限りは再リクエストは発生しない(が、消されると新しいユーザーとして再度UUIDを発行します)
    • webページ用にサーバを建てないようにした結果。こうなった :innocent:
  • /invitees : PUT
    • ページに登録した情報とUserIDをセットにして登録リクエスト
    • UserIDが正しく複合できるか確認
    • 問題なければ、登録情報が正しい値かチェックする
    • すべてOKであればDynamoDBに登録

という流れ。

特に嫁側の方に登録時のハードルを上げないようにしたかったので、下を意識してざっくり作りました。

  • ログインせずに登録できる。
  • 一度画面閉じたあとに登録情報を更新できるようにidをもたせておきたい
  • 私の友達が 絶対いたずらデバッグするので簡単に登録APIを叩かれないようにした

lambdaへのデプロイ

シンプルなAPIで aws-cliもいれたので、package.jsonに以下のように記述

"zip": "zip lambda.zip -r node_modules src",
"first": "aws lambda create-function --function-name <lambdaに登録するfunction名> --zip-file fileb://lambda.zip --region <登録するlambdaのregion> --handler src/index.handler --runtime nodejs8.10 --role <登録するfunctionにアタッチするrole>"

これは新規登録用。nodeとかは古いので適当に置き換えてください :bow:
アタッチしているroleについては後で説明します。

コードができたら zip化 -> firstでアップロード&function作成
をしています。

"deploy": "npm run zip && npm run upload",
"upload": "aws lambda update-function-code --function-name <lambdaに登録したfunction名> --zip-file fileb://lambda.zip --profile cli --publish",

こちらは更新用
一つ上のzip化するコマンドにあわせて、awsにアップロードしてfunctionを更新するuploadコマンド。
そして、それらを一発で行うための deployコマンドです。

ここは特別に複雑なことはしてないです。
前項のaws-cli導入AWSLambdaFullAccessがポリシーとしてアタッチされている状態であれば、これで新規登録や更新が完了します。

image.png
Lambdaで登録したregionで開くと、上の通りに登録されていることが確認できると思います。

そしてLambdaにアタッチしているroleですが、 CloudWatchにログを流す DynamoにアクセスしてR/Wするといったポリシーをふったroleをアタッチしています。

詳細説明は省きます :bow:
ただ、Lambdaのログを流すために設定しておくことで、問題があったときにも気づけるので登録しておきましょう。
Amazon CloudWatch Logs とは - Amazon CloudWatch Logs

DynamoDBへ登録

DynamoDBにデータを登録していく準備をします。
上記のリンクからテーブルの作成を選び、テーブル名やプライマリキーを設定して作成を押します。
今回は、前項あたりで説明した UserIDをプライマリキーに設定するため、uid:文字列として設定しました。
…うろ覚えですが、コレ以上はDynamoDBでの設定はなかったはずです。

コード上ではここらへん
https://github.com/tyabata/lambda-api-invite/blob/master/src/dynamo.js#L30

普通のDBみたいにテーブル定義がどうとか行わなくても、データをputすればそのobjectの要素通りにカラムを自動で作ります。そこらへんはお手軽。

ただ、PUTするときに空文字とかを入れようとするとエラーになるので、チェックして弾いてあげるか
設定されていないときのデフォルト値が必要であれば、登録前に足してあげましょう。

const AWS = require('aws-sdk');
// 登録先のregionを設定する
AWS.config.update({
  region: 'us-east-1'
});
const docClient = new AWS.DynamoDB.DocumentClient();
docClient.put(
   {
   // テーブル名
    TableName: "table名をここに",
    Item: item
   },
   (error, data) => { // 割愛

コード概要はこんな感じです。登録などに必要な権限的なものは、実行しているLambdaにアタッチされているロールのポリシーに依存しています。

前項でIAMでユーザーを作ったときと同様に

  • IAMの左カラムのロールからロールを作成
  • ロールを使用するサービスとして Lambdaを選択
  • ロールにアタッチするポリシーとして AmazonDynamoDBFullAccessをセット
  • タグは任意(管理用です)
  • 最後にロール名とロールの説明を書いて完了。

ちなみにFullAccessはいらないぜ。といった場合は、他にもロールがあるのですが適切なものがない場合は独自でポリシーを作成することもできます。が、これもここでは省略します。

動作確認

とりあえずここまでちゃんと設定できているか確認のためにテストしてみましょう。

直接リクエスト送るには、この先のAPI Gatewayへの登録などが必要になります。
しかし、Lambdaのテストを使って、試しに処理を実行させることは可能です。

image.png
Lambdaの関数の画面から、右上のテストボタンの左にあるプルダウンを選択しテストイベントの設定を押します。
そこで、画像のようにhandlerが受け付けたときに処理するbodyを用意してあげることで 疎通確認とDynamoへの登録の確認ができると思います。

API GatewayとLambdaを接続

API Gatewayを使い、そのバックポスト先として先程作成したLambdaのfunctionのARN(Amazon Resource Name)を指定することで、外からhttpリクエストでLambdaを実行できるようになります。

API Gatewayを選択すると、下のように新規作成時の画面が表示されます。

ここではREST API新しいAPIを選択し、API名を入力して作成を開始します。

APIのパスとメソッドの設定


API作成後に上記のような画面になるので アクションからリソースの作成を選択します。

  • リソース名 : 適切な名前
  • リソースパス : 今回は/invitees

をすることで、リソース一覧にパスが追加されます。
/invitees以下にパスを設定する場合は選択した状態で、上のようにアクションから作成を同じ手順で行います。

リクエストメソッドを足す場合も アクションからメソッドの作成を選ぶと、一覧に新しいプルダウンが表示されるのでメソッドを選ぶと、下記のような画面が表示されるので
今回は 総合タイプにLambda関数。Lambda関数に 先程作成したLambda functionのARNを設定してください。

ARNは、Lambdaのfunctionの画面右上にある文字列で
arn:aws:lambda:us-east-1:111111111111:function:invitees_prod
みたいな形式のものです。
その他、regionなどはLambdaにあわせて設定して保存を押すと作成完了です。

image.png

リクエスト/レスポンスの詳細設定

ここから、API Gatewayの詳細を設定していきます。
今回は作ったもののうち、PUT : /inviteesについて説明していきます

項目 概要
メソッドリクエスト 作成したタイミングの値が入ってる。今回はこのまま
総合リクエスト リクエスト情報の制御的なところ。マッピングテンプレートをいじります(後ほど)
Lambda 設定されているLambda functionの名前が表示されていればOK
総合レスポンス 返すレスポンスを制御するところ。ここに400や500のパターンを追加する(後ほど)
メソッドレスポンス こちらも200, 400, 500の3つを追加しておく

リクエストのマッピングについて

コレはパッと身よくわからなくてつらかったので、ここでは概要だけ。
詳細はリファレンスや先人の知恵をおかりするのをオススメします。
API Gateway マッピングテンプレートとアクセスのログ記録の変数リファレンス - Amazon API Gateway

上記で設定した項目のうち 総合リクエストのを選択し、下部にあるマッピングテンプレートの項目を選びます。

「テンプレートが定義されていない場合」を選択し、Content-Typeapplication/jsonを選びます。

image.png

追加すると、さらに下にテンプレートを入力する欄が表示されるので、今回は下のように入力しました。

{
    "method": "$context.httpMethod",
    "body" : $input.json('$'),
    "path" : "$context.resourcePath",
    "headers": {
        #foreach($param in $input.params().header.keySet())
        "$param": "$util.escapeJavaScript($input.params().header.get($param))"
        #if($foreach.hasNext),#end
        #end
    }
}

とりあえずわからなくてもいいです。私もドキュメントにならった説明しかできなないです。
拡張したくなったらリファレンスを読みましょう :innocent:
雑にいうとAPI Gatewayで受けたリクエストを展開してLambdaのコードで受けやすい形に変換してるといったところになると思います。

上のようにテンプレートにはめ込んだものを handlerの第一引数で受け取って処理を実行しています。
https://github.com/tyabata/lambda-api-invite/blob/master/src/index.js#L22

レスポンスのマッピングについて

Lambdaがいくらエラーメッセージを返しても、API Gatewayは200を返してしまいます。
そこで、特定の文字列が含まれるときは 400500で返せるように設定します。

統合レスポンスを選択します
スクリーンショット 2019-06-23 18.04.22.png

ココらへんはシンプルに
.*"status" *: *400.*という文字列がレスポンスに含まれていれば 400を返す
といった設定です。500も同様。

次に一つページをもどって、全体の画面からメソッドレスポンスを選択し、HTTPのステータスという項目に400や500などを追加します。
これで、リクエストに対して適切なステータスコードを返せるようになります。

APIのデプロイ

忘れがちですが、これをやらないと反映されないので、メソッド作成等と同様にアクションから APIのデプロイを押しましょう。
デプロイ対象のステージが表示されるので、選択してデプロイを押すことで初めて反映されます。

image.png

…ステージの説明を書き忘れていましたが、コンソールの左にAPIごとにステージという項目があり
ソレを選択して、 betaとかprodとかステージを分けておくと、開発環境と本番環境を分けておくことができます。

例えば betaステージの場合は、下のようなパスになります。
https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/beta

スクリーンショット 2019-06-23 19.31.37.png
このURLはCloud Frontの設定でも使います。

動作確認

最後に動作確認として、メソッドの実行画面の左側にある 雷マークがついたテストを選択してテストを行います。
メソッドテストと表示されている画面に遷移したら、一番したのリクエスト本文の項目に
リクエストするbodyの情報をセットします。

右側に実行時のログが表示されます。ただしく設定ができていれば、成功時のレスポンス内容がログに表示されると思います。

CloudFrontでS3とAPI Gatewayにバックポストする

やっとServer Sideの説明の最後に来ました…! 総集編?です。

取得したドメインを設定したCloud Frontでリクエストを受け取って、s3からhtmlを返すか、APIにリクエストするかをパスによってバックポストする設定を追加していきます。

2つのオリジンの追加

  • Cloud Frontを開いて、Create Distributionから作成
  • WebGet Startedを選択
  • Origin Domain NameにAPI Gatewayの設定を追加していきます。
  • https://xxxxxxxx.execute-api.us-east-1.amazonaws.com/betaというURLなので下の通りに設定する
    • Origin Domain Name xxxxxxxx.execute-api.us-east-1.amazonaws.com
    • Origin Path /beta
    • Origin IDは勝手に入力されるやつのまま
    • Origin Protocol PolicyHTTPS Only
  • Behavior Settingsは以下の通り
    • Viewer Protocol Policy Redirect HTTP to HTTPS
    • Allowed HTTP Methods GETとPUTが選択できる一番下の項目
  • Distribution Settings
    • Default Root Object : http:web.xxx.net/にアクセスしたときに/index.htmlにアクセスするように設定する

何か漏らしてる気がするけど、これで一旦作成を完了させる。
これでhomeに新しく作成されたDistributionが表示される。
作成完了には少し時間がかかりますが、作成が完了したら次に Create Originでs3の設定を足していきます

image.png

  • Create Originを選択
  • Origin Domain Name でs3のオリジンを選択する(サジェストで表示されます)
  • Origin Access IdentityCreate a New Identiyを選択する。(理想 : こうすることで、s3へのアクセスをcloud frontのみにできる。が、今回はやってないです)

これでs3のオリジンも追加しました。

アクセスしたパスでバックポストするオリジンを分ける

次に Origins and Origin Groupsの隣の Behaviorsタブを選択して
Create behaviorを追加します。

今回は、jsやcssなどをs3に置くので API以外のパスはすべてs3に流れるように設定します

で設定したのが以下の2つのパス

  • Default(*) : S3にバックポストする
    • Viewer Protocol PolicyRedirect HTTP to HTTPS
    • Allowed HTTP MethodsGET HEAD
    • Cache Based on Selected Request Headersは whitelist
      • Originとリクエスト情報だけforwardする(しなくてもいいけど)
  • invitees/* : API Gateway にバックポストする
    • GETとPUTは受け付けられるように
    • Cache Based on Selected Request Headersは whitelist
      • Originと一応Authorizationだけforwardしてる
      • キャッシュがじゃまになるAPIしかないので、cacheの設定は問題ないように修正。

CNAMEや証明書の設定

最初に作った証明書をやっと使うときが来ました。
DistributionのGeneralタブから、Editボタンを押し設定を編集していきます。

  • CNAME(Alternate Domain Names) : web.xxxx.net といった証明書にあわせて自分がつけたホスト名を設定
  • SSL Certificate : Custom SSL Certificateと書いてるほうを選択して先程作成した証明書を選択する。

ソレ以外はだいたいデフォの設定のままでOK

おまけ Error Pageを設定

最後におまけで、Error Pageの設定をします。
ほぼ全部のパスがs3に流れるが、適当なパスを入れるとs3のエラー画面が表示されてしまいs3使ってる感がモロバレなので、設定をします。

image.png

Create Custom Error Responseを選択して

  • HTTP Error Code : 403
  • Error Caching Minimum TTL : よしなに
  • Customize Error Response : YES
    • Response Page Path : /error.html
    • HTTP Response Code : 404

s3がなにもないパスにアクセスしたときに403を返すので、これで代わりにerror用のhtmlを表示させることができます。

動作確認

おつかれさまでした!備忘録的に書いてるので何か漏らしてる気がしますが、
これで問題がなければ 設定したドメインでアクセスしたら s3作成時においた index.htmlが表示でき、APIのパスにアクセスすればAPIの結果が返ってくるようになるはずです。

…もし動かなかったらごめんなさい。 :bow:
ググれば多分解決できる程度にn番煎じネタではあると思います…たぶん

開発詳細 : Client Side

とりあえず一息ついたら次に表示系の開発をしていきましょう。
Serverと比べるとだいぶ楽かもです。

一応補足
https://github.com/nuxt/nuxt.js/releases/tag/v2.6.0
この記事は、2019/04/05にv2.6がでるより前に作ったものの備忘録です。

一応 nuxt-tsを削除してnuxtに置き換えてみたので、気になったら下の変更箇所を見てみてください
https://github.com/tyabata/web-invite/commit/6d99fbbdde76b84b6c8baa4d1c7b73f3a5683afa

NuxtとTypeScriptの開発環境を構築する

ここは、僕が2019/2頃にやった nuxt-tsもすでに古いので、公式や他の皆々様が書いている記事をご覧になる方が良いと思います
https://ja.nuxtjs.org/guide/typescript/

create-nuxt-appで作るところは一緒で下の設定になります。
(対話式で導入するモジュールを選んでいきます)

> Generating Nuxt.js project in /Users/xxxxx/Documents/workspace/vscode/sample
? Project name sample
? Project description My sensational Nuxt.js project
? Use a custom server framework none
? Choose features to install Progressive Web App (PWA) Support, Linter / Formatter, Prettier, Axios
? Use a custom UI framework vuetify
? Use a custom test framework jest
? Choose rendering mode Single Page App
? Author name xxxxx
? Choose a package manager yarn

TypeScript対応はここから下を入れてjsをtsに入れ替えていく作業になると思います。

yarn add ts-node @nuxt/typescript

簡単なページはこれで完結できるのですが
webpackがラップされてる感じなのでアレコレ拡張するのが煩わしく感じることもあると思うので、プロダクトにあわせて使う使わないは判断すれば良いかなと思います。

ページを作る

といっても、基本はVueとVuexです。

  • Nuxtにのっとって書く
  • axiosで用意したAPIと通信をする
  • Vuetifyで基本的なデザイン作成
  • ロジックの分離を意識する

といったぐらいのことしか…やってないかも :thinking:
とりあえず書きます!

create-nuxt-appから少しだけ設定を変える

create-nuxt-appをつくると、ルートにstorepageといったフォルダができます。
ちょっぱやで作る分にはいいのですが、気になる人(自分含む)は
例えばsrcというフォルダ以下に移したい場合はsrcDirというフィールドに設定します。

nuxt.config.ts
const config: NuxtConfiguration = {
  srcDir: 'src/',
  mode: 'spa',
...

また、cssを書く用にPostCSSを入れたかったので

ここに、ページで使うcssを設定
https://github.com/tyabata/web-invite/blob/master/nuxt.config.ts#L47

そしてnuxt.config.jsのbuild以下に下のように設定を入れました。

nuxt.config.ts
  build: {
    extend(config: any, context: any) {
    },
    cssSourceMap: true,
    postcss: {
      plugins: {
        'postcss-import': {},
        'postcss-mixins': {},
        'postcss-preset-env': {},

        'postcss-nested': {},
        // css minify
        csswring: {}
      }
    }
  }

これで設定は完了です。このアプリではsrc/assets/postcsss以下にPostCSSを使ったcss郡がおいてあります。
…post cssをassetは違う気がするな :thinking:

ちなみに テンプレのvueファイルでPostCSSをつかった記述をする場合は下の通りにstyleタグを足します

index.vue
<template>
</template>

<script lang="ts">
</script>

<style lang="postcss" scoped>
  @import '@/assets/postcss/invite.css';
</style>

Vuetifyをつかってレイアウトを作る

Vue.js Material Component Framework — Vuetify.js
今回作ったページはほぼほぼVuetifyの力をつかって調整していて、cssを使ったのは微調整ぐらいでした。
Vueでマテリアルデザインに沿ったレイアウトを作るためのいろんな機能を提供してくれるので
基本的にはタグを埋め込んで終わり。という感じです。

一応元Android開発をしていたので、ToolbarとかSnackbarとかきいて何かわかりますが、聞き馴染みのない方には最初苦労するかもしれません(主に検索で)
比較的公式のドキュメントもまとまっている……と思います。

ページのコンポーネント構造について

そもそも今回作ったコンポーネントの構造を雑に説明するとこんな感じです。

layouts/default.vue
<template>
  <v-app>
    <nuxt/>
  </v-app>
</template>

上はおまじない的なやつです。vuetifyに欠かせない部分になります。
このテンプレートをベースに、index.vueはつくっています

index.vue(概略)
<template>
  <v-container>
    <!-- 下タブの選択によってアニメーションしながら切り替えるためのコンポーネント -->
    <v-window /> 
    <v-snackbar />
    <v-bottom-nav />
  </v-container>
</template>

レスポンシブなページの対応はだいたいVuetifyがやってくれるのでcssはほぼ何もがんばりません。
また、v-bottom-navなどは、ページに下部に固定できたり、カードや検索窓なども提供されているので、アプリっぽいwebページを作るにはとても強力なツールかなと思いました(雑感)

コンポーネント紹介

v-window

Windows — Vuetify.js
ページ遷移のトランジションが簡単にできちゃうコンポーネント
saaaample.gif

    <v-window v-model="activePage" :touchless="true">
      <v-window-item :value="'home'">
        <home/>
      </v-window-item>
      <v-window-item :value="'register'">
        <register/>
      </v-window-item>
      <v-window-item :value="'other'">
        <other/>
      </v-window-item>
    </v-window>

デフォルトは、スワイプによる画面遷移が備わっていますが
調整する暇もないし、タブによるアクションのみにするため
touchless = trueとしています。

あとは上の通りですが、activePageに入ってる値によって出す表示を切り替えているだけです。
ページによって要素の高さが違うと思うので、遷移時に気になる場合はscroll位置の調整などもしてみてください。

ロジックの分離について

今回の要件だけならVueだけでもよいのですが、NuxtでVuexが簡単に入れられるので
意識して実装してみました。

Vuex とは何か? | Vuex

image.png

公式の図からVuexにおけるフロー図です。
このwebページでは、ducksパターンでstoreを作成しています。
React + Reduxでもそうなのですが、action typeactionreducer(Redux) mutation(Vuex)をそれぞれ分けると管理がつらいので、どうせそれぞれが密なものであれば一つのファイルでまとめよう。っていうデザインパターンです。

erikras/ducks-modular-redux: A proposal for bundling reducers, action types and actions when using Redux

あとは

  • Vueでイベントがあれば、Actionを呼ぶ
  • Actionごとの処理をする
  • 処理後に状態の変更をcommitをしてstateに反映する
  • Vue側で新しい状態を反映する

という流れにそって書きました。

それぞれが、役割以上のロジックを持たないようにしましたが
今回はそれとあわせて一つ意識していることについて書いておきます。

「templateは状態の変え方をしらない」ようにする

Smart UIにならないようにしましょうというやつです。
Atomicデザインを目指したり目指さなかったりしても、可能な限りViewで表示以外のロジックを持つべきではないと思います。

例えば Vueファイルのクラス内で、ボタンが押されたときに次のページに遷移するという処理のために

 this.$store.dispatch('goToNextPage', current + 1)

という書き方をしたこともあると思いますが、一度ここで立ち止まって考えてみましょう。

本当に次のページは「currentに1を足した値」でいいのか

「そりゃ1ページの次は2ページでしょ?」といえば普通なのですが、それはあくまで現実の話で
コードに落とし込んだときに、この書き方は「Viewが表示の変え方を+1することと知ってしまっている」状態になります。
もしかしたら、文字列で管理されているかもしれません。

例えばですが、表示系からは「次のページへいく」という振る舞いだけ呼び出し

 this.$store.dispatch('goToNextPage')

そして、Actionで以下のように「次のページは、今のページ + 1である」という振る舞いの詳細を定義する。

goToNextPage(context: ActionContext<IState, any>, payload: any) {
    const nextPage = context.state.current + 1;
...

という書き方で、可能な限りViewからロジックを剥がすことで
そのコンポーネントの再利用性が高まっていくと思います :thumbsup:

余談ですが、ReduxでContainer Componentで書いたとき、connectの第三引数の mergeProps を使うことで、テンプレート側に状態を渡さずに dispatchイベントで現在の状態を知る。といった書き方もできるので、「テンプレート側に値を渡してたぜ!」という方はぜひぜひ。

import { connect } from 'react-redux'
// 色々省略
export default connect(
   mapStateToProps,
   mapDispatchToProps,
   mergeProps // これ
}(component)

redux connect mergePropsとか調べれば色々出てくるかと思います!
react-redux/connect.md at master · reduxjs/react-redux

デプロイをする

最後の工程です。
index.htmlの出力s3のアップロードについて書いていきます。

htmlの出力

今回はサーバまわりを書いてないですが
create-nuxt-appで作っている場合、/pages以下のvueファイルの名前にならってエントリポイントが作成されます。

そして、nuxt generateコマンドはそのエントリでアクセスしたときに表示されるhtmlをファイルとして出力します。

/pages
- index.vue
- error.vue

↓このようなファイルをおいている場合、上のコマンドを実行すると

dist/index.html
dist/error.html

というファイルと関連するcssやjsが出力されていると思います。
こちらをs3にアップロードしていきます。

generateの結果をs3へアップロードする

とうとう最後の作業です。以下のようなコマンドをpackage.jsonのscriptに足しています。

package.json
    "upload": "aws s3 sync ./dist s3://${npm_package_config_bucket} --include \"*\" --acl public-read --cache-control \"max-age=900\" --profile=cli",

ビルドする内容が複雑になりすぎて、scriptに書きたくない量になりそうなら
適宜いい感じにgulpとか使ってあげてください。
npm configの変数については後述しています。

まずaws s3 syncコマンドで、指定のフォルダをまるっとs3の特定バケットにアップロードしています
aws s3 sync ./dist s3://${npm_package_config_bucket}

ソレ以外のオプションについては↓のとおりです。

option 説明
include excludeとあわせて使うオプションです。今回は不要で実はミスしていました
acl s3においたコンテンツに対するACL設定です。 public-readで全Userが参照可能になります
cache-control s3においたコンテンツにCache-Controlを設定します
profile aws-cliにdefault以外のユーザーを設定してるとき、コマンドを実行するユーザー設定を指定します

不要なファイルを上げないようにexcludeといったオプションも細かく設定していくべきなときもありますが、今回は特に気にせず nuxt generateした結果を全部アップロードしています。

これで、全てが完了です(たぶん…)

おまけ: npm configで公開したくない値をscript上で変数化する

ついでですが npm configを使ってgit上にバケット名を公開しないようにしています。
privateな場所であれば気にしなくてもいいですが、今回は最終的にpublicなリポジトリに公開をするため、npm configでscript上で使われる値をセットできます。

例としてpackage.jsonに定義したnameappnameの場合

npm config set appname:bucket <値>

とsetするとscriptで

"hoge": "echo ${npm_package_config_bucket}"

のように npm_package_config_<変数名> 形でscript上で展開することもできます。

動作確認

ここまでやったすべての確認をしていきましょう。

  • 作成したドメインでアクセスができた
  • 作成したドメインのルートにアクセスしたら index.htmlにアクセスするようになっているので、s3にデプロイしたindex.htmlが表示された
  • 初回アクセスで、userIdを取得するために /inviteesのパスにリクエストして、レスポンスが返ってきた
  • 入力フォームに値を入力してOKを押したら登録成功した
  • DynamoDBに登録した情報が表示されている

備忘録的に順にかいてますが、3月前の記憶がベースなのでもしかしたら足りない情報があるかもしれません…

もし失敗してCloudFrontにキャッシュ設定をしたせいでs3を更新しても反映されない。
といった場合、CloudFrontの画面から Invalidationのタブを選択して、Create Invalidationのボタンを押すとパスを入力する画面が表示されると思います。

image.png

こちらの入力欄に/*と入れてInvalidateを実行すると まるっとキャッシュを消してくれます(実行完了に少しだけ時間がかかります)
注意 やりすぎると課金にかかわるので、やりすぎないかお財布と相談してください :bow:

まとめ

言い訳になりますが、2019/2末ぐらいには開発完了していてそれを出してから放置をしていたため、この備忘録も当時の記憶を頼りに書いてるのでだいぶヌケモレあるんじゃないかな…と思います。
フロントにいたっては、あまり特殊実装もしてないのであとは「コード見て」状態になってますね…/(^o^)\

多分おかしいところあるきがするのでアレば教えてください :bow:

とりあえず整理もせずひたすら書いたのでクソ長いですが、
もしここまで読んでくださった方がいたら本当にありがとうございました!

会社だと整った環境があるので、なかなか1から作ることはないからこそ
お勉強としてやってみましたが…それでもAWSで完結するんだから楽ですよね :innocent:

パーティも終わってこれも書き終わったのでAWSの解約をして
趣味のゲームづくり(Unity)に力を注いでいきたいなという そんな今日このごろ

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