AtlassianのLocalStackを使ってみてなんとなく理解するまでのお話

Atlassianが「LocalStack」なんてとても便利そうなものを出していたけど、なかなか使い方を解説しているページが見つからなかったので、とりあえず使いながらなんとなく中身を理解するまでのお話。

https://github.com/atlassian/localstack
スクリーンショット 2017-04-23 17.53.59.png

起動

いくつかGithubで利用方法が紹介されていますが、今回はdockerでの利用をしてみます。

$ docker run -it -p 4567-4578:4567-4578 -p 8080:8080 atlassianlabs/localstack
2017-04-23 08:50:15,876 INFO supervisord started with pid 1
2017-04-23 08:50:16,879 INFO spawned: 'dashboard' with pid 7
2017-04-23 08:50:16,885 INFO spawned: 'infra' with pid 8
(. .venv/bin/activate; bin/localstack web --port=8080)
. .venv/bin/activate; exec localstack/mock/infra.py
Starting local dev environment. CTRL-C to quit.
 * Running on http://0.0.0.0:8080/ (Press CTRL+C to quit)
 * Restarting with stat
Starting local Elasticsearch (port 4571)...
Starting mock ES service (port 4578)...
Starting mock S3 server (port 4572)...
Starting mock SNS server (port 4575)...
Starting mock SQS server (port 4576)...
Starting mock API Gateway (port 4567)...
Starting mock DynamoDB (port 4569)...
Starting mock DynamoDB Streams (port 4570)...
Starting mock Firehose (port 4573)...
Starting mock Lambda (port 4574)...
Starting mock Kinesis (port 4568)...
Starting mock Redshift server (port 4577)...
 * Debugger is active!
2017-04-23 08:50:18,537 INFO success: dashboard entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
2017-04-23 08:50:18,538 INFO success: infra entered RUNNING state, process has stayed up for > than 1 seconds (startsecs)
 * Debugger PIN: 844-652-544
Ready.

とりあえず起動はしたみたい。で、http://localhost:8080/にアクセスしてみたけど、こんな感じで何も表示されず。

スクリーンショット 2017-04-23 17.59.04.png

使ってみてわかりましたが、要は本当に各種サービスが以下のアドレスで利用できるようにしてくれたもののようです。

全部試すのもアレなので、とりあえず馴染み深いDynamoDBとS3を使ってみる。

DynamoDBのテーブル作成

全然関係ないですが、http://localhost:4569/にアクセスすると以下のレスポンスをもらえます。デフォルトはus-east-1で動いている想定のようで。(Githubページにも書いてあった。バインドすればいくつか設定を変更できるようですね。)

healthy: dynamodb.us-east-1.amazonaws.com 

では早速テーブルをCLIから作ってみます。一応作成前にlist-tablesをしてみますが、もちろん何も登録されていません。

$ aws --endpoint-url=http://localhost:4569 dynamodb list-tables
{
    "TableNames": []
}

こちらを参考にさせていただいて、create-tableコマンドを発行します。

$ aws --endpoint-url=http://localhost:4569 dynamodb create-table --table-name test --attribute-definitions AttributeName=testId,AttributeType=S --key-schema AttributeName=testId,KeyType=HASH --provisioned-throughput ReadCapacityUnits=1,WriteCapacityUnits=1
{
    "TableDescription": {
        "TableArn": "arn:aws:dynamodb:us-east-1:000000000000:table/test", 
        "AttributeDefinitions": [
            {
                "AttributeName": "testId", 
                "AttributeType": "S"
            }
        ], 
        "ProvisionedThroughput": {
            "NumberOfDecreasesToday": 0, 
            "WriteCapacityUnits": 1, 
            "ReadCapacityUnits": 1
        }, 
        "TableSizeBytes": 0, 
        "TableName": "test", 
        "TableStatus": "CREATING", 
        "KeySchema": [
            {
                "KeyType": "HASH", 
                "AttributeName": "testId"
            }
        ], 
        "ItemCount": 0, 
        "CreationDateTime": 1492937089.534
    }
}

なんか作られたっぽいですね。もう一度list-tablesをしてみます。

$ aws --endpoint-url=http://localhost:4569 dynamodb list-tables
{
    "TableNames": [
        "test"
    ]
}

出来上がってますね。なるほど、本当にAWS上でやる操作をEndpointURLを変更するだけで操作できてしまうようです。これは思っていたよりも便利かも。

S3のバケットを作成してみる

S3のバケットも作成できるのか試してみます。まずバケットのリストを見てみますが、当然何もありません。

$ aws --endpoint-url=http://localhost:4572 s3 ls
(何も表示されず)

では作成してみます。

$ aws --endpoint-url=http://localhost:4572 s3 mb s3://kojiisd-test/
make_bucket: s3://kojiisd-test/
$ aws --endpoint-url=http://localhost:4572 s3 ls
2006-02-04 01:45:09 kojiisd-test

まじか、これは便利。ただdockerでサービス起動したら停止時に中身が消えてしまうから、できれば作成したものが残るような起動方法の方が色々試そうとしたら適していそうですね。
(作成時間がはちゃめちゃですが、とりあえずそこまで問題にはならないかな)

ちなみにDynamoDBのテーブルとS3のバケットを作成してから気づきましたが、http://localhost:8080/にアクセスしたら、作成したものが表示されていました。なるほど、そのためのDashBoardだったのか。素敵。

スクリーンショット 2017-04-23 18.20.02.png

まとめ

どれくらいどこまで何ができるのかは気になりますが、一般的なことであればだいたいローカルでできるような気がします。しかもEndpointURLを変更するだけで良さそうなので、これはかなり便利かも。今度作成したアプリを全てLocalStackで動かしてみるとかやってみようかな。

とりあえず、もっとこれは知られるべきと、思いました。

続きを読む

EC2のEBSボリュームをresize2fsせずに拡張する

概要

EC2のEBSボリュームを拡張する(10G -> 90G)
EC2はすでに起動済みのものとする。

背景

EBSのボリュームを拡張したので、ファイルシステム拡張のコマンドを実行!

$ sudo resize2fs /dev/xvda1
The filesystem is already 8972864 blocks long.  Nothing to do!

パーティションの容量が少ないためディスクの拡張ができず、怒られてしまいました・・・。
Linux パーティションの拡張が必要そうですが、ちょっと手順が多い。

しかし、サイズをEBSのサイズをあらかじめ拡張し、スナップショットから新規作成することで
コマンドを実行せずともwebコンソールからディスクを拡張できたので手順をメモ。

事前準備

  • EC2で使用してるEBSのID、ルートデバイスの名前を控える。

スクリーンショット 2017-04-19 20.14.28.png

  • EC2のインスタンスID、アベイラビリティーゾーンを控える。

手順

EBSボリュームサイズを拡張する

※EC2を停止しなくても拡張できますが、拡張中は不安定になります。

ELASTIC BLOCK STORE > ボリューム > アクション > Modify Volume
Sizeの値を変更します。

スクリーンショット 2017-04-19 21.38.05.png

EBSボリュームのスナップショットを作成する

※EC2を停止しなくてもスナップショットを作成できますが、停止したほうが安全です。

ELASTIC BLOCK STORE > ボリューム > アクション > スナップショットの作成

EC2を停止する

以降の作業はEC2を停止しないと行えません。

EBSボリュームをEC2からデタッチする

ELASTIC BLOCK STORE > ボリューム > アクション > ボリュームのデタッチ

既存のEBSをデタッチします。

EBSボリュームのスナップショットから新しいボリュームを作成する

ELASTIC BLOCK STORE > スナップショット > アクション > ボリュームの作成

EC2と同じアベイラビリティゾーンにする必要があります。

スクリーンショット 2017-04-19 20.11.33.png

新規作成したEBSボリュームをEC2にアタッチ

ELASTIC BLOCK STORE > ボリューム > アクション > ボリュームのアタッチ

アタッチしたいEC2のインスタンスを選択します。
事前に控えておいたデバイスを入力します。

スクリーンショット 2017-04-19 20.24.48.png

EC2を起動

これで作業完了です!

ディスクボリュームが拡張されているか確認する

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  90G  0 disk
└─xvda1 202:1    0  90G  0 part /

$ df -h
ファイルシス       サイズ  使用  残り 使用% マウント位置
/dev/xvda1            89G   84G   5G   18% /

最後に

使用していないEBSは削除しましょう!
EC2にアタッチしていなくても、料金がかかってしまいます。

続きを読む

CloudFormationの特徴とはじめ方

はじめに

本記事はCloudformationを始めようとしている人向けです。
細かい技術的な話よりは、特徴だったり始め方の手順だったりが書かれています。
公式ドキュメントを舐めるのが面倒な人はちょうどいいです。

概要

インフラ環境をテンプレート化し、何度実行しても同じ環境を簡単に構築することができる。
AWSのアイコンをペタペタ並べていくことでテンプレートを新規作成する「Design Template」機能や、既存のインフラ環境を自動でテンプレート化してくれる「CloudFormer」という機能がある。
また、コードでテンプレートを作成することも可能であり、デフォルトの言語はJSONとなっている。

メリット

大きな特徴として、以下が挙げられる。

  1. インフラがコードで管理できること
  2. 作成・削除・更新が容易であること

何がもたらされるのか

  • コードで管理できるということは、バージョン管理が容易になる。
    「インフラの成長過程がわかる」とよく言われる所以
  • オペレーションミスなし、作業者の技術力に依存しない環境構築が可能になる
  • 運用中、値変更があってもリスクなしでアップデートできる
  • 開発環境など限定的にしか使用しないインフラのランニングコストを削減できる
  • 既存のインフラ環境をすぐに可視化できる(構成図を作成・更新する工数削減)

CloudFormationの金額

無料で使用できる。立ち上がったリソースに対して課金が発生する。

機能

CloudFormation Designer

おなじみのAWSアイコンを配置し、繋いでいくことでテンプレートが作成できる。
作成したDesignは保存、ダウンロード、ローカルから開く、などができる。
アイコンは単純なAWSサービスだけではなく、ルートテーブルやVPCEndpointなど、普段は見ないアイコンも存在する。
理由は、この機能は構成図を作成するためのものではなく「テンプレートを作成する」機能のため、詳細なポリシーまで指定する必要がある。

CloudFormer

既存で構築されているインフラ環境をテンプレートに落とし込める機能。
自動でCloudFormation用のインスタンスを作成し、テンプレート作成準備を数分で構築する。
すべてのCloudFormationのリソースをサポートしている。
注意点としては、新規インスタンスを一台起動させて実行するため、課金が発生する。

詳しい特徴は以下の通り

  • どのリソースをテンプレートに含めるか制御可能
  • リソースを選択すると、従属するリソースも自動選択(変更可能)
  • EC2インスタンスを選択すると、そのインスタンスが起動された元のAMIが指定される

CloudFormerで生成されたテンプレートを手修正し、最終的なテンプレートとして利用することを推奨している

開始手順

CloudFormerスタックを作成する

  1. Cloudformation画面にて「Create New Stack」をクリック
  2. 「Select a sample template」のプルダウンから「Cloudformer」を選択
  3. 「Next」で次ページ
  4. 必要な情報を入力する
  5. Stack Name CloudFormerのスタック名
  6. Password CloudFormerにてテンプレートを作成するページへログインするためのパスワード
  7. Username CloudFormerにてテンプレートを作成するページへログインするためのユーザ名
  8. VPCSelection CloudFormer用インスタンスが立ち上がるVPC。特に変更はしない

  9. 「Next」で次ページ

  10. 必要な情報を入力する(特に設定しなくても進められる)

  11. TagとKeyの設定。リソースにタグを適用できるため、識別や分類したい場合は設定する

  12. Permissionsの権限がないとCloudformationは実行できないため、必ず実行権を持ったロールアカウントにする

  13. 「Next」で次ページ

  14. サマリを確認後、「Create」をクリック

  15. スタック作成が実行されるため、起動完了の「CREATE_COMPLETE」となるまで待機

Cloudformationテンプレートを作成する

  1. CloudFormerのスタックを選択し、「Outputs」タブを選択
  2. 「Value」に表示されるURLを踏み、テンプレート作成画面へ飛ぶ
  3. 「アクセス保護されてないページ」の警告を通過する
  4. テンプレート化を実施するリージョンを選択し「Continue」をクリック
  5. テンプレートに含めたいリソースの選択が続く。含める含めないを考慮しながら「Continue」をクリックしていく
  6. サマリを確認後、「Done」をクリック
  7. テンプレートが作成されているので、S3のどのバケットに保存するか選択する
  8. 終了

Cloudformerスタックを削除する

  1. テンプレート化が確認されたらスタックを削除(インスタンス分が課金されている)
  2. 終了

JSONテンプレート

テンプレートはデフォルトでJSON形式で記述される。
いくつかのセクションに分かれており、そのうち「Resources」セクションのみ必須で記述する必要がある。

セクションは以下の通り

  • Format Version(任意)

    CloudFormationのテンプレートバージョンを指定する
    
  • Description(オプション)
    テンプレートを説明するテキスト文字列。テンプレートのパラメータ等を入力する際に表示される
    
  • Metadata(オプション)
    テンプレートに関する追加情報を提供する
    
  • Parameters(任意)
    実行時にユーザに入力を求めるパラメータを定義する。インフラ構成は変えず、IPやインスタンスタイプを変えたいなどの運用で便利。
    
  • Mappings(任意)
    HashTableのようなもの。条件パラメータ値の指定に使用できる。リージョンやユーザ入力パラメータによって値が変わるものに利用する。Mappingsを利用することでテンプレートの再利用性が向上する
    
  • 条件(オプション)
    条件つきのリソースの制御が可能
    
  • 変換
    サーバレスアプリケーションの場合は、使用するAWS SAMのバージョンを指定する
    
  • Resources(必須)
    インスタンスやS3など、使用するリソースを指定する。
    
  • Output(任意)
    スタック構築後にCloudFormationから出力させる値(DNS名やEIP値など)
    

テンプレート作成のためのいくつか注意点(共通)

  1. CloudFormation Designerでアイコンを並べていくだけではテンプレートとして機能しない
  2. テンプレート作成し保存しただけではCloudFormationは実行できない
  3. 立ち上げたいインフラ環境のすべてをひとつのテンプレートとして作成すべきではない

1. CloudFormation Designerに関わらず、CloudFormationテンプレートではリソース同士の「依存関係」が重要になる

  • ここで言う依存関係とは、リソースを作成する順序
  • AリソースがBリソースを依存関係に指定している場合、Bリソースが作成されるまでAリソースは作成されない
  • この依存関係は、リソースを構築する上で避けて通ることができない
    ※備考※
    VPC内の一部リソースはゲートウェイを必要とする。
    VPC、ゲートウェイ、ゲートウェイアタッチメントを AWS CloudFormation テンプレートで定義する場合、
    ゲートウェイを必要とするリソースはすべて、そのゲートウェイアタッチメントに依存することになる。
    たとえば、パブリック IP アドレスが割り当てられている Amazon EC2 インスタンスは、
    同じテンプレートで VPC リソースと InternetGateway リソースも宣言されている場合、VPC ゲートウェイのアタッチメントに依存する。
    

現在、次のリソースは関連付けられたパブリック IP アドレスを持ち、VPC 内にある場合VPC ゲートウェイのアタッチメントに依存する。(2017年4月現在)

・Auto Scaling グループ
・Amazon EC2 インスタンス
・Elastic Load Balancing ロードバランサー
・Elastic IP アドレス
・Amazon RDS データベースインスタンス
・インターネットゲートウェイを含む Amazon VPC のルート

2. テンプレート作成後、スタックとして成立させる

  • パラメータを設定すること

    インスタンスタイプなどの値はスタック作成時に設定するケースが多い
    

3. 削除したくないリソースを予め決定しておくこと

  • CloudFormationで起動させたインフラ環境を停止する場合、インフラ環境を削除することになる

    削除対象のテンプレート内に固定IPが割り振られてたインスタンスがあった場合、インフラ環境を再構築した際に割り振りが変更してしまう。
    普遍なリソースはテンプレートを別にする
    

参考

AWS Cloudformationユーザガイド
http://docs.aws.amazon.com/ja_jp/AWSCloudFormation/latest/UserGuide/Welcome.html

CloudFormation超入門
http://dev.classmethod.jp/beginners/chonyumon-cloudformation/

AWS Black Belt Tech シリーズ 2015 – AWS CloudFormation
https://www.slideshare.net/AmazonWebServicesJapan/aws-black-belt-tech-2015-aws-cloudformation?qid=9c031227-6000-4a12-bf18-49cda2c6bfe9&v=&b=&from_search=1

続きを読む

OVAファイルをAMIとして取り込む

ステップ 3: イメージとして VM をインポートする – VM Import/Export」に基づいて OVA ファイルをアップロードして AMI にする手順をまとめた。

S3 バケットの作成

OVA ファイルをいったん S3 バケットにアップロードする必要があるため、バケットを作成する。

① 名前とリージョンで以下の指定を実施

  • 「バケット名」に任意の文字列を入力
  • 「リージョン」に適切なリージョン(通常は「アジアパシフィック(東京)」を指定
  • 「次へ」ボタンを押下

② プロパティの設定

  • 必要に応じて設定
  • 「次へ」ボタンを押下

③ アクセス許可の設定

  • (「パブリックアクセス許可を管理」を開き「認証済みの AWS ユーザー」の「読み込み」をチェック)
  • 「次へ」ボタンを押下

④ 確認

  • 「バケットを作成」ボタンを押下

awscli のインストール

brew install awscli
aws configure

今回は Mac で作業したので Homebrew でインストール。環境に応じて適宜対応。

サービスロールの作成

ポリシーの作成

cat << "_EOF_" > trust-policy.json
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Principal": { "Service": "vmie.amazonaws.com" },
         "Action": "sts:AssumeRole",
         "Condition": {
            "StringEquals":{
               "sts:Externalid": "vmimport"
            }
         }
      }
   ]
}
_EOF_
aws iam create-role --role-name vmimport --assume-role-policy-document file://trust-policy.json

create-role コマンドで vmimport というロールを作成する。

ロールへのポリシーのアタッチ

cat << "_EOF_" > role-policy.json
{
   "Version": "2012-10-17",
   "Statement": [
      {
         "Effect": "Allow",
         "Action": [
            "s3:ListBucket",
            "s3:GetBucketLocation"
         ],
         "Resource": [
            "arn:aws:s3:::disk-image-file-bucket"
         ]
      },
      {
         "Effect": "Allow",
         "Action": [
            "s3:GetObject"
         ],
         "Resource": [
            "arn:aws:s3:::disk-image-file-bucket/*"
         ]
      },
      {
         "Effect": "Allow",
         "Action":[
            "ec2:ModifySnapshotAttribute",
            "ec2:CopySnapshot",
            "ec2:RegisterImage",
            "ec2:Describe*"
         ],
         "Resource": "*"
      }
   ]
}
_EOF_
aws iam put-role-policy --role-name vmimport --policy-name vmimport --policy-document file://role-policy.json

disk-image-file-bucket (2箇所) は適宜変更のこと。
put-role-policy コマンドで上記で作成したロールにポリシーをアタッチする。

OVA ファイルのアップロード

aws s3 cp disk-image-file.ova s3://disk-image-file-bucket/

カレントディレクトリーにある disk-image-file.ova ファイルを disk-image-file-bucket バケットにアップロードする。

OVA ファイルのインポート

cat << "_EOF_" > containers.json
[
  {
    "Description": "OVA Disk Image",
    "Format": "ova",
    "UserBucket": {
        "S3Bucket": "disk-image-file-bucket",
        "S3Key": "disk-image-file.ova"
    }
  }
]
_EOF_
aws ec2 import-image --description "OVA Disk Image" --disk-containers file://containers.json

JSON ファイル内に "Description": があるが、オプションでも --description を指定しないと以下のエラーが出る。

A client error (InvalidParameter) occurred when calling the ImportImage operation: The service role <vmimport> does not exist or does not have sufficient permissions for the service to continue

ステータスの確認

aws ec2 describe-import-image-tasks --import-task-ids import-ami-XXXXXXXX
IMPORTIMAGETASKS    OVA Disk Image  import-ami-XXXXXXXX 2   active  pending
SNAPSHOTDETAILS 0.0 OVA
USERBUCKET  disk-image-file-bucket  disk-image-file.ova
IMPORTIMAGETASKS    OVA Disk Image  import-ami-XXXXXXXX 28  active  converting
SNAPSHOTDETAILS 771973632.0 VMDK
USERBUCKET  disk-image-file-bucket  disk-image-file.ova
IMPORTIMAGETASKS    OVA Disk Image  import-ami-XXXXXXXX 30  active  updating
SNAPSHOTDETAILS 771973632.0 VMDK
USERBUCKET  disk-image-file-bucket  disk-image-file.ova
IMPORTIMAGETASKS    OVA Disk Image  import-ami-XXXXXXXX 37  active  updating
SNAPSHOTDETAILS 771973632.0 VMDK
USERBUCKET  disk-image-file-bucket  disk-image-file.ova
IMPORTIMAGETASKS    x86_64  OVA Disk Image  import-ami-XXXXXXXX BYOL    Linux   59  active  booting
SNAPSHOTDETAILS /dev/sda1   771973632.0 VMDK
USERBUCKET  disk-image-file-bucket  disk-image-file.ova
IMPORTIMAGETASKS    x86_64  OVA Disk Image  ami-XXXXXXXX    import-ami-XXXXXXXX BYOL    Linux   completed
SNAPSHOTDETAILS /dev/sda1   771973632.0 VMDK    snap-XXXXXXXXXXXXXXXXX
USERBUCKET  disk-image-file-bucket  disk-image-file.ova

watch コマンドを使ってもいいかもしれない。

続きを読む

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ではなく名前の方が分かりやすいと思います。
参考になれば幸いです。

参考

続きを読む

Mastodonでgit pullしたらビルドエラー

概要

EC2で稼働させているMastodongit pullした後にdocker-compose buildしたら下記エラーが出てハマった。

環境

  • AWS EC2(t2.medium)
  • Ubuntu Server 16.04 LTS

エラー内容

ubuntu@ip-***-***-**-**:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 460d49537428
Step 7/9 : RUN BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 68a8d45af6e8
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
ERROR: unsatisfiable constraints:
  build-base (missing):
    required by: world[build-base]
  ffmpeg (missing):
    required by: world[ffmpeg]
  file (missing):
    required by: world[file]
  imagemagick (missing):
    required by: world[imagemagick]
  libpq (missing):
    required by: world[libpq]
  libxml2 (missing):
    required by: world[libxml2]
  libxml2-dev (missing):
    required by: world[libxml2-dev]
  libxslt (missing):
    required by: world[libxslt]
  libxslt-dev (missing):
    required by: world[libxslt-dev]
  nodejs (missing):
    required by: world[nodejs]
  postgresql-dev (missing):
    required by: world[postgresql-dev]
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs     libpq     libxml2     libxslt     ffmpeg     file     imagemagick  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 11

解決方法

$ git checkout $(git describe --tags `git rev-list --tags --max-count=1`)

ハマってる最中にREADMEに安定バージョン使ってねって追記されてましたとさ。。

追記

v1.2にしたところ下記のエラーが再現。調査中。

ubuntu@ip-***-**-**-***:~/mastodon$ docker-compose build
redis uses an image, skipping
db uses an image, skipping
Building streaming
Step 1/9 : FROM ruby:2.4.1-alpine
 ---> 5eadd5d1419a
Step 2/9 : LABEL maintainer "https://github.com/tootsuite/mastodon" description "A GNU Social-compatible microblogging server"
 ---> Using cache
 ---> 95a4b711ef32
Step 3/9 : ENV RAILS_ENV production NODE_ENV production
 ---> Using cache
 ---> 499e95f00e13
Step 4/9 : EXPOSE 3000 4000
 ---> Using cache
 ---> 167a91f421f4
Step 5/9 : WORKDIR /mastodon
 ---> Using cache
 ---> e185ae07f027
Step 6/9 : COPY Gemfile Gemfile.lock package.json yarn.lock /mastodon/
 ---> Using cache
 ---> 06b33c54cfd4
Step 7/9 : RUN echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*
 ---> Running in 0b9005a839d9
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/main/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/main: No such file or directory
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
fetch http://dl-cdn.alpinelinux.org/alpine/v3.4/community/x86_64/APKINDEX.tar.gz
ERROR: http://dl-cdn.alpinelinux.org/alpine/v3.4/community: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
fetch https://nl.alpinelinux.org/alpine/edge/main/x86_64/APKINDEX.tar.gz
140162439957356:error:140770FC:SSL routines:SSL23_GET_SERVER_HELLO:unknown protocol:s23_clnt.c:794:
ERROR: https://nl.alpinelinux.org/alpine/edge/main: Permission denied
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
OK: 21 MiB in 29 packages
WARNING: Ignoring APKINDEX.167438ca.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.a2e6dac0.tar.gz: No such file or directory
WARNING: Ignoring APKINDEX.65bdaf85.tar.gz: No such file or directory
WARNING: The repository tag for world dependency 'nodejs@edge' does not exist
WARNING: The repository tag for world dependency 'nodejs-npm@edge' does not exist
WARNING: The repository tag for world dependency 'imagemagick@edge' does not exist
ERROR: Not committing changes due to missing repository tags. Use --force to override.
ERROR: Service 'streaming' failed to build: The command '/bin/sh -c echo "@edge https://nl.alpinelinux.org/alpine/edge/main" >> /etc/apk/repositories  && BUILD_DEPS="     postgresql-dev     libxml2-dev     libxslt-dev     build-base"  && apk -U upgrade && apk add     $BUILD_DEPS     nodejs@edge     nodejs-npm@edge     libpq     libxml2     libxslt     ffmpeg     file     imagemagick@edge  && npm install -g npm@3 && npm install -g yarn  && bundle install --deployment --without test development  && yarn --ignore-optional  && yarn cache clean  && npm -g cache clean  && apk del $BUILD_DEPS  && rm -rf /tmp/* /var/cache/apk/*' returned a non-zero code: 255

続きを読む

AWSの無料SSLを使ってmastodonインスタンスを立てる手順

サーバー周りをAWSで固めてmastodonインスタンス( https://mastodon.kumano-ryo.tech )を立てたので、その手順と資料をまとめました。

SSLは無料で使えますが、サーバーの使用量とかは普通にかかります。とはいえ、お1人〜10人鯖ぐらいならEC2のmicroでも動かせるので、そんなにお金はかからなそう。
立て終わった後に書いたので、記憶違いなどあるかもしれません。コメント・編集リクエストしてください :pray:

なぜAWSなのか

人数が少ないときは安く済ませられるし、リソースが足りなくなってもお金を突っ込めばスケールできるから(要出典)。

必要なもの

  • ドメイン
  • メールを配信する手段
  • AWSのアカウント
  • docker・Web・Linux・AWSなどについての基本的な知識

構成

  • mastodonの公式レポジトリに出てくるdocker-compose.ymlを使う
  • 無料SSLを使うためにRoute53とEastic Load Balancerを使う
  • Redisを外に出すためにElastic Cacheを使う(Optional)
  • Postgresを外に出すためにRDSを使う(Optional)
  • メール送信サービスとしてはSendGridを使った
    • 本筋とは関係ないので今回は省略
    • SMTPが使えればなんでもいい

手順

  1. 設定ファイルを埋める
  2. SSL接続を可能にする
  3. HTTPでのアクセスを禁止する
  4. 画像や動画などをS3に保存するようにする
  5. RedisとPostgresを外に出す(Optional)

設定ファイルを埋める

まずはEC2のインスタンスを立てましょう。OSはubuntu、インスタンスタイプはmediumでいいと思います。microにすると、CPUだかメモリが足りなくてAssetsのコンパイルでコケます。
しかし、サーバー自体はmicroでもそこそこ快適に動くので、デプロイが終わったらmicroにしましょう。
Security Groupの設定については、とりあえず80番と22番が任意の場所から接続できるようになってれば良いです。

そしてEC2上で、mastodon本体をcloneしてきます:

$ git clone https://github.com/tootsuite/mastodon.git

公式のREADMEを参考に、mastodonの設定をします。この時、LOCAL_HTTPSについては、falseにしてください

sudo docker-compose up まで行けたら、EC2インスタンスの80番ポートを3000番ポートにリダイレクトします:

$ sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port 3000

これにて http://yourdomain.com/から、mastodonインスタンスにアクセスできるようになりました!!!!

SSL接続を可能にする

とはいえhttpでログインするのは怖いので、SSLの設定をしましょう。
Elastic Load Balancerの無料SSLを使います。

ELBの作成

Elastic Load Balancerを作ります。
EC2のダッシュボード > ロードバランサー > ロードバランサーの作成から、ロードバランサーを作成します:

  • リスナー

    • HTTPSとHTTPの両方を選んでください
  • 証明書の選択

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

    • 任意のIPアドレスからHTTPSが受けられるようなセキュリティグループを作成して設定する
  • ターゲットグループ

    • HTTPだけを追加する
  • ヘルスチェック

    • HTTPで/aboutにする
  • ターゲットの登録

    • 先ほど作成したEC2のインスタンスを選択する

      • ポートは80番を選択する

※作成されたELBのarnはメモっておきましょう。

ELBとインスタンスの接続

EC2ダッシュボード > ターゲットグループから、先ほど作成したターゲットグループを選び、「ターゲット」に登録してあるEC2インスタンスの状態がhealthyであることを確認する。

もしunhealthyであれば、ドキュメント等を参考にして解決しましょう。

ドメインとRoute 53を連携する

ドメインをELBで使えるようにします。
Route 53 > HostedZones > Create Hosted ZonesからHostedZonesを作成します。
HostedZoneを作成したら、ドメインのNSをHostedZonesのNSの値で置き換えます(参考: お名前.comのドメインをAWSで使用する4つの方法
)。
そして、Create Record Setから、Type ARecord Setを作成します。この時、ALIASをYesにして、Alias Targetを先ほどのELBのarnに設定します。

Congrats!

ここまでの手順で、https://yourdomain.comから自分のmastodonインスタンスにアクセスできるようになったはずです!やったね!

もし繋がらなかった場合は、ELBのセキュリティグループやターゲットグループの設定を見直してみてください。

最終的な状態では、

  • ELBにはHTTPSかHTTPでアクセスできる

    • ELBのリスナーに80番ポートと443番ポートが設定されている
  • ELBに入ってきたHTTPSまたはHTTPのアクセスは、EC2の80番ポートに転送される
    • ターゲットグループの「ターゲット」のEC2インスタンスの「ポート」の欄が80である
  • EC2のインスタンスは80番ポートでアクセスを受ける
  • 以上を妨げないようなセキュリティグループの設定である
    • ELBとEC2やその他のAWSのサービスが、すべて別のセキュリティグループを持つ

が満たされているはずです。

HTTPでのアクセスを禁止する

前節まででSSLでのアクセスが実現されましたが、依然HTTPでのアクセスも可能です。
EC2インスタンスへのアクセスがELB経由で行われるようにし、また、HTTPでアクセスされたときはHTTPSにリダイレクトするようにしましょう。

まず、EC2インスタンスのセキュリティグループのインバウンドルールについて、HTTPの行の「送信元」の欄に、ELBのセキュリティグループのID(sg-********)を入力して保存します。
これによって、ELBからのみEC2インスタンスにHTTPSでアクセスできるようになりました。

次に、EC2インスタンスの中で、以下のような設定ファイルでnginxを起動します:

server {
  listen 80;

  location / {
    if ($http_x_forwarded_proto != 'https') {
      rewrite ^ https://$host$request_uri? permanent;
    }
  }
}

これにより、HTTPアクセスをHTTPS付きのURLにリダイレクトすることができます。

画像や動画などをS3に保存するようにする

この状態だと、アップロードされた画像・動画やユーザーのアイコン画像は、すべてdockerコンテナの中に保存されます。
そのため、sudo docker-compose downするたびに、画像と動画が消えてしまうし、アイコン画像もなくなってしまいます。
これではうまくないので、メディアファイルのストレージをS3に設定しましょう。

まず、S3のバケットを作成します。バケットの名前とリージョンは覚えておきましょう。
次に、AWSのIAMコンソールからユーザーを作成して、アクセスキーを取得します。このとき、既存のポリシーを直接アタッチから、** AmazonS3FullAccess**を追加しておきましょう。

そして、.env.production.sampleのコメントアウトしてある設定を利用して、以下のように設定を書き加えます:

S3_ENABLED=true
S3_BUCKET=${your-bucket-name}
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
S3_REGION=${your-bucket-region}
S3_PROTOCOL=https
S3_ENDPOINT=https://s3-${your-bucket-region}.amazonaws.com

これで、画像・動画がS3にアップロードされるようになりました。

RedisとPostgresを外に出す(Optional)

前節までで、所望のmastodonインスタンスが手に入ったと思います。
この構成では、中央のEC2インスタンスの中で、Rails・Redis・Postgres・Sidekiq・nodejsの5つのDockerコンテナが動いていることになり、負荷が集中しています。

ところで、AWSはRedis専用のインスンタンスを提供していますし(ElasticCache)、postgres専用のインスタンス(RDS)も提供しています。
実際どのぐらい効果があるのかわかりませんが、これらのコンテナを中央のEC2から切り離してみましょう。

この辺からは適当です。

インスタンスを立てる

PostgresとRedisのインスタンスを立てます。

このとき、Postgresの設定は、mastodonのレポジトリ内の設定ファイル(.env.production)のDB_*変数に合わせて適当に変えましょう。
DB_HOSTの値は後で取得します。Redisも同様です。

次に、PostgresとRedisのセキュリティグループを編集して、EC2のインスタンスからアクセスできるように設定します。
「HTTPでのアクセスを禁止する」の節と同じ要領でできます。

Postgresのインスタンスタイプについてですが、無料枠に収まるようにmicroにしましたが、ちゃんと動いてるようです。Redisのインスタンスは、無料枠が無いようなので、適当にmicroにしました。今のところ大丈夫そうです。

PostgresとRedisのEndpointをメモっておきます

.env.productionを編集

.env.productionREDIS_HOSTDB_HOSTを、先程メモったEndpointに書き換えます。

docker-compose.ymlを編集

mastodonのdocker-compose.ymlを修正して、dbコンテナとredisコンテナの部分をコメントアウトしましょう。また、他のコンテナのdepend_onの部分もそれに合わせてコメントアウトします。

再起動

$ sudo docker-compose build
$ sudo docker-compose up -d

にてmastodonを再起動します。
これによって、RedisとPostgresがAWSのサービスを利用する形で切り出されたことになります。

その他

続きを読む

Serverlessでデプロイするアカウントのプロファイルを変更する

Serverless側で何も設定していない状態では、[default]のプロファイルが選択されるようです。プロファイルを指定してデプロイする方法を説明します。

serverless.xmlの編集

provider.profileに使用するプロファイル名を指定します。

serverless.yml
provider:
  name: aws
  runtime: nodejs6.10

# you can overwrite defaults here
  stage: dev
  region: ap-northeast-1
  profile: vsv1 # 追記 !!

参考

https://serverless.com/framework/docs/providers/aws/guide/credentials/#creating-aws-access-keys

※AWS CLIで複数プロファイルを管理する方法は、以下の記事が参考になります。
http://qiita.com/ryuzee/items/e3ce493f132f1981f57a

続きを読む

EC2を停止/起動したらネットワークに接続できなくなった

症状

すでに稼働中のEC2で作業をし、必要があったためEC2を停止/起動した。
すると、今まで動いていたデプロイコマンドが動かなくなった。

調べたところ、EC2から外部のネットワークに接続できなくなっていた。
具体的にはssh接続でEC2にログインし、以下のコマンドを実行しても一向に応答がない状態となってしまった。

$ ping www.google.co.jp

原因

このEC2は開発環境として使用していて、複数のdev環境が動いている。
それぞれのdev環境に固定IP(Elastic IP)を割り当てるために、複数のElastic IPをEC2に関連付けている。

EC2の停止/起動により、EC2のパブリックIPが変更されたのだが

なんと

既に関連付けられていたElastic IPと同一のパブリックIPアドレスが割り当てられたのである。

add_text.jpg

だからpingを打っても応答がない(循環していた?)状態になっていたんですね・・・。

対応

EC2の停止/起動で再度パブリックIPを割り当て直して完了。
これを機にElastic IPを新しく発行して、EC2の固定IPに割り当てました。

最後に

既に関連付けられているIPと同じIPがパブリックIPとして割り当てられることあがある、
なんてことが、まさか起きるとは思っていたなかったため
原因を探すのに時間がかかってしまいました:disappointed_relieved:
確率で起こりうることとはいえ、AWS側で何かしらのチェックが入っていることを期待してしまっていました。

続きを読む

AWSでSSH接続できなくなってしまったときの復旧方法

AWSでEBSを追加したらSSH接続できなくなってしまった事案があり、復旧にちょっとハマったので備忘も兼ねて記載していきます。
オンプレサーバーと違って、クラウドだとSSH接続できないと何もできなくなっちゃいますからね。

手順

  1. 対象インスタンスのEBSのスナップショットを取得(バックアップ用)
  2. 作業用インスタンスを作成
  3. 作業用インスタンスに、対象インスタンスのEBSをアタッチ&マウント
  4. SSH等の設定を見直し&修正
  5. 対象インスタンスに、元のEBSをアタッチ

SSH接続できなくなっちゃう原因

以下のような時に、この方法は役立ちます。

  • ファイアーウォールの設定ミス
  • キーペアの紛失
  • OS設定ミスで起動しなくなっちゃった

1. 対象インスタンスのEBSのスナップショットを取得(バックアップ用)

現状のバックアップをとっておくために、対象のEBSを右クリックしてスナップショットを取得しましょう。EC2_Management_Console.png

2. 作業用インスタンスを作成

SSHできなくなっちゃったインスタンスと同様のインスタンスを作ります。
対象インスタンスを右クリックして出てくる、「同様のものを作成」で簡単にできちゃいます。
EC2_Management_Console.png

3. 作業用インスタンスに、対象インスタンスのEBSをアタッチ&マウント

対象のインスタンスをストップしてEBSをデタッチします

EC2_Management_Console.png

作業用インスタンスに、先程デタッチしたEBSをアタッチします

  • ボリュームのアタッチを選択
    EC2_Management_Console.png

  • 作業用インスタンスIDを選択
    EC2_Management_Console.png
    デバイスは def/sdf で問題ないはず。(エラーが出る場合は環境に合わせて変更してください)

作業用インスタンスにSSH接続して先程アタッチしたEBSをマウントします

  • 作業用インスタンスにSSH接続します
  • ディスクの状態を確認
[centos@ ~]$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0  30G  0 disk 
└─xvda1 202:1    0  30G  0 part /
xvdf    202:80   0  30G  0 disk 
└─xvdf1 202:81   0  30G  0 part 
  • マウントポイントを作成
sudo mkdir /mnt/xvdf
  • ディスクをマウント
mount -t xfs /dev/xvdf /mnt/xvdf/ -o nouuid
  • マウントすると中身のファイルが見えるようになるので、SSHやファイアーウォールの設定を見直す。
  • 作業用インスタンスからEBSをデタッチ
  • 対象インスタンスにEBSをアタッチ

続きを読む