EC2 instance起動時にtagをつけるTagSpecifications

AWSCLIでEC2 instance起動時に同時にタグをつける方法としては、instance起動してinstance-idを取得しておいて、パイプでつないでtagをつけたり、スクリプトの中で後でタグ付けする方法があったと思います。
http://kurochan-note.hatenablog.jp/entry/2017/01/08/220155

AWSCLI EC2 Run-Instanceのなかに–tag-specificationsというoptionが入って、run-instancesの中でタグが作成できるようになりました。地味なアップデートかもしれませんが、結構うれしいです。

instanceの詳細はjsonに記述して、下記のように指定して実行します。

aws ec2 run-instances --cli-input-json file://instance.json

EC2は山ほど設定項目があるので、generate-cli-skeltonでフォーマットを出力して、必要な項目だけ入力して、不必要なものは消すとinstanceの詳細を記述したjsonの完成です。Gitにでも入れておきましょう。
http://docs.aws.amazon.com/cli/latest/userguide/generate-cli-skeleton.html

aws ec2 run-instances --generate-cli-skeleton

Instanceの設定詳細を記述したjsonサンプル

instance.json
{
    "ImageId": "<image-id>",
    "KeyName": "<my-key>",
    "SecurityGroupIds": [
        "<my-sgid>"
    ],
    "InstanceType": "<instance-type>",
    "BlockDeviceMappings": [
        {
            "VirtualName": "Root",
            "DeviceName": "/dev/sda1",
            "Ebs": {
                "VolumeSize": 100,
                "DeleteOnTermination": true,
                "VolumeType": "gp2"
            }
        }
    ],
    "Monitoring": {
        "Enabled": false
    },
    "SubnetId": "<subnet-id>",
    "DisableApiTermination": false,
    "IamInstanceProfile": {
        "Name": "<instance-iam-role>"
    },
    "TagSpecifications":[
        {
            "ResourceType": "instance",
            "Tags": [
              {
                "Key": "Name",
                "Value": "<server-name>"
              },
              {
                "Key": "ClusterName",
                "Value": "<cluster-name>"
              },
              {
                "Key": "Application",
                "Value": "<myapp>"
              },
              {
                "Key": "CostCenter",
                "Value": "<my-cost-center>"
              },
              {
                "Key": "Environment",
                "Value": "Test"
              },
              {
                "Key": "User",
                "Value": "<user-name>"
              }
            ]
        },
        {
          "ResourceType": "volume",
          "Tags": [
            {
              "Key": "Device",
              "Value": "<device-name>"
            },
{
              "Key": "CostCenter",
              "Value": "<my-cost-center>"
            },
            {
              "Key": "backup_key",
              "Value": "true"
            }
          ]
        }
    ]
}

続きを読む

EC2のボリューム(EBS)容量拡張方法検証 (AmazonLinux)

結論を3行で

検証

【遂に来た!】EBS でボリュームサイズを変更できるようになりました(ボリュームタイプ変更も) | Developers.IO を参考に、稼働中のインスタンスにアタッチ済みのボリュームのサイズを増やしてみます。

今回ボリュームを増やしたいインスタンスはこのような感じです。

インスタンス的には/dev/xvdaというところに30GBのボリュームがあります。

ec2-user@ip-172-31-10-224 ~ $  df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       30G  3.8G   26G  13% /
devtmpfs        490M   56K  490M   1% /dev
tmpfs           499M     0  499M   0% /dev/shm

ec2-user@ip-172-31-10-224 ~ $  lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  30G  0 disk
└─xvda1 202:1    0  30G  0 part /

ほとんど元記事のとおりですが以下のような感じです。

スクリーンショット_2017-05-18_11_30_15.png

Modify Volumeを押します

スクリーンショット 2017-05-18 11.30.31.png

今回は 30GB -> 100GBにします

スクリーンショット 2017-05-18 11.32.51.png

パフォーマンスが変更されるまで時間がかかるとのこと。。yesを押す。

スクリーンショット 2017-05-18 11.33.00.png

完了。

スクリーンショット 2017-05-18 11.34.08.png

ボリュームの状態は optimizing...0% という表示になりますが、もうこの状態で ディスクの拡張は終わっています。

スクリーンショット 2017-05-18 11.34.46.png

awscli的には $ aws ec2 describe-volumes-modifications と叩くと進捗が表示されます(引数なしでOK)

$ aws ec2 describe-volumes-modifications
{
    "VolumesModifications": [
        {
            "TargetSize": 100,
            "TargetVolumeType": "gp2",
            "ModificationState": "optimizing",
            "VolumeId": "vol-0e92fb2e26dfd9687",
            "TargetIops": 300,
            "StartTime": "2017-05-18T02:34:07.151Z",
            "Progress": 0,
            "OriginalVolumeType": "gp2",
            "OriginalIops": 100,
            "OriginalSize": 30
        }
    ]
}

"Progress": 0 ですが、lsblk を叩くともう反映されていることがわかります。

ec2-user@ip-172-31-3-117 ~ $  lsblk
NAME    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  100G  0 disk             # <- 100になってる
└─xvda1 202:1    0   30G  0 part /

次に resize2fs すればよいのですが、以下のような感じで怒られます。

resize2fs 1.42.12 (29-Aug-2014)
resize2fs: Device or resource busy while trying to open /dev/xvda
Couldn't find valid filesystem superblock.

今回はパーティションの設定がされているためと思われます。パーティションを利用している場合の設定はこちら。http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/storage_expand_partition.html
ただルートパーティションの場合は面倒そうなので、起動時に実行されるresize2fsに任せることにしました。

見たところAmazonLinuxの場合 /etc/cloud/cloud.cfg.d/00_defaults.cfg の中で resize2fs の記述があるので、再起動時に実行されるようです。

こうして、何も考えずにrebootすることにより、dfの結果が変わりました :tada:

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvda1       99G  9.1G   90G  10% /           # <- 99GBに増えてる
devtmpfs        490M   56K  490M   1% /dev
tmpfs           499M     0  499M   0% /dev/shm

続きを読む

クラウドサーバーサービス(IaaS)の性能比較・ディスク編~IOPSの計測~

はじめに

 今回は以下のクラウドサーバーを対象にIOPSを計測し、ストレージの性能を比較します。IDCFクラウドについては初期のゾーンであるhenryと最新ゾーンのluxの2種類を計測し、ゾーン間の違いも計測してみます。

計測対象:

  • Amazon Web Services (AWS) (ボリュームタイプ:gp2/SSD)
  • Google Cloud Platform (GCP) (SSD)
  • IDCFクラウド(ゾーン:lux/SSD)
  • IDCFクラウド(ゾーン:henry/HDD)

1. 前提知識

 ディスクの性能がなぜ大事?IOPSとは?
 (自明な方は読み飛ばして下さい:bow:

1.1 仮想化環境とストレージ性能

  • 仮想化環境ではストレージの性能がネックになりやすい
  • HDDはシーケンシャルI/O、SSDはランダムI/Oが得意
  • 仮想化環境では主にランダムアクセスに対する性能が重要になる(複数の仮想マシンからのアクセスが発生するから)

参考:
 IT機器の進化とHDD性能とのギャップ
 ストレージのいろは|ソフトバンク コマース&サービス
 クラウドを加速させるSSD技術 - @IT

1.2 IOPSとは?

  • ストレージ性能を図る指標
  • ディスクが1秒当たりに処理できるI/Oアクセスの数
  • IOPSが高ければ高いほど,高性能なディスクと言える
  • IOPSはブロックサイズが小さいと大きな値になる傾向がある
  • IOPSは読み込みと書き込みの割合で値が大きく変わる

参考:
 サーバー選択の基礎 – Part4 IOPSを理解する:ITpro
 ストレージのいろは|ソフトバンク コマース&サービス

2. 計測方法

2.1 実施環境

  • 対象サーバ
AWS(EC2) GCP IDCF(HDD) IDCF(SSD)
ゾーン ap-northeast-1 asia-northeast1-a henry lux
マシンスペック m4.large(2vCPUメモリ8GB) n1-standard-2(2vCPUメモリ7.5GB) standard.M8 (2vCPUメモリ8GB) standard.M8 (2vCPUメモリ8GB)
OS CentOS 7 CentOS 7 CentOS 7 CentOS 7
ディスクサイズ 8GB 10GB 15GB 15GB

マシンスペックはAWSのm4.largeに合わせてみました。
ディスクサイズはデフォルトのままとします。

2.1 ツール

 ベンチマークツールとしてfioを使用します。

2.1.1 fioのインストール

  • # yum install epel-release
      まず、EPELのリポジトリをインストールします。

  • # yum repolist all epel*   
      追加したリポジトリが有効かどうか確認。   

  • # yum install fio
      fioをインストール。

2.1.2 計測パラメータの設定

 小さめのブロックサイズの方がベストエフォートに近いはずなので、今回はブロックサイズを4KBに設定し、以下のパターンで比較してみます。

  • ランダムリード
  • ランダムライト
  • ランダムリードライト

 パラメータ条件をfioのパラメータ設定ファイルとして準備します。

Rand-Read-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-Read-4k]
readwrite=randread
size=4G
bs=4k
iodepth=32
numjobs=1
Rand-Write-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-Write-4k]
readwrite=randwrite
size=4G
bs=4k
iodepth=32
numjobs=1
Rand-ReadWrite-4k.fio
[global]
ioengine=libaio
direct=1
ioscheduler=noop
invalidate=1
group_reporting
directory=/home
runtime=60

[Rand-ReadWrite-4k]
readwrite=randrw
size=4G
bs=4k
iodepth=32
numjobs=1

 fioを実行します。
fio -f Rand-Read-4k.fio -filename=/tmp/fio_test -output Rand-Read-4k.result
fio -f Rand-Write-4k.fio -filename=/tmp/fio_test -output Rand-Write-4k.result
fio -f Rand-ReadWrite-4k.fio -filename=/tmp/fio_test -output Rand-ReadWrite-4k.result

2. 計測結果

AWS GCP IDCF(henry) IDCF(lux)
Rand-Read-4k 3110 2749 2664 80282
Rand-Write-4k 3110 1814 2540 41347
Rand-ReadWrite-4k 1555 884 1269 27666

※ 2017/05/15の実測値です。

  • AWSはボリュームタイプをgp2にしたので、3000IOPSがバーストの上限です。これ以上のI/O性能が求められる場合は Provisioned IOPS [PIOPS]を選択するべき、ということですね。
  • IDCFの最新ゾーンであるluxは非常に高いI/O性能を持っていることがわかりました。
  • 同じIDCFでも、古いゾーンであるhenryはluxよりもだいぶディスク性能が劣ることがわかりました。でも、ディスク性能に限って言えばGCPと大差ないようです。

続きを読む

S3への書き込みを比較してみる

環境

項目 設定
OS AmazonLinux (version 2017.03)
インスタンスタイプ t2.micro
ボリュームタイプ gp2
pythonバージョン Python 3.5.3 (pyenv)
AWS CLIバージョン aws-cli/1.11.82
goofysバージョン 0.0.10
s3fsバージョン V1.80

goofysのインストール

こちらの記事を参考にさせてもらいました。
goofysを使ってAmazon LinuxにS3をマウントする。

コマンドメモ
# yum install golang fuse
# export GOPATH=$HOME/go
# go get github.com/kahing/goofys
# go install github.com/kahing/goofys

exportのところは、/root/.bash_profileの末尾に書き足してsourceした。

インストールできたのでバージョンを確認。結構若い。

goofysのバージョン
[root@ip-172-30-0-5 ~]# ./go/bin/goofys --version
goofys version 0.0.10

これ、/usr/sbinかなんかの下に入れたほうがよかったな。
mvしてやろうと思ったけどgoのこと詳しくないからとりあえず触らないでおこう…ださいけど。

では、マウント。

[root@ip-172-30-0-5 ~]# /root/go/bin/goofys jucco-s3-speed-test /goofys
[root@ip-172-30-0-5 ~]# df -h
Filesystem           Size  Used Avail Use% Mounted on
devtmpfs             488M   56K  488M   1% /dev
tmpfs                497M     0  497M   0% /dev/shm
/dev/xvda1           7.8G  2.3G  5.5G  30% /
jucco-s3-speed-test  1.0P     0  1.0P   0% /goofys

特にレスポンス無しでマウントが完了。

s3fsのインストール

本家の手順で実施。
https://github.com/s3fs-fuse/s3fs-fuse

# yum install automake fuse fuse-devel gcc-c++ git libcurl-devel libxml2-devel make openssl-devel
# git clone https://github.com/s3fs-fuse/s3fs-fuse.git
# cd s3fs-fuse
# ./autogen.sh
# ./configure
# make
# make install
[root@ip-172-30-0-5 s3fs-fuse]# s3fs --version
Amazon Simple Storage Service File System V1.80(commit:f02b1bc) with OpenSSL
Copyright (C) 2010 Randy Rizun <rrizun@gmail.com>
License GPL2: GNU GPL version 2 <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

成功したっぽい。こっちは/usr/local/bin/s3fsにファイルを発見。
では、マウント。とりあえずオプションは最低限。なんでIAM Role名指定するんだろう…。

[root@ip-172-30-0-5 s3fs-fuse]# s3fs jucco-s3-speed-test /s3fs -o iam_role=administrator
[root@ip-172-30-0-5 s3fs-fuse]# df -h
Filesystem           Size  Used Avail Use% Mounted on
devtmpfs             488M   56K  488M   1% /dev
tmpfs                497M     0  497M   0% /dev/shm
/dev/xvda1           7.8G  2.3G  5.4G  30% /
jucco-s3-speed-test  1.0P     0  1.0P   0% /goofys
s3fs                 256T     0  256T   0% /s3fs

まず使える容量としてはgoofysが上。ペタバイトかぁ。

簡易測定

ddコマンドで測ってみる。

コマンド
time dd if=/dev/zero of=<path>/1024MB.tmp ibs=1M obs=1M count=1024

結果1 : ddコマンドで比較

goofys s3fs local disk
56.3 MB/s 30.1 MB/s 74.2 MB/s

おおっ、確かに全然違う。

AWS CLIとも比較してみたかったので、平等にローカルディスクからコピーする時間を比較してみた。

コマンド
time cp /root/1024MB.tmp <path>/

or

time aws s3 cp /root/1024MB.tmp s3://jucco-s3-speed-test/

結果2 : cpコマンドで比較

goofys s3fs aws s3 cp local disk
0m21.362s 0m36.652s 0m19.168s 0m15.783s

goofysよりもaws cliが早いのは、file systemとして動かすための余計な処理が挟まっているからかな。
まあでもs3fsと比べるとそんなに大したズレでもないか。

bonnie++で測定

こちらを参考にしました。
https://hesonogoma.com/linux/UsageOfBonnie.html

コマンドメモ
# wget http://www.coker.com.au/bonnie++/bonnie++-1.03e.tgz
# tar xf bonnie++-1.03e.tgz
# cd bonnie++-1.03e
# ./configure
# vi bonnie.h
 ※#define MinTime (0.5) → #define MinTime (0.01)
# make

あとから気づいたけどrpmも転がっている系だったのか…
ともあれ、makeが完了。

[root@ip-172-30-0-5 bonnie++-1.03e]# ./bonnie++ help
usage: bonnie++ [-d scratch-dir] [-s size(MiB)[:chunk-size(b)]]
                [-n number-to-stat[:max-size[:min-size][:num-directories]]]
                [-m machine-name]
                [-r ram-size-in-MiB]
                [-x number-of-tests] [-u uid-to-use:gid-to-use] [-g gid-to-use]
                [-q] [-f] [-b] [-D] [-p processes | -y]

Version: 1.03e

こんな感じで測定する。

コマンド
./bonnie++ -d <path> -n 256:1024:1024:16 -u root

-nのところは「ファイル数 256*1024、最大ファイルサイズ 4096byte、最小ファイルサイズ 512byte、ディレクトリ数 16」という意味らしい。
→やたら時間かかるから途中から-nオプション消した。(が、これがのちに失敗だったことに気づく。測定結果には関係ないけど。

結果をはりたかったけど

早速goofysの測定でコケた。

[root@ip-172-30-0-5 bonnie++-1.03e]# ./bonnie++ -d /goofys -n 256:1024:1024:16 -u root
Using uid:0, gid:0.
Writing with putc()...done
Writing intelligently...done
Rewriting...Can't read a full block, only got 0 bytes.
Can't read a full block, only got 0 bytes.
Bad seek offset
Error in seek(0)

ファイル読み込みに失敗?タイムアップなので今度リトライしてみよう。

[2017/5/5 追記]

bonnie++のソースをgrepしてみたら、とりあえずreadって関数で落ちたみたい。
たぶんこれ。
Man page of READ

ssize_t read(int fd, void *buf, size_t count);

read() はファイルディスクリプター (file descriptor) fd から最大 count バイトを buf で始まるバッファーへ読み込もうとする。

goofysくんはこの関数のリクエストをうまくハンドリングできないってことなのかな…
にわか知識でこれ以上コメントしたくないので、とりあえず期待通りに動作してくれないケースがあることがわかっただけで良しとしよう。

結果3 : bonnie++で比較

s3fsのほうはbonnie++でちゃんと測定できたのでlocal diskと比較。

分類 項目 単位 s3fs local disk
Sequential Output Per Chr K/sec 28,616 67,706
Block K/sec 30,211 67,248
Rewrite K/sec 24,039 59,861
Sequential Input Per Chr K/sec 24,015 62,497
Block K/sec 29,827 62,503
Random Seeks /sec 72.8 10,965
Sequential Create Create /sec 7 141,305
Read /sec 69 1,177,106
Delete /sec 27 183,235
Random Create Create /sec 6 143,461
Read /sec 94 98
Delete /sec 29

※結果が早すぎて測定不可だった。オプションの指定が悪かった。

差がすごい…
特にRandom Create(時間あたりに作成可能なランダムな命名規則のファイル…だと思う)にいたっては23,910倍。
これは使い道を限定するなぁ。

生ログはこちら。

■s3fs
Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
ip-172-30-0-5    2G 28616  28 30211   2 24039   1 24015  22 29827   0  72.8   0
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16     7   0    69   0    27   0     6   0    94   0    29   0
ip-172-30-0-5,2G,28616,28,30211,2,24039,1,24015,22,29827,0,72.8,0,16,7,0,69,0,27,0,6,0,94,0,29,0
■local disk
Version 1.03e       ------Sequential Output------ --Sequential Input- --Random-
                    -Per Chr- --Block-- -Rewrite- -Per Chr- --Block-- --Seeks--
Machine        Size K/sec %CP K/sec %CP K/sec %CP K/sec %CP K/sec %CP  /sec %CP
ip-172-30-0-5    2G 67706  64 67248   3 59861   3 62497  60 62503   1 10965   9
                    ------Sequential Create------ --------Random Create--------
                    -Create-- --Read--- -Delete-- -Create-- --Read--- -Delete--
              files  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP  /sec %CP
                 16 141305 100 1177106  86 183235  98 143461  98 +++++ +++ 185059  99
ip-172-30-0-5,2G,67706,64,67248,3,59861,3,62497,60,62503,1,10965.1,9,16,141305,100,1177106,86,183235,98,143461,98,+++++,+++,185059,99

s3fsは遅すぎて(?)CPU負荷があまりあがってない模様。都度AWS API発行 + NWネックで遅くなっている気がする。

この記事はこのへんでー。

続きを読む

EBSボリュームのサイズ変更をやってみる

環境

項目 設定
OS AmazonLinux (version 2017.03)
インスタンスタイプ t2.micro
ボリュームタイプ gp2
pythonバージョン Python 3.5.3 (pyenv)
AWS CLIバージョン aws-cli/1.11.82

その他詳細

a.拡張対象ディスクの初期状態
{
    "Volumes": [
        {
            "VolumeType": "gp2",
            "VolumeId": "vol-05c927a2a0afec131",
            "Iops": 100,
            "CreateTime": "2017-05-04T01:17:37.368Z",
            "State": "in-use",
            "Size": 10,
            "Encrypted": false,
            "AvailabilityZone": "ap-northeast-1a",
            "Attachments": [
                {
                    "VolumeId": "vol-05c927a2a0afec131",
                    "DeleteOnTermination": false,
                    "State": "attached",
                    "AttachTime": "2017-05-04T01:17:37.000Z",
                    "Device": "/dev/sdb",
                    "InstanceId": "i-074b8466302e6dc1a"
                }
            ],
            "SnapshotId": ""
        }
    ]
}
b.拡張対象ディスクの変更情報は無し
[root@ip-172-30-0-5 work]# aws ec2 describe-volumes-modifications --volume-id vol-05c927a2a0afec131 --region ap-northeast-1

An error occurred (InvalidVolumeModification.NotFound) when calling the DescribeVolumesModifications operation: Modification for volume 'vol-05c927a2a0afec131' does not exist.
c.OSから見た初期状態
[root@ip-172-30-0-5 ~]# lsblk /dev/xvdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvdb 202:16   0  10G  0 disk /mnt
[root@ip-172-30-0-5 ~]# df -h /mnt
Filesystem      Size  Used Avail Use% Mounted on
/dev/xvdb       9.8G   23M  9.2G   1% /mnt

手順

1.拡張対象のディスクを10GiB→11GiBに拡張する

コマンド
aws ec2 modify-volume --volume-id vol-05c927a2a0afec131 --size 11 --region ap-northeast-1
レスポンス
{
    "VolumeModification": {
        "TargetVolumeType": "gp2",
        "OriginalSize": 10,
        "StartTime": "2017-05-04T05:32:52.500Z",
        "TargetIops": 100,
        "TargetSize": 11,
        "OriginalVolumeType": "gp2",
        "OriginalIops": 100,
        "VolumeId": "vol-05c927a2a0afec131",
        "ModificationState": "modifying",
        "Progress": 0
    }
}

2.変更の進行状況をチェック

コマンド
aws ec2 describe-volumes-modifications --volume-id vol-05c927a2a0afec131 --region ap-northeast-1
変更リクエスト発行から間髪入れずに発行した時のレスポンス
{
    "VolumesModifications": [
        {
            "OriginalSize": 10,
            "OriginalVolumeType": "gp2",
            "TargetIops": 100,
            "TargetSize": 11,
            "StartTime": "2017-05-04T05:32:52.500Z",
            "OriginalIops": 100,
            "ModificationState": "modifying",
            "TargetVolumeType": "gp2",
            "Progress": 0,
            "VolumeId": "vol-05c927a2a0afec131"
        }
    ]
}
変更リクエスト発行からちょっと経ってから発行した時のレスポンス
{
    "VolumesModifications": [
        {
            "TargetSize": 11,
            "TargetVolumeType": "gp2",
            "Progress": 2,
            "OriginalVolumeType": "gp2",
            "VolumeId": "vol-05c927a2a0afec131",
            "OriginalSize": 10,
            "StartTime": "2017-05-04T05:32:52.500Z",
            "ModificationState": "optimizing",
            "OriginalIops": 100,
            "TargetIops": 100
        }
    ]
}

状態がoptimizingに進行。{"Progress":"2"} の部分が進捗状況。完了するとこうなる。

拡張完了後
{
    "VolumesModifications": [
        {
            "EndTime": "2017-05-04T05:38:04.434Z",
            "TargetSize": 11,
            "TargetIops": 100,
            "ModificationState": "completed",
            "StartTime": "2017-05-04T05:32:52.500Z",
            "TargetVolumeType": "gp2",
            "OriginalVolumeType": "gp2",
            "VolumeId": "vol-05c927a2a0afec131",
            "Progress": 100,
            "OriginalSize": 10,
            "OriginalIops": 100
        }
    ]
}

1GiB増やすのに05:12って結構かかるのね。
進捗状況は直前まで2%だったので、相変わらず適当なパーセントな模様。

3.OSから見た状態をチェック

コマンド
lsblk /dev/xvdb
[root@ip-172-30-0-5 work]# lsblk /dev/xvdb
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvdb 202:16   0  11G  0 disk /mnt

ちゃんと拡張されました。

回数制限があった気がするので、何回かやってみようとしたところ…

[root@ip-172-30-0-5 work]# aws ec2 modify-volume --volume-id vol-05c927a2a0afec131 --size 12 --region ap-northeast-1

An error occurred (VolumeModificationRateExceeded) when calling the ModifyVolume operation: You've reached the maximum modification rate per volume limit. Wait at least 6 hours between modifications per EBS volume.

あ、そういう制限だったのか。ドキュメントにもあった。

APIReference ModifyVolume

If you reach the maximum volume modification rate per volume limit, you will need to wait at least six hours before applying further modifications to the affected EBS volume.

今度気が向いたら拡張中の性能を測ってみよう。

続きを読む

[AWS] Terraform で EFS 作って、EC2 起動時にマウントさせておく

Terraform を使うと、EFS を作成して EC2 にマウントさせておくなんてことが簡単にできます。
Autoscaling 環境で Web ドキュメントルートを共有したい時とかに便利なんで、みんな使えばいいと思うよ。
なお、この記事の想定読者は AWS はダッシュボードからポチポチしてインスタンス立てたりしてるけど、そろそろインフラをコードで管理したいな。Terraform とか便利そうだねー使ってみたいねーって人です。
てわけで、単純に EC2 立ち上げても面白くないので EFS をマウントさせてみました。

そもそも、Terraform ってなんだ?って人は、以下のページとか参考になると思います。
Terraform簡易チュートリアル on AWS

実際の設定

Terraform は、特定のディレクトリ下にある拡張子が .tf なファイルを全部読み込んでいい感じにリソースを起動してくれます。なので、機能別に .tf 作成していってみましょう。

メイン設定

まず、メインの設定を作成。
プロバイダーとか、設定ファイル内で使用する変数とか設定していってみましょうか。

main.tf
# 今回のプロジェクト名
variable "project" {}
variable "domain" {}

# AWS リソースを起動するリージョンとかの情報
variable "region" { default = "us-west-2" }
variable "azs" {
    default {
        "a" = "us-west-2a"
        "b" = "us-west-2b"
        "c" = "us-west-2c"
    }
}

# AMI ID (Amazon Linux)
variable "ami" { 
    default {
        "us-west-2" = "ami-8ca83fec"
    }
}

# EC2 接続用の SSH 鍵の公開鍵
variable "ssh_public_key" {}

provider "aws" {
    region = "${var.region}"
}

variable で設定した値は tf ファイル内で ${var.region} のようにして参照可能です。
また、terraform の各種コマンドを実行する際に以下のようにパラメータとして変数を渡して上書きすることもできます。

$ terraform plan \
  -var 'project=example' \
  -var 'domain=example.com'

同じディレクトリ内に terraform.tfvars というファイルがあれば、それを読み込んで値が上書きされたりします。この辺の詳細は以下を参照してください。
Input Variables – Terraform by HashiCorp

provider "aws" は、aws を使いますよって宣言です。
以下のように アクセスキーを書いておくこともできますが、それやるとうっかり github とかに公開した時とかに切ない目にあうのでやめたほうが吉でしょう。

provider "aws" {
    access_key = "__ACCESS_KEY__"
    secret_key = "__SECRET_KEY__"
    region = "us-west-2"
}

環境変数 AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY を読み込んでいい感じでやってくれるので、僕は direnv 使って作業ディレクトリ内で環境変数を変更することで対応してます。
(もちろん、この場合でも .gitignore.envrc を含めておいて間違って公開しないようにしないと切ない目にあうので注意)

VPC の作成

こんな感じの .tf ファイルで VPC と subnet が作成できます。

vpc.tf
## VPC
resource "aws_vpc" "app" {
    cidr_block           = "172.31.0.0/16"
    enable_dns_hostnames = true
    enable_dns_support   = true
    instance_tenancy     = "default"

    tags {
        "Name" = "${var.project}"
    }
}

## Subnet
resource "aws_subnet" "a" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.0.0/20"
    availability_zone       = "${lookup(var.azs,"a")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-a"
    }
}

resource "aws_subnet" "b" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.16.0/20"
    availability_zone       = "${lookup(var.azs,"b")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-b"
    }
}

resource "aws_subnet" "c" {
    vpc_id                  = "${aws_vpc.app.id}"
    cidr_block              = "172.31.32.0/20"
    availability_zone       = "${lookup(var.azs,"c")}"
    map_public_ip_on_launch = true

    tags {
        "Name" = "${var.project}-subnet-c"
    }
}

resource "aws_subnet" の中に ${aws_vpc.app.id} ってのが出てきましたね。
Terraform の中では、管理下にあるリソースの情報を他のリソースの設定でも参照することが可能です。
各リソースで使用できる値が異なってくるので、その辺は公式ドキュメント読みましょう。
例えば aws_vpc で使用できる値は aws_vpc を参照すればわかります。

また、${lookup(var.azs,"a")} ってのも出てきましたね。
これは Terraform の組み込み関数です、lookup は配列の中からキーをもとに値を探す関数です。
詳しくは Built-in Functions を読んでください。

ついでに Internet Gateway と Route Table も設定しておきましょう。

route-table.tf
## Internet Gateway
resource "aws_internet_gateway" "igw" {
    vpc_id = "${aws_vpc.app.id}"
}

## Route Table
resource "aws_route_table" "rtb" {
    vpc_id     = "${aws_vpc.app.id}"
    route {
        cidr_block = "0.0.0.0/0"
        gateway_id = "${aws_internet_gateway.igw.id}"
    }
}

resource "aws_route_table_association" "route_a" {
    subnet_id = "${aws_subnet.a.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

resource "aws_route_table_association" "route_b" {
    subnet_id = "${aws_subnet.b.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

resource "aws_route_table_association" "route_c" {
    subnet_id = "${aws_subnet.c.id}"
    route_table_id = "${aws_route_table.rtb.id}"
}

IAM ロールの作成

次に EC2 に割り当てるための IAM ロールを作ってみましょう。
ポリシーは、AWS が用意している AmazonEC2RoleforDataPipelineRole と、EC2 から CloudwatchLogs にログを送信するためのカスタムポリシーを作ってアタッチしてみます。

iam-role.tf
## For EC2 instance Role
resource "aws_iam_role" "instance_role" {
    name               = "instance_role"
    path               = "/"
    assume_role_policy = <<POLICY
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
POLICY
}

## AmazonEC2RoleforDataPipelineRole
resource "aws_iam_role_policy_attachment" "data-pipeline" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforDataPipelineRole"
}

## PutCloudwatchLogs
resource "aws_iam_policy" "put-cloudwatch-logs" {
    name        = "AmazonEC2PutCloudwatchLogs"
    description = ""
    policy      = <<POLICY
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Effect": "Allow",
            "Resource": "*"
        }
    ]
}
POLICY
}

resource "aws_iam_role_policy_attachment" "put-cloudwatch-logs" {
    role       = "${aws_iam_role.instance_role.name}"
    policy_arn = "${aws_iam_policy.put-cloudwatch-logs.arn}"
}

aws_iam_roleassume_role_policy のところと、aws_iam_policypolicy のところでヒアドキュメントが出てきましたね。
こんな風に複数行にわたるインラインポリシーはヒアドキュメントで記述することが可能です。
また、以下のように別ファイルにしておいて読み込ませることも可能です。
管理しやすい方でやってください。

iam-role.tf
resource "aws_iam_role" "instance_role" {
    name               = "instance_role"
    path               = "/"
    assume_role_policy = "${file("data/instance_role_assume_policy.json")}"
}
data/instance_role_assume_policy.json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": "ec2.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

セキュリティグループの作成

EC2 から EFS へのアクセスは 2049 番ポートを介して行われるので、EFS が所属するセキュリティグループに穴を開けないといけません。
EC2 は 80, 443, 22 を解放してみます。

security-group.tf
## For EC2
resource "aws_security_group" "ec2" {
    name        = "${var.project}-EC2"
    description = "for ${var.project} EC2"
    vpc_id      = "${aws_vpc.app.id}"

    ingress = [
        {
            from_port       = 80
            to_port         = 80
            protocol        = "tcp"
            cidr_blocks     = ["0.0.0.0/0"]
        },
        {
            from_port       = 443
            to_port         = 443
            protocol        = "tcp"
            cidr_blocks     = ["0.0.0.0/0"]
        },
        {
            from_port       = 22
            to_port         = 22
            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"]
    }
}

## For EFS
resource "aws_security_group" "efs" {
    name        = "${var.project}-EFS"
    description = "for ${var.project} EFS"
    vpc_id      = "${aws_vpc.app.id}"

    ingress {
        from_port       = 2049
        to_port         = 2049
        protocol        = "tcp"
        security_groups = ["${aws_security_group.ec2.id}"]
    }

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

EFS の作成

こんな感じで EFS が作成できます。
各サブネットごとにマウントターゲットを作成して、そいつをセキュリティグループに所属させる形ですね。

efs.tf
resource "aws_efs_file_system" "app" {
  tags {
        "Name" = "${var.domain}"
  }
}

resource "aws_efs_mount_target" "app-a" {
  file_system_id  = "${aws_efs_file_system.app.id}"
  subnet_id       = "${aws_subnet.a.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

resource "aws_efs_mount_target" "app-b" {
  file_system_id = "${aws_efs_file_system.app.id}"
  subnet_id      = "${aws_subnet.b.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

resource "aws_efs_mount_target" "app-c" {
  file_system_id = "${aws_efs_file_system.app.id}"
  subnet_id      = "${aws_subnet.c.id}"
  security_groups = ["${aws_security_group.efs.id}"]
}

EC2 の作成

さて、いよいよ EC2 です。
ここでは、user-data を使って、初回ローンチ時に EFS をマウントさせてしまいます。
さらにマウントした EFS 内に html/ ってディレクトリを作成して、そいつを /var/www/html にシンボリックリンクしてみましょうか。
と言っても、こんな感じで大丈夫です。

ec2.tf
## IAM Instance Profile
resource "aws_iam_instance_profile" "instance_role" {
    name = "instance_role"
    role = "${aws_iam_role.instance_role.name}"
}

## SSH Key
resource "aws_key_pair" "deployer" {
  key_name   = "${var.project}"
  public_key = "${var.ssh_public_key}"
}

## EC2
resource "aws_instance" "app" {
    ami                         = "${lookup(var.ami,var.region)}"
    availability_zone           = "${aws_subnet.a.availability_zone}"
    ebs_optimized               = false
    instance_type               = "t2.micro"
    monitoring                  = true
    key_name                    = "${aws_key_pair.deployer.key_name}"
    subnet_id                   = "${aws_subnet.a.id}"
    vpc_security_group_ids      = ["${aws_security_group.ec2.id}"]
    associate_public_ip_address = true
    source_dest_check           = true
    iam_instance_profile        = "${aws_iam_instance_profile.instance_role.id}"
    disable_api_termination     = false

    user_data                   = <<USERDATA
#!/bin/bash
az="${aws_subnet.a.availability_zone}"
efs_region="${var.region}"
efs_id="${aws_efs_file_system.app.id}"
efs_mount_target="${aws_efs_mount_target.app-a.dns_name}:/"
efs_mount_point="/mnt/efs/$${efs_id}/$${az}"
web_doc_root="/var/www/html"

# EFS Mount
/usr/bin/yum -y install nfs-utils || /usr/bin/yum -y update nfs-utils
if [ ! -d $${efs_mount_point} ]; then
  mkdir -p $${efs_mount_point}
fi
cp -pi /etc/fstab /etc/fstab.$(date "+%Y%m%d")
echo "$${efs_mount_target}    $${efs_mount_point}   nfs4    defaults" | tee -a /etc/fstab
mount $${efs_mount_point}

# create Web document root
if [ -d $${web_doc_root} ]; then
  rm -rf $${web_doc_root}
fi
if [ ! -d $${efs_mount_point}/html ]; then
  mkdir $${efs_mount_point}/html
  chown ec2-user:ec2-user $${efs_mount_point}/html
fi
ln -s $${efs_mount_point}/html $${web_doc_root}
chown -h ec2-user:ec2-user $${web_doc_root}
USERDATA

    root_block_device {
        volume_type           = "gp2"
        volume_size           = 10
        delete_on_termination = true
    }

    tags {
        "Name"          = "${var.domain}"
    }
}

user_data は長めのシェルスクリプトなので、可読性が悪いから ${file("data/user_data.sh")} とかってやって別ファイルで管理したいですよね。
でも待ってください、ヒアドキュメントでやってるのは理由があるのです。

ヒアドキュメントで書くと、user_data 用のシェルスクリプトの中で Terraform の変数が使えます。
マウントするには EFS の ID とか、マウントターゲットの dns_name とか必要になってきますが、それを作成前に知らなくてもこのように書いておけるのです。便利ですね。
その代わり、user_data 用のシェルスクリプト内でローカルな環境変数を使いたい場合は $${efs_mount_point} のように書いてあげてくださいね。

ざっと、こんな感じです。
慣れちゃえば、tf ファイルを使い回しできるので便利ですよ。
また、すでに作成済みの AWS リソースを Terraform 管理下に置きたい場合は

$ terraform import aws_instance.app ${instance_id}

のようにして管理下に置くことができます。
管理されているリソースは terraform.tfstate というファイルに書き込まれます。
さらに別プロダクトになるのですが Terraforming と言うツールを使用すると、既存の AWS リソースから Terraform 用の tf ファイルを作成したり、terraform.tfstate を作成したりもできるので便利です。
Terraforming については、Terraforming で既存のインフラを Terraform 管理下におく を参考にしてください。

実際にリソース作成してみる

tf ファイル書いたら

$ terraform plan

で、設定ファイルに誤りがないか?既存のリソースへの影響はどの程度あるのかが確認できます。
実際に反映させたい時は

$ terraform apply

で、おっけ。

では、良い Terraform を!

続きを読む

EC2をAnsibleで管理する

はじめに

AnsibleにはAWSのリソースを操作できるモジュールが豊富に用意されています。

今回は、定番のEC2をAnsibleで管理してみます。

やること

  • EC2インスタンス作成

ポイント

ec2モジュールは、セキュリティグループについては名前で指定できるのですが、サブネットはIDで指定する必要があります。

しかし、サブネットIDをAnsibleのYAMLに書きたくないので、サブネット名からIDを取得する実装とします。

前提

AWS関連のモジュール実行にはbotoが必要です。
credential情報は環境変数かaws configureでセットしてある必要があります。

下記リソースを前提に進めます。

  • VPC

    • AnsibleVPC
  • キーペア
    • keypair
  • サブネット
    • public-a
    • public-c
  • セキュリティグループ
    • common
    • web_server

sample

以下のようなEC2インスタンスを作成します。

  • testinstance1

    • AmazonLinux
    • アベイラビリティゾーンA
    • セキュリティグループ
      • common,web_server
  • testinstance2
    • AmazonLinux
    • アベイラビリティゾーンC
    • セキュリティグループ
      • common

ディレクトリ構成

ディレクトリ構成
site.yml
roles/
|--ec2/
|  |--tasks/
|  |  |--main.yml
hosts/aws    #inventory
host_vars/
|--localhost.yml

inventory

AWSリソース関連モジュールはすべてlocalhostで実行するので、下記のようなインベントリファイルを用意します。

hosts/aws
[aws]
localhost

vars

こんな感じに変数を定義します。今回はhost_varsで定義しました。

host_vars/localhost.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    vpc:
      name: AnsibleVPC    # ターゲットのVPC名
    ec2:
      testinstance1:
        ami_image: ami-56d4ad31 # Amazon Linux
        key_name: keypair # キーペア名
        security_group:
          - common
          - web_server
        instance_type: t2.micro
        device_name: /dev/xvda
        device_type: gp2
        volume_size: 8 # EBSのディスクサイズ(GB)
        subnet: public-a # サブネット名
        assign_public_ip: yes
        tags:
          Name: testinstance1
          Role: test
      testinstance2:
        ami_image: ami-56d4ad31 # Amazon Linux
        key_name: keypair # キーペア名
        security_group:
          - common
        instance_type: t2.micro
        device_name: /dev/xvda
        device_type: gp2
        volume_size: 8 # EBSのディスクサイズ(GB)
        subnet: public-c # サブネット名
        assign_public_ip: yes
        tags:
          Name: testinstance2
          Role: test

Role

まずVPCを特定するためにidが必要ですが、こちらと同様、VPC名でidを取得します。

今回はリストではなくディクショナリとしてEC2インスタンスを定義しましたので、with_dictでループさせます。

前述のように、サブネットはIDで指定する必要があるので、ec2_vpc_subnet_factsモジュールでIDを取得します。

定義されたEC2インスタンスの全てのサブネット名とIDのディクショナリを作成し、後続taskで参照します。

あとはec2モジュールで作成しますが、exact_count: 1を指定することで重複作成を防止します(stop状態だと作成されてしまいますが)。

roles/vpc/tasks/main.yml
---
- name: vpc_id取得
  ec2_vpc_net_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      "tag:Name": "{{ my_vars.aws.vpc.name }}"
  register: vpc_net_fact

- debug: var=vpc_net_fact

- name: subnet id取得
  ec2_vpc_subnet_facts:
    region: "{{ my_vars.aws.common.region }}"
    filters:
      vpc_id: "{{ vpc_net_fact.vpcs[0].id }}"
      "tag:Name": "{{ item.value.subnet }}"
  with_dict: "{{ my_vars.aws.ec2 }}"
  register: subnet_fact
  when: my_vars.aws.ec2 is defined

- name: subnet dict作成
  set_fact:
    subnet_dict: >-
      {%- set dict = {} -%}
      {%- for i in range(subnet_fact.results|length) -%}
      {%-   set _ = dict.update({subnet_fact.results[i].subnets[0].tags.Name: subnet_fact.results[i].subnets[0].id}) -%}
      {%- endfor -%}
      {{ dict }}
  when: my_vars.aws.ec2 is defined

- name: EC2インスタンスを作成
  ec2:
    image: "{{ item.value.ami_image }}"
    instance_type: "{{ item.value.instance_type }}"
    region: "{{ my_vars.aws.common.region }}"
    key_name: "{{ item.value.key_name }}"
    group: "{{ item.value.security_group }}"
    vpc_subnet_id: >-
      {%- set id = subnet_dict[item.value.subnet] -%}
      {{ id }}
    instance_tags: "{{ item.value.tags }}"
    assign_public_ip: "{{ item.value.assign_public_ip }}"
    private_ip: "{{ item.value.private_ip }}"
    wait: yes
    wait_timeout: 300
    volumes:
      - device_name: "{{ item.value.device_name }}"
        device_type: "{{ item.value.device_type }}"
        volume_size: "{{ item.value.volume_size }}"
        delete_on_termination: true
    count_tag:
      Name: "{{ item.value.tags.Name }}"
    exact_count: 1
    user_data: |
      #!/bin/bash
      # 初期設定スクリプトなど
  with_dict: "{{ my_vars.aws.ec2 }}"
  register: ec2
  when: my_vars.aws.ec2 is defined

- debug: var=ec2

site.yml

site.yml
---
- name: ec2
  hosts: localhost
  connection: local
  roles:
    - role: ec2

実行

Command
$ ansible-playbook -i hosts/aws -l localhost site.yml

まとめ

ネット上のサンプルでもサブネットIDを指定している例がほとんどですが、実際YAMLで管理する場合、IDではなく名前の方が分かりやすいと思います。
参考になれば幸いです。

参考

続きを読む

AWS CloudFormation(CFn) ことはじめ。(とりあえずシンプルなEC2インスタンスを立ててみる)

AWS CloudFormation(CFn) ことはじめ。(とりあえずシンプルなEC2インスタンスを立ててみる)

まえおき

  • 世のはやりは Infrastructure as Code なのです (博士)
  • AWS でもやって当然なのです (助手)

前提

目的

  • 趣味で AWS いじる範囲だと GUI で EC2 インスタンス建てるのに慣れてしまっていつしか EC2 インスタンスに必要な要素を忘れがちではないですか?

    • セキュリティグループ
    • デフォルトサブネット有無
    • パブリック自動IP付与
    • etc …
  • コード化の際にいろいろ気付きもあり、あとでコードを見返せば必要な要素を一覧することもできるという一挙両得
  • ひとり適当運用で セキュリティグループ や キーペア ぐっちゃぐちゃに増えていってませんか? (自分だけ?)
  • 運用をコード化しつつ見直して行きましょう

今回は CFn の練習がてら EC2 に以下の要素を盛り込んでいきます

  1. AMI

    • Amazon Linux
  2. タイプ
    • t2.micro
  3. 手元の作業PCのから SSH 接続出来るようにする
    • サブネット

      • パブリックIP自動付与
    • キーペア
  4. セキュリティグループ
    • 22 番ポートが開いている
  5. IAM プロファイル(適宜)

実行環境

  • AWS CLI を利用します
  • 適宜 pip 等でインストールして下さい

ref. https://github.com/aws/aws-cli

テンプレートを準備する

  • JSON, YAML などで記載します
  • 自分で使いやすい方を選びましょう
  • JSON は(一般に)コメントが利用できないため、コメントを書き込みたい場合は YAML を選択しましょう

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/cfn-whatis-concepts.html

1. CFn の構文とプロパティ(EC2インスタンス)

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/template-formats.html

  • 基本は以下の入れ子構造になります

  • Resources

    • {リソース名}

      • Type
      • Properties
        • {各プロパティ}
  • YAML の場合は以下 (注意: デフォルトサブネットを削除している場合などで以下のサンプルをそのまま使うと失敗します)

Resources:
        myec2instance:
                Type: "AWS::EC2::Instance"
                Properties:
                        ImageId: "ami-859bbfe2" #Amazon Linux AMI
                        InstanceType: "t2.micro"
  • JSON の場合は以下のようになります (注意: デフォルトサブネットを削除している場合などで以下のサンプルをそのまま使うと失敗します)
{
"Resources" : {
    "myec2instance" : {
        "Type" : "AWS::EC2::Instance",
        "Properties" : {
            "ImageId" : "ami-859bbfe2",
            "InstanceType" : "t2.micro"
            }
        }
    }
}

2. 必要なプロパティを収集しましょう

ref. http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/aws-properties-ec2-instance.html#aws-properties-ec2-instance-prop

必要な機能 プロパティ名 タイプ 種別
AMIイメージID ImageId String ami-859bbfe2 既定値
インスタンスタイプ InstanceType String t2.micro 既定値
サブネットID SubnetId String subnet-f7ea1081 ユーザ個別
キーペア KeyName String aws-tokyo-default001 ユーザ個別
セキュリティグループ SecurityGroupIds List sg-f9b58f9e ユーザ個別
IAMロール名 IamInstanceProfile String ec2-001 ユーザ個別

2-1. AWS CLI (と jq コマンド)を利用すると以下のように AMIイメージID を検索できます

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-images.html
ref. http://docs.aws.amazon.com/ja_jp/AWSEC2/latest/UserGuide/finding-an-ami.html#finding-an-ami-aws-cli

% aws ec2 describe-images --owners amazon \
--filters "Name=name,Values=amzn-ami-hvm-2017*x86_64-gp2" \
| jq '.Images[] | { Name: .Name, ImageId: .ImageId }'
{
  "Name": "amzn-ami-hvm-2017.03.rc-1.20170327-x86_64-gp2",
  "ImageId": "ami-10207a77"
}
{
  "Name": "amzn-ami-hvm-2017.03.0.20170401-x86_64-gp2",
  "ImageId": "ami-859bbfe2"
}
{
  "Name": "amzn-ami-hvm-2017.03.rc-0.20170320-x86_64-gp2",
  "ImageId": "ami-be154bd9"
}

2-2. サブネットも同様に

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-subnets.html

% aws ec2 describe-subnets \
| jq '.Subnets[] | { CIDR: .CidrBlock, PublicIPMapping: .MapPublicIpOnLaunch, SubnetId: .SubnetId }'
{
  "CIDR": "172.31.0.192/26",
  "PublicIPMapping": true,
  "SubnetId": "subnet-7bf8a60d"
}
{
  "CIDR": "172.31.0.128/26",
  "PublicIPMapping": true,
  "SubnetId": "subnet-f7ea1081"
}

2-3. キーペア名

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-key-pairs.html

% aws ec2 describe-key-pairs \
> | jq '.KeyPairs[].KeyName'
"aws-tokyo-default001"

2-4. IAM (以下手抜き)

ref. http://docs.aws.amazon.com/cli/latest/reference/iam/list-roles.html

% aws iam list-roles | jq '.Roles[]'

2-5. セキュリティグループ

ref. http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-security-groups.html

% aws ec2 describe-security-groups | jq '.SecurityGroups[]'

3. テンプレートを書きます

JSONの場合
% cat cloudformation-study.json
{
    "Description" : "test template",
    "Resources" : {
        "myec2instance" : {
            "Type" : "AWS::EC2::Instance",
            "Properties" : {
                "ImageId" : "ami-859bbfe2",
                "InstanceType" : "t2.micro",
                "SubnetId" : "subnet-f7ea1081",
                "KeyName" : "aws-tokyo-default001",
                "SecurityGroupIds" : [ "sg-f9b58f9e" ],
                "IamInstanceProfile" : "ec2-001"
            }
        }
    }
}
YAMLの場合
% cat cloudformation-study.yaml
Resources:
        myec2instance:
                Type: "AWS::EC2::Instance"
                Properties:
                        ImageId: "ami-859bbfe2" #Amazon Linux AMI
                        InstanceType: "t2.micro"
                        SubnetId: "subnet-f7ea1081"
                        KeyName: "aws-tokyo-default001"
                        SecurityGroupIds: [ "sg-f9b58f9e" ]
                        IamInstanceProfile: "ec2-001"

4. では実行します

4-1. ユニークなスタック名を確認します

スタックが1個もない場合の出力例
% aws cloudformation describe-stacks
{
    "Stacks": []
}

4-2. 実行

コマンド
% aws cloudformation create-stack --stack-name create-ec2-001 --template-body file://cloudformation-study.json
実行結果
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2"
}

4-3. 実行ステータスはマネコン(WEB GUI)か、さきほど実行したコマンドで確認できます

  • “StackStatus”: “CREATE_COMPLETE” を確認
% aws cloudformation describe-stacks
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2",
            "Description": "test template",
            "Tags": [],
            "CreationTime": "2017-04-10T07:05:40.261Z",
            "StackName": "create-ec2-001",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}

4-4. EC2 インスタンスが作成されていて、SSH で接続可能なことを確認します

% aws ec2 describe-instances --filter "Name=instance-state-name,Values=running"
以下の例はキーファイルがあるディレクトリで実行したもの
% ssh -i "aws-tokyo-default001.pem" ec2-user@ec2-hogehoge.ap-northeast-1.compute.amazonaws.com

5. テンプレートの更新

5-1. AMI イメージID を ami-0099bd67 に変更してみましょう

% aws cloudformation update-stack --stack-name create-ec2-001 --template-body file://cloudformation-study.json
{
    "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2"
}
% aws cloudformation describe-stacks
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:ap-northeast-1:942162428772:stack/create-ec2-001/1b059fa0-1dbc-11e7-9600-50a68a175ad2",
            "Description": "test template",
            "Tags": [],
            "CreationTime": "2017-04-10T07:05:40.261Z",
            "StackName": "create-ec2-001",
            "NotificationARNs": [],
            "StackStatus": "UPDATE_IN_PROGRESS",
            "DisableRollback": false,
            "LastUpdatedTime": "2017-04-10T07:20:37.436Z"
        }
    ]
}

5-2. 何が起こったか確認できましたか?

Screenshot 2017-04-10 16.22.28.png

からの

Screenshot 2017-04-10 16.22.56.png

最初に作成した EC2インスタンス は自動シャットダウンからターミネートされ、あたらしい EC2インスタンス が起動しました

5-3. はい、そういうことですね

% aws cloudformation describe-stack-events --stack-name create-ec2-001 \
| jq '.StackEvents[] | { ResourceStatus: .ResourceStatus,Timestamp: .Timestamp }'

(snip)

{
  "ResourceStatus": "DELETE_COMPLETE",
  "Timestamp": "2017-04-10T07:22:47.470Z"
}
{
  "ResourceStatus": "DELETE_IN_PROGRESS",
  "Timestamp": "2017-04-10T07:21:40.963Z"
}

(snip)

6. おわり

7. おまけ

  • コードですので github などに置いて差分管理するとなお良いかと
  • 認証情報などをコード内に書かないように注意

https://github.com/hirofumihida/my-cfn

続きを読む

AWS WEBサーバー検証用のTerraform

EC2でWEBサーバーを構築し、さっと検証したいときのTerraform

作成するもの

iamRole
vpc
subnet
ec2
security group

AWS access_key secret_key ec2のkey_nameの設定を各環境ごとにおこなう。

設定

provider "aws" {
  access_key = "******"
  secret_key = "******"
  region     = "ap-northeast-2"
}

data "aws_availability_zones" "available" {}

resource "aws_vpc" "sample-test-vpc" {
  cidr_block           = "10.1.0.0/16"
  instance_tenancy     = "default"
  enable_dns_support   = "true"
  enable_dns_hostnames = "true"

  tags {
    Name = "sample-test-vpc"
  }
}

resource "aws_iam_instance_profile" "base_profile" {
  name  = "BaseIAMRoleProfile"
  roles = ["${aws_iam_role.base_role.name}"]
}

resource "aws_iam_role" "base_role" {
  name = "BaseIAMRole"

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

resource "aws_iam_role_policy" "instance_role_policy" {
  name = "BaseIAMRolePolicy"
  role = "${aws_iam_role.base_role.id}"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
{
      "Effect": "Allow",
      "Action": ["organizations:DescribeOrganization"],
      "Resource": "*"
    }
  ]
}
EOF
}

//subnet とりあえず1つ作る
resource "aws_subnet" "sample-test-subnet" {
  count                   = 1
  vpc_id                  = "${aws_vpc.sample-test-vpc.id}"
  cidr_block              = "10.1.${count.index}.0/24"
  availability_zone       = "${data.aws_availability_zones.available.names[count.index]}"
  map_public_ip_on_launch = true

  tags {
    Name = "sample-test-vpc.sample-test-subnet-${count.index}"
  }
}

resource "aws_internet_gateway" "sample-gw" {
  vpc_id = "${aws_vpc.sample-test-vpc.id}"

  tags {
    Name = "sample-gw"
  }
}

resource "aws_route_table" "sample-route-table" {
  vpc_id = "${aws_vpc.sample-test-vpc.id}"

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = "${aws_internet_gateway.sample-gw.id}"
  }
}

resource "aws_route_table_association" "sample-test-subnet-route-table-association" {
  count          = 2
  subnet_id      = "${element(aws_subnet.sample-test-subnet.*.id, count.index)}"
  route_table_id = "${aws_route_table.sample-route-table.id}"
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners      = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

// EC2インスタンス作成 
resource "aws_instance" "sample-ec2" {
  ami                         = "${data.aws_ami.amazon_linux.id}"
  instance_type               = "t2.micro"
  key_name                    = "your-keyname"
  associate_public_ip_address = true
  iam_instance_profile        = "${aws_iam_instance_profile.base_profile.name}"
  subnet_id                   = "${aws_subnet.sample-test-subnet.0.id}"
  vpc_security_group_ids      = ["${aws_security_group.sample-sec.id}"]

  tags {
    Name = "TEST"
  }
}

//セキュリティグループ 80 443 22 を許可 ip制限は各自で。 
resource "aws_security_group" "sample-sec" {
  name        = "sample-sec"
  description = "test-sg for tf test"

  vpc_id = "${aws_vpc.sample-test-vpc.id}"

  //アウトバウンド

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
  /*インバウンド*/
  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}




続きを読む

AWSのEC2にJenkinsをインストール(1/3)

■流れ

  • EC2インスタンスの作成
  • EC2インスタンスにJavaをインストール
  • EC2インスタンスにJenkinsをインストール & 疎通確認

■EC2インスタンス作成


■AMIの選択

  • Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Typeを選択

■インスタンスタイプの選択

  • t2.mediumを選択
  • 次の手順:インスタンスの詳細の設定

■インスタンスの設定

  • インスタンス数
  • 購入のオプション
  • ネットワーク
  • サブネット
  • 自動割り当てパブリック IP
  • IAM ロール
  • シャットダウン動作
  • 削除保護の有効化
  • モニタリング
  • テナンシー
    の設定を行う

■ストレージの追加

  • サイズ:10GB
  • ボリュームタイプ:汎用SSD(GP2)を選択

■add tags

  • キー:name
  • 値:表示される名前

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

  • SSH TCP 22 IP/サブネット
  • HTTP TCP 80 IP/サブネット
  • HTTPS TCP 80 IP/サブネット
  • カスタムTCPルール 8080 IP/サブネット(疎通確認用)

■確認

  • 作成

続きを読む