ALB(Application Load Balancer)でWebサービスを冗長化する

概要

ALBを使ってアプリケーションを冗長化する手順です。

HTTPS接続でアプリケーションにアクセス出来るところまでをこの記事で紹介します。

前提条件

以下の事前条件が必要です。

  • VPCの作成を行っておく
  • 最低でも2台のWebサーバインスタンスを起動させておく事
  • ロードバランサー用サブネットの作成が行われている事(後で説明します。)

事前準備その1(ロードバランサー用サブネットの作成)

以下は公式サイトに書かれている内容です。

ロードバランサーのアベイラビリティーゾーンを指定します。ロードバランサーは、これらのアベイラビリティーゾーンにのみトラフィックをルーティングします。アベイラビリティーゾーンごとに 1 つだけサブネットを指定できます。ロードバランサーの可用性を高めるには、2 つ以上のアベイラビリティーゾーンからサブネットを指定する必要があります。

今回検証で利用している東京リージョンには ap-northeast-1aap-northeast-1c の2つのアベイラビリティーゾーンが存在するので、それぞれでサブネットの作成を行います。

サービス → VPC → サブネット → 「サブネットの作成」より作成を行います。

ap-northeast-1a で サブネットを作成します。
以下のように入力を行います。

  • ネームタグ

    • account_api_alb_1a
    • 開発環境アカウント用APIのALB用と分かる名前を付けています。分かりやすい名前であれば何でも構いません。
  • VPC

    • 利用対象となるVPCを選択します。
  • IPv4 CIRD block

    • 192.0.30.0/24
    • ネットワークの設計方針にもよりますが今回は 192.0.30.0/24 を割り当てます。

alb_subnet_step1.png

続いて ap-northeast-1c でも同じ要領でサブネットを作成します。
※先程とほとんど同じなので、入力内容に関しての詳細は省略します。

alb_subnet_step2.png

事前準備その2(SSLの証明書の用意)

SSLで接続を可能にするのでSSL証明書の用意が必要です。

今回は検証なので自己証明書を利用する事にします。

以前、LAMP 環境構築 PHP 7 MySQL 5.7(前編) という記事を書きました。

こちらに載っている手順を参考に自己証明書を用意します。

ALB(Application Load Balancer)の新規作成

ここからが本題になります。
サービス → EC2 → ロードバランサー → ロードバランサーの作成 を選択します。

alb_step1.png

Step1 ロードバランサーの設定

基本的な設定を行っていきます。
名前を入力します。(今回はaccount-api-alb)という名前を付けました。

インターネットに公開するサービスを想定しているので、スキーマは「インターネット向け」を選択します。

ロードバランサーのプロトコルにHTTPSを追加します。

alb_step2-1.png

アベイラビリティーゾーンに先程作成したサブネットを割り当てます。

alb_step2-2.png

Step2 セキュリティ設定の構成

SSL証明書の設定を行います。

alb_step2-3.png

証明書の名前は分かりやすい名前でOKです。

プライベートキーには事前準備で作成した、プライベートキーを入れます。
-----BEGIN RSA PRIVATE KEY----- から -----END RSA PRIVATE KEY----- までを全てコピーして下さい。

パブリックキー証明書には -----BEGIN CERTIFICATE----- から -----END CERTIFICATE----- までの内容を全てコピーして下さい。

セキュリティポリシーは ELBSecurityPolicy-2016-08 を選択します。

※2017-05-22 現在、この手順で問題なく証明書の追加が出来るハズなのですが Certificate not found というエラーが発生しロードバランサーの作成に失敗してしまいます。

証明書のアップロードを aws-cli を使って事前に実施するようにしたら上手く行きました。

証明書のアップロード
aws iam upload-server-certificate --server-certificate-name self-certificate --certificate-body file://crt.crt --private-key file://private.key

file:// を付けるのがポイントです。これがないと上手くアップロード出来ませんでした。

--server-certificate-name には任意の名前を入力して下さい。

上手く行くと下記のようなレスポンスが返ってきます。

証明書アップロードのレスポンス
{
    "ServerCertificateMetadata": {
        "ServerCertificateId": "XXXXXXXXXXXXXXXXXXXXX",
        "ServerCertificateName": "self-certificate",
        "Expiration": "2018-05-22T04:14:02Z",
        "Path": "/",
        "Arn": "arn:aws:iam::999999999999:server-certificate/self-certificate",
        "UploadDate": "2017-05-22T05:58:44.754Z"
    }
}

アップロード完了後に「AWS Identity and Access Management(IAM)から、既存の証明書を選択する」を選んで先程アップロードした証明書を選択して下さい。

alb_step2-3.1.png

この問題については 既存の ELB に SSL 証明書を追加しようとすると Server Certificate not found for the key というエラーになる件の解決方法 を参考にさせて頂きました。

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

セキュリティグループの設定を行います。

alb_step2-4.png

Step4 ルーティングの設定

ターゲットグループの新規作成を行います。

alb_step2-5.png

名前、プロトコル、ヘルスチェック用のURLの設定等を行います。

Step5 ターゲットの登録

ロードバランサーの配下で起動するインスタンスを選択します。

alb_step2-6.png

作成に必要な情報入力は以上となります。

確認画面に進み作成を行いしばらくすると、ロードバランサーが作成され利用可能な状態となります。

※サービス → EC2 → ロードバランサー より確認が出来ます。

alb_step3.png

動作確認

サービス → EC2 → ロードバランサー よりDNSが確認出来るので、動作確認を行います。

curl -kv https://account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com/
*   Trying 0.0.0.0...
* TCP_NODELAY set
* Connected to account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com (0.0.0.0) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
* Server certificate: system
> GET / HTTP/1.1
> Host: account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com
> User-Agent: curl/7.51.0
> Accept: */*
>
< HTTP/1.1 404 Not Found
< Date: Mon, 22 May 2017 07:26:02 GMT
< Content-Type: application/json
< Transfer-Encoding: chunked
< Connection: keep-alive
< Server: nginx/1.12.0
< X-Request-Id: 76c7e41f-1a4e-4328-972c-b98055e84395
< Cache-Control: no-cache, private
<
* Curl_http_done: called premature == 0
* Connection #0 to host account-api-alb-000000000.ap-northeast-1.elb.amazonaws.com left intact
{"code":404,"message":"Not Found"}

各Webサーバのログを確認すると、処理が振り分けられているのが、確認出来ます。

本番環境での運用に向けて

ここまで簡単に作成が出来ましたが実環境で運用を行うにはまだまだ考慮が必要な点が多いです。

  • SSL証明書を正式な物にする(自己証明書で運用とかはさすがに厳しいと思います)
  • 独自ドメインでのアクセスを可能にする
  • 各EC2のログに記載されているIPがロードバランサーの物になっている

※これらの手順は順次行っていく予定ですので、準備が出来次第記事を書く予定です。

最後まで読んで頂きありがとうございました。

続きを読む

IPv6でアクセスすると"via IPv6"って出るやつ

IPv6でアクセスすると”via IPv6″って出る例のやつ作りました。
(HTMLタグ貼るだけのやつが見つからなかったので)

表示してみる

IPv6から繋ぐと
Screen Shot 2017-05-22 at 3.19.22.png
が表示されます。

IPv4から繋ぐと
Screen Shot 2017-05-22 at 3.19.41.png
が表示されます。

使い方

<span id="kibousoft-viav6"></span>
<script type="text/javascript">
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://viav6.kibousoft.co.jp/', true);
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200){
   var dom = document.getElementById('kibousoft-viav6');
   dom.innerHTML = xhr.responseText;
 }
};
xhr.send(null);
</script>

ソースコード

汚いですが直書きです。大したことしてない。

index.php
<a href="https://github.com/kibousoft/viav6_web/" style="text-decoration: none; color: white;">
<?php
$ip = $_SERVER['REMOTE_ADDR'];
$headers = apache_request_headers();
if ($headers['X-Forwarded-For']) {
    $ip = $headers['X-Forwarded-For'];
}

if (preg_match('/^(([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([1-9]?[0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$/', $ip)) {
    echo '<div style="background: linear-gradient(#FF0000, #FF99CC); padding: 5px; border: 1px solid #333333; border-radius: 3px; font-size: 13px; width: 50px; text-align: center; font-family: sans-serif;">via IPv4</div>';
} else {
    echo '<div style="background: linear-gradient(#0000FF, #99CCFF); padding: 5px; border: 1px solid #333333; border-radius: 3px; font-size: 13px; width: 50px; text-align: center; font-family: sans-serif;">via IPv6</div>';
}
?>
</a>

CORSの話

外部からXHRで取得される可能性のあるサイトでは、
Access-Control-Allow-Origin , Access-Control-Allow-Methods ヘッダーを返す必要があります。
.htaccessで以下を設定しました。

.htaccess
Header set Access-Control-Allow-Origin "*"
Header set Access-Control-Allow-Methods "GET"

インフラの話

最初Amazon API Gatewayでやろうとしたんですが、API GatewayはIPv6対応していませんでした。
なので、OpsWorksでPHP App Serverを立てて動かしています。
OpsWorksにも以下の問題がありました。

  • Application Load Balancer(IPv6対応)には対応していない
  • EC2へのIPv6アドレスのアタッチには対応していない
  • セキュリティグループでIPv6のTCP 80番が許可されていない

そのため、上記の設定は手動で行いました。

備考

  • Happy Eyeballsの関係で、サイトにはIPv4で繋がって、XHRはIPv6で繋がるケースもあるよねとか細かい話はなしで。

続きを読む

Amazon ECSを用いたDocker本番運用の実現

はじめに

現在お手伝いしているアカウンティング・サース・ジャパンにて、ECSを使ったDockerの本番運用を始めたので、その一連の流れについてまとめました。

税理士向け会計システムを扱うアカウンティング・サース・ジャパンでは最近Scalaでの新規プロジェクトが立ち上がってきており、既存のプロジェクトはJavaであったり、Erlangであったりと様々な言語が用いられていますが、インフラ人員が少ないということもあり、なるべくシンプルなインフラ構成を実現する必要がありました。

そういった中、各アプリケーションをDocker化することでインフラとしては共通基盤としてのDockerクラスタのみの管理になり、運用コストが下がるのではないかという仮説からDocker化を進めることになりました。クラスタを実現するに辺りKubenatesなどの選択肢もありましたが、今回はECSを選択し、下記のようにAWSのマネージドサービスを最大限に活用しています。

  • オーケストレーションツール: Amazon EC2 Container Service (ECS)
  • サービスディスカバリ: Application Load Balancer (ALB)
  • Dockerレジストリ: Amazon ECR
  • ログ、メトリクス収集: CloudWatch, CloudWatch Logs
  • 監視: CloudWatch Alarms
  • Infrastructure as Code: CloudFormation
  • CIツール: Jenkins

各技術の選定理由

今回Docker化を行うに辺り、下記を優先的に技術選定を行いました。

  • 運用が楽であること
  • 構成がシンプルで、技術の学習コストが低いこと

まずは、オーケストレーションツールの選定です。候補に上がったのは、Docker Swarm、Kubernetes、ECSです。

DockerのSwarm modeは本番での運用例が技術選定時点であまり見当たらなかったので候補から落としました。次にKubernetesとECSですが、海外の事例などではどちらも多く使われているようです。

今回は多機能さよりも運用に手間がかからない方が良いと考え、マネージドサービスであるECSが第一候補にあがりました。ここは詳細に調査したというよりも、ある種勢いで決めています。その上でやりたいことが実現できるかどうか一つ一つ技術検証を行った上で導入判断を行いました。

同じようにマネージドサービスを優先的に使ったほうが良いという考えで、ログなどでもCloudWatchを使っています。

AWSインフラをコードで記述するものとしてはTerraformが良く取り上げられている気がしますが、個人的にはいくつかの理由でCloudFormationを推しているのでこちらを使っています。

CIツールですが、社内の標準であるJenkinsをそのまま使うことにしました。

全体構成

下記のような構成になっています。

スクリーンショット 2017-05-21 12.46.39.png

ざっくりと説明すると、developmentブランチにプッシュするとGithub HookでJenkinsがDockerイメージをビルドして、ECRにPushします。ユーザはJenkinsでDeployジョブを実行(あるいはBuildの後続ジョブとして自動実行)し、CloudFormationにyamlファイルを適用することでTask, Service, ALB, Route53設定, CloudWatch設定を一通り実行します。またECSのClusterはあらかじめCloudFormationテンプレートを作成して作っておきます。

Task/Serviceの更新についてはCloudFormationを経由しない方がシンプルかとは思いまいしたが、Service毎に管理するRoute53やCloudWatchと合わせて一つのテンプレートにしてしまうのが良いと判断しました。

ここまでやるなら専用のデプロイ管理ツールを作った方がとも思ったのですが、業務委託という立場で自分しかメンテができないものを残すものは躊躇されたため、あくまでAWSとJenkinsの標準的な機能を組み合わせて実現しています。

CloudFormationテンプレートの解説

上記の流れが全てなので理解は難しくないと思いますが、一連の処理で重要なポイントとなるのはCloudFormationテンプレートなのでこれについてだけ触れておきます。長いテンプレートなのでざっくりとだけ雰囲気を掴んでもらえればと思います。

ECSクラスタのテンプレート

cluster作成用のCloudFormationテンプレートは下記のようになっています。

gist:cluster.yaml

一見複雑に見えますが、Amazon EC2 Container Service テンプレートスニペットを参考に作ると簡単に作成できると思います。

(あまりそのまま書くと会社に怒られそうなため)省略していますが、実際にはここにECSクラスタの監視を行うCloudWatch Alarmなどを設定することで、監視設定までこのテンプレートだけで完了します。

ECSクラスタはインフラチーム側であらかじめ用意しておき、リソースが足りなくなったときなどには適宜インスタンス数を変更したりクラスタ自体を別途作ったりしていきます。オートスケーリングを導入すればそれすら必要なくなります(今回はDocker運用が初めてだったので知見がたまるまで手動での対応にしています)。

インフラ側としての責務はここまでで、下記のテンプレートで定義される個別のサービスについてはアプリ開発者側の責務として明確に責任境界を分けました。(もちろん実際にはサポートはかなりの部分でしています。)

これにより全員が今までよりインフラに近い領域まで意識するように個人の意識が変わっていくことを期待しています。

個別サービス用テンプレート

開発環境、ステージング環境、プロダクション環境などそれぞれで同一のテンプレートを使うようにし、パラメータを使用します。そのパラメータをJenkinsのジョブ内で注入することで実現します。VPCなどの環境で決まる値はJenkinsジョブで実行するスクリプト内で定義し、アプリケーションごとの値は environment.yaml というファイルを用意してスクリプトから読み込みます。

environment.yamlは例えば下記のようになっています。アプリケーション開発者は、特殊なことをしない限りは service.yaml をインフラチームが用意したservice.yamlをコピーして、environment.yamlだけ編集すれば良い形になっています。DSLですら無いのでアプリ側のメンバーも心理的な抵抗が少ないようで良かったです。

environment.yaml
images:
- xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-image
parameters:
  default:
    TaskMemory: 512
    TaskMaxMemory: 990
    ImageRepositoryUrl: xxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/hoge-image
    ServiceDesiredCount: 1
  dev:
    ClusterName: dev-default
    JavaOpts: "-Xmx256MB"
  stg:
    ClusterName: stg-default
    JavaOpts: "-Xmx256MB"
  prod:
    ClusterName: default
    JavaOpts: "-Xmx1500MB -Xms1500MB"
    TaskMemory: 1990
    TaskMaxMemory: 1990
    ServiceDesiredCount: 2

そして service.yaml は下記のようなファイルです。

gist:service.yaml

これもAmazon EC2 Container Service テンプレートスニペットから作ればすぐにできるのではないかと思います。(もちろん全てのパラメータは一つ一つ値を検討します。)

こちらもCloudWatch周りや重要でないところは削除しています。色々と手で削ってるのでコピペだと動かない可能性大ですが雰囲気だけ掴んで貰えればと思います。

このファイルは全アプリケーションで同一ファイルを使うのではなく、アプリケーションごとにコピー/編集して利用します。全体の変更を行うときには全プロジェクトのファイルを更新しなければいけませんが、共通基盤がアプリケーション側を制約しないように、プロジェクト毎のyamlファイル管理としています。ファイルの配置場所は各Gitリポジトリに配置するのが理想ですが、現状ではDocker運用になれてくるまで全てのyamlファイルを管理するリポジトリを作成してインフラチーム側が主に編集する形を取っています。

デプロイ

あとは、このservice.yamlとenvironment.yamlを組み合わせてデプロイするRubyスクリプトでもJenkinsのPipelineのコードでも適当に書いてJenkinsのJobを登録すれば完了です。(environment.yamlファイルを読み込んで aws cloudformation create-stack でservice.yamlと共にパラメータとして渡すだけなので簡単です!)

新規アプリ開発時も社内標準のservice.yamlとenvironment.yamlをファイルを持ってきて、environment.yamlを修正した上で、Jenkinsにジョブを登録すればすぐにDockerクラスタへのデプロイ準備が整います。しかも、上記のテンプレート例では割愛していますが、テンプレートには監視項目/通知設定まで書かれているので、インフラ側で設定を行う必要もなく監視が開始されます。CloudFormation最高ですね。

おわりに

実際の運用ではミッションクリティカルなアプリケーションならではの品質管理のために、JenkinsのPipeline機能を利用して開発→検証→リリースまでのデプロイメントパイプラインを実現しています。

アプリケーションのSECRETなどコミットしない情報をどう管理するかも検討する必要がありますが、これは管理の仕方はチームによって異なると思ったため割愛しています。

また、ログ解析としてはS3に出されたALBのログをRedash+Amazon Athenaでエラー率やアクセス数を分析できるようにし、CPU使用率やメモリ使用率などのパフォーマンス状況をCloudWatchの内容をGrafanaで可視化しています。これによりログ収集の基盤などを作らずに必要な可視化を実現することができました。ベンチャーでは分析基盤の運用も大きなコストになってしまうため、こういった工夫も必要です。(もちろん重要なKPIについては別途分析する仕組みが整っています。)

今回の構成が最高とは思いませんが、ある程度満足行くところまではできたかなと思います。もっとよくできるよ!とか一緒にやりたいな!とかもっと詳細聞きたいな!いう方はぜひ @miyasakura_ までご一報ください。

続きを読む

【AWS】ELB(CLB)

はじめに

タダです。
AWS認定資格の試験勉強のためにELBの情報をまとめた自分用メモになります。
あくまで個人用のメモになるので、その点はご了承ください。
※違う内容書いているなどありましたらご指摘いただけると幸いです。
※本記事では、Classic Load Balancerの情報をまとめていきます。
※随時アップデートがあれば更新していきます。

サービス概要

ELBは2つのタイプに分けられる

  • Classic Load Balancer

    • アプリまたはネットワークレベルのいずれかの情報に基づいてルーティングする
    • 複数のEC2インスタンス間でシンプルなトラフィックのルーティングに適する
  • Application Load Balancer
    • リクエストパスまたはホストベースでルーティングする
    • 複数のサービスにルーティングしたり、同じEC2インスタンスの複数のポート間で負荷分散するのに適する

特徴

  • スケーラブル

    • ELB自体も負荷に応じてスケールアウト
  • 可用性向上
    • 複数AZへの振り分け、インスタンスのヘルスチェック
  • マネージドサービス
  • ルーティング方式
    • 基本はラウンドロビン
  • コネクションタイムアウト
    • クライアントからの無通信状態が続くと接続を自動で切断する(デフォルト60秒)
  • ELB自体のスケーリング
    • じんわりとしたトラフィックの増加には対応できる

      • 逆に急激なトラフィックの増加にはスケーリング対応できないため、事前にPre-Warming(暖機運転)の申請をAWSに行う
  • スティッキーセッション
    • 同じユーザーから来たリクエストをすべて同じEC2に送信
    • アプリケーションでのセッション情報、一時ファイルなどをEC2が保持する構成の場合に必要
    • HTTP/HTTPSでのみ利用可能
    • EC2の増減を柔軟に管理できるようセッション情報は別のDBやキャッシュサーバに保持させるのが好ましい
  • Connection Draining
    • EC2インスタンスとELB間の登録解除、ヘルスチェック失敗した時にルーティングを中止して、処理中のリクエスト終わるまで一定期間まつ
  • X-Forwarded-For
    • HTTPまたはHTTPSロードバランサーを使用する場合に、クライアントの IP アドレスを識別するのに役立つ
  • Proxy Protocol
    • ELBはProxy Protocolバージョン1をサポートしている
    • Proxy Protocolヘッダーはバックエンド接続用にTCPを使う場合、クライアントのIPアドレスを識別するのに役立つ
    • 有効化する場合の前提条件は以下の通り
      • ELBの背後にプロキシサーバがいないこと
      • インスタンスでProxy Protocol情報を処理できるか確認する
      • リスナー設定でProxy Protocloがサポートされているかを確認する
  • 他のサービスとの連携機能
    • AutoScaling
    • Route53
    • CloudFormation
    • OpsWorks
    • Elastic Beanstalk
    • CloudWatch
    • EC2
    • S3など

サポートプロトコル

Classic Load Balancerのサポートプロトコルは、以下の通り

  • HTTP
  • HTTPS
  • SSL(セキュアTCP)
  • TCP

ロードバランサーのタイプ

ロードバランサーのタイプとして、外向けロードバランサーと内向けロードバランサーがある

  • 外向けロードバランサー : インターネットからアクセスできるELB
  • 内向けロードバランサー : VPC内やオンプレ環境からのみアクセスできるELB

モニタリング

ELBの監視を行う場合は以下の方法がある

  • CloudWatchメトリクス
  • ELBのアクセスログ
    • ELBへのリクエストの詳細情報をキャプチャしているので、確認する
  • CloudTrailログ

トラブルシューティング

よくELBのトラブルシューティングでHTTPステータスから設定不足を確認することがあるので、ドキュメントの情報をまとめる

  • HTTP 400: BAD_REQUEST

    • 原因:クライアントが誤ったリクエストをしてしまったため発生
    • 解決策:直接インスタンスに接続し、クライアントリクエストの詳細をキャプチャする
  • HTTP 405: METHOD_NOT_ALLOWED
    • 原因:リクエストヘッダー内のメソッドの長さが127文字を超える
    • 解決策:メソッドの長さを確認する
  • HTTP 408: Request Timeout
    • 原因:指定されたコンテンツのサイズが実際に創始されたコンテンツサイズと一致しないや、クライアントとの接続が閉じているため発生
    • 解決策:リクエストを生成しているコードを調べ、リクエストをより詳細に調べることのできる登録済みインスタンスに直接送信する、レスポンスが送信される前にクライアントが接続を閉じないようにする
  • HTTP 502: Bad Gateway
    • 原因:インスタンスからの応答の形式が適切でないか、ロードバランサーに問題がある
    • 解決策:インスタンスから送信された応答が HTTP 仕様に準拠していることを確認する
  • HTTP 503: Service Unavailable
    • 原因:ロードバランサーにリクエストを処理する能力が不足または、登録されたインスタンス(正常なインスタンス)が存在しない
    • 解決方法:前者の場合は一時的な問題のため、一旦放置氏それでも解決しない場合はAmazon Web Servicesサポートへ問い合わせ
    • 後者の場合、正常なインスタンスを登録する
  • HTTP 504: Gateway Timeout
    • 原因:アプリケーションの応答が、設定されているアイドルタイムアウトよりも長くかかっているまたは、録されたインスタンスがELBへの接続を終了させている
    • 解決策:前者は、ELB側のアイドル接続のタイムアウト設定を伸ばす
    • 後者の場合は、MWのKeepAlive設定を有効にしてELBのタイムアウト設定よりも大きい値を定義する

参考

更新日時

2017/05/01 初回投稿

続きを読む

Elastic Beanstalkトラブルシューティング集

公式のトラブルシューティングに載ってないものをまとめてみました。
いろいろハマった気もしますが、覚えているやつだけ記載しています。

と、その前に

基本的なデバッグ方法としては以下の通りです。

  1. eb create をするときに –debug をつける
  2. eb ssh して直接インスタンスの中を確認する
  3. eb logs する

インスタンスが生成できていなければ 1. 、インスタンスが生成できていれば 2. 、pingで疎通できれば 3. で探っていく感じになります。

Q1. Application Load Balancer を選択するとエラーになる

eb create するときに Select a load balancer type で application を選択すると、こんな感じのエラーが出る。

eb : Configuration validation exception: Invalid option value: 'null' (Namespace: 'aws:ec2:vpc', OptionName: 'Subnets'): Specify the subnets for the VPC for load balancer type application.

A1. コマンドオプションで指定する

VPC が複数ある環境の場合、Elastic Beanstalk がどの VPC を選んだらいいか判断できないようで、このエラーが発生するみたいです。
なので、 eb create のコマンドオプションで VPC 関連の情報を指定すれば OK です。

eb create xxxxxxxx \
    --elb-type application \
    --vpc.id vpc-xxxxxxxx \
    --vpc.elbsubnets subnet-xxxxxxxx,subnet-xxxxxxxx \
    --vpc.ec2subnets subnet-xxxxxxxx,subnet-xxxxxxxx \
    --vpc.elbpublic \
    --vpc.publicip

Q2. リソースが作成できない

以下のようなエラーが出てリソースの作成に失敗する。

Stack named 'awseb-e-xxxxxxxxxx-stack' aborted operation. Current state: 'CREATE_FAILED' Reason: The following resource(s) failed to create: [ALB, AWSEBV2LoadBalancerTargetGroup, AWSEBBeanstalkMetadata, AWSEBLoadBalancerSecurityGroup].

A2. 権限を付与する

実行ユーザーに AWSElasticBeanstalkFullAccess というポリシーをアタッチすれば OK です。
ここでいう実行ユーザーとは ~/.aws/config で指定している IAM ユーザーのことです。

Q3. composer / bundler などで落ちる

hooks/appdeploy/pre にある shell を実行するタイミングで落ちる。
例えばこんなエラーです。

/var/log/eb-activity.log を見てもだいたい解決しないのはお約束です。

ERROR: [Instance: i-xxxxxxxx] Command failed on instance. Return code: 1 Output: [CMD-AppDeploy/AppDeployStage0/AppDeployPreHook/10_composer_install.sh] command failed with error code 1: /opt/elasticbeanstalk/hooks/appdeploy/pre/10_composer_install.sh
++ /opt/elasticbeanstalk/bin/get-config container -k app_staging_dir
+ EB_APP_STAGING_DIR=/var/app/ondeck
+ cd /var/app/ondeck
+ '[' -f composer.json ']'
+ export COMPOSER_HOME=/root
+ COMPOSER_HOME=/root
+ '[' -d vendor ']'
++ /opt/elasticbeanstalk/bin/get-config optionsettings -n aws:elasticbeanstalk:container:php:phpini -o composer_options
+ PHP_COMPOSER_OPTIONS=--no-dev
+ echo 'Found composer.json file. Attempting to install vendors.'
Found composer.json file. Attempting to install vendors.
+ composer.phar install --no-ansi --no-interaction --no-dev

なんとかかんとか

Hook //opt/elasticbeanstalk/hooks/appdeploy/pre/10_composer_install.sh failed. For more detail, check /var/log/eb-activity.log using console or EB CLI.

この場合は出ているエラーのうち、なんとかかんとかの部分で判断できます。

A3-1. Cannot allocate memory

Cannot allocate memory と出ているなら composer の実行時にメモリが足りていない可能性が高いので、.ebextensions でメモリを増やす設定を追加します。

.ebextensions
option_settings:
  aws:elasticbeanstalk:container:php:phpini:
    memory_limit: 1024M

インスタンスを大きくするとかできるなら楽ちんです。
(swap ファイル作るでも解決できるはずだが調べてない)

A3-2. ErrorException

[ErrorException] Undefined index: hash

ERROR: bundle install failed!
の場合、lock ファイルおかしい可能性があります。

eb ssh をしてみて、lock ファイルを消してライブラリをインストールすると分かったりします。

# ruby なら
cd /var/app/ondeck/
sudo rm Gemfile.lock
bundle install

# php なら
cd /var/app/ondeck/
sudo rm composer.lock
composer.phar install

僕の場合は composer 自体のバージョンがローカル環境とあっていないためにエラーになっていました。
なので、.ebextensions でバージョンを指定して対応しました。

.ebextensions
commands:
  01updateComposer:
    command: export HOME=/root && /usr/bin/composer.phar self-update 1.5-dev

Q4. DB に繋がらない

エラーログをみると DB に繋げないっていうのです。

A4. ローカル IP からのアクセスを変更する

原因としては Application Load Balancer を選択したせいで subnet の IPv4 CIDR が複数のブロックになった可能性が疑われます。

なので、MySQL にログインして該当のブロックアドレスに GRANT してあげれば OK です。

use mysql;
SELECT Host, User, Password, Select_priv, Insert_priv,Update_priv, Delete_priv FROM user; -- 権限を調べてみる

GRANT ALL PRIVILEGES ON database.* to hoge@"ブロックアドレス%" IDENTIFIED BY 'password' WITH GRANT OPTION;

Q5. 環境を削除しても消えない

Elastic Beanstalk のイベントでこんな感じになって環境が消せないという症状です。

The environment termination step failed because at least one of the environment termination workflows failed.

A5. 問い合わせたら消してくれるっぽい

まあ影響ないし置いといたらいいんじゃないかな。。。

続きを読む

ECSのチュートリアル – コンテナ運用を現実のものにする

皆さんこんにちは

以前にコンテナの何がいいのかを説明する資料を作りました。
「やっぱりコンテナ運用しか無いな」
「コンテナを使わざるを得ない」
「とりあえずdocker入れとけばいいや」
という声が聞こえてきそうです(幻聴)

一方で、コンテナで運用するとなると、新たなる監視体制が必要となります。
例えば、Dockerのコンテナが突然停止した場合、その状況を検知し、新しくコンテナを立ち上げる必要があります。
また、そもそもコンテナが乗っている環境をスケールする必要が出たりして、その場合は新しくスケールされた環境でもコンテナを立ち上げなければならないし、その設定をサーバに書かなければならないけど、そうなると結局コンテナが乗っているサーバがブラックボックス化したりするわけで、なんとかうまく回そうとすると、それなりに気を配ることが多くなってきます。

最近はlaradockを使用する開発者が増えてきた(ような気がする)ので、コンテナ運用で幸せになるためにECSを使ってみた記録をチュートリアル形式で再現しておきます。

え?備忘録?ははは

ECSとは

ECSはAWSが提供しているコンテナ運用サービスです。
ECSの概念のちょっとしたものは以前に書いたECSで困ったときに読むための俺的Q&Aに書いてありますが、今回はECSでたてるWEBサーバに絞って超かんたんなポンチ絵を作ってみました。

ecs.png

あまりややこしいところはなさそうですが、簡単に見ていきましょう。

クラスター、コンテナインスタンス

コンテナインスタンスはdockerのコンテナのホストになるインスタンスで、クラスターはこのコンテナインスタンスの群れ( オートスケーリンググループ )です。
コンテナインスタンスにはdockerとコンテナの状態を監視してこちらに伝えてくれるエージェントが入っています。
コンテナ運用時には、個々のインスタンスには興味が無いので、「クラスター」と一括りにしてしまいます。

タスク、ECR

タスクはアプリケーションのまとまりを一つ以上のコンテナ動作で定義します。
今回の例ではnginxを動かすコンテナとphp-fpmを動かすコンテナのふたつで、webサーバのタスクを定義していて、nginxが外部からのリクエストを受け付け、php-fpmに渡しています。
コンテナを定義するイメージはECRというAWSが提供しているプライベートなdockerイメージのレジストリから取得しています。

サービス

サービスは3つの仕事をします。
1つ目はタスクが現在必要とされている個数を保っているかを監視し、その数を下回っていた場合、タスクを立ち上げて必要数を保つことです。
2つ目は現在の負荷状況を鑑みて、タスクを増減させます。この設定はCloudWatchのアラーム機能と連携させます。
3つ目はtarget groupと立ち上がったタスクを紐付けることです。

また、タスクの配置を各AZに均等に配置したりと、よく気配りのきくやつです。

ALB、target group

ALBはロードバランサーです。昔のクラシックなELBと違って、間にtarget groupと言うものを設置して、細かくルーティングできるようになっています。
target groupは実際のサーバへの振り分けを実施し、ヘルスチェックもします。

今回はこれらを利用して簡単なWEBサーバを運用できる状況に持っていきましょう

コンテナの準備

とりあえずコンテナを作る

運用したくとも、そのもととなるコンテナイメージがないと何も始まりません。
そこで、例によってLaraDockを利用してlaravelアプリを作ってみましょう。
プロジェクトの作成はここを見てもらうとして(ダイマ)、LaraDockとほぼ同じ動作をしてもらうために、Dockerfileをコピーしちゃいましょう。

$ cp -r ../laradock/nginx ./
$ cp -r ../laradock/php-fpm ./

これでDockerfileと設定ファイルをコピーできます。

今回、コンテナの中は完全に固定化…つまり、スクリプトを内部に含ませてしまうため、各Dockerfileの後部に

COPY ./ /var/www/

これを追加しておきます
また、nginxのdefaultのコンフィグファイルも必要なので、nginxのDockerfileには

COPY nginx/sites/default.conf /etc/nginx/conf.d/

も追加しておきます。
なにはともあれ、イメージを作成してみましょう。

$ cp nginx/Dockerfile ./ && docker build -t niisan/nginx .
$ cp php-fpm/Dockerfile-70 ./Dockerfile && docker build -t niisan/php-fpm .

これでイメージが作られているので、試しにサーバを立ててみましょう。

$ docker run -d --name php-fpm niisan/php-fpm
$ docker run -d --link php-fpm:php-fpm -p 80:80  niisan/nginx
$ curl 127.0.0.1
<!DOCTYPE html>
<html lang="en">
    <head>
...

うまく動いているようです。

コンテナをアップロードする

次にECRにコンテナをアップロードするのですが、初めての場合、ちょっとビビります。
けばけばしいタイトルとともに「今すぐ始める」ボタンが登場し、その次のページで妙な選択肢を迫ってきます。

ECSを始めることを。。。強いられているんだ!

(こういうところ、あまり好きじゃないです)
ここではとりあえず、「Amazon ECR によりコンテナイメージをセキュアに保存する」だけを選択しておきます。
すると、リポジトリの作成用のウイザードが迫ってきます。

ECR

とりあえずリポジトリ名にnginxと入れて次のステップへ
今度はECRにイメージをアップロードする手順が出てきますので、この通りにやってみます。
前回の記事でも言及したように、ここだけはAWS CLIを使用する必要がありますので、頑張って入れておきましょう。

$ aws ecr get-login

これで出てくるログインコマンドをコマンドラインにコピーして実行すればログイン完了です。
既にイメージは作ってあるので、タグを張り替え、アップロードしましょう。

$ docker tag niisan/nginx:latest **************.ecr.ap-northeast-1.amazonaws.com/nginx:latest
$ docker push *****************.ecr.ap-northeast-1.amazonaws.com/nginx:latest

これで完了です。
手順ページの完了ボタンを押すと、いまアップロードしたイメージのレジストリができていることを確認できます。

そのまま、左のバーにあるリポジトリを選択し、同じようにphp-fpmもアップロードしてみましょう。
1分もかからず終わるでしょう。

ECR登録済み

デプロイ

タスクを定義する

コンテナのイメージもアップロードできたことですし、早速タスクを定義していきましょう。
と言っても、今回の場合、内容は異様なほど簡単ですんで、すぐに終わるでしょう。

タスクの名前は適当に決めていいと思います。(今回は webtest という名前にしました。)
早速コンテナを入れていきましょう。まずはphp-fpmから行きます。「コンテナを追加」ボタンを押すと次のモーダルが出てきます。

タスクを設定

php-fpmの設定はこれだけで十分でしょう。
次にnginxを設定します。基本的にはphp-fpmと同様にイメージと名前を設定するわけですが、二つほど、違う設定があります。

ポートフォワード

まずはポートの設定です。
nginxは外部から接続できなければならないので、ホストからポートフォワードするように設定しました。

内部リンク

もう一つはphp-fpmへのリンクです。
これで、タスクの定義は完了しました。

クラスターを作る

タスクを作ったので、こいつを動かしてみましょう。
こいつを動かすためには、動作環境が必要となりますが、それがクラスターです。
早速クラスターを作っていきましょう。

クラスターの作成

せこいですが、コンテナが動作するインスタンスは一番ちっちゃいやつを使います。
次にネットワーキングの設定ですが、これはデフォルトのvpcを使ってお茶を濁しておきます。

商用環境では独自のVPC使おう

では作成しましょう。

クラスター作成完了

クラスターが出来上がりました。

作ったクラスターを選択して、早速タスクを動かしてみましょう。
クラスター名を選択し、タスクタブからタスクの開始ボタンを押すと、動作するタスクを選択する画面になります。

タスクの実行

では動作してみましょう。
しばらくすると、次のような状態になっていると思います。

動いているっぽい

どうやら動いているようです。
ECSインスタンスからコンテナのホストになっているインスタンスを参照して、public dnsを調べましょう。
それをブラウザにはっつけると、Laravelのページが表示されます。
Webアプリのデプロイ完了
うまくいきました!

サービスを使う

とりあえずデプロイできて、動作も確認できました。
とはいえ、これを運用するのはまだまだ厳しいところです。

  • タスクが死んだ場合の復旧をどうすればいいのか
  • コンテナを更新したときのデプロイ方法はどうなるのか
  • コンテナのスケーリングはどうするのか

この辺をサービスに解決してもらうとしましょう。

タスクを停止してみる

とりあえず、タスクを停止します。クラスターのタスクタブで、先ほど動かしたタスクを落とします。
当然ですが、落としたタスクは復活したりしないので、この時点で先に動かしたWEBサーバはダウン状態となります。

イメージを更新する

これからロードバランサーを使用するに当たり、ヘルスチェック用のルーティングをします。
今回はlaravel5.4を使う(使っちゃってる)ので次のようなroutingを用意します。

routes/web.php
Route::get('/healthcheck', function () {
    return ['health'=>'ok'];
});

この状態で、Dockerのイメージビルドを再実行します。
コンテナを再度動かすと次のような挙動を示します。

$ docker run -d --name php-fpm niisan/php-fpm
$ docker run -d --link php-fpm:php-fpm -p 80:80  niisan/nginx
curl 127.0.0.1/healthcheck
{"health":"ok"}

これをECRに再アップロードしておきます。

タスクを更新する

次に、タスクを更新していきます。
タスクはバージョン管理されていて、タスクはの更新は正確には新しいリビジョンを作るということになります。

例えば、今動いているリビジョンのタスクを新しいリビジョンのタスクに置き換えることで、サーバを更新したことと同じ状況にできますし、いざ新しいリビジョンが障害を起こすようなものであった場合は古いリビジョンに戻してロールバックすることもできます。

タスク定義->webtestでタスク定義のリビジョン一覧が現れます。今は一個しか無いと思うので、それを選択し、新しいリビジョンの作成ボタンを押しましょう。
今回の変更はnginxのポートの部分になります。

ダイナミックポート

なんとホストポートが空欄になっていますが、これがダイナミックポートという仕組みになります。
これはコンテナインスタンスで開いているポートを選んで、nginxコンテナの80ポートに繋げる仕組みで、ALB + target groupと組み合わせるときに使用します。

ALBを用意する

ひとまずコンテナの調整はこの辺にしておいて、ロードバランサーの設定を始めましょう。
まず、サービス -> EC2 -> サイドバーのロードバランサーを選択し、ロードバランサーを作成ボタンを押しましょう。
ALBの設定
Application load balancer を選択して、次へを押すとロードバランサーの設定に移ります。
今回はデフォルトのVPCを使うので、名前以外は全部デフォルトで問題なしです。
サブネットも二つあると思うので、両方共チェックしておきましょう。
一応、プロトコルはHTTPにしておきましょう。
セキュリティの警告は、今回は別に秘密通信でもないので、必要ないです。

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

セキュリティグループはとにかく設定しておきましょう。
覚えやすい名前にしておくと、後で便利かもしれません。

空のターゲットグループ

ターゲットの登録画面になりますが、今はターゲットがないので、そのまま次へで、作成を完了させます。
これで、とりあえずロードバランサーの作成が完了しました。

最後に、ロードバランサーのセキュリティグループからコンテナインスタンスのセキュリティグループへの通信経路を確保しておきましょう。
コンテナインスタンスのセキュリティグループ( EC2ContainerService-*** )のinboundを編集しておきましょう。

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

ロードバランサーからのinboundで全てのtcpを選択しておきましょう。
ダイナミックポートを使うと、開いているポートを使うのでどのポートが割り当てられるかわかりません。

サービスの作成

ここまで来てようやくサービスの作成が初められます。
再びEC2 Container Serviceから先程作成したクラスターを選択し、サービスを選択します。
作成ボタンがあるので、これを押しましょう。
サービス作成
まず、タスクはWebtestを選ぶわけですが、ここではリビジョンを含めて指定することになります。
サービス名は・・・とりあえずタスクのファミリー名と同じにしています。
画面下部に行くとELBを設定するボタンがあるので押してみましょう。
スクリーンショット 2017-03-18 22.39.45.png
するとこんな画面になるので、さっき作ったALBを選択し、負荷分散用のコンテナがnginxで、ポートが0:80になっていることを確認したら、ELBへの追加ボタンを押しましょう
スクリーンショット 2017-03-18 22.41.36.png
するとこんな画面になるのでターゲットグループを先程追加したものに変更して保存を押しましょう。

すると、サービスにALBの情報が登録されるので、この状態でサービスを作成しましょう。

スクリーンショット 2017-03-18 23.41.08.png

クラスターの状態がこのようになれば、成功です。
あとは、ロードバランサーのDNSにアクセスして、
Webアプリのデプロイ完了
これが出れば成功です。

まとめ

実はこれ書き始めたのって随分と前なのですが、途中でコンテナ運用の何がいいのかを説明するための資料作りしていたら、時間が立ってしまったのです。
まあ、ECSって使ってみるとすごく楽で、更にスケーリングしたり、他のサービスと相乗りさせたりマイクロサービス化させたりがすごく簡単にできるのですが、Webアプリを動かすに当たっては、わりとハマりどころが多いので、一貫したチュートリアル形式のものを作ってみました。
特にハマりどころなのは、ALB -> コンテナインスタンスにおけるセキュリティグループの設定だと思います。
考えてみれば当たり前なのですが、まあ、忘れることが多いです。
とはいえ、そんなところを超えてしまえば、後はコンテナで簡単に運用できる環境を量産できるので、皆さん是非とも使ってみてください。

今回はそんなところです。

参考

コンテナに挫折したあなたへ – 超わかりやすい発表資料です!師匠と呼びたい

続きを読む