多段CNAMEとパフォーマンス、およびAWS Route53のエイリアスレコードについて

概要

CNAMEレコードの設定値(参照先1)に、別の箇所でCNAMEとして設定されたドメイン名を指定することはできるか。できたとして、問題は無いのか気になったので調べた。

言葉にするとわかりにくいが、下記のような設定が可能かどうかだ。

web2.example.con. IN CNAME web1.example.com. 
web1.example.com. IN CNAME www.example.com.
www.example.com. IN A  192.0.2.1

“web2″のCNAMEで”web1″が指定され、”web1″のCNAMEで”www”が指定されるよな、CNAMEが連鎖する設定を「多段CNAME」とこの記事では呼ぶ。2

そして、多段CNAMEの短所を補うAWS Route53のエイリアスレコード(Aliasレコード)について最後に紹介する。

結論

  • 多段CNAMEの設定は可能
  • ただし、パフォーマンスに問題があるため避けたほうがよい。使う場合も多段の階層を深くしない
  • AWSでは、エイリアスレコード(Aliasレコード)を使える場合は使ったほうがよい

多段CNAMEは可能か

直感的に多段CNAMEは問題を起こしそうな気がしなくもないが、実際のところどうなのか。

DNSの基本的な仕様はRFCは1034と1035にまとめられているが3、いずれも多段CNAMEを禁止する記述は見当たらなかった。
下記の『CNAMEとパフォーマンス』でも軽く触れたが、RFC 1034に書かれているネームサーバとリゾルバの動作を見ても、問題なさそうだ。

ただし、多段CNAMEは循環的な参照を作りうる4ので、キャッシュDNSサーバ側でなんらかの制限が必要になる。
(BINDでは再帰問い合わせの回数に制限があるらしい)

CNAMEの循環的な参照については、RFCでも触れられている。

The amount of work which a resolver will do in response to a
client request must be limited to guard against errors in the
database, such as circular CNAME references, and operational
problems, such as network partition which prevents the
resolver from accessing the name servers it needs. While
local limits on the number of times a resolver will retransmit
a particular query to a particular name server address are
essential, the resolver should have a global per-request
counter to limit work on a single request.
(RFC 1034)

深すぎる多段CNAMEは、キャッシュDNSによる問い合わせが打ち切られる可能性があるため、避けるべきといえる。

(余談)CNAMEにおける制限

CNAMEに関する制限事項といえば、よく知られた話だが「CNAMEは他のリソースデータと共存できない」というものがある。

If a CNAME RR is present at a node, no other data should be present;
(RFC 1034より引用)

この件に関してはRFC 1912が詳しく、このRFC内の例を用いると、下記の設定が許可されない。(NOT ALLOWED)

           podunk.xx.      IN      NS      ns1
                           IN      NS      ns2
                           IN      CNAME   mary
           mary            IN      A       1.2.3.4

podunk.xx.にCNAMEを指定すると同時に、NSとしても指定しているため、「no other data should be present」に反している。

修正するには、下記のようする。

           podunk.xx.      IN      NS      ns1
                           IN      NS      ns2
                           IN      A       1.2.3.4
           mary            IN      A       1.2.3.4

CNAMEをAレコードに変更している。

CNAMEとパフォーマンス

CNAMEをつかうことで、問い合わせのパフォーマンスが低下する。

なぜなら、キャッシュDNSサーバはCNAMEの応答を受け取ったとき、その受け取ったドメイン名についての問い合わせを始めから実行する必要があるためだ。

つまり、キャッシュDNSサーバが”www.example.com” のAレコードを問い合わせている最中で、「www.example.com の正規名はwww.example.org である」と知った場合、今までの問い合わせを打ち切って”www.example.org” の問い合わせを実行する必要がある。

通常の2回分の問い合わせが行われることになり、あきらかに問い合わせの効率が下がる。
さらにいうと、CNAMEの段数が増えれば増えるほど、問い合わせの回数は増えていく。

このときの動作の詳細は、RFC 1034の『5.5.2 Aliases』を読むとよい。

ただし、CNAMEを多段に用いてもパフォーマンスの低下が少ないケースもある。それは、自身のゾーン内、もしくは委譲先のゾーンでCNAMEが解決される場合だ。
この点については、同じくRFC 1034の『4.3.2. Algorithm』を読んでほしい。

AWS Route53のエイリアスレコードについて

エイリアスレコード(Aliasレコード)とは

エイリアスレコードは、CNAMEとよく似た機能を提供するが、その弱点をカバーする。

まず、エイリアスレコードを知らない人のために、AWS公式ドキュメントから引用する。

Amazon Route 53 は “エイリアス” レコードを提供します (Amazon Route 53 固有の仮想レコード)。[中略]エイリアスレコードは CNAME レコードのように機能するので、DNS 名(example.com)を別の「ターゲット」DNS 名(elb1234.elb.amazonaws.com)にマッピングできます。それらはリソルバーに表示されていないという点で、CNAME レコードと異なります。リソルバーには、A レコードと結果として生じるターゲットレコードの IP アドレスだけが表示されます。

(AWS Route53ドキュメント、『よくある質問』より引用)

先ほど述べた通りだが、キャッシュDNSのサーバがCNAMEの応答を受け取ったときの動作は、返されたドメイン名に対する問い合わせを再度実行する。
これが問い合わせのコストの増加となり、問い合わせのパフォーマンス(主にクライアントへ応答を返す時間)が悪化してしまう。

しかし、エイリアスレコードを使うことで、Aレコードと同じコストでCNAMEと同様の機能を使うことができる。

エイリアスレコードとCNAME

ELBを作成すると、my-loadbalancer-1234567890.us-west-2.elb.amazonaws.comのようなドメイン名がAWSから発行される。

このドメイン名は人間にとってわかりにくいため、”my-web.example.com” のようなドメイン名を別名でつけることがある。

このとき、CNAMEを使った場合、キャッシュDNSサーバは”my-web.example.com” の問い合わせ中にCNAMEとして”my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com” を受け取り、”my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com”の問い合わせをルートドメインから行うことになる。

一方でエイリアスレコードを使った場合、CNAMEの応答は行われず、一連の”my-web.example.com” の問い合わせの中で、”my-loadbalancer-1234567890.us-west-2.elb.amazonaws.com” のAレコードが返される。

エイリアスレコードを使用するための条件

なぜこのようなことができるかというと、参照先のドメイン名の解決をRoute 53内部で行なっているためだ。

したがって、エイリアスレコードを使用するには2つの条件を満たす必要がある。

  • ユーザはRoute 53を使うこと(そもそもエイリアスレコードはAWSの拡張機能)
  • 参照先が、AWSの限定されたサービスであること(CloudFront, ELB, 静的ウェブサイトとしてのS3など)

エイリアスレコードを使った場合のデメリットは特に無いので、使用できるケースであれば積極的に使うべきだ。

エイリアスレコードの詳細については、Route 53のドキュメントの『エイリアスリソースレコードセットと非エイリアスリソースレコードセットの選択』を読んでほしい。

参照先として指定できるドメイン名についても、このドキュメント内に書いてある。

(余談)パフォーマンスではない、エイリアスレコードのメリット

私が思うエイリアスレコードの最大のメリットはパフォーマンスだが、AWSのドキュメントでは触れられていない。

『よくある質問』には下記のように書いてある。

静的なウェブサイトをホスティングするよう設定されている CloudFront ディストリビューションおよび S3 バケットには、CNAME を使用する代わりに、CloudFront ディストリビューションまたは S3 ウェブサイトバケットにマッピングされる “エイリアス” レコードを作成することを推奨します。エイリアスレコードには 2 つの利点があります。1 つは、CNAME とは異なり、エイリアスレコードは zone apex (例えば、www.example.com ではなく example.com) に対しても作成できることです。もう 1 つは、エイリアスレコードへのクエリは無料であることです。

また、AWS Trusted AdvisorでAWSの設定をチェックしたときに、エイリアスレコードを使える箇所でCNAMEを使っていた場合にワーニングになる。

参考文献

  1. RFC 1034 『DOMAIN NAMES – CONCEPTS AND FACILITIES

  2. RFC 1035『DOMAIN NAMES – IMPLEMENTATION AND SPECIFICATION』
  3. RFC 1912『Common DNS Operational and Configuration Errors』
  4. AWS Route53ドキュメント
  5. DNSのRFCの歩き方( http://dnsops.jp/event/20120831/DNS-RFC-PRIMER-2.pdf )
    • 記事を書いてから気づいたが、DNSのRFCを自力で読む前に目を通すと良さそう

  1. CNAMEは”canonical name”なので”正規名”などどしたほうが正確だが、わかりやすさを重視して”参照先”とした 

  2. 「多段CNAME」をググるとそれなりにヒットするので、ある程度一般的な言葉かもしれない 

  3. 関連するRFCは他にもある. 参考文献の3を参照 

  4. 「web.example.com -> www.example.com -> web.example.com …」のような循環的な参照 

続きを読む

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で動かしてみるとかやってみようかな。

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

続きを読む

メモ: Route 53 で Privacy Protection がなかなか有効にならないときには、ドメインのロックを有効にして Don’t hide にしてから hide にするといいよ

Amazon Route 53で汎用JPドメインを申請してみた

の方法を参考にしつつ、自分も Privacy Protection を有効化しました。

とても些細なハマりどころなのですが、
1 日ほど待っても WHOIS 情報が自分の住所のまま、ということがありました。

ワークアラウンドで解決したのでやり方を書いておきます。

1. Privacy Protection を有効化するにはドメインのロックが必要

連絡先情報を非表示にできるのは、移管できないようにドメインがロックされている場合のみです。ドメインを Amazon Route 53 に移管する場合または から移管する場合は、WHOIS クエリでお客様の連絡先情報が表示されるように、プライバシー保護を無効にする必要があります。移管が完了したらプライバシー保護を再度有効にすることができます。

http://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/domain-privacy-protection.html

わすれがちです。ドメインのロックを有効にしましょう。

Registered Domains > ドメインを選択 > Transfer lock で (enable) をクリックすると有効になります。

2. ドメインのロックと Privacy Protection を有効にする順番

ここが私の躓きのポイントでした。

ドメインのロックを有効化しても、その前に既に Privacy Protection が有効化されている状態だと、自動的に WHOIS 情報は更新されることはないようです。

一度 Privacy Protection を無効にしてから有効にすることで強制的に更新する、というワークアラウンドが必要になります。

つまり

  1. ドメインのロックを有効化
  2. Privacy Protection を無効化 (Edit contacts からラジオボタンを Don’t hide contact information と選んで Save)
  3. (無効化処理が完了するまで数十秒ほど待つ)
  4. Privacy Protection を有効化 (Edit concats からラジオボタンを Hide contact information if … と選んで Save)

という手順になります。

ここに気が付かないと「おかしいな、ちゃんと有効化にしてるのにな〜、すぐには反映されないのかな〜」と何度も Edit contacts したり、何時間か反映を待ってみたり、無駄な時間を過ごす羽目になります……。

続きを読む

Angular2をAWSのS3とCloudFrontでホスティングする

Angular2を使い始めたら、すぐAngular4がリリースされましたね。この世界の動きは早いですね:older_man_tone2:

今日はやっとAngular2をAWSでサーバレスでホスティングしてみました。方式も手順も難しくはないですが、すぐ忘れてしまいますので、いつも通りメモしたいと思います。

AWSでホスティング

Angular2とサーバの関係

Angular2はコンパイル後はindex.htmlとcss, javascriptのセットからなる、静的なコンテンツですので、Webサーバを選ばずホスティングできるようです。

AWSで静的コンテンツをホスティングする

AWSの完全ガイドはこちらですが、少々難しいので、端折りながら…

基本的には、

1. S3にコンテンツをアップロードする
2. CloudFrontで、S3のコンテンツを配信する
3. Route 53で、CloudFrontのホスト名に対して公開したいホスト名をエイリアスで付ける

という手順になるようです。

S3にRoute53でエイリアスを付けることも可能ですので、S3だけでもホスティングはできるのですが、S3はHTTPsに対応していないようです。そのため、今回の手順は(HTTPsに対応している)CloudFrontまで入れています。

実際の手順

1. S3での作業

AWSの公式ガイドはこちらです。

1-1. バケットを作成

バケット名は、ホストするサーバ名にします。
※ここではAWSの例通り、『example.com』とします。

1-2. 静的ウェブサイトホスティングを有効にする

バケットのプロパティタブで、上記の項目をクリックし、ラジオボタンで『有効にする』を選択します。

AWS入力欄
- インデックスドキュメント:index.html
- エラードキュメント:error.html

を入力して保存します。

Angular2を ng build コマンドでトランスパイルすると、index.htmlが生成されますので、インデックスドキュメントはそれを参照する形になります。error.htmlはAngular2では用意されませんので、自分でエラーページを作って、名前をerror.htmlにします。

1-3. アクセス許可で、バケットポリシーを作成する

バケットのプロパティタブで、上記の項目をクリックし、『バケットポリシーの編集』ボタンを押して、ダイアログに以下のポリシーJSONを貼り付けます。

ポリシー
{
  "Version":"2012-10-17",
  "Statement":[{
    "Sid":"AddPerm",
        "Effect":"Allow",
      "Principal": "*",
      "Action":["s3:GetObject"],
      "Resource":["arn:aws:s3:::example.com/*"
      ]
    }
  ]
}

コロンの後ろに続く、『example.com』の記載は、ホストするサーバ名(バケット名)になります。

1-4. Angular2ファイルのアップロード

ng buildコマンドを実行し、生成されたコンテンツ(デフォルトでは、distフォルダ)の中の、全てのファイル(index.html, xx.buldle.js, xx.buldle.js.map)を作成したS3バケットにアップロードします。

独自に、favicon, error.htmlを作成している場合は、それも同じS3バケットにアップロードします。

1-5. S3でのホスティングの確認

プロパティタブ>静的ウェブサイトホスティングで表示されるエンドポイントをクリックして、ブラウザに表示されれば成功です。

エンドポイント例)
example.com.s3-website-ap-northeast-1.amazonaws.com

以上でS3での作業は完了です。

2. CloudFrontでの作業

2-1. Distributionを新規作成する

まず、Web distributionを作成するボタンを押し、その後に以下を設定します。

  • Origin Domain Nameに、S3バケットのエンドポイントを設定
  • Viewer Protocol PolicyをRedirect HTTP to HTTPSに設定(任意)
  • Allowed HTTP Methodsに、GET, HEAD, OPTIONSを設定(任意)※1
  • Alternate Domain Names(CNAMEs)にRoute 53で設定するホスト名を設定
  • SSL Certificateを、Custom SSL Certificateを設定(任意)※2
  • Default Root Objectに『index.html』を設定 ※3

※1) CORSをする場合は、プリフライトリクエストに対応するためにOPTIONSを有効にする必要があります。
※2) CloudFrontのHTTPs化については、こちらこちらを参考にしました。
※3) デフォルトルートオブジェクトについては、こちらの説明をご参照ください。

3. Route53の作業

DistoributionのDomain Name(例:dxxxxxxxx.cloudfront.net)を、ホスト名のエイリアスに設定すれば完了です。

Route53入力例)
Name: example.com
Alias Target: dxxxxxxxx.cloudfront.net

参考)S3に対してRoute 53でホスト名を割り振る場合

HTTPsが必要ない場合には、CloudFrontまで使わずにS3だけでホスティングすることができます。

このとき、Route 53でエイリアスとして指定するのは、S3のwebホスティングを示すドメイン名部分です。面白いことに『example.com』のような固有値が必要ありません。

Route53入力例)
Name: example.com
Alias Target: s3-website-ap-northeast-1.amazonaws.com

私も最初は『example.com』を付けたFQDN(example.com.s3-website-ap-northeast-1.amazonaws.com)を設定してハマりまして、こちらを参考にさせて頂いて上記の設定に気が付きました。

Route53でTargetが表示されない場合は、こちらのページをご参考にしてみたら良いかもしれません。

以上です:grinning:

続きを読む

Terraformでstate lockを触ってみた。

はじめに

初めてTerraformを触る折に
「v0.9以上だとstate lockっていう機能があるから使ったほうが良いよ」
というアドバイスをもらいました。
(そもそも、stateってなんですか?から始まるわけですが・・・)

初めてTerraformを触った時に、公式ドキュメントだけでは???な状態だったので
痕跡として残しておきます。

結果として、まだ使いこなせてません。というか僕の使い方は果たしてあっているのだろうか
なので情報としては不足しているかもしれませんが、とりあえず備忘録的に残します。
またわかったらUpdateします。

そもそもState Lockとは

完全に理解しないで書いてますが
Terraformは「自分が知っているリソース(EC2やS3)しか関与しないよ」というポリシーで
どのリソースを自分が扱ったか、というのをtfstateというJSONファイルで管理しているようです。

このtfstateファイルは、一人でTerraformを動かす場合は問題無いのですが
複数人でTerraformをいじる必要が出てきた時に問題で、それは「backend」モジュールを使うことで回避してきたようですが
同じタイミングでterraformを実施した場合、その部分までは制御しきれてなかったようです。

で、v0.9以上?から、「Plan/Applyをしているタイミングではロックする」機能が実装されたようで。
せっかくなので導入してみました。

公式サイト:https://www.terraform.io/docs/state/locking.html

準備

手動で準備が必要なものは
・terraformを実行するユーザのCredential情報
→ めんどくさかったので test-terraformとかいうユーザを別途作成しました。
・S3 Bucketの作成
→ terraform-s3-state とかいう名前で作りましょう
・DynamoDBの作成
→ 名前はなんでも良いです。terraform-state-lock とかで作りましょう。
  プライマリキーを「LockID」としてください。それ以外では動きません。
作り終えたら、読み込み・書き込み容量ユニットは最低の1にしておいたほうが良いと思います。

※ S3とDynamoDBをterraformで管理はしないほうが良さげです。
どのみち、初回実行時に「そんなリソース無いんだけど!」ってTerraformが怒ります。

動くまで

今回はState Lockの話がメインなので作るリソースはなんでも良いです。
リージョンが関係無いRoute53を題材にします。

route53.tf
# 適当に1ゾーン作ります。

resource "aws_route53_zone" "test_zone" {
    name    =   "test.lockstate.com"
}
settings.tf
# ネーミングよくないですが、providerとbackendの設定します

provider "aws" {
    access_key = "ACCESS_KEY"
    private_key = "PRIVATE_KEY"
}

terraform {
    backend "s3" {
        bucket     = "terraform-s3-state"
        key        = "terraform.tfstate"
        region     = "s3とdynamoがいるregion"
        lock_table = "terraform-state-lock"
    }
}

これで、「該当のディレクトリに移動して」$ terraform plan してください。(怒られます。)

Backend reinitialization required. Please run "terraform init".
Reason: Initial configuration of the requested backend "s3"

The "backend" is the interface that Terraform uses to store state,
perform operations, etc. If this message is showing up, it means that the
Terraform configuration you're using is using a custom configuration for
the Terraform backend.

とりあえず terraform init しろよと言われるので言われるがままに。

$ terraform init

ただし、以下の通り、認証情報がねーんだけど!って怒られる。なんやねん。

Initializing the backend...

Error configuring the backend "s3": No valid credential sources found for AWS Provider.
  Please see https://terraform.io/docs/providers/aws/index.html for more information on
  providing credentials for the AWS Provider

Please update the configuration in your Terraform files to fix this error
then run this command again.

ここらへんちゃんとよくわかってないですが、この問題の対処として2つありました。
A. settings.tfのbackendにaccess_key/secret_keyの2つを渡してCredential情報を平文で書く。
-> 変数でいいじゃん!と思ったんですが、変数で渡すと、Terraformがよしなにやる「前に」
  Backendが立ち上がるために違う方法で渡してくれ、と怒られました。
以下のようなメッセージ

% terraform init 
Initializing the backend...
Error loading backend config: 1 error(s) occurred:

* terraform.backend: configuration cannot contain interpolations

The backend configuration is loaded by Terraform extremely early, before
the core of Terraform can be initialized. This is necessary because the backend
dictates the behavior of that core. The core is what handles interpolation
processing. Because of this, interpolations cannot be used in backend
configuration.

If you'd like to parameterize backend configuration, we recommend using
partial configuration with the "-backend-config" flag to "terraform init".

B. 環境変数にaccess_key/secret_keyの2つを食わせる
  → 今はこちらを採用中。

init or plan実行時に、状況に応じて「ローカルのStateをS3にコピーするか / S3からStateファイルをローカルにコピーするか」聞かれるかもしれません。
初回(もしくはテスト)であればYes、すでに何回か実行しているような状況ではnoでいいんじゃないでしょうか。

これで、terraform plan (or apply)を実行すると
DynamoDBのLockDBに対して誰が使用しているか、のロック情報が書き込まれ
終わったタイミングでリリースされます。

ターミナルやシェルを複数立ち上げ、同じぐらいのタイミングで
terraform plan( or apply )を実行すると、ロックが成功できた奴以外はエラーで落とされます。
ただし、ロックがリリースされる前の実行中などにCtrl-Cなどで強制終了させるとロックが残り続けるので注意。
$ terraform force-unlock ID情報
で強制ロック解除できます。

今僕が解決できてない問題点

公式ドキュメントを見る限り、「terraform remote コマンドは terraform initコマンドに置き換えたよ!」と言っています。
また、backendを使用する、backendを書き換えた、などのタイミングに起因して terraform init を求められます。

が、terraform initは、「実行されたディレクトリに対して .terraform/terraform.tfstate」を吐き出します。
そしてそれが無いと怒ります。
まだ試せてはいませんが、以下のようなtfの配置をしていたりした場合
root配下でinitを実行しても、tfファイルがありませんと怒られる状況で
tfファイルがあるディレクトリまで移動しないとinitができない状態です。

$ terraform init ./route53/tf
とするとroot直下にtfファイルがコピーされる状況です。
なので、リソースごとに区切る、とか環境ごとに区切る、とかはうまくできていません。
別の見方をすれば、環境ごとに一つのディレクトリでリソース類は全部その中で管理しろよ!
というHashiCorpからのお達しなのか・・・?
これは、新しく実装されたEnvironmentを試せ、ということなんでしょうか。。。

と思ったらBackendConfigなるものがあるらしいのでそれを試してみよう。

root
├── settings
│   └── tf
│   └── settings.tf
├── route53
│   └── tf
│   └── route53.tf
└── ec2
└── tf
└── ec2.tf

Terraformとの付き合い方がよくわからない

続きを読む

純粋なシェルスクリプトだけでAWSを操りたい!〜POSIX原理主義でクラウドも侵略〜

この記事は、POSIX原理主義とシェルショッカー日本支部に感化されちゃって、なんとか実践しようとしている、なんちゃって雑魚戦闘員がお送りします。POSIX原理主義でもクラウドを侵略できますよ、というお話です。これで、POSIX原理主義がますます強力になれるんじゃないかと勝手に思っています。

まずコマンドの紹介から、

axs - UNIX哲学を守ったつもりのsimpleなawsコマンド

A simple ‘aws’ command ‘axs’ written in POSIX sh. axs(access) to aws(amazon web services) with posixism.

このコマンドは、AmazonWebServicesにアクセスし、数々のwebサービスを利用したり、アプリを構築したりするために作られた、POSIX原理主義に基づく、なんちゃってawsコマンドです。

https://github.com/BRAVEMAN-L-BRID/axs

作った経緯

AWS APIツールは「いつでも、どこでも、すぐ」使えるわけではない。

バージョン依存や環境依存の大きい、AWS APIツールはいつでもどこでもすぐに使えるわけではありません。それに加えて、SDKやCLIも、それらをインストールできない環境だと、そもそも役に立ちません。意外なこと使えない環境を目にする機会も多いです。

しかしながら、環境やバージョンアップに悩まされずにAWSのサービスを利用したい、というニーズも時にはあるのではないでしょうか?
浅くネットサーフィンした限りでは、そのように認識しています。

このaxsコマンドは、そのニーズに応えるために作りました。

POSIX shに準拠しているつもりなので、Windows, Mac, Linuxなどで、それこそコピーしてくるだけで「いつでも、どこでも、すぐ」動きます。

大量のオプション、引数からの解放

作った経緯2です。awsコマンドの大量の引数やオプション、サービスごとに異なる数々のサブコマンドにヘキヘキした覚えはありませんか?私はあります。
あれはUNIX哲学を守っていません。コマンドとクラウドへ渡すパラメーターがぐちゃぐちゃに混ざったシェルスクリプトを書くのは、もう、こりごりです。

このコマンドでは、その煩わしさから解放されます(嘘、かもしれません)。また、UNIX哲学を守ったつもりで、RESTfulの概念も大事にしています。直感的な操作が可能かもしれません。

スクリプトを使った自動化も楽になる可能性があります。

使い方

ダウンロードしたら、このリポジトリの/binディレクトリにPATHを通してください。

0. 準備

~/.aws/credentialsファイルに以下のようにアクセスキーIDとシークレットアクセスキーを記述してください。パーミッションには気を付けてください。

[default]
aws_access_key_id = hogehoge
aws_secret_access_key = mogemoge

1. 基本

使い方は簡単です。設定ファイル(データ形式は後述)をaxsコマンドで読み込むだけです。

例えば、catコマンドを使って

$cat config_file | axs

とできますし、もしくは、引数に設定ファイルを置くだけです

$axs config_file

2. 設定ファイルのデータ形式について

AWSはREST APIを用いています。そこで、今回は、正しいのかどうかは横に置いておいて、HTTPにちなんだデータ形式の設定ファイルを記述します。

以下のような形式をとることにしました。

METHOD URI                    (←リクエストライン)
Key Value                   (←キーバリュー形式に分解したクエリ)
key Value                   (←キーバリュー形式に分解したクエリ)
Key Value                     (←キーバリュー形式に分解したクエリ)
Host: ec2.ap-northeast-1.amazonaws.com  (←必須ヘッダー)
Content-Type: application/hoghoge     (←場合によっては必須のヘッダー)
X-Amz-moge: hogehogehoge                (←設定などの追加のヘッダー)

(bodyの部分コンテンツの中身)
xmlとかjsonとかバイナリデータ         (←コンテンツの中身)

基本的に、Host,Content-Typeヘッダのみが必須だと考えてもらっていいです。

AWS APIを利用するので、body部には、基本的にxmlやjsonを記述します。

ただし、場合によっては画像ファイルや音声ファイルなどのバイナリデータをアップロードしなければならない時もあります。また、xml,jsonが長く煩雑な時は、リクエストライン、クエリ、ヘッダまでの部分とbody部を分離したいと思う時もあるでしょう。

そのような時に、axsコマンドの-fオプションを使います。

3. -fオプション

body部を分離して別ファイル(body.txtなど)にしてaxsコマンドを使う場合には以下のようにします。
※ここではヒアドキュメントを使用しています。

$cat<<END | axs -f moon.jpg
PUT /image.jpg
Host: Bucket.s3.amazonaws.com (東京リージョンの場合はbucket.s3-ap-northeast-1.amazonaws.com)
Content-Type: image/jpeg
END

*この例では、ローカルのmoon.jpgという画像ファイルをs3のBucketバケットにimage.jpgという名前で保存しています。

4. -qオプション

 これはAPIアクセス後に返ってくる、レスポンスのレスポンスヘッダーを表示するかいなかを決定するオプションです。
 デフォルトではレスポンスヘッダを表示します。AWSのREST APIがヘッダ情報をよく扱うので、汎用性を高めるためにそうのようにしてあります。

例えば、以下のような違いになります。

$cat config_file | axs 

HTTP 200  OK
hoge: hogehoge
hage: hagehoge
hoga: hogレスポンスヘッダー

<xml ......>
<DescribeInstances>...........</...>
.......

-qオプションを利用した場合は、レスポンスヘッダが省略されxmlやjsonやバイナリが直に帰ってきます。ですので、パイプでつないで加工したりするのに便利です。


cat config_file | axs -q > polly.mp3
               (pollyに喋ってもらう)


cat config_file | axs -q | parsrx.sh(POSIX原理主義製xmlパーサー)
               (xmlが返ってくる)


cat config_file | axs -q | parsrj.sh(POSIX原理主義製jsonパーサー)
               (jsonが返ってくる)

TIPS

  • 設定ファイルの書き方は、AWS API referenceなどを参照してください。設定ファイルの記述はクエリ部分以外はHTTPと同じです。
    深く悩まずに記述できることでしょう。

  • Content-Lengthヘッダ,x-amz-content-shaナンチャラヘッダ,Autorizationヘッダは自動生成されるので、考慮する必要はありません。

  • 私も仲間に加えてもらった秘密結社シェルショッカー日本支部のPOSIX原理主義製の他コマンドと相性がいいです。
    これを機に秘密結社シェルショッカー日本支部よりダウンロードしてくることをお勧めします。
    中でもmojihameコマンドとの相性は抜群です。https://github.com/ShellShoccar-jpn/installer

例えば、クエリAPIとmojihameコマンド

テンプレファイルを用意します、template

GET /
QUERY
%1 %2
QUERY
Version 2016-11-15
Host: ec2.ap-northeast-1.amazonaws.com
Content-Type: application/x-www-form-urlencoded

設定の用意、config.txt

Action Hogehoge
AAAAA dededed
hogemoge ahahaha

いざアクセス

cat config.txt | mojihame -lQUERY template - | axs -q | parsrx.sh | 加工

素晴らしいparsrsのコマンドを用いれば、無駄に多くのコマンドを用いずにレスポンスの解析もできます

ちなみに

cat config.txt | mojihame -lQUERY template -

までの結果だけ抜き出すと以下のようになっています。

GET /
Action Hogehoge
AAAAA dededed
hogemoge ahahaha
Version 2016-11-15
Host: ec2.ap-northeast-1.amazonaws.com
Content-Type: application/x-www-form-urlencoded

実際にAWSにaxs(アクセス)してみよう!

以下の内容ではシェルショッカー日本支部の開発しているコマンドを多く使うかもしれません。mojihameコマンドとParsrsは必須です。また、記述の簡略化のために、ヒアドキュメントを多用します。

S3にアクセスして、東京リージョンで静的webサイトのホスティングをしてみよう!

0.好きな名前のbucketをおく(PUTする)

cat <<END | axs
PUT /
Host: BucketName.s3-ap-northeast-1.amazonaws.com
Content-Type: application/xml

<CreateBucketConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"> 
  <LocationConstraint>ap-northeast-1</LocationConstraint> 
</CreateBucketConfiguration >
END

1.bucketの中に公開するオブジェクトindex.htmlをおく(PUTする)

公開するものなので、誰でも読み込み可能にするために、設定として追加のヘッダx-amz-acl: public-readを付与しています。

cat <<-END | axs -f index.html     (←ローカルファイル)
PUT /index.html
Host: BucketName.s3-ap-northeast-1.amazonaws.com
Content-Type: text/html
x-amz-acl: public-read
END

2.bucketの中に公開するオブジェクトerror.htmlをおく(PUTする)

これは、エラーページ用のhtmlです。

cat <<END | axs -f error.html
PUT /Error.html 
Host: BucketName.s3-ap-northeast-1.amazonaws.com
Content-Type: text/html
x-amz-acl: public-read
END

3.bucketの中に公開するオブジェクトimage.jpgをおいて(PUTする)、やっぱり消す(DELETE)

index.htmlに載せる画像をbucketにputしましたが、気に入らなかったのでやっぱりDELETEすることにしました。

cat <<END | axs -f moon.jpg
PUT /image.jpg
Host: BucketName.s3-ap-northeast-1.amazonaws.com
Content-Type: text/html
x-amz-acl: public-read
END

やっぱり気に入らないので消す

cat <<END | axs
DELETE /image.jpg
Host: BucketName.s3-ap-northeast-1.amazonaws.com
END

4.バケットをウェブサイト用に修正する(PUTする)

s3を静的webサイトとして活用します。

s3のwebサイトホスティングでは、ルーティングルールを使用して、特定のHTTPエラーコードをチェックする条件を指定できます。ルールを追加すれば、リクエストエラーが発生した場合、リクエストを再ルーティングしたりできます。つまりwebサイトの機能を持たせることができるのです。

たとえば、リクエストエラーを処理し、別のホストにルーティングしてみましょう。次のルーティングルールは、HTTPエラー404のイベントでEC2インスタンスにリダイレクトします。

ExamplePage.htmlページをリクエストしてHTTP 404エラーが発生したとします。すると、そのリクエストは、指定されたEC2インスタンスのreport-404/testPage.htmlに再ルーティングされます。ルーティングルールが存在しない状態で、HTTPエラーが発生した場合には、Error.htmlを返します。

cat <<END | axs
PUT /
website
Host: BucketName.s3-ap-northeast-1.amazonaws.com
Content-Type: application/xml

<WebsiteConfiguration xmlns='http://s3.amazonaws.com/doc/2006-03-01/'>
  <IndexDocument>
    <Suffix>index.html</Suffix>
  </IndexDocument>
  <ErrorDocument>
    <Key>Error.html</Key>
  </ErrorDocument>

  <RoutingRules>
    <RoutingRule>
    <Condition>
      <HttpErrorCodeReturnedEquals>404</HttpErrorCodeReturnedEquals >
    </Condition>
    <Redirect>
      <HostName>ec2-11-22-333-44.compute-1.amazonaws.com</HostName>
      <ReplaceKeyPrefixWith>report-404/</ReplaceKeyPrefixWith>
    </Redirect>
    </RoutingRule>
  </RoutingRules>
</WebsiteConfiguration>

5.TIPS:独自ドメインで静的webサイトを公開する場合

上記までの設定を用いて、s3で静的webサイトのホスティングを行うと、webサイトの公開アドレスは、AWS側で決定されたendpoitとなります。例えばs3-website.s3.amazonaws.comのようなものです。

しかし、自分のドメインでwebサイトを公開したいと思う時もあるでしょう。

その場合は、バケット名を独自ドメイン名にして、Route53で設定してあげれば可能です。

例えば、あなたの所得したドメインがwebsite.comだった場合。バケット名をwebsite.comにします。あとは、Route53で設定してあげると、website.comでwebサイトを公開できます。

6.TIPS:独自ドメインのバケットを用いるときの注意

独自ドメインでサイトを公開しない通常の時は、以下のようにホストヘッダにバケットの名前を含めることができます。そしてこちらの方がレファレンス的には正しいです。

cat <<END | axs
PUT /
website
Host: BucketName.s3-ap-northeast-1.amazonaws.com
END

なおこの場合のwebサイトのアドレス以下のようになります。

http://BucketName.s3-website-ap-northeast-1.amazonaws.com

しかし、独自ドメインをバケット名に使用し、ホストヘッダに含める場合は、axsコマンドでは、エンドポイントの名前解決ができません。したがってドメイン名のバケットを作る場合には、ターゲットuriにバケット名を含めます。

cat <<END | axs -f index.html
PUT /website.com/index.html
Host: s3.amazonaws.com
Contetnt-Type: text/html
END

この操作では、us-east-1リージョンにあるwebsite.comバケットに、index.htmlをPUTしています。なおAWS APIで用いるendpointについては、レファレンスを参照してください。サービスとリージョンごとに形式が異なる場合が多いです。
http://docs.aws.amazon.com/ja_jp/general/latest/gr/rande.html

7.TIPS:ヒアドキュメントで記述した内容を設定ファイルに残したい

そんな時はteeコマンドを使います 。ヒアドキュメントに記述した内容がconfig_fileに保存されます。

cat <<END | tee config_file | axs
GET /
Host: s3.amazonaws.com
END

Amazon Pollyにアクセスして音読させてみよう!

Amazon Pollyはテキストの音声変換サービスです。誰でも非常に簡単に利用できるサービスで、AWSの中でも敷居は低いです。とりあえずやってみましょう。何かのアプリ開発に役に立つかもしれません。

また、AWSのフルマネージドサービスなので利用者がインフラ構成やコーディングについて心配する必要は微塵もありません。東京リージョンでは利用できないので注意してください。

なお、ここではmojihameコマンドを使います

1.テンプレートファイル speech.templateを用意します。

お馴染みのHTTP形式の設定ファイルのテンプレです。使い回しが可能なので、メンテナンス効率は上がるかもしれません。aws cliのようにパラメータとコマンドをごちゃまぜにしたスクリプトを書く必要がなくなります。

POST /v1/speech
Host: polly.us-east-1.amazonaws.com
Content-Type: application/json

{
  "OutputFormat": "mp3",
  "Text": "%1",
  "VoiceId": "Mizuki"
}

2.pollyにアクセスしておしゃべりしよう!

ここまできたら簡単、あとはAmazon Pollyにアクセスしておしゃべりするだけです。

$echo "こんにちはQiita" | mojihame polly.template - | axs -q > result.mp3

ちなみにaxsコマンドの前までを抜き出すと以下のようになっています。

POST /v1/speech
Host: polly.us-east-1.amazonaws.com
Content-Type: application/json

{
  "OutputFormat": "mp3",
  "Text": "こんにちはQiita",
  "VoiceId": "Mizuki"
}

3.長文の小説もPollyに喋ってもらうことができます。

「小説家になろう」というサイトから小説をもらってきて、pollyに音読させましょう。pollyには文字数制限がありますので、何文字かに区切ってリクエストを出し、返ってきたレスポンスを対象ファイルに上書きしていきましょう。

下記のcurl先のurlのncode下の番号は小説とページによって異なります。

$curl http://ncode.syosetu.com/txtdownload/dlstart/ncode/XXXXXXXX/ | 
tr -d 'n' | 
sed 's/.{250}/&@/g' | 
tr '@' 'n' | 
while read LINE; do 
echo $LINE | mojihame polly - | axs -q ; 
done >> polly.mp3

EC2にアクセスしてインスタンスを立ててみよう!

このセクションではmojihameコマンドとparsrx.shを使います。

0.テンプレートファイルec2.templateの用意

以下のファイルはmojihameコマンドで使います。

GET /
QUERY
%1 %2
QUERY
Version 2016-11-15
Host: ec2.ap-northeast-1.amazonaws.com
Content-Type: x-www-form-urlencoded

1.VPCを作ろう

cat <<END                       | 
Action CreateVpc
CidrBlock 10.0.0.0/16
END
mojihame -lQUERY ec2.template - | 
axs -q                          |
parsrx.sh                       | 
grep 'vpcId'                    | 
awk '{print $2}'

(結果としてvpcIdが表示されます)

ヒアドキュメントではなく、config_file1に設定項目であるAction CreateVpcとCidrBlock 10.0.0.0/16を記述したとしたら以下のようになワンライナーになります。

 cat config_file1 | mojihame -lQUERY ec2.template - | axs -q | parser.sh | grep 'vpcId' | awk '{print $2}'

2.puclic-subnetを作ろう

作成したvpcにinternet gatewayをアタッチし、パブリックサブネットを作成します。以下省略

3.ec2インスタンスを立てよう

作成したpublic-subnet内にec2インスタンスを作成します。以下省略

今回は面倒臭かったので色々省略しましたが、1、2、3の流れは全て簡潔で似通っています。次回があれば、AWS上に3-tierアーキテクチャを構成する完全自動化スクリプトを組んでみたいと思います。もちろんaxsコマンドを使って。

Amazon Rekognitionにアクセスして画像解析してみよう!

Amazon Rekognitionは、深層学習により、画像解析を行なってくれるAWSのフルマネージドサービスです。ユーザーがコーディングの心配をすることなく、簡単にすぐに、どこでも利用することができます。非常に利便性が高く、将来必要不可欠になるであろうサービスの一つです。

1.S3にバケットを配置して、顔を含んだjpeg画像を置いてみる

リージョンはusを選択してください。他リージョンではまだサービスを開始していません。

  • バケットを作る
cat <<END | axs
PUT /
Host: s3-to-rekognition.s3.amazonaws.com
  • 画像ファイルを配置する
cat <<END | axs -f my-face.jpg
PUT /my-face.jpg
Host: s3-to-rekognition.s3.amazonaws.com
Content-Type: image/jpeg

2.Rekognitionで画像解析して、要素を抽出してみる

これにはDetectLabels APIを使用します。

DetectLabelsは、入力として提供される画像(JPEGまたはPNG)内における実世界の要素を検出して、ラベルを付与します。花、木、テーブルなどのオブジェクトは当然含まれ、さらには、結婚式、卒業、誕生日パーティーのようなイベントシーン、風景、夕方、自然などの概念までが含まれます。

結果は整形されていないjsonで帰ってきます。これでは読み取りづらいので、ヘッダ情報を排して、jsonパーサーにかけてしまいます。もちろん、POSIX原理主義jsonパーサーparsrj.shを使って。

cat <<END | axs -q | parsj.sh
POST /
Host: rekognition.us-east-1.amazonaws.com
X-Amz-Target: RekognitionService.DetectLabels
Content-Type: application/x-amz-json-1.1

{
   "Image":{
      "S3Object":{
         "Bucket":"s3-to-rekognition",
         "Name":"my-face.jpg"
      }
   }
}
END

私の顔画像をアップロードして解析した結果は以下のようになりました。

$.Labels[0].Confidence 99.2739486694336
$.Labels[0].Name Human
$.Labels[1].Confidence 99.27888488769531
$.Labels[1].Name People
$.Labels[2].Confidence 99.27890014648438
$.Labels[2].Name Person
$.Labels[3].Confidence 82.6961669921875
$.Labels[3].Name Dimples
$.Labels[4].Confidence 82.6961669921875
$.Labels[4].Name Face
$.Labels[5].Confidence 82.6961669921875
$.Labels[5].Name Smile
$.Labels[6].Confidence 54.255836486816406
$.Labels[6].Name Selfie
$.Labels[7].Confidence 50.784881591796875
$.Labels[7].Name Female
$.Labels[8].Confidence 50.784881591796875
$.Labels[8].Name Girl
$.Labels[9].Confidence 50.784881591796875
$.Labels[9].Name Woman
$.Labels[10].Confidence 50.74814987182617
$.Labels[10].Name Glasses
$.Labels[11].Confidence 50.74814987182617
$.Labels[11].Name Goggles

多分、私は二重で目が大きいので上のような結果になってしまったのだと思います。サングラスとか眼鏡の可能性が指摘されてます。ちなみに顔アップの自撮り写真をあげたのですが、それも認識されているようですね。えくぼとスマイルまで認識されています。rekognitionには女顔として認識されたようでした。

以上のようにかなり詳しく要素を検出してくれます。別の例ではテーブルクロスなども検出していました。他にもAmazon Rekognitionでは顔の判別や比較、リストができます。ですので、例えば、普段見ない顔を検出した時にアラートを送信するようなアプリケーションを作れば、自作警備システムなんかも作成できるかもしれませんね!東京オリンピックに備えて笑

まとめ

さて、これでクラウドの力を借りれば、POSIX原理主義でも、様々なアプリケーションを開発できることがわかりましたね!さらには、機械学習や深層学習の力を借りた強力なものまでも、比較的簡単にできちゃうことがわかったと思います!しかも環境やバージョンに悩まされることなしで!

わーい!POSIX原理主義シェルスクリプトは楽しいですね!

※ここまでの流れで、AWS自体がPOSIX原理主義と対極じゃんて声が聞こえてきそうですが、そこはあまり関係ありません。AWSは道具というよりサービスです。ほらAmazon Web Servicesって言います。それにフルマネージドなサービスを多く活用するようにすれば、バージョンアップによる不具合とは比較的無縁です。

ということで、私は、実は、POSIX原理主義は、クラウドととても相性がいいのではないかと勝手に思っています(素人考え)。

「必要なもの」POSIX準拠コマンドと交換可能性を担保したコマンド

  • openssl ver1.0.0以上
  • utconv (同梱、秘密結社シェルショッカー日本支部)
  • urlencode (同梱、秘密結社シェルショッカー日本支部)
  • cat
  • awk
  • sed
  • printf
  • echo
  • grep
  • curl
  • wget
  • eval

感想

意味がるのか知りません。あるかもしれないし、ないかもしれません。ただ楽しかったですとだけ付け加えておきます。

「シェルかっこいい、クラウドかっこいい」
→「なんか作りたい」
→「POSIX原理主義すげー」
→「POSIX原理主義でもクラウドwebサービスの力を借りたい!」
→「一番サービスが充実していそうなAWSを操りたい」
→「awsコマンド的なものをPOSIX原理主義で開発しよう!」
という動機で始めました。作者は初Qiita,教育学部卒業したてのずぶの素人で数ヶ月前までIT系とは無縁でした。技術的な誤りを多分に含んでいる可能性があります。その時は指摘してくださると助かります。

続きを読む

全力AWSでMastodonサーバー立てました chitose.moe

https://chitose.moe/

スポンサーが1社付いてるのですぐに消えたりはしないはず。
メール送信数制限の都合で1日のユーザー登録数には上限がある。
メールが届かない時は暫く待つか24時間後に再送信。
とはいえ5万なので上限になることはないか。

環境

  • AWS
  • EC2 Container Service
  • ecs-cli
  • RDS(PostgreSQL)
  • ElastiCache(Redis)
  • S3
  • CloudFront
  • ALB
  • SES

やれるだけやって分散した。
いくらでもサーバー増やせるけどたぶんそこまで必要にはならない。

最後SESのsandbox解除待ちで時間かかった…。

途中段階の役に立たないメモ

ローカルで動かすだけならVagrantのほうが早い。
最初から管理者アカウントも作られる。

docker-composeはproduction用。

git clone https://github.com/tootsuite/mastodon
cd mastodon
sudo curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-darwin-amd64-latest
sudo chmod +x /usr/local/bin/ecs-cli
ecs-cli help
ecs-cli configure -p default --cluster mastodon
ecs-cli up ...
aws ecr get-login
docker login ...
aws ecr create-repository --repository-name mastodon
docker build -t mastodon .
docker tag mastodon:latest ...
docker push ...

docker-compose.ymlを編集
– buildの代わりにimage: mastodon
– DB永続化するためコメントを消す

ecs-cli compose service up

ここで上手く動かなくなった。
Elastic Beanstalkでやろうとしたけどこっちもだめ。
いろいろやってるうちにオリジナルのdocker-compose.ymlが事前に用意したDocker image使うようになってた。
ECS使う方法に戻る…。

assetsを削除して再生成してS3にアップ。
precompileを繰り返すとファイルが増える…?

rm -rf ./public/assets
docker-compose run --rm web rails assets:precompile

aws s3 cp ./public/assets s3://{S3バケット}/assets --recursive --acl public-read --cache-control "max-age=604800"

S3に置くと/public/assets以下がないのでprecompile済みファイルが使われない。
sprockets-manifestが必要。
S3に置くのはやめた。
image: gargron/mastodonは使わず自分でビルドする方法に戻る…。

.dockerignoreを変更してimageにassetsも含まれるように。

#public/assets

docker-compose.ymlをコピーしてaws.ymlを作りこっちを書き換えて行く。
docker-compose.ymlはローカル用に元のまま。
あ、当然gitのブランチは分けてる。

AWSのECSでは

  • buildが使えない
  • volumesで相対パスが使えない

という仕様なのでそれに合わせる。
volumesはよくわからないのでほぼ使わないように…。

ここからも色々苦労したけど細かすぎてもう忘れたのでメモ程度。
AWSのサービスで使えるものは使う。
db,redisはもちろん分離。
S3も当たり前に使う。
nginxは不要だった。nginx使おうとして無駄に混乱…。
ポートマッピングはELB。
httpsへのリダイレクトはCF。

LOCAL_HTTPS=falseでも問題なくhttpsで動くけどメール内のURLだけhttpになる。
リダイレクトされるので妥協。

ecs-cli compose serviceで--load-balancer-name付きで起動だけどうしてもできなかったので諦めた。

/api/v1/streamingはサブドメインにした。
STREAMING_API_BASE_URLで指定すればそれが使われる。
LOCAL_HTTPS=falseだとwebsocketでエラー出てたので無理矢理な対応。

S3_BUCKETS3_HOSTNAMEはS3のドメインそのまま使う用で
自分のサブドメイン使う場合はS3_CLOUDFRONT_HOSTも設定する。
CF使ってるかに関係なく静的ホスティングなら。

nginx使わなくても動いてるけど何か問題あれば後で修正していく。

docker-compose.ymlあるから簡単に動かせるかと思ったけどそんなことはなかった。
EC2上でdocker-compose upすれば簡単だけどそれじゃAWSの意味がない。

ALBの使用

その後調べて分かったので追記。
--load-balancer-nameはClassic Load Balancer用なので違う。
ALBは--target-group-arnを使う。
ただしサービスごとに一つしか設定できないのでサービスを複数作るしかない?

ecs-cli compose -f aws.yml --project-name mastodon-web service create --target-group-arn {web用のターゲットグループ} --container-name web --container-port 3000 --role ecsServiceRole

ecs-cli compose -f aws.yml --project-name mastodon-api service create --target-group-arn {streaming用のターゲットグループ} --container-name streaming --container-port 4000 --role ecsServiceRole

--project-nameの指定も必要になった。

ecs-cli compose -f aws.yml --project-name mastodon-web service up

AutoScalingでEC2インスタンス数を増減。
一つのEC2内で複数のタスクが動くし、タスクもAutoScalingできるようになった。

サービスを分けるならaws.ymlも分割したほうがメモリの無駄もない。

最終版

ごちゃごちゃしたのでまとめ。

aws-web.yml

mem_limitは分からないので仮。
portsの0が動的ポートのために必要。
もしくは0:部分なしでいいかも。こっちは未検証。

version: '2'
services:

  web:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: bundle exec rails s -p 3000 -b '0.0.0.0'
    mem_limit: 536870912
    ports:
      - "0:3000"

aws-api.yml

version: '2'
services:
  streaming:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: npm run start
    mem_limit: 268435456
    ports:
      - "0:4000"

  sidekiq:
    restart: always
    image: {自分のimage}
    env_file: .env.production
    command: bundle exec sidekiq -q default -q mailers -q pull -q push
    mem_limit: 268435456

ALB

ターゲットグループ

  • port3000のweb用
  • port4000のstreaming用

を作る。

リスナーHTTPS 443
ヘルスチェックのポートをトラフィックポートにする。

  • HostがAPI用のサブドメインならstreaming
  • Pathが/ならweb

というルールを設定する。

CloudFrontはこのALBをOriginにする。
/assets以下はキャッシュ強く。他は短め。

Route53はCloudFrontを指定。

今後のためのアップデート手順

masterブランチを最新にしてからマージ。

DB_HOSTはRDSを指定してるのでdb:migrateはローカルから直接行う。
これはどうなんだろうとは思うけど他の方法が分からなかった。

docker-compose build
docker-compose run --rm web rails db:migrate
docker-compose run --rm web rails assets:precompile

docker imageの更新はECRの手順通りに。

aws ecr get-login --region ap-northeast-1 | bash

docker build ...
docker tag ...
docker push ...

ecs-cli使ったほうが少し短いかも。

docker build ...
docker tag ...

ecs-cli push ...

後はup。upはymlが変更されてないと以前のタスクのままなのでimageだけ更新した場合は更新されない。
CIで動かしてdocker tagを設定してymlの書き換えまで自動化かな。

ecs-cli compose -f aws-web.yml --project-name mastodon-web service up
ecs-cli compose -f aws-api.yml --project-name mastodon-api service up

動きさえすれば運用段階では楽になる。

続きを読む

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のサービスを利用する形で切り出されたことになります。

その他

続きを読む