AWS
ElasticBeanstalk
docker

それではElastic Beanstalkの本気をお見せしよう

それでは前回宣言した通り、Elastic Beanstalkの設定ファイルを用いた環境構築の自動化をご紹介しようと思います。

はじめに

今回の大きなテーマは.ebextensionsを使った環境構築の自動化です。
そのための題材として、ナウいWebアプリのHTTPS対応をやってみたいと思います。

SSL証明書はACMに登録しているものを使う前提です。

なので今回の内容としては

  1. .ebextensionsの作成
  2. Elastic Beanstalkへのデプロイ
  3. Route53での名前解決

となります。

ナウい環境って?

こんなの

d4664c9e-b6ec-d8ba-d1a8-da0e2f05acc9.png

事前準備

ACMへの証明書の登録

まず、「そもそもドメイン持ってないよ」という方は、Route53のコンソールから適当なドメインを取得してください。
手順はこの記事がわかりやすいかと思います。

実践

では、色々と設定を.ebextensionsに追記して行きますが、
例によって適宜公式ドキュメントを確認すると捗ります。

そういえば.ebextensionsって何?

.ebextensionsはElastic Beanstalkの設定ファイル...ではなく設定ファイル群を格納するディレクトリです
プロジェクトルートの直下に.ebextensionsディレクトリを作り、その中に拡張子が.configのyaml形式かjson形式のファイルを置くことで、自動的に読み取って設定してくれます。

jsonでも大丈夫らしいですが、公式のドキュメントのサンプルとかでも基本的にyamlで記載しているので、
ここでもyamlで書きます。

HTTPS対応

HTTPS対応と一言で表してましたが、まずは前回つくった環境をベースにやるべきことを列挙します。

  • ロードバランサーの443ポートを開けてHTTPSを待ち受けるようにする
  • ロードバランサーが443で受けたリクエストをEC2のインスタンスに流す
  • ロードバランサーにACMにある証明書を紐づける

まず思いつくのはこれぐらいですね。
しかし、これだけだとロードバランサーがポート80で受け取ったHTTPがそのままDockerコンテナまで流れてしまうので、HTTPSの意味がありません。
よって、

  • ロードバランサーがHTTPで受けたリクエストをHTTPSにリダイレクトする

ということも必要になります。

ロードバランサーのポートを開けるぞ!

って時にはaws:elbv2:listener:<port_number>:という名前空間を使います。
この場合だとaws:elbv2:listener:443:ですね。

それでは.ebextensionsの下にload-balancer.configとか作って、設定を書いてみましょう。

.ebextensions/load-balancer.config
option_settings:
  aws:elbv2:listener:443:
    DefaultProcess: default
    ListenerEnabled: true
    Protocol: HTTPS

最初に目をひくoption_settingsというのは、Elastic Beanstalkの設定を書くときの約束事です。
なので、その気になればCloud Formationのテンプレートとかぶち込んでもっと色々できるのですが、今回は割愛します。

この中だとDefaultProcessってのが謎だと思いますが、プロセスというのは「ロードバランサーからEC2への通信の定義」(プロトコルとかポートとか、ヘルスチェックの仕方とか)ぐらいに捉えればいいかと思います。
なので、ここでは「443ポートでリクエストを受けたらdefaultって名前のプロセスを実行するよ」みたいな意味です。
プロセス自体の設定は後述します。

ACMの証明書を使うんだ!

証明書の紐付けはaws:elbv2:listener:<port_number>:の下にSSLCertificateArnsという項目を作り、そこにACMのARNを記載します。
こんな感じで。

.ebextensions/load-balancer.config
option_settings:
  aws:elbv2:listener:443:
    DefaultProcess: default
    ListenerEnabled: true
    SSLCertificateArns: arn:aws:acm:ap-northeast-1:<account-id>:certificate/<certificate>
    Protocol: HTTPS

楽やのう。

EC2にリクエストを流すぜ!

さてさて、ここで先ほど話題になったプロセスを定義します。
プロセスを定義する名前空間はaws:elasticbeanstalk:environment:process:<process_name>:です。
そこにEC2のどのポートにどのプロトコルで通信するかを定義します。

.ebextensions/load-balancer.config
option_settings:
  aws:elbv2:listener:443:
    DefaultProcess: default
    ListenerEnabled: true
    SSLCertificateArns: arn:aws:acm:ap-northeast-1:<account-id>:certificate/<certificate>
    Protocol: HTTPS

  aws:elasticbeanstalk:environment:process:default:
    Port: 80
    Protocol: HTTP
    StickinessEnabled: true # スティッキーセッションの有効化
    HealthCheckPath: /      # ヘルスチェック対象のパス
    HealthCheckTimeout: 30  # ヘルスチェックのタイムアウト時間(秒)
    HealthCheckInterval: 60 # ヘルスチェックの間隔(秒)
    MatcherHTTPCode: 200    # 「このコードが帰ってきたらヘルスチェック成功!」と言えるHTTPのステータスコード

上記の設定の意味合いをざっくり口語体で説明すると、
「ロードバランサーの443ポートでHTTPSの待受をするよ!リクエストがきたらdefaultっていうプロセスを始めるんだ!defaultプロセスってのはEC2の80ポートにHTTPでリクエストを流すんだよ!60秒ごとにヘルスチェックもしちゃうよ!」って感じです。
(ざっくりしすぎやろなぁ...)

HTTPで来たリクエストはHTTPSでリダイレクト!

さて、今度はHTTPでアクセスされたらHTTPSにリダイレクトさせる設定をしていくわけですが、
残念ながらロードバランサーにはそのような機能は無いため、EC2に頑張ってもらいます。
しかし、HTTPSの証明書のあれこれなどはロードバランサーで終了し、そこから先の通信はHTTPなのでEC2でプロトコルの種類を判別させることはできません。

というわけで↓のような方針を取ります。

ロードバランサーがリクエストを

  • 443で受け取る -> EC2の80に流す
  • 80で -> EC2の80以外の適当なポート(今回は81を使用)に流す

EC2のnginxがリクエストを

  • 80で受け取る -> Dockerコンテナへ
  • 81で受け取る -> HTTPSでリダイレクトさせる

まずはnginxの設定を書くため、.ebextensions/nginx.configを作成します。

.ebextensions/nginx.config
files:
  /etc/nginx/conf.d/redirect.conf:
    mode: "000644"
    owner: root
    group: root
    content: |
      # Redirect HTTP To HTTPS

      server {
        listen 81;
        rewrite ^ https://$host$request_uri permanent;
      }

filesという名前空間を使うことで、Elastic Beanstalkが作ったEC2インスタンスにファイルを置くことができます。
あとは、先ほどの要領でロードバランサーの設定も追記します。

.ebextensions/load-balancer.config
option_settings:
  aws:elasticbeanstalk:environment:
    LoadBalancerType: application

  aws:elbv2:listener:443:
    DefaultProcess: default
    ListenerEnabled: true
    SSLCertificateArns: arn:aws:acm:ap-northeast-1:<account-id>:certificate/<certificate>
    Protocol: HTTPS

  aws:elasticbeanstalk:environment:process:default:
    Port: 80
    Protocol: HTTP
    StickinessEnabled: true # スティッキーセッションの有効化
    HealthCheckPath: /      # ヘルスチェック対象のパス
    HealthCheckTimeout: 30  # ヘルスチェックのタイムアウト時間(秒)
    HealthCheckInterval: 60 # ヘルスチェックの間隔(秒)
    MatcherHTTPCode: 200    # 「このコードが帰ってきたらヘルスチェック成功!」と言えるHTTPのステータスコード

  aws:elbv2:listener:80:    # ロードバランサーが80ポートで受けた時の定義
    DefaultProcess: http
    ListenerEnabled: true
    Protocol: HTTP

  aws:elasticbeanstalk:environment:process:http:
    Port: 81
    Protocol: HTTP
    HealthCheckPath: /
    HealthCheckTimeout: 30
    HealthCheckInterval: 60
    MatcherHTTPCode: 301    # リダイレクトされるのでコードは301が帰ってくるはず

さあ、デプロイしようか

「すぐに環境が作れるよ」がうたい文句なので、エンバイロメントを新しく作ることにします。
VPCの設定とかをまたコマンドで打つのはだるいので、その辺も設定に書いておきます。

新しくec2.configというファイルを作ります。

.ebextensions/ec2.config
option_settings:
  aws:ec2:vpc:
    VPCId: vpc-5920d83d   # VPCのID
    Subnets: subnet-9ce850ea,subnet-8128ead9    # EC2のサブネットのID
    ELBSubnets: subnet-9ce850ea,subnet-8128ead9 # ELBのサブネットのID
    AssociatePublicIpAddress: true

  aws:autoscaling:asg: # オートスケーリングの設定
    MinSize: 1
    MaxSize: 2

  aws:autoscaling:launchconfiguration:
    EC2KeyName: <key_name>                             # SSHキー
    SSHSourceRestriction: tcp, 22, 22, <my_ip_address> # SSHのアクセス権限 お使いのIPアドレスを

さて、これでeb createと行きたいところですが、ソースと思しきファイルがないとElastic Beanstalkはサンプルアプリでエンバイロメントを構築するのですが、
その場合ファイルのアップロードが行われないためせっかく書いた.ebextensions以下の設定がガン無視されます。

なので適当なDockerfileを用意しておきます。

Dockerfile
FROM ubuntu:12.04

RUN apt-get update
RUN apt-get install -y nginx zip curl

RUN echo "daemon off;" >> /etc/nginx/nginx.conf
RUN curl -o /usr/share/nginx/www/master.zip -L https://codeload.github.com/gabrielecirulli/2048/zip/master
RUN cd /usr/share/nginx/www/ && unzip master.zip && mv 2048-master/* . && rm -rf 2048-master master.zip

EXPOSE 80

CMD ["/usr/sbin/nginx", "-c", "/etc/nginx/nginx.conf"]

これでeb createしてみましょう。
ロードバランサーのタイプは設定に書いてても聞かれるので、必ずapplicationと答えましょう。

エンバイロメントが出来上がってからElastic Beanstalkのコンソールに記載されているURLにアクセスすると、
こんな画面になります。
ただし、この状態ではSSL証明書のドメインとリクエストURLのドメインが異なるため証明書のエラーが発生します。
スクリーンショット 2016-10-28 23.14.43.png

仕上げはRoute53!

さあ、あとはRoute53をElastic Beanstalkに向けるだけです。
Route53のコンソールからまず取得しているドメインでHosted zoneを作成し、適当なサブドメインでレコードセットを作ります。

タイプをAレコードのIPv4にすると候補にElastic Beanstalkのアプリが候補に出ますので、そいつを選んでやりましょう。
今回私はwww.mic-u-neko-masshigura.netというドメインを使います。

スクリーンショット 2016-10-28 23.21.01.png

こんなしょぼいサンプルアプリのために.netを使うとは、なんて贅沢なんでしょう。

レコードセットを作ったらwww.mic-u-neko-masshigura.netにアクセスします。

スクリーンショット 2016-10-28 23.25.26.png

アドレスバー左の鍵のマークが緑色になっているので、SSLが成功していることがわかります。
あと、画像からじゃわかりませんが、HTTPでアクセスしてもHTTPSにリダイレクトされます。

終わりに

長い戦いを経て、ナウい環境を設定ファイルに落とし込むことができました。
ここまで長い道のりでしたが、あとはこの.enextensionsさえあれば同様の環境がコマンド一発で量産できます。

オンプレで用意しようとしたら考えるのも嫌なぐらい面倒で、AWSでも手動で毎回構築するのは結構面倒臭い環境を(最初に設定ファイルを用意するのが面倒とはいえ)簡単に構築して量産できるのは魅力ではないでしょうか?

個人的な印象ではElastic Beanstalkってまだまだあまり使われていないような気がしますが、
上手く使えば本当に便利ですのでこれを機に皆さんどんどんElastic Beanstalkに触れてみてください!