多段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 …」のような循環的な参照 

続きを読む

【AWS】CloudWatch

はじめに

タダです。
AWS認定試験勉強のためにCloudWatchのドキュメントを読んだ自分用メモになります。

サービス概要

  • AWSリソースと、AWSで実行されているアプリをリアルタイムでモニタリングするマネージドサービス
  • ClouWatchアラームはユーザーが定義したルールに基づいて通知を送信したり、モニタリングしているリソースに自動的に変更を加えられる
    • EC2のCPU使用率およびディスク読み書きをモニタリングし、増加する負荷を処理する追加のインスタンスを追加のインスタンスを開始できるかをはんだんする
  • EC2のモニタリングタイプ
    • 基本モニタリング:無料、データは5分間隔
    • 詳細モニタリング:追加料金が必要、データは1分間隔
  • アラームのステータス
    • OK : 定義された閾値を下回ってる状態
    • アラーム : 定義された閾値を上回っている状態
    • 不足 : データが不足のため、状態を判定できない
      • データポイント自体が存在しない状態
      • OKとアラームはデータポイントが存在する状態で評価
        • したがって必ずしも不足状態が障害を表すステータスじゃない

専門用語

  • 名前空間

    • CloudWatchメトリクスのコンテナ。 AWS/ Service という命名規則。

      • ex: AWS/EC2、AWS/RDS、AWS/ELB
  • メトリクス
    • CloudWatchに発行された時系列のデータポイントのセット

      • データの粒度は最小が1分〜3分や5分間間隔のものまである
    • メトリクスはリージョンごとに管理される
    • 最大15ヶ月まで保管される
      • 期間が 60 秒 (1 分) のデータポイントは、15 日間使用できる
      • 期間が 300 秒 (5 分) のデータポイントは、63 日間使用できる
      • 期間が 3600 秒 (1 時間) のデータポイントは、455 日 (15 か月) 間使用できる
      • https://aws.amazon.com/jp/cloudwatch/faqs/
  • ディメンション
    • 監視の範囲

他のサービスとの連携

  • SNS

    • CloudWatchアラームを使う時に連携する
  • AutoScaling
    • ユーザー定義のポリシー、ヘルスステータスチェックおよびスケジュールに基づいてEC2を自動的に起動、終了できる
  • CloudTrail
    • CloudTrailログにCloudWatch API実行を記録できる
  • IAM
    • CloudWatchの操作権限を管理する

アクション機能

モニタリングの状態によってアクションを定義する

  • SNS通知
  • EC2アクション
    • EC2 Auto Recovery(対応するインスタンスはC3,4/M3/R3/T2で、EC2-Classicとハードウェア専有インスタンスは未対応)
    • EC2 再起動/停止/起動
  • AutoScaling

CloudWatch Events

AWSリソースの変更を示すシステムイベントのほぼリアルタイムのストリームを、EC2インスタンス、Lambda、SQSキュー、ECSタスク、ステップ関数ステートマシン、SNSトピック、Kinesisストリーム、組み込むターゲットに振り分けるのがCloudWatch Events

専門用語

  • イベント

    • AWSリソースにおける変化で、CloudWatch Eventsのトリガーとなる。
  • ターゲット
    • イベントを処理する
  • ルール
    • 一致した受信イベントを検出し、処理のためにターゲットに振り分ける

他のサービスとの連携

  • CloudTrail

    • CloudWatch Eventsのログをとれる
  • CloudFormation
    • CloudWatch Eventsのルールを作れる
  • Config
    • Config Rulesがトリガーされると、CloudWatch Eventsによってキャプチャできるイベントが生成される
  • Kinesis Stream
  • Lambda

イベントタイプ

  • EBSイベント

    • スナップショット通知
    • ボリューム通知
  • EC2イベント
    • インスタンスの状態変更通知
  • EC2 System Managerイベント
    • RunCommandの実行の通知
    • EC2 Automationステップステータス変更の通知
  • EC2メンテナンスウィンドウイベント
  • ECSイベント
    • ECSコンテナインスタンスの状態変更
    • ECSタスクの状態変更
  • EMRイベント
    • クラスター状態の変更
    • EMRステップ状態の変更
  • AutoScalingイベント
    • EC2インスタンス起動のライフサイクルアクション
    • EC2インスタンスの起動/失敗
  • APIコールイベント
  • CodeDeployイベント
    • デプロイ状態変更通知
  • AWSマネジメントコンソールサインインイベント
  • AWS Healthイベント
  • KMSイベント
  • スケジュールイベント
  • TrustedAdvisorイベント

CloudWatch Logs

  • CloudWatch Logsは、EC2、CloudTrailおよびその他のソースのログファイルの監視、保存、アクセスができる
  • 特徴として、EC2インスタンス/CloudTrailのログをリアルタイムでモニタリングできる他に、ログデータをアーカイブできる

専門用語

  • ログイベント

    • モニタリングされてるアプリまたはリソースによって記録されたアクティビティの記録
    • イベント発生時のタイムスタンプおよび生のイベントメッセージ(UTF-8)
  • ログストリーム
    • 同じリソースを共有する一連のログイベント(ログフォルダのサブフォルダのイメージ)
  • ロググループ
    • ログの出力箇所(ログフォルダのイメージ)
  • メトリクスフィルタ
    • ログイベントから特定の文字列のフィルタリングが可能
  • 保持設定
    • ログの保持期間を設定する

他のサービスとの連携

  • CloudTrail
  • Kinesis Streams
    • ITインフラのログデータ、アプリのログ、SNSメディア・マーケットデータフィード、ウェブのクリックストリームデータの取り込みと処理をリアルタイムでできる
    • CloudWatch Logsに収集されたログをリアルタイムにKinesis Streamに転送可能
  • Lambda
    • ログの出力先がCloudWatch Logs
  • S3
    • ログデータをS3にエクスポートする
    • ログデータはエクスポート出来るようになるまで最大12時間かかる場合ある

参考

続きを読む

Terraform Best Practices in 2017

Terraform Best Practices in 2017

以下のブログをベースにver0.9の新機能のstate environmentsや、backend、remote stateを活用してベストプラクティスを考えた。
細かい話は以下のブログを参照いただき、ver0.9に対応した内容だけ記載します。
Terraformにおけるディレクトリ構造のベストプラクティス | Developers.IO

サンプルコード

サンプルコードを置きましたので、イメージが付かない場合は以下を見てみて下さい。
(適当に作ったので間違えてたらプルリクください)
https://github.com/shogomuranushi/oreno-terraform

ディレクトリ構造

├── environments
│   ├── not_immutable
│   │   ├── provider.tf
│   │   ├── backend.tf
│   │   ├── variable.tf
│   │   ├── main.tf
│   │   └── output.tf
│   └── immutable
│       ├── provider.tf
│       ├── backend.tf
│       ├── variable.tf
│       ├── main.tf
│       └── output.tf
└── modules
    ├── compute
    │   ├── ec2.tf
    │   ├── elb.tf
    │   ├── output.tf
    │   ├── userdata.sh
    │   └── variable.tf
    ├── db
    │   ├── main.tf
    │   ├── output.tf
    │   └── variable.tf
    └── vpc
        ├── main.tf
        ├── output.tf
        └── variable.tf

ディレクトリ構造のポイント

1. environments配下の分け方

  • tfstateファイルで管理する範囲が大きいと問題があった際の影響範囲が大きくなるため実行単位を小さくする
  • 今回の場合は、not_immutableとimmutableで分けて、それぞれの配下でterraformを実行する
  • terraformの実行単位を分けるとterraform間での値の受け渡しが通常とは異なり、 remote state 機能を利用する必要がある
    • 以下のようにdataを定義することで、remote側のoutputを参照できるようになる

    • 注意点
      • 制約としてremote先のmoduleの先のoutputは読み取れないのでmodule直下(root)でoutputを定義する必要がある
      • tfstateの管理方法をs3にした状態でstate environmentsを使った時のs3のprefixは env:/<environment>/ なるため以下のように記述する
remote_state参照方法(backend.tf内に記述)
data "terraform_remote_state" "not_immutable" {
  backend = "s3"
  config {
    bucket = "< backetname >"
    key    = "env:/${terraform.env}/not_immutable/terraform.tfstate"
    region = "< region >"
  }
}
moduleへの渡し方
module "compute" {
    source         = "../../modules/compute"

    vpc            = "${data.terraform_remote_state.not_immutable.vpc}"
}

2. dev/stg/prodなどの環境の分け方

  • ver0.9以前はdev,stg,prodなどはディレクトリを分けることで、tfstateを競合させないようにしていたが、ver0.9で追加された state environments を利用して環境を分ける
  • terraform env new dev を打つことでdevの環境が作られる

    • デフォルトでは直下に terraform.state.d というディレクトリができ、その配下に環境毎にtfstateが管理される
  • terraform env list を打つことで現在のenviromentを参照可能

state_environmentsの実行方法
$ terraform env new dev

$ terraform env list
  default
* dev
  stg
  • その前にtfstateはs3に置いたほうが良いと思うので、以下の記述も入れて terraform init を実行することでtfstateをs3で管理出来る状態になる。その後に terraform apply を実行することでtfstateが生成される

    • なお、s3をbackendにすると /env:/< environment > が補完され < backetname >/env:/< environment >/immutable/terraform.tfstate のように管理される
backend.tf
terraform {
  backend "s3" {
    bucket = "< backet name >"
    key    = "immutable/terraform.tfstate"
    region = "us-west-2"
  }
}
initの実行方法
$ terraform init

state environmentsの活用方法

今まではdevやstg、prodを別のディレクトリで管理していたため、それぞれのディレクトリにvariableを置くような形だったが、state environmentsの登場により1つのディレクトリで複数の環境を扱えるようになった。
そこで如何に効率的に複数の環境を扱えるか考えた結果、以下になった。

  • map関数をガンガン使う

    • environments配下のvariable.tfには以下のようにmapで定義する
  • map関数のkeyの部分をドット区切りでenv情報を入れる
    • 環境毎に値を定義出来る

      • env毎の切り替え方法は、値取得時に "vpc-${lookup(var.common, "${terraform.env}.region", var.common["default.region"])}"
        }
        のように ${terraform.env} にdevやstgが入りvalueとして参照可能になる
    • envの値が無ければdefaultを参照するように定義する方法は以下
      • defaultの指定方法は "vpc-${lookup(var.common, "${terraform.env}.region", var.common["default.region"])}"
        }
        のように ${lookup(key, value, default) で指定可能

それらを踏まえたコードは以下

variable側
variable "common" {
    default = {
        default.region     = "us-west-2"
        default.project    = "oreno-project"

        dev.region         = "us-west-2"
        stg.region         = "us-west-2"
        prd.region         = "ap-northeast-1"
  }
}

# VPC
variable "vpc" {
    type = "map"
    default = {
        default.cidr       = "10.0.0.0/16"
        default.public-a   = "10.0.0.0/24"
        default.public-c   = "10.0.1.0/24"
        default.private-a  = "10.0.2.0/24"
        default.private-c  = "10.0.3.0/24"
    }
}
module呼び出し時
module "vpc" {
    source       = "../../modules/vpc"
    common       = "${var.common}"
    vpc          = "${var.vpc}"
}
module内からvariableの値を取得する時
resource "aws_vpc" "vpc" {
    cidr_block                  = "${lookup(var.vpc, "${terraform.env}.cidr", var.vpc["default.cidr"])}"
    enable_dns_support          = "true"
    enable_dns_hostnames        = "true"
    tags {
        Name                    = "vpc-${lookup(var.common, "${terraform.env}.project", var.common["default.project"])}"
    }
}
参考:こんな感じでproviderでもlookup可能
provider "aws" {
    region = "${lookup(var.common, "${terraform.env}.region", var.common["default.region"])}"
}

まとめ

  1. 影響範囲を小さくするため、terraformの実行単位は小さくしましょう
  2. terraform間の受け渡しは remote state を使いましょう
  3. state environments を使って環境を分けましょう
  4. map関数を使ってmodule等に渡す時などのコードを簡素化しましょう
  5. map関数 & state environments & default定義を使ってvariableを効率化させましょう

以上

続きを読む

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. 問い合わせたら消してくれるっぽい

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

続きを読む

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 の割当もすぐにできます。割当についてはまたの機会に

続きを読む

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が同じリージョンにある場合セキュリティの問題はなさそう

続きを読む

インフラチームによくある極小サーバーやバッチジョブを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.

続きを読む

ELB を経由すると keepalive ヘッダーが勝手に付与される

AWS Elastic Load Balance 配下にぶら下がっている EC2 インスタンスのベンチマークを、keepalive on/off それぞれの状態で取ろうとたのですが、「どうしても keepalive を off にできない」という状況にハマりました。

後学のためにメモ。

nginx で keepalive を off にする方法

nginx.conf に以下のような設定を書きます。

/etc/nginx/nginx.conf
http{
  keepalive_timeout 0;
  # (何も書かない場合はデフォルト値の 75 になる)
}

設定後は nginx -s reload を忘れずに。

keepalive の有効/無効の確認

この状態で、以下のように ELB 経由でアクセスすると、あたかも keepalive が有効のように見えてしまいます。

$ curl -I http://foo.bar/

HTTP/1.1 200 OK
Server: nginx
Date: Wed, 19 Apr 2017 05:19:08 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 105370
Connection: keep-alive    # <- nginx の設定に関わらず必ず付与される
...

一方、プライベート側から nginx に直接アクセスすれば、ちゃんと keepalive は無効化されています。

$ curl -I http://10.2.8.97/

HTTP/1.1 200 OK
Server: nginx
Date: Wed, 19 Apr 2017 05:19:08 GMT
Content-Type: text/html; charset=utf-8
Content-Length: 105370
Connection: close    # <- keepalive が無効になっている
...

まとめ

keepalive が無効化されていることを確認したければ、ELB を経由せずに直接 nginx を叩きましょう。

補足: ELB 側で keepalive off にできないか?

できない ようです。

Classic Load Balancer のアイドル接続のタイムアウトを設定する – Elastic Load Balancing

[Configure Connection Settings] ページに、[Idle timeout] の値を入力します。アイドルタイムアウトの範囲は 1~3,600 秒です。

(0 には指定できない)

続きを読む