AWS の Certificate Manager(ACM) で SSL 証明書を発行

はじめに

SSL 対応するのって個人的には面倒くさい作業のイメージがあったのですが、
さすが Amazon 先生、Certificate Manager を使えばサクッとできてしまいます。
何より ELB を使っていればインスタンス代がかかるだけで無料なのが素晴らしい!!
今回は文字だけで恐縮ですが、ざっとやり方を。

さっそく発行

ドメイン名の追加

AWS のサービスから Certificate Manager を選択し、
今すぐ始める をクリックします。

すると ドメイン名の追加 の画面に行くので、ドメインを入力し、確認とリクエスト で次の画面に遷移します。

画面上に下記説明があるよう、ドメインの入力にはワイルドカードを使ってサブドメインも指定できます。

SSL/TLS 証明書により保護するサイトの完全修飾ドメイン名 (www.example.com など) を入力します。同じドメイン内の複数のサイトを保護するには、アスタリスク (*) を使用して、ワイルドカード証明書をリクエストします。たとえば *.example.com とすると、www.example.com、site.example.com、images.example.com が保護されます。

サブドメインがない場合もある場合も SSL 証明書を発行したい時には
まずどちらかのドメインを入力した後に この証明書に別の名前を追加 をクリックして、
もう片方の名前を追加します。

確認とリクエスト

先程の この証明書に別の名前を追加 をクリックすると、
SSL 証明書によって保護する名前の確認画面に行くので、
ただしいドメイン名が入力されているかを確認後、確定とリクエスト をクリック。

間違っていた場合は 戻る で修正します。

検証

先程の 確定とリクエスト をクリックすると、画面に表示されているメールアドレスにメールが飛ぶので、そのメール内の URL を踏んで 承認 すれば証明書の発行完了です。

まとめ

これだけで SSL 証明書の発行完了。
ELB の割当もすぐにできます。割当についてはまたの機会に

続きを読む

S3+CloudFrontで、index.htmlが落ちてくる/付加されないばあい

概要

S3+CloudFrontで静的なページをAWSにもっていこうとした所、引っかかった部分のまとめ。

  • w3mで接続確認しようとすると、内容が表示されずにindex.htmlがダウンロードされる
  • Content-Typeを疑い、opensslでつなごうとするとSSLのエラー

以上の状態からの対応作業

CustomSSL(SNI)の証明書の場合の接続確認

  • 証明書が有効か確認しようとしてみるとエラー
$openssl s_client -connect example.com:443
no peer certificate available
  • サーバーネームを指定する必要がある
$openssl s_client -connect example.com:443 -servername example.com

GET / HTTP/1.0で400 Bad Request

GET / HTTP/1.0

HTTP/1.1 400 Bad Request

Server: CloudFront
Date: Mon, 24 Apr 2017 03:00:56 GMT
Content-Type: text/html
Content-Length: 551
Connection: close
  • きちんとHostもつける
GET / HTTP/1.0
Host: example.com

HTTP/1.1 200 OK
Content-Type: application/x-directory
Content-Length: 0
Connection: close

CloudFrontで設定したドメイン / へのアクセス時に、index.htmlが補完されない(デフォルトルートオブジェクト)

  • S3で設定していたとしても、CloudFront側で設定しなければindexは補完されない
HTTP/1.1 200 OK
Content-Type: application/x-directory
Content-Length: 0
Connection: close

http://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/DefaultRootObject.html

SNIのところと、indexの補完が出ずに二日も時間取ってしまった悲しみ…

続きを読む

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:

続きを読む

AWSにDjango環境を構築する

versions

Python : 2.7
Django : 1.10

Environment

EC2インスタンス : Amazon Linux
WSGI : gunicorn
Proxy : nginx

install python packages

sudo yum update
sudo yum install gcc
sudo yum install python-devel
sudo yum install python-psycopg2
sudo yum install postgresql-devel
pip install -r requirements.txt

reverse proxy

sudo yum install nginx
sudo vi /etc/nginx/nginx.conf
  • nginx.confに追記 line : 49
location / {
    proxy_pass http://127.0.0.1:8000;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}

# http://mydomain/static/ をDjangoのsettings.pyでSTATIC_ROOTに指定したディレクトリへルーティング
location /static/ {
    autoindex on;
    alias   /usr/share/nginx/html/django-project-name/staticfiles/;
}
sudo chmod 777 -R /usr/share/nginx/html ※1
sudo service nginx restart
gunicorn django-project-name.wsgi -D

Memo

python manage.py runserver 0.0.0.0:8000  # 全てのホストアドレスからのアクセスを許可
python manage.py migrate
python manage.py createsuperuser

# 事前にsettings.pyにSTATIC_ROOTを設定する (defaultでは記載されていない)
python manage.py collectstatic  # STATIC_ROOTに指定したディレクトリに静的ファイルが生成される

※1 : このディレクトリ下にDjangoプロジェクトを設置
当初はSTATIC_ROOTに設定したディレクトリのシンボリックリンクのみをここに貼ったが
静的ファイルを読み込む際に403エラーが発生してしまうためプロジェクトごとこのディレクトリに設置
所有がrootのディレクトリに対して、パーミッションを777に設定してしまうのは多少疑問が残るところ

SSL証明書

証明書はAWS Certificate Managerで発行し、Elastic Load Balancerにインストール
ELBとEC2間の通信はhttpとなるが、ELBとEC2が同じリージョンにある場合セキュリティの問題はなさそう

続きを読む

AWS SSLの掛け方 (ドメインはお名前.com)

サイトを立ち上げる際にhttpsにしないと信頼がないということで、AWSでSSLを使うことにしてみた。
つまづいたことを残す。

つまづいたこと

  • SSL証明書を発行して、ロードバランサに付与したけど、httpsで通信で暗号化されない。

    •  お名前.comのDNSレコード設定でインスタンスのIPアドレスをAレコードで設定していた。
    •  ロードバランサで暗号化するので、インスタンス直で接続しても暗号化されないのは当たり前。。
    •  ロードバランサのDNS名をCNAMEレコードで設定して解決。
  • ロードバランサにインタンスを設定したが、http/httpsでも応答なし。

    • セキュリティグループにhttps/httpを通す設定がされていなかった。

続きを読む

インフラチームによくある極小サーバーやバッチジョブをECSで集約したい

すごい。この記事には文字しかない。

話の発端

ほとんどアクセスが無く、あっても昼間しかなく、しかし消すわけにはいかない社内サービスのようなデモサービスのようなサーバーが豪勢にもEC2単独インスタンスを持っていると、CPUを使わなすぎてリソースがもったいなかったり、たまったCPUクレジットを捨ててしまったりしてもったいない。日時バッチサーバーみたいなものも似たような状況で、バッチ稼働時以外は上と同じ無駄を今も発生させている。

では、全部Elastic Container Serviceでコンテナにしてインスタンスを集約して、昼間ためたクレジットを夜中の日時バッチで使うのはどうか、という手を考えた。外国リージョンではAWS Batchってやつが使えるようになっているので、そいつが東京リージョンに来たときにうまいことECSから移行できると最高に素敵だ。

各ミニサービスのECSへの集約

上述ミニサーバー群はまがいなりにもサーバーで、しかもSSLなので、IPを固定したい。

が、コンテナにEIPを付けることはできず、Global IPを持たせたい場合は、かわりにELBを使うのが定石1

なのだが、さして重要でもないのにELBを使うのはオーバースペック、というかお金の無駄なので、Unmanagedなコンテナインスタンスを作成し、その中でnginxを動かして、コンテナにリバースプロキシすればーーー、ということを考えていた。コンテナインスタンス障害時にはまるごと作り直せばいいようなサービスレベルだし、それが簡単にできちゃうのがコンテナの長所だ。

が、じゃあそもそもnginxもコンテナでよくね、っていうことに気付いた。コンテナ間でリバースプロキシしちゃえばいいじゃん。

ELB無しでSSLできるコンテナインスタンスの作成

つまりコンテナインスタンスにEIPをつけたい、ということ。ECSのウィザードからクラスタを作成する場合、コンテナインスタンスのカスタマイズ可能項目は少ない。カスタム可能 (つまりコンテナインスタンス作成時に指定可能) なものは

  • インスタンスタイプ
  • ディスク容量
  • VPC
  • Security Group

だけで、Public IPとPrivate IPは自動的に割り当てられるため、コンテナインスタンス作成時にIPやEIPの指定はできない。なので、このあとに、作成されたEC2インスタンスに対して

  1. EIPを作成
  2. EIPをコンテナインスタンスのインターフェースに割り当て

ってやるとコンテナインスタンスのIPを安全に固定できる。

実際にEIPを付けた直後からコンテナに疎通可能になっていて、しかもECSから見えるコンテナインスタンス情報も自動で更新され、Global IPは新しいEIPになっていることが確認できる。便利な世の中になったもんだ。

コンテナ間リンク

nginxもコンテナなので、リバースプロキシ先はコンテナのリンクでOK、と思っていたのだが、なんとコンテナのリンクは同じタスク定義に属したコンテナ間のみで可能とのこと2。つまりexternal_linkは書けない。でも今回のユースケースだと、コンテナごとにタスク定義を変えるタイミング (Docker imageの更新とか) は異なるので、コンテナごとにタスク定義を作ることになる。

ってことは、コンテナごとに80:10080443:10443みたいなPort Mappingを書き、nginxはこのポートとコンテナインスタンスのローカルアドレスで、各コンテナへリバースプロキシする。まぁPort Mappingは複数コンテナインスタンスにまたがってコンテナを配置をしたい場合とかに結局必要だよね、たぶん。

Unmanaged Container Instanceを作りたい場合

ちなみに、どうしてもオレオレカスタムなUnmanagedコンテナインスタンスを作りたいときは

  1. 自力でEC2インスタンスを作成する

    • AWSが用意しているECS用のAMIを使う
    • か、それ以外を使う場合は自分でAgentのインストールなどが必要
    • めんどくさいけどEC2でインスタンスをいろいろいじれる
  2. ECSクラスタ作成時に空のクラスタを作る
  3. さっきのインスタンスをクラスタに追加する

という手順で可能らしい3

でもnginxもコンテナにしちゃうなら、ECSウィザード経由でデフォルトのAMI使う方が再作成と運用が楽な気がする。できるだけ外の世界へ出て行こうとしないのがパブリッククラウドの鉄則。

AWS Batch

ドキュメントを†熟読† (3分) した感じ、ECSにジョブキューを付けて、順番やリトライ、キューごとの計算環境の割り当て、実行時間やリソースの制限、優先度とかを定義できるようにしたものっぽい。†熟読†して思ったことは、

  • 要はPBS4サービスみたいなもんじゃね? PBSのインストールと運用は意外にめんどくさいらしい。というか需要がニッチすぎて活発には開発されてないらしい。通常SQSとかAMQPが実装された何かを使うんじゃないかしら。
  • GPUインスタンスが使えたりとか、AWSをスパコン的に使いたい人にはグッとくるのかもしれない5が、いるのかそんな人?並列性能上げると青天井でお金かかるし、1週間かかる機械学習だとやっぱり青天井でお金かかるし、そういう人は京とかどっかのスパコン借りた方が安いんじゃ……ぶつぶつ……

というわけで、AWS Lambdaで時間足りない人向け、という位置づけが現実的。

ECSからAWS Batchへの移行を考える

東京リージョンで今でも使えるECSから、今後東京リージョンにも展開されるであろうAWS Batchへの移行を考えると、既存のECSクラスタをAWS BatchのCompute Environment (CE) に変換できるとうれしいぞ。ドキュメントを読んでみると

By default, AWS Batch managed compute environments use the latest Amazon ECS-optimized AMI for compute resources.6

どうやらCEはECSのAMIをそのまま使うだけっぽいので、ECSクラスタを作ったあとにCEに追加できるのでは?と思ったが、 (画面上からは) ECSクラスタをCEに変換することはできなさそう。逆はできる、つまりCEをECSクラスタとして扱うことはできる。というか、CEを作った時点で空のECSクラスタが作成されている。末尾にUUIDとかついた、とてもゴチャったECSクラスタが作成される。

AWSというサービスの哲学として、Manageされるもの (e.g. ECSクラスタ) はManageするもの (e.g. CE) にはできない。これは言われれば分かるけど、気付かないと「不便だ」と不要な精神の不衛生を招く。

というわけで、ECSからそのままBatchに移行はできなさそうなので、今回のように特定のEIPを使い続けたいような場合、ECSコンテナインスタンスのEIPを、CEから生まれたECSコンテナインスタンスに移行時に付け替えることになる。

API使っても瞬断が出るが、まぁそもそもそこまでサービスレベルは高くない。はずだ。

でもこれって、ECSクラスタ間でコンテナインスタンスの移動ができれば解決するんじゃ?

と思って調べてみると

各コンテナインスタンスには、それぞれに固有の状態情報がコンテナインスタンスにローカルで保存され、Amazon ECS にも保存されているため、コンテナインスタンスを 1 つのクラスターから登録解除して別のクラスターに再登録しないでください。コンテナインスタンスリソースを再配置するには、1 つのクラスターからコンテナインスタンスを終了し、新しいクラスター内の Amazon ECS に最適化された最新の AMI で、新しいコンテナインスタンスを起動することをお勧めします。7

ぐはっ……。

サーバー系はECSに残し、バッチ系だけAWS Batchに、という住み分けが清潔なのかもしれないけど、それじゃ集約にならないなぁ。

Refs.

続きを読む

CRMのF-RevoをAWS Lightsailで構築したときにハマったこと(Apache,let’s encrypt)

はじめに

CRM(F-Revo)を導入したときにハマってしまったことを書いていきます。
F-Revoの環境についてはこちらです。
Lightsailどうこうではない部分もあります。

CRMをインストールしようとしたらページが真っ白になった件

F-Revoのインストール設定を入力したあと、こんなページまで行きます。

20150917093149.png

しかしこのあと次のページに遷移すると真っ白なページになって、戻るも進むなくなり、二進も三進もいかなくなりました。
原因はCRMがPHP7に対応していないからでした。
現場の開発はPHP7でやっていたため、できればPHP7を使用してF-Revoをインストールしたかったのですが、プライオリティは低いということでPHP5.6を再インストールしました。

参考
オープンソースなので、自己責任でソースを書き換えればPHP7でも動作するという書き込みもありましたのでご参考までに。
Vtiger with PHP7
https://discussions.vtiger.com/index.php?p=/discussion/183662/vtiger-with-php7/p1
PHP 7 compatibility
http://code.vtiger.com/vtiger/vtigercrm/issues/197

Let’s encryptがうまく実行されない件

オレオレ認証局ではなく、Let’s encryptを利用して証明書を作成しようと試みましたが失敗した話です。
結論から言うと、mod24_sslがインストールされていないからに落ち着きます。
「Let’s encryptはとにかく簡単。パッケージをインストールしてコマンドを叩けば自動で証明書の作成がされるよ」とみたいな話ではあるものの、凡ミスのせいで鮮やかにコケたというお話です。

ではコケるまでの手順を見ていきましょう。
1. インストールコマンドを叩く

$ wget https://dl.eff.org/certbot-auto
$ chmod a+x certbot-auto
$ ./certbot-auto --agree-tos --webroot-path <RootDirectory> -d <DomainName> -m <E-Mail>
※--webroot-pathはhttpdを実行したまま証明書を発行できるオプションです。

はい! ここでコケました!
そして下記のエラーが出力されました。

Saving debug log to /var/log/letsencrypt/letsencrypt.log
Obtaining a new certificate
Performing the following challenges:
tls-sni-01 challenge for <DomainName>
No vhost exists with servername or alias of: <DomainName> (or it's in a file with multiple vhosts, which Certbot can't parse yet). No vhost was selected. Please specify ServerName or ServerAlias in the Apache config, or split vhosts into separate files.
Falling back to default vhost *:443...
Waiting for verification...
Cleaning up challenges
Failed authorization procedure. <DomainName> (tls-sni-01): urn:acme:error:connection :: The server could not connect to the client to verify the domain :: Failed to connect to <IPaddress>:443 for TLS-SNI-01 challenge

IMPORTANT NOTES:

翻訳すると、「DomainNameを持つ仮想ホストは存在しません」みたいなことが書いてあり、「Apacheの設定でServerNameまたはServerAliasを指定するか、仮想ホストを別々のファイルに分割してください」ともあります。
試しにApache設定にVirtualHostを追記しましたがエラーに変化はないです。
/etc/resolv.confの設定ミスというわけでもなかったです。
443が云々というエラーメッセージもあるのでssl.confを見に行ってみると、ssl.confがないことに気づきました。
というわけで、mod24_sslをyum installし、Let’s encryptにて証明書発行が実行できました。

常時https接続に設定変更したらCRMのWEBページにたどり着けなくなった件

少し話が長くなりますので、結論からいうとconfig.inc.phpに記載されたsite_URLがhttp://~になっていたことと、/etc/httpd/conf.d/le-redirect-DomainName.com.confが原因でした。

事の始まり

Let’s encryptのコマンドを無事実行し、いよいよ終了が近づくと「httpもhttpsもイケるようにする?それとも常時https接続にする?」みたいな二択が表示されます。

・・・で、もともと常時https接続にする予定だったこともあり、常時の方を選択しました。
「よーしこれで80ポートは閉めていいな?」ということで、LightsailのGUIコンソール側でセキュリティルールを変更しました。
そしてこのあとユーザさんから「ログインできない!」と続々お問い合わせが来てしまったのです。
見てみると、エラー内容はタイムアウトでした。

となると、まず疑うべきは

  • 「常時https接続する」を選択したときに、自動でどこかにその設定を読み込むコンフィグが作成された可能性

ということです。そのコンフィグが作成されたこと一点が原因でログイン画面に行けないのであれば、コンフィグ名を変更してやることで今までのようにhttp接続でログインページにたどり着けるようになるはずです。

はい、ありました。le-redirectと名前がついてるので決定的です。
中身を見てみると、httpsにRewriteしているようです。とりあえずこのファイル名を変更し、httpdを再起動します。

/etc/httpd/conf.d/le-redirect-DomainName.com.conf
<VirtualHost _default_:80>
ServerName <DomainName>:443
ServerAlias <DomainName>
ServerSignature Off

RewriteEngine On
RewriteRule ^ https://%{SERVER_NAME}%{REQUEST_URI} [END,QSA,R=permanent]

ErrorLog /var/log/httpd/redirect.error.log
LogLevel warn
</VirtualHost>

ただ、これだけだと80ポートを閉じているのでhttp通信もできない状態。再度Lightsailの80ポートを開きます。
これで一旦、http通信ではあるけれどログインページにはこれまで通りいけるはず!

  • 強制リダイレクトしてるファイルを読み込ませない
  • http通信を許可する

応急処置が終わったら次はhttpsで接続できるようにしていきます。

そもそもなんでHTTPS接続できなかったか

いわゆるリダイレクトループと言えばいいんでしょうか。

リクエスト→http→強制でhttpsに変換→config.inc.phpに記載されたsite_URLがhttp://~のためhttpになる→強制でhttpsに→→→というイメージです。

config.inc.phpを一部抜粋するとこんな感じで記述されています。

/var/www/html/config.inc.php
$site_URL = 'http://<IPaddress>';

つまりこれを、httpsと記述してやればいいわけです。もっというと、ドメイン名でURLを表記させたいので以下のように変更しました。

/var/www/html/config.inc.php
#$site_URL = 'http://<IPaddress>/~';
$site_URL = 'https://<DomainName>/~';

そして改めて、必要のない80ポートを閉じます。
これで私の環境ではhttps接続が可能になりました!

おわりに

長くなりましたが、大抵ネットワーク関連で死ぬときは設定漏れか把握してないファイルがいるみたいなときなんですかね。ともあれユーザが安全、安定して使用してもらえるよう運用していきたいです。

続きを読む

個人的mastodonの出来事(技術編)

何の話か

結果的に中規模インスタンス(一応某リストで上位100以内にはいっているから現状間違ってないはず)を4台立てることになったので、その経緯とかそこで知ったこととか書きます。

得られた知見

総括として、

  • 100-300人程度であればDBあたりはまだネックにならずメモリが真っ先に悲鳴をあげた(1GBしかない場合)
  • 100人超えると何もいじってないt2.microだときつい
  • 300人超えたくらいでもt2.smallでとりあえずスペック的には動く

ただし、金銭的な都合でRDSやElastiCache、複数EC2による冗長構成などをとっていないために、インスタンスサイズを変更する時など絶対にサービスが止まるので、そこは今回妥協しています。
また、CloudFrontやS3を使うこともできるのですがこちらも金銭的事情と今の所なんとかなっているので使ってないです。

環境としては

  • mailにmailgun
  • SSL証明書にLet’s Encrypt
  • サーバーはだいたいAWS
  • 最低限のメトリクス監視はmackerel

です。

mastodonをはじめたところ

4/12に多分Twitterでみかけてmastodonを始めた。現mstdn.jpインスタンスでアカウントを作りました。
しばらく楽しくやってたのですが、どんどん人が増えるのでコンセンプト的には分散させたほうがいいよなーという思いとか、mastodonで知り合った人にボドゲインスタンスほしいって言われたとか、この勢いなら人くるんじゃね?という企みとかそういうのがあって立てようかなと考え始めました。
結果、4/12は見送ったものの、4/13にmstdn.jpが不安定なこともあり分散する流れになるだろうと判断してそれなら急ごうとその夜から構築を開始することにしました。

1台目 tablegame.mstdn.cloud

どうせAWSとか使うと金銭的に維持できないと思い、家にあったサーバーを流用することに。
帰りにLANカードとバカHUBを買って帰宅。
マシン構成としては

  • ubuntu 14.04 LTS
  • CPU: Sempron 145
  • RAM: 4GB

と非常に貧弱ながらも、ジャンルがニッチなのでこれで試そうと突っ込みました。
ちなみに、Dockerは使っておらず、理由としてはDockerにそこまで明るくないので死活監視とかDB周りをいじる必要が出た時にうまく弄れる自信がなかったためです。

構築で苦労したことは、LANとFletsのPPPoE直の物理インタフェース2本構成になったので、そのroutingとかiptablesわすれたとかその辺です。
また、設定を間違えたままresorceをbuildしたところ、アプリの設定など以外の動的に変わる部分(メインのタイムラインですね)が何も表示されない事態に陥り、そこで困ったりしました。
消してrebuildしたら治りましたが。
あとは、ubuntu 14.04なのでデフォルトのdaemon化する設定が使えず、現状nohupで動かしています。streamのnodeがよく落ちるのでそこだけpythonで監視して落ちてれば再起動するスクリプトをcronで回すという手荒い対応です。

結果として現在300人弱ですが、快適に動いています。

2台目 socialgame.mstdn.cloud

思ったよりうまく動いて、昼休みに先輩と喋っていた勢いで2台目を作りました。
こちらはジャンル的に人が集まる可能性もあったので、AWSを使って、でもまずは最小構成でということでt2.microにすべて載せる構成で作りました。

こちらはUbuntu 16.04なので公式ドキュメント通りに構築しました。(ただしこちらも非Dockerで)

ちゃんと動いたのは良かったものの、その日の夜にメモリを使いきりswapし始めた途端にアクセスさえ困難な状況に。
この時、sshで入れない状況だったのですが公式ドキュメント通りに作っていたのが功をなし、AWSコンソールから直接停止、インスタンスサイズアップで難を逃れました。

結果t2.small一台で現状300人超えたくらいですが、快適に動いています。

3台目, 4台目

2台目を参考に構築しました。4台目のR18可能なソシャゲインスタンス構築の話についてはできれば別の記事で書きます。

技術的タスク

  • バージョンアップが早いのでバージョンアップをどう実施していくか。
  • SSL証明書の更新やLet’s Encryptについてもっと知る
  • mailgunが一部制限かかっているのでそれの対応

最後に

タスク整理とかもあったので書いてみたんですが、これ、得られる情報ありますかね。。。
とりあえずお一人様や100人未満のインスタンスなら多分t2.microで動くと思うので無料枠でお試しとかするといいですよ、くらいかもしれないです。

続きを読む

SSLで運用しているmastodonとS3の連携のハマりどころ

mastodonの画像とか動画をS3で管理しようと思ったらハマったので解決策を書きます。

ハマりどころ

.env.productionのS3の設定項目のうち、S3_ENDPOINTもちゃんと設定しないと、S3のSSLがうまく働かない。

原因

S3_ENDPOINTを設定しないと、画像のURLがhttps://${bucket-name}.sp-${region}.amazonaws.com/.....のようになる。
ところが、S3のSSL鍵はsp-${region}.amazonaws.comドメインに対してのものなので、「不正なSSL鍵です」という警告が出て、ブラウザでアクセスできない。

結論

.env.productionに以下のようなオプションを書き加えればよい。

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

感想

.env.production.sampleの書き方がわかりづらいと思った。

続きを読む

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

続きを読む