お名前ドットコムのドメインをAWSで使う

ドメインはりつけるの2回目だったのに完全にやり方を忘れちゃってたので備忘録

必要なもの

  • お名前ドットコムのドメイン
  • AWS
    • EC2インスタンス
    • ElasticIP
    • route53

簡単な流れ

  1. EC2インスタンスにElasticIPを割りあてる
  2. route53にDNSの設定と、ElasticIPの登録
  3. お名前ドットコムにroute53の情報を登録

EC2インスタンスにElasticIPをわりあてる

Elastic IPで「新しく割り当て」をクリックし、作成
アクション > アドレスの関連付け からインスタンスを選択し、EC2インスタンスを割りあてる

route53の設定

route53にログイン
https://console.aws.amazon.com/route53/

スクリーンショット 2017-12-09 22.46.26.png

Created Hosted Zoneを押すと下の画面になる

スクリーンショット 2017-12-09 22.46.39.png

Domain Nameのところにお名前ドットコムで取得したドメイン名を入力し、createする

サブドメインを使いたかったらここでかく

たとえばドメインがhogehoge.comで、www.hogehoge.comを割り当てたかったら「www.hogehoge.com」ってかく
そのままドメイン使いたかったら「hogehoge.com」てかく

image.png

作ったらこんなかんじになる
ns-から始まる4行をお名前ドットコムに登録するので、このページは開いたままにしておくとよいかも

で、さらにcreate Record Setを押すと下の画面になる

image.png

ここで、Nameをからにしたまま、Aliasはnoで、valueのところにEC2インスタンスを割り当てたElasticIPをかく

そしてcreate

これでroute53の設定は終了

お名前ドットコムの設定

スクリーンショット 2017-12-09 23.05.25.png

ドメイン設定 > DNS関連機能の設定に進む

スクリーンショット 2017-12-09 23.07.19.png

内部ドメイン一覧でチェックをいれ、「次へ進む」

image.png

DNSレコード設定を利用するの「設定する」を押すと下の画面になる

スクリーンショット 2017-12-09 23.09.29.png

typeを「NS」にする
ここで、サブドメインをつけてる場合、ホスト名のところにサブドメイン名をかく
「www.hogehoge.com」だったら「www」をかく
TTLとVALUEは、さっきのroute53の設定に出てきたns-から始まる4行の情報を一行ずついれていく

これであとはボタンをぽちぽち押していけば完了

反映に5分くらいかかってた

続きを読む

Route53 で S3 バケットへ alias レコードを作った際のリダイレクトのふるまい

 Amazon S3 は、全てのリクエストをリダイレクトや、パスに応じたリダイレクトを設定できる機能を持っています。これは Static Web Hosting という設定を入れることにより実現可能です。実際の技術詳細については以下の公式ドキュメントを参照してください。

リダイレクト設定

gochiusa.53ningen.com に対して gochiusa.com へのリダイレクト設定作業は以下の 3 STEP です

  1. S3 バケットを gochiusa.53ningen.com という名前で作成する
  2. Static Web Hosting 機能を有効にして、すべてのリクエストを gochiusa.com にリダイレクトする設定を入れる
  3. Route 53 に gochiusa.53ningen.com に対して先ほど作ったバケットへのエイリアスを張る A レコードを作成する

動作確認

作業後、設定を dig で確認します

% dig gochiusa.53ningen.com @ns-771.awsdns-32.net.

; <<>> DiG 9.8.3-P1 <<>> gochiusa.53ningen.com @ns-771.awsdns-32.net.
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 23376
;; flags: qr aa rd; QUERY: 1, ANSWER: 1, AUTHORITY: 4, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;gochiusa.53ningen.com.     IN  A

;; ANSWER SECTION:
gochiusa.53ningen.com.  5   IN  A   52.219.68.26

;; AUTHORITY SECTION:
53ningen.com.       172800  IN  NS  ns-1431.awsdns-50.org.
53ningen.com.       172800  IN  NS  ns-2010.awsdns-59.co.uk.
53ningen.com.       172800  IN  NS  ns-393.awsdns-49.com.
53ningen.com.       172800  IN  NS  ns-771.awsdns-32.net.

;; Query time: 59 msec
;; SERVER: 205.251.195.3#53(205.251.195.3)
;; WHEN: Sat Dec  2 02:45:43 2017
;; MSG SIZE  rcvd: 192

解決される 52.219.68.26 は S3 の静的配信サーバーの IP の模様で、振る舞い的には Host ヘッダと同じ名前を持つ S3 バケットへリダイレクトをかけるという形のようです。それを確認するために次のようなリクエストを送ってみます。

% curl -I 52.219.68.26
HTTP/1.1 301 Moved Permanently
x-amz-error-code: WebsiteRedirect
x-amz-error-message: Request does not contain a bucket name.
x-amz-request-id: C6B9BBFE84DAC0E0
x-amz-id-2: L0Hv8iRFv2kIRyLUUlhre6cqJpGKfdPo+LYF4EfjgcIaA3W4L+TOzVhs+U+H5W/J3NRp4Jfnn2A=
Location: https://aws.amazon.com/s3/
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 17:50:44 GMT
Server: AmazonS3

x-amz-error-message: Request does not contain a bucket name. というメッセージからわかるようにバケットネームの指定がないというエラーで AWS S3 のトップページに飛ばされます。次に Host ヘッダをつけるとどうなるか試してみましょう。

% curl -I -H "Host: gochiusa.com" 52.219.68.26
HTTP/1.1 404 Not Found
x-amz-error-code: NoSuchBucket
x-amz-error-message: The specified bucket does not exist
x-amz-error-detail-BucketName: gochiusa.com
x-amz-request-id: DF0A27572D2A69C2
x-amz-id-2: 6PAUKw9BxMEQjZ7ELLQELCOfXdCAMYpQEuH9fN8pdvK3HVCHwUTU3DIZTAaC+vJTevpEsi+jo4I=
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 17:53:07 GMT
Server: AmazonS3

The specified bucket does not exist というエラーメッセージに変わりました。ではそろそろ真面目に先ほど作った gochiusa.53ningen.com という値を Host ヘッダにつけて叩いてみましょう。

% curl -I 52.219.68.26 -H "Host: gochiusa.53ningen.com"
HTTP/1.1 301 Moved Permanently
x-amz-id-2: JIFBN5bduooIXFt6ps6DKsGca1jEWKmojrNJqTx+7MPXEBeHUK3A/BMehFQlsjyYRvzgPcZ2U4w=
x-amz-request-id: 53093E7405B86943
Date: Fri, 01 Dec 2017 17:54:49 GMT
Location: http://gochiusa.com/
Content-Length: 0
Server: AmazonS3

正常に 301 で http://gochiusa.com/ にリダイレクトされていることが確認できました。

実験からわかること

  1. S3 バケットへの alias レコードセットの制約の理由が推測できる

    • Route53 で S3 static web hosting しているバケットに alias を張る際は、公式ドキュメントに記載されているように Record Set の Name と S3 バケット名が一致している必要がある
    • これは Host ヘッダをみて、対象の S3 静的配信コンテンツへのリダイレクトを行なっている仕組みから生まれる制約だろうということが、上記の実験から分かる
  2. S3 バケットへの alias を張っているドメインを指す CNAME を張ると正常に動作しない
    • 実際に gochiusa2.53ningen.com に対して gochiusa.53ningen.com を指すような CNAME レコードセットを作ると以下のような実験結果になる
% dig gochiusa2.53ningen.com @ns-771.awsdns-32.net.

; <<>> DiG 9.8.3-P1 <<>> gochiusa2.53ningen.com @ns-771.awsdns-32.net.
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 2232
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 4, ADDITIONAL: 0
;; WARNING: recursion requested but not available

;; QUESTION SECTION:
;gochiusa2.53ningen.com.        IN  A

;; ANSWER SECTION:
gochiusa2.53ningen.com. 300 IN  CNAME   gochiusa.53ningen.com.
gochiusa.53ningen.com.  5   IN  A   52.219.0.26

;; AUTHORITY SECTION:
53ningen.com.       172800  IN  NS  ns-1431.awsdns-50.org.
53ningen.com.       172800  IN  NS  ns-2010.awsdns-59.co.uk.
53ningen.com.       172800  IN  NS  ns-393.awsdns-49.com.
53ningen.com.       172800  IN  NS  ns-771.awsdns-32.net.

;; Query time: 120 msec
;; SERVER: 2600:9000:5303:300::1#53(2600:9000:5303:300::1)
;; WHEN: Sat Dec  2 03:10:55 2017
;; MSG SIZE  rcvd: 216

% curl -I 52.219.68.26 -H "Host: gochiusa2.53ningen.com"
HTTP/1.1 404 Not Found
x-amz-error-code: NoSuchBucket
x-amz-error-message: The specified bucket does not exist
x-amz-error-detail-BucketName: gochiusa2.53ningen.com
x-amz-request-id: 294AEC84E239A2AC
x-amz-id-2: VAWMR0xq0Gw1cJOdipIgJBRsx6RMYaaP679GD/hKomU3yOFJ6m7/n62h9wraTQoirBYKofnO2WM=
Transfer-Encoding: chunked
Date: Fri, 01 Dec 2017 18:04:22 GMT
Server: AmazonS3

続きを読む

spfレコードに対象hostのIPアドレスを追加しまくったら長いと怒られた

前提

  • DNSはroute53
  • サーバはクラウド
    • IPアドレスセットがバラバラになる
  • メールは各サーバから
  • システムが構築されるたびにwebサーバ群のIPを登録
    • +ip4:123.456.789.0/24 こんな感じ

増やしすぎて怒られる

512バイトが上限なのでまだいけると思ってたけど怒られる、、、
kako-wEIvFfYWAh0XzByQ.png

サポートされる DNS リソースレコードタイプ – Amazon Route 53 – http://docs.aws.amazon.com/ja_jp/Route53/latest/DeveloperGuide/ResourceRecordTypes.html

TXT レコードには、スペースで区切られた、二重引用符で囲まれた文字列のリストが含まれます。1 つの文字列には、最大 255 文字が含まれます

RFCで連結した後の1つのレコードが450バイト以下であることが目安とされているため
妥当と思われる255バイトで制限かかってるところが多いらしい

 

分割をしよう

間違いから学ぶSPFレコードの正しい書き方 : 迷惑メール対策委員会 – http://salt.iajapan.org/wpmu/anti_spam/admin/operation/information/spf_i01/

TXTレコードを単純に分けてはダメらしい

baseレコードとincludeレコードに分ける

オリジナル

"v=spf1 mx ip4:123.456.000.0/24 +ip4:123.456.111.0/24 +ip4:123.456.222.0/24 +ip4:123.456.333.0/24 +ip4:123.456.444.0/24 +ip4:123.456.555.0/24 +ip4:123.456.666.0/24 +ip4:123.456.999.0/24 +ip4:123.456.888.0/24 +ip4:123.456.999.0/24 include:spf-bma.mpme.jp ~all"

分割

baseレコード:mydomain.com. IN TXT

"v=spf1 mx ip4:123.456.000111.0/25  include:spf.mydomain.com include:spf-bma.mpme.jp ~all"

includeレコード:spf.mydomain.com IN TXT

"v=spf1 +ip4:123.456.111.0/24 +ip4:123.456.222.0/24 +ip4:123.456.333.0/24 +ip4:123.456.444.0/24 +ip4:123.456.555.0/24 +ip4:123.456.666.0/24 +ip4:123.456.999.0/24 +ip4:123.456.888.0/24 +ip4:123.456.999.0/24 ~all"

レコードチェックする

無事通りましたヽ(・∀・)人(・∀・)ノ
http://www.kitterman.com/spf/validate.html
実際のメールでも確認
 

問題点

includeレコードの中身が増え続けていくのですぐに同じ問題にぶち当たる可能性

解決策案

  • スーパー楽な手段

    • 第2オクテットで登録 : 123.456.0.0/25

      • 漏れが発生する可能性は残る
  • さらに入れ子にする
    • ベース – include:ドメインtxt include:txt ~all”
    • ベース – include:ネットワークセット include:ネットワークセット ~all”
      • ネットワークセット – include:ドメイン名txt ~all”
    • ドメイン名別にtxtを用意すると削除がしやすい
  • MXをアウトソースにする
    • 余計な作業が減る

      • お金はかかる

続きを読む

Amazon SESで受信したメールを転送したい

前置き

SESで受信する特定のアドレス宛のメールを外部の別アドレスへ転送したい。SESの設定でこのへんをポチポチっとやれば…と思ったが、できない。できそうでできない。
そう、SESの標準機能ではメールの転送はできない。

対応方針

思い当たる案は2つ。
1. 受信メッセージをS3へ出力し、それをLambdaで拾って別アドレスへ送信する案
2. Amazon WorkMailを使う案
Lambdaを使う案が主流かもしれないが、自分の場合は品質とスピードを重視して結果的には案2を採用した。

案1 S3への出力とLambdaを使う

SESの受信ルールで、受信メッセージをS3へ出力し、それをLambdaでどうにかして別アドレスへメール送信するのが定石のようだ。

こういった情報を参考にプログラムを作成することになるのだが、文字コードや添付ファイルの有無、メッセージを編集してから転送したい、などいろいろ考慮していくとMIMEの深遠なる世界に迷い込んでなかなか大変なことになる。

加えて、SESのS3アクションで「Encrypt Message」オプションを有効化するとS3に格納するメッセージデータが暗号化されるが、それをLambda(Python)側で複合する方法がわからずハマった。というか今も解決できていない。(KMSも絡んで話が長くなるのでこれについてはあらためて別の記事にしたい。)
公式ドキュメント

スクリプト

lambda_function.py
# -*- coding: utf-8 -*-
import boto3
import json
import re
import os

#転送先アドレス(カンマ区切りで複数指定)
FORWARD_TO = os.environ['forward_to'].split(",")

#メール保存先バケット名
S3_BUCKET = "s3-bucket-name"

#転送メールの送信元ドメイン名
DOMAIN_NAME = "hogehoge.jp"

s3  = boto3.client('s3')
ses = boto3.client('ses', region_name="us-east-1")

def lambda_handler(event, context):
    #本来の送信者
    MAIL_SOURCE = event['Records'][0]['ses']['mail']['source']

    #転送メールの送信者
    MAIL_FROM = MAIL_SOURCE.replace('@','=') + "@" + DOMAIN_NAME

    #メール保存先のフォルダ名
    S3_OBJECT_PREFIX = event['Records'][0]['ses']['receipt']['recipients'][0].split("@")[0] + "/"

    #S3上のメールファイル
    s3_key = S3_OBJECT_PREFIX + event['Records'][0]['ses']['mail']['messageId']

    #メールファイル取得
    try:
        response = s3.get_object(
            Bucket = S3_BUCKET,
            Key    = s3_key
        )
    except Exception as e:
        raise e

    #メールヘッダの書き換え
    try:
        replaced_message = response['Body'].read().decode('utf-8')
        replaced_message = re.sub("\nTo: .+?\n", "\nTo: %s\n" % ", ".join(FORWARD_TO), replaced_message,1)
        replaced_message = re.sub("\nFrom: .+?\n", "\nFrom: %s\n" % MAIL_FROM, replaced_message,1)
        replaced_message = re.sub("^Return-Path: .+?\n", "Return-Path: %s\n" % MAIL_FROM, replaced_message,1)
    except Exception as e:
        raise e

    #メール送信
    try:
        response = ses.send_raw_email(
            Source = MAIL_FROM,
            Destinations= FORWARD_TO ,
            RawMessage={
                'Data': replaced_message
            }
        )
    except Exception as e:
        raise e

send_raw_emailを使い、受信したメールのBODYをそのまま送信するのであれば比較的シンプルに実装できそうだ。ただし、一般的なメール転送でよくあるような、メール本文の冒頭に元メールのヘッダ情報を追記するようなことは行っていないため、元メールの送信者がわからないという問題がある。メール本文を編集しようとするとsend_emailを使えば良さそうだが、文字コードやHTMLメール、添付ファイルの考慮など、前述のとおりディープな世界へ足を踏み入れることになり、大変。なのでここでは諦めてシンプルに。

FORWARD_TO = os.environ['forward_to'].split(",")
転送先のメールアドレスはLambdaの環境変数を使って設定する。カンマ区切りの文字列で複数を指定することも可能にしている。

MAIL_FROM = MAIL_SOURCE.replace('@','=') + "@" + domain_name
ここでひと工夫。元メールの差出人アドレスを転送メールの差出人アドレス内に埋め込む。

S3_OBJECT_PREFIX = event['Records'][0]['ses']['receipt']['recipients'][0].split("@")[0] + "/"
SESがS3へ出力する際のPrefixに合わせていれば何でも良いが、ここでは受信アカウントをPrefixとしている。

案2 Amazon WorkMailを使う

月額4USDのコストが許容できるのであればSESの受け口としてWorkMailが使える。WorkMailには転送機能がある。

WorkMailのセットアップ方法は以下がわかりやすい。
AWS WorkMailを使ってみたら想像以上に便利だった

WorkMailでの転送設定は以下(英語の公式ドキュメント)を参考に。
How do I set up an email forwarding rule in Amazon WorkMail?

Lambdaで実現できるはずの機能を月額4USDで逃げるのはエンジニアとして負けた気がしてしまうが・・・スピードと確実性を優先するならアリかと。

続きを読む

クラウドベンダーの比較

はじめに

3大クラウドと呼ばれているAWS、GCP,Azureのリソースを比べてみました。
2017年11月時点の比較となります。

インフラ・サービスレベル

比較項目 AWS GCP Azure 備考
データセンター 各地で借りている すべて自前 各地で借りている(一部自前)
仮想化技術 Xen KVM Hyper-V
リージョン(国内) 1個所 1個所 2個所
リージョン(全国) 15個所 12個所 36個所
SLA 99.95 99.95 99.95 仮想サーバ

サービス面

比較項目 AWS GCP Azure 備考
仮想サーバ Amazon EC2 Google Compute Engine Azure Virtual Machines
仮想サーバ対応OS Amazon Linux,CentOS,RedHat,Windows Server,等 CentOS,RedHat,SLES,Windows Server,等 CentOS,RedHat,Windows Server,等
仮想サーバディスク SSD,HDD SSD,HDD SSD,HDD
仮想サーバスナップショット
仮想サーバオートスケール
コンテナ Amazon ECS Container Engine Container Service
RDB RDS Cloud SQL SQL Database
RDB冗長化
RDBリードレプリカ
RDB DB種別 Aurora,MySQL,MariaDB,Oracle,SQL Server,PostgreSQL MySQL,PostgreSQL SQL Server
NoSQL DynamoDB Cloud Datastore Cosmos DB
ビックデータ Redshift BigQuery App Service
メール Amazon SES
モニタリングツール CloudWatch Stackdriver Azure Monitor
ロードバランサー(L4) CLB Network load balancing Azure Load Barancer
ロードバランサー(L7) ALB HTTP load balancing Application Gateway
CDN Amazon CloudFront Google Cloud CDN Azure CDN
VPN Amazon VPC Google Cloud VPN VPN Gateway
DNS Route53 Google Cloud DNS Azure DNS
専用線 Direct connect Dedicated Interconnect Express Route

サポート面

比較項目 AWS GCP Azure 備考
ランク低 開発者 ($29/月 or 利用料の 3%/月) シルバー ($150/月) Standard($300/月)
ランク中 ビジネス($100/月 or 利用料 の10%/月) ゴールド($400/月) Professional Direct($1,000/月)
ランク高 エンタープライズ($15,000/月 or 利用料の10%) プラチナ(問合せ) Premier(問合せ)

費用面(リージョン日本)

比較項目 AWS GCP Azure 備考
課金単位
ディスカウント リザーブドインスタンス(前払い) 継続利用割引、利用確約の割引(前払い) エンタープライズ契約(前払い)
仮想サーバ(Type) t2.medium(2vCPU,4GB) n1-standard-2(2コア,7.5GB) A2 V2(2コア,4GB)
仮想サーバ(時) $0.0464(5.336円)※1 $0.0950(10.925円)※1 $0.15(17.25円)※1
仮想サーバ(月) $33.408(3841.92円)※1 $48.17(5539.55円)※1,※2 $108(12,420円)※1
インターネットへの転送量 $0.140/GB(10TBまで) $0.12/GB(1TBまで) $0.138/GB(5GB-10TB、5GBまでは無料)
ストレージ利用料 $0.025/GB (最初の50TB/月まで) ※S3 $0.016/GB 月 ※Regional Storage $0.2/GB(最初の50TB/月まで) ※BLOG Storage

※1 $1=115円換算
※2 継続利用割引含む

総括

 AWS、GCP,Azureでのサービス面では同じようなサービスを展開していることが判明。
 インフラ・サービスレベルでは、Azureがリージョン36個とTOPに。
世界的に見て幅を利かせているように思えた。
ただ、GCPはすべて自前のセンターでクラウドサービスを展開していることから、
新しいことをやるにも制約が低いように思えた。
私のイメージ的に一番シェアが高いAWSは、インフラ面ではGCP,Azureに劣っている結果となった。
 費用面では、Azureは割高。AWSが思ったよりも頑張っているように思えた。
 イメージ的にGCPは費用は安いと思っていたが、仮想サーバ比較だと、継続利用割引を使用してもAWSのほうが安い結果となった。
 ただ、費用に関しては、日々値下げ合戦が繰り広げられているので、今後のベンダーさんの努力に期待です。

最後に、費用面での算出に使用した見積もりツールです。
【AWS】http://calculator.s3.amazonaws.com/index.html
【GCP】https://cloud.google.com/products/calculator/?hl=ja
【Azure】https://azure.microsoft.com/ja-jp/pricing/calculator

続きを読む

AWSをさわってみたのでつまずいた点などをメモ

本記事はサムザップ Advent Calendar 2017の5日目の記事です。
昨日は@tomoriakiさんのゲームエンジニアが紙ヒコーキワークショップをやってみたでした

前提

・ インフラ歴半年
・ 業務では主にGCPを使用
・ GCPでできることをAWSで再現
・ 基本的にはterraformで構築
・ 大体検証期間は2週間くらい

目指したこと

既存のプロジェクト(GCP)と同等の開発環境構築

手順

※ 下記に記載する順番どおりに行ったわけではなく、記載上の問題で番号を振っています。

1. VPC構築

まずは何はともあれVPCとサブネット、サブネット配下にインスタンスを1台作成しました。

つまずき1
インスタンスにsshアクセスできない!
※ FWの設定(awsではセキュリティーグループ(sg)っていうのかな)はingress port:22許可したのに

原因と解決
インターネットゲートウェイ(igw)ってものをサブネットにアタッチしないとprivateなサブネットになってしまい、アクセス出来ないとのこと。


2. VPCにpriveteとpublicのサブネット構築

1.でigwを置いたけどそもそも接続出来ない領域分けたいよね、ということでサブネットを2つ作ってigwをアタッチするpublicサブネットとアタッチしないpriveteサブネットを作成
それぞれにインスタンスを1台ずつ置きpriveteインスタンスにはpublicインスタンスからinternal接続する

つまずき2
privateサブネットに置いたインスタンスでyum updateできない

原因と解決
privateサブネットだと外へのアクセスもできないらしい
NATゲートウェイ(ngw)というものをpublicサブネットに配置して、それ経由でpriveteサブネットのインスタンスは外へのアクセスをできるようにした。

※ 最初は名前的にEgress Only インターネットゲートウェイだと思ったけど、こちらはipv6らしい

TIPS
S3への接続はなにもしないと外部経由になってしまうらしい
エンドポイント(ep)というものをVPCに配置するとinternalで接続できるのでVPCを作ったらとりあえず、epも配置しよう!!


3. プロジェクトと開発環境の構成

1,2でVPCはだいたい出来たけど、
・ プロジェクトが違う場合
・ 環境(development, staging, productionなど)が違う場合
どのようにネットワークを分けて構築すればいいかを考えた。

参考
下記サイトでアカウントを分ける場合とVPCを分ける場合を分かりやすくまとめている
こちらを参考に本番と開発を分け(パターン4)、開発はVPCで分ける(パターン2-1)ように考慮した
https://dev.classmethod.jp/cloud/aws/account-and-vpc-dividing-pattern/


4. LBの作成

TCP, HTTP, HTTPSロードバランサの作成

つまずき3
terraformのmoduleロードバランサ関連多くてどれ使えばいいか分からん

原因と解決

terraform module名 awsでの呼び方 備考
elb classic ロードバランサ
alb application ロードバランサ 非推奨
(今後のバージョンアップで削除予定)
lb application ロードバランサ
network ロードバランサ
基本的にはこちらを使用する

TIPS
application LBはマルチAZ構成を強制されているのでゾーンの違うpublicなサブネットを2つ用意する必要がある。


5. インスタンスのホスト名解決

gcpではinternalな通信にホスト名を使用できる。

つまずき4
awsだとホスト名がipでつけられそもそも分かりづらいので、ホスト名を設定しつつ名前解決をしたい。

原因と解決
インスタンス登録時にtags:Nameにインスタンス名を設定し、そのインスタンス名とprivateIPをroute53でレコード登録する

サーバ自体はterraformのprovisionerで下記を実行

sudo sed -i -e "s/localhost.localdomain/[ホスト名]/g" /etc/sysconfig/network
sudo hostname [ホスト名]

まとめ

・ awsはgcpに比べてやれることが多い分、使う側がネットワークの知識とか結構求められるな!という印象を受けました。terraformのコード量もgcpの2倍くらいになった気がする。

・ マルチAZ構成を考える際は結構パフォーマンスに注意する必要がありそう。別ゾーンへのアクセスはパフォーマンスが落ちるという検証結果が結構あるので、問題ないように構成を考えないといけない

・ あとはざっくりgcpはブラックリスト、awsはホワイトリストなセキュリティーな印象

terraformのコード

https://github.com/bayguh/terraform_aws


明日は@Gaku_Ishiiさんの記事です。

続きを読む

KubernetesでAWS ALBを自動作成する〜ついでにRoute53 Record Setも

kube-ingress-aws-controllerを使います。

kube-ingress-aws-controllerとは

Zalandoが公開している、Kubernetes用のIngress Controllerの一つです。

ZalandoはKubernetes界隈では著名な、ヨーロッパでファッションECをやっている企業です。
Kubernetesコミュニティへの様々な形で貢献していて、今回紹介するkube-aws-ingerss-controllerや先日紹介したexternal-dnsもその一つです。

何かできるのか

KubernetesユーザがAWSを全く触らずとも

  • ALBの自動作成
  • ALBに割り当てるTLS証明書(ACM管理)を自動選択

をしてくれます。

使い方

KubernetesのIngressリソースを普段通りつくります。

kubectl create -f myingress.yaml

すると、1~2分ほどでIngressリソースに書いたホスト名でインターネットからアクセスできるようになります。

open https://myingress.exapmle.com

Ingress Controllerとは

KubernetesのIngressはL7ロードバランサのスペックのようなもので、そのスペックから実際にL7ロードバランサをセットアップするのがIngress Controllerの役割です。

coreos/alb-ingress-controllerとの違い

coreos/alb-ingress-controller

  • Ingressリソース一つに対して、1 ALBをつくります

zalando-incubator/kube-ingress-aws-controller

  • ACM証明書のドメイン一つに対して、ALBを割り当てます
  • 同じドメイン名に対するルートを含むIngressリソースは、一つのALBにまとめられます
  • ALBのターゲットグループにEC2インスタンスを割り当てるところまでしかやってくれない!ので、実際にIngressとして利用するためには他のIngress Controllerを併用する必要があります

kube-ingress-aws-controllerのセットアップ手順

Security Groupの作成

kube-ingress-aws-controllerはALBに割り当てるSecurity Groupまでは自動作成してくれないので、AWSコンソール等で作成します。

kube-ingerss-aws-controllerのドキュメントにはCloudFormationを使った手順がかいてあります。

同等のSecurity Groupをawscliで作る場合は以下のようなコマンドを実行します。

CLUSTER_NAME=...
VPC_ID=vpc-...

aws ec2 create-security-group \
  --description ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
  --group-name ${CLUSTER_NAME}-kube-aws-ingress-controller-alb \
  --vpc-id $VPC_ID | tee sg.json

SG_ID=$(jq -r '.GroupId' sg.json)

aws ec2 create-tags --resources $SG_ID --tags \
  "Key=\"kubernetes.io/cluster/$CLUSTER_NAME\",Value=owned" \
  "Key=\"kubernetes:application\",Value=kube-ingress-aws-controller"

aws ec2 authorize-security-group-ingress \
  --group-id $SG_ID \
  --ip-permissions '[{"IpProtocol": "tcp", "FromPort": 80, "ToPort": 80, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}, {"IpProtocol": "tcp", "FromPort": 443, "ToPort": 443, "IpRanges": [{"CidrIp": "0.0.0.0/0"}]}]'

aws ec2 describe-security-groups --group-id $SG_ID

# あとで: 不要になったらクラスタやVPCの削除前に以下のように削除
aws ec2 delete-security-group --group-id $SG_ID

IAMポリシーの割当

kube-ingerss-aws-controllerのドキュメントにIAM Policy Statementの一覧がかいてありますが、要約すると

  • CloudFormationスタックのCRUD権限
  • ACM証明書、VPC、RouteTable、Subnet、Security Group、AutoScalingGroup、EC2 InstanceのGet/List/Describe権限
  • ALBのCRUD権限

が必要です。

kube-awsのcluster.yamlの場合は、以下のように書きます。

worker:
  nodePools:
  - iam:
      policy:
        statements:
        - effect: Allow
          actions:
          - "autoscaling:DescribeAutoScalingGroups"
          - "autoscaling:AttachLoadBalancers"
          - "autoscaling:DetachLoadBalancers"
          - "autoscaling:DetachLoadBalancerTargetGroup"
          - "autoscaling:AttachLoadBalancerTargetGroups"
          - "elasticloadbalancing:AddTags"
          - "elasticloadbalancing:DescribeLoadBalancers"
          - "elasticloadbalancing:CreateLoadBalancer"
          - "elasticloadbalancing:DeleteLoadBalancer"
          - "elasticloadbalancing:DescribeListeners"
          - "elasticloadbalancing:CreateListener"
          - "elasticloadbalancing:DeleteListener"
          - "elasticloadbalancing:DescribeTags"
          - "elasticloadbalancing:CreateTargetGroup"
          - "elasticloadbalancing:DeleteTargetGroup"
          - "elasticloadbalancing:DescribeTargetGroups"
          - "elasticloadbalancingv2:DescribeTargetGroups"
          - "elasticloadbalancingv2:DescribeLoadBalancers"
          - "elasticloadbalancingv2:CreateLoadBalancer"
          - "elasticloadbalancingv2:DeleteLoadBalancer"
          - "elasticloadbalancingv2:DescribeListeners"
          - "elasticloadbalancingv2:CreateListener"
          - "elasticloadbalancingv2:DeleteListener"
          - "elasticloadbalancingv2:DescribeTags"
          - "elasticloadbalancingv2:CreateTargetGroup"
          - "elasticloadbalancingv2:DeleteTargetGroup"
          - "ec2:DescribeInstances"
          - "ec2:DescribeSubnets"
          - "ec2:DescribeSecurityGroup"
          - "ec2:DescribeRouteTables"
          - "ec2:DescribeVpcs"
          - "acm:ListCertificates"
          - "acm:DescribeCertificate"
          - "iam:ListServerCertificates"
          - "iam:GetServerCertificate"
          - "cloudformation:Get*"
          - "cloudformation:Describe*"
          - "cloudformation:List*"
          - "cloudformation:Create*"
          - "cloudformation:Delete*"
          resources:
          - "*"

kube-aws-ingress-controllerをデプロイ

$ kubectl apply -f kube-aws-ingress-controller.yaml
kube-aws-ingress-controller.yaml
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: kube-ingress-aws-controller
  namespace: kube-system
  labels:
    application: kube-ingress-aws-controller
    component: ingress
spec:
  replicas: 1
  selector:
    matchLabels:
      application: kube-ingress-aws-controller
      component: ingress
  template:
    metadata:
      labels:
        application: kube-ingress-aws-controller
        component: ingress
    spec:
      containers:
      - name: controller
        image: registry.opensource.zalan.do/teapot/kube-ingress-aws-controller:latest
        env:
        - name: AWS_REGION
          value: ap-northeast-1

併用するIngress Controllerをデプロイ

今回はskipperを使ってみます。

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: skipper-ingress
  namespace: kube-system
  labels:
    component: ingress
spec:
  selector:
    matchLabels:
      application: skipper-ingress
  updateStrategy:
    type: RollingUpdate
  template:
    metadata:
      name: skipper-ingress
      labels:
        component: ingress
        application: skipper-ingress
    spec:
      hostNetwork: true
      containers:
      - name: skipper-ingress
        image: registry.opensource.zalan.do/pathfinder/skipper:latest
        ports:
        - name: ingress-port
          containerPort: 9999
          hostPort: 9999
        args:
          - "skipper"
          - "-kubernetes"
          - "-kubernetes-in-cluster"
          - "-address=:9999"
          - "-proxy-preserve-host"
          - "-serve-host-metrics"
          - "-enable-ratelimits"
          - "-experimental-upgrade"
          - "-metrics-exp-decay-sample"
          - "-kubernetes-https-redirect=true"
        resources:
          limits:
            cpu: 200m
            memory: 200Mi
          requests:
            cpu: 25m
            memory: 25Mi
        readinessProbe:
          httpGet:
            path: /kube-system/healthz
            port: 9999
          initialDelaySeconds: 5
          timeoutSeconds: 5

WorkerノードのSecurity Group設定変更

今回はskipperをつかうことにしたので、kube-ingress-aws-controllerが作成したALBからアクセスする先はskipper(がいるEC2インスタンス)になります。

Security GroupへALBからskipperがいるEC2インスタンスへの通信をブロックしたままだとGateway Timeoutになってしまいます。そうならないように、ALB用につくったSGから、WorkerノードのSGへの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)の通信を許可しましょう。

ALB側SGのOutboundを絞っていないのであれば、Worker側SGのInboundを追加すればOKです。

Ingressリソースの作成

apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: nginx
spec:
  rules:
  - host: nginx-ingress.example.com
    http:
      paths:
      - backend:
          serviceName: nginx
          servicePort: http

---

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  type: ClusterIP
  ports:
  - name: http
    port: 80
    targetPort: 80
  selector:
    app: nginx

---

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80

ログの確認

$ k logs kube-ingress-aws-controller-6bbd8f9d6c-bcqph
2017/12/06 12:33:52 starting /bin/kube-ingress-aws-controller
2017/12/06 12:33:54 controller manifest:
2017/12/06 12:33:54     kubernetes API server:
2017/12/06 12:33:54     Cluster ID: k8s3
2017/12/06 12:33:54     vpc id: vpc-12345678
2017/12/06 12:33:54     instance id: i-07e29f841f676ca00
2017/12/06 12:33:54     auto scaling group name: k8s3-Nodepool1-MMF7MXKI9350-Workers-BZWB5IAV7JW8
2017/12/06 12:33:54     security group id: sg-8368a6fa
2017/12/06 12:33:54     private subnet ids: []
2017/12/06 12:33:54     public subnet ids: [subnet-12345678 subnet-23456789]
2017/12/06 12:33:54 Start polling sleep 30s

30秒経過後、以下のようにCloudFormationスタックが作成される。

2017/12/06 12:34:24 Found 1 ingresses
2017/12/06 12:34:24 Found 0 stacks
2017/12/06 12:34:24 Have 1 models
2017/12/06 12:34:24 creating stack for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
2017/12/06 12:34:25 stack "arn:aws:cloudformation:ap-northeast-1:myawsaccountid:stack/k8s3-b9dbfe3/caf1f3a0-da81-11e7-9e21-500c28b97482" for certificate "arn:aws:acm:ap-northeast-1:myawsaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created
2017/12/06 12:34:25 Start polling sleep 30s

作成されたAWSリソースの確認

1〜2分待ってスタックがCREATE_COMPLETE状態になれば成功。

コンソールでCloudFormationスタックのResourcesの内容を見ると、何がつくられたのかがわかる。

image.png

つくられるのは以下の4つ。

  • HTTPListener: 80番ポート用のListener
  • HTTPSListener: 443番ポート用のListener
  • LB: ALB
  • TG: kube-ingress-aws-controllerがデプロイされているノードが登録されたTargetGroup

ALB

リスナー

443番ポート用のListenerには、Ingressリソースに書いたドメインに対応するACM証明書が選択されています。

今回は*.example.com用のワイルドカード証明書を事前に用意しておいたのですが、Ingressにnginx-ingress.example.comというホスト名を設定したところ、ちゃんとワイルドカード証明書を探し出してくれました(かしこい)。

image.png

ターゲットグループ

kube-aws-ingress-controllerがデプロイされたノードのASGをコンソールでみてみると、ターゲットグループに割り当てられていました。EC2インスタンスを直接TargetGroupに登録していくような方法だとインスタンスが落ちた場合などが怖いですが、ちゃんとしてますね。

image.png

Route53 RecordSetの作成

これだけだとALBが作成されただけなので、nginx-ingress.example.comでアクセスできないはずです。

しかし、昨日デプロイしたexternal-dnsがIngressリソースとALBを検知して、勝手にRecordSetをつくってくれていました。

stern_external-dns.log
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com A"
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Desired change: CREATE nginx-ingress.example.com TXT"
external-dns-768686fd4c-zpnlx external-dns time="2017-12-06T12:43:53Z" level=info msg="Record in zone example.com. were successfully updated"

image.png

ちゃんとALBへのA(lias)レコードを作成してくれていますね。

 インターネットからアクセスしてみる

nginx-ingress.example.comにブラウザからアクセスしてみて、以下のようなnginxのウェルカムページが表示されてば成功です。おつかれさまでした。

image.png

まとめ

kube-ingress-aws-controllerを使うと、Kubernetesユーザはkubectl createするだけでALBとRecordSetをよしなにセットアップしてくれます。
ALBの作成・管理やRoute53 RecordSetの作成のためにいちいちインフラエンジニアを呼び出したくない!というようなセルフサービス好きの会社さんでは特に役立つのではないでしょうか?!

トラブルシューティング

unable to get details for instance “i-0346d738155e965d8”

IAMポリシーが足りないときに出るエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-6bvv8
2017/12/06 07:11:51 starting /bin/kube-ingress-aws-controller
2017/12/06 07:11:53 unable to get details for instance "i-0346d738155e965d8": NoCredentialProviders: no valid providers in chain. Deprecated.
    For verbose messaging see aws.Config.CredentialsChainVerboseErrors

required security group was not found

Security Groupがないか、またはSecurityGroupのタグが間違っているか、EC2インスタンスにkubernetes.io/cluster/クラスタ名=ownedというタグがついていない場合のエラーです。

$ k logs kube-ingress-aws-controller-7f7974ff58-xqgrq
2017/12/06 08:10:40 starting /bin/kube-ingress-aws-controller
2017/12/06 08:10:41 required security group was not found

CloudFormationで「At least two subnets in two different Availability Zones must be specified」

KubernetesのWorkerノードのASGが単一のAZに割り当てられているときのエラー。ALBの仕様で、最低2つのAZが必要。

kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:06 Start polling sleep 30s
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:36 Found 1 ingresses
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Found 0 stacks
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 Have 1 models
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 creating stack for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" / ingress ["kube-system/nginx"]
kube-ingress-aws-controller-7f7974ff58-rp7t8 controller 2017/12/06 12:01:37 stack "arn:aws:cloudformation:ap-northeast-1:myaccountid:certificate:stack/k8s3-b9dbfe3/360e1830-da7d-11e7-99f7-500c596c228e" for certificate "arn:aws:acm:ap-northeast-1:myaccountid:certificate:certificate/64f33935-05ac-4e6b-b1ae-58973d556a74" created

image.png

instance is missing the “aws:autoscaling:groupName” tag

ASG以外で作ったEC2インスタンスにkube-ingress-aws-controllerがデプロイされてしまったときのエラー。

$ k logs kube-ingress-aws-controller-7f7974ff58-m6ss2
2017/12/06 12:25:59 starting /bin/kube-ingress-aws-controller
2017/12/06 12:25:59 instance is missing the "aws:autoscaling:groupName" tag

kube-aws-ingress-controllerは、デフォルトではASGに設定されたSubnetをALBのSubnetに流用する。
そのためにASGを探すとき、EC2インスタンスについたaws:autoscaling:groupNameというASGが自動的につけてくれるタグをヒントにするため、ASG以外でつくったEC2インスタンスではこのエラーが出てしまう。

Ref: Spot Fleet support · Issue #105 · zalando-incubator/kube-ingress-aws-controller

Issueも出ているが、まだASG以外は対応していない。ワークアラウンドとしては、kube-ingress-aws-controllerのaffinityでASGでつくったノードにだけスケジュールされるようにすることが考えられる。

kube-awsの場合、awsNodeLabels機能をオンにすると、ASGでつくったノードには”kube-aws.coreos.com/autoscalinggroup”というラベルが付与されるので、それを前提にすると以下のようなaffinityをかけばOK。

              affinity:
                nodeAffinity:
                  requiredDuringSchedulingIgnoredDuringExecution:
                    nodeSelectorTerms:
                    - matchExpressions:
                      - key: "kube-aws.coreos.com/autoscalinggroup"
                        operator: "Exists"

504 Gateway Time-out

ALB経由でnginxにアクセスしようとしてこのエラーがかえってきた場合、ALB用につくったセキュリティグループからkube-aws-ingress-controllerが動いているEC2インスタンスへのアクセスを許可できていない可能性があります。

EC2インスタンス側のSGに、ALB用SGからの9999番ポート(kube-ingress-aws-controllerと組み合わせて使うskipperのhostPortに指定した)への通信をを許可するようなInboundルールを追加しましょう。

続きを読む

ゼロから始めるLINEBot(AWS×node.js) ③投稿された画像をS3に保存

前回の記事の続きです。
ゼロから始めるLINEBot(AWS×node.js)②オウム返しする
https://qiita.com/tenn25/items/498f768a58ba6a6de156

この記事で分かること

  • LINEに投稿された画像をS3に保存する

この記事の対象者

  • いろいろと初心者(自分がそうなので)
  • MessagingAPIのような外部APIを使ったことがない
  • AWSもあんま詳しくない(アカウントは持ってるくらい)

流れ

第1回 とりあえず動かす
1.LINE Developersの登録/設定
2.実装(Lambda/node.js)
3.定期実行の設定(CloudWatch)

第2回 オウム返しの実装
4.ドメイン取得,DNS設定,SSL証明書の設定(Route53/ACM/SNS/S3)
5.イベントの受け取り(APIGateWay)
6.ユーザへの返信(Lambda/node.js)

第3回 ユーザ情報や投稿画像の保存(この記事)
7.ストレージ(S3やDB)との連携

投稿画像の取得

投稿からS3保存までの流れは以下の通りです。
①ユーザがLINEに画像を投稿する。
②画像は一旦LINEサーバに保存され、WebHookによって、APIGateWayが叩かれる。
③APIGateWayの裏でLambdaが実行される。
④Lambdaの処理でLINEサーバへ画像を取得する。
⑤Lambdaの処理でS3に画像を保存する。

S3への保存ファイル名は「ユーザID/現在時刻.jpg」としようと思います。

LINEにメッセージが投稿されると、WebHookを通じてLambdaにリクエストが届きますが、
リクエストのevent.events[0].message.typeがimageであれば画像が投稿されたと判断できます。

画像は一旦LINE側のサーバに保存されているようなので、
https://api.line.me/v2/bot/message/{messageId}/content
に対してGETリクエストを投げる必要があります。

GETリクエストの結果をdataとしてバッファに流し込めばS3に転送できそうです。

index.js

var https = require('https');
var AWS = require('aws-sdk');

AWS.config.update({
    accessKeyId: process.env.AWS_S3_ACCESS_KEY_ID,
    secretAccessKey: process.env.AWS_S3_SECRET_ACCESS_KEY,
    region: process.env.AWS_S3_REGION
});

exports.handler = (event, context, callback) => {

    //リクエストを取得
    event = event.events[0];
    var replyToken = event.replyToken;
    var message = event.message;
    var userId = event.source.userId;


    if(message.type === 'text'){
        //テキストが投稿された場合の処理はこちら

    }else if(message.type === 'image'){
        //画像が投稿された場合の処理はこちら
        var s3 = new AWS.S3();
        var message_id = message.id;

        //投稿された画像はmessage_idによって取得
        var send_options = {
            host: 'api.line.me',
            path: '/v2/bot/message/'+ message_id +'/content',
            headers: {
                "Content-type": "application/json; charset=UTF-8",
                "Authorization": " Bearer " + process.env.CHANNEL_ACCESS_TOKEN
            },
            method:'GET'
        };

        var data = [];

        //LINEサーバに対してリクエストを投げると画像(data)が取得できるのでS3に保存する
        var reqImg = https.request(send_options, function(res){
            res.on('data', function(chunk){
              data.push(new Buffer(chunk));
            }).on('error', function(e){
              console.log("ERROR: " + e.stack);
            }).on('end', function(){
                //ここらへんはPromiseで書いた方がいいかもしれない

                //ファイル名として現在時刻を取得
                var nowDate = new Date();
                var nowTime = nowDate.getTime();

                var params = {
                    Bucket: process.env.S3_BUCKET_NAME, // ←バケット名
                    Key: userId + '/' + nowTime + '.jpg', // ←バケットに保存するファイル名
                    Body: Buffer.concat(data)
                };
                s3.putObject(params, function(err, data) {
                    context.done();
                });
            });
        });
        reqImg.end();
    }
};

node.jsの話になってしまいますが、
④Lambdaの処理でLINEサーバへ画像を取得する。
⑤Lambdaの処理でS3に画像を保存する。
このへんが非同期処理になってしまうため、
④が完了してから⑤が開始するようになんらか工夫する必要ああります。(上記の書き方かPromiseを使うなど)

続きを読む

AWS + Docker + Jenkins = 同一サーバで複数ドメインの開発環境構築

株式会社オズビジョン@smiraxです。

以前新規事業を立ち上げた際に開発環境周りでいろいろと苦労をしました。
その中でもうまくいったなーと思う一例があったのでそちらを紹介しようと思います。

課題

開発している環境で当時課題に上がっていたのが
「サーバにデプロイしたいです。12~13時の間使わせてください」
というデプロイ予約。

本番へのデプロイであれば、下手にチーム内で混乱がおきないようにするために必要な声かけかもしれません。
ですが開発環境でわざわざデプロイの予約をしなくてはいけないのはめちゃくちゃ面倒です。
さらにこの予約が混み合えば
「今日デプロイできなかったので明日にします」
こんな結果になってしまえばこれほど時間の無駄はないです。

対策

そこで対策として私が取った方法が
Dockerを使って複数ドメインの開発環境を作ること でした

環境

・AWS
・Docker
・Jenkins
・Capistrano
(※Capistranoで、AWSへのデプロイができることが肝になっています。)

サーバ側方法

EC2インスタンスにDockerをインストールします。
(色々な方が記載していると思うのでインストール方法などは割愛します)

Dockerはポートフォワーディングができるので複数のポートを開けてrunしておきます。

複数ポートで起動し、アクセスの確認をしたらAWSコンソールで、新しくELBを作ります。

Dockerで開けたポートと同じ数ELBを用意します。
このとき、ELBにもポートフォワーディングがあるので、Dockerで開けたポートと同じように設定します。

あとは作成したそれぞれのELBに起動したEC2インスタンスを接続します。

そうすることで、それぞれのELBにアクセスすれば、ポートフォワーディングされて同一インスタンスの中の別コンテナにアクセスできるようになります。

あとはAWSのRoute53を使って、それぞれのELBに対して設定をすれば終了です。

Jenkins側方法

Jenkins側ではGitHubにpushしたユーザ名を取得します。
そのユーザ名を判別し、デプロイするときに必要なファイルを書き換えます。
(当時はsymfonyを使っていたのでparameters.ymlを書き換えてました)

デプロイするサーバのディレクトリも、GitHubのユーザごとに分けて自動で作成するように設定をします。
またデプロイするときに、Dockerコンテナを自動で作り直すようにしておけば常に綺麗な状態を保てるので、自分は作り直してました。

やってみて

Jenkinsを使ったデプロイを1から構築してみて、正直わからないことばかりでしたがかなり勉強になりました。
正直もっといい方法があるような気がしなくもないですが、自分の中では結構満足しています。
EC2インスタンスを複数用意する必要もなく、少しはコストの削減もできたなーと思ってます。


次は弊社に第二新卒で入社したEngがredashについて記載してくれるようです!
弊社集計の基盤となっているものをガリガリ使っているのでどんな内容になるのか楽しみですー!

続きを読む

自動Blue-Greenデプロイをansibleで構築してみた

自己紹介

一年目のインフラエンジニア
クラウドなんだからBlue-Greenデプロイをやりたいと言われ、構築して運用してみた。

Blue-Greenデプロイの構築方法

Blue-Greenデプロイの概要

AWSで環境を構築するにあたり以下の図のように設計してみました。
この図の通り、ALBにてルーティングしているターゲットグループに紐づいているEC2を切り替えることで、Blue Greenデプロイメントを行っています。
AWSのホワイトペーパー(p. 32)にもあったのですが、DNSのルーティングを行う方法に比べ、Rollbackにおけるリスクを減らすことができます。

デプロイのために作成したplaybook

実際にansibleでplaybookを組んでみました。
なんかrolesがたくさんあるのは、ひとつひとつのrolesをシンプルにしたかったからです。
なぜなら、rolesが複雑になるとあとで管理が面倒になる、と先輩に言われたからだったり、、、
(今はとても実感しています。本当に分けた方がいい。。。)

---
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prd"}

playbookの解説

  • EC2立ち上げ

まずはEC2を立ち上げるrolesを作成、ここは後々他のデプロイ機能でも活用できるように切り出してみました。セキュリティ的な意味で、userはrootではなく別にユーザーを作成。

provision.yml
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

※EC2を立ち上げてRoute53に登録、SSH接続の確認まで行うrolesを例として載せておきます。変数は別のplaybookで指定しています。(気になるところあればコメントください。)

roles/ec2/tasks/main.yml
- name: Provision EC2 Box
  local_action:
    module: ec2
    key_name: "{{ ec2_keypair }}"
    group_id: "{{ ec2_security_group }}"
    instance_type: "{{ ec2_instance_type }}"
    image: "{{ ec2_image }}"
    vpc_subnet_id: "{{ ec2_subnet_ids[(inventory_hostname_short[-1:] | int % 2 )] }}"
    region: "{{ ec2_region }}"
    assign_public_ip: no
    instance_profile_name: "{{ ec2_iam_role | default }}"
    wait: true
    user_data: "{{ lookup('template', 'roles/ec2/templates/user_data_hostname.yml.j2') }}"(※hostnameを動的に付けたかったのでtemplateを作成しusr_dataを使用)
    exact_count: 1
    count_tag:
      Name: "{{ inventory_hostname }}"
    instance_tags:
      Name: "{{ inventory_hostname }}"
      Environment: "{{ ec2_tag_Environment }}"
      Service: "{{ service_name }}"
    volumes:
    - device_name: /dev/xvda
      device_type: gp2
      volume_size: "{{ ec2_volume_size }}"
      delete_on_termination: true
  register: ec2

- debug: var=item
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Setup for DNS in AWS Route 53
  route53:
    aws_access_key:
    aws_secret_key:
    command: create
    private_zone: True
    zone: bdash.inside
    record: '{{ inventory_hostname }}.bdash.inside'
    type: A
    ttl: 60
    value: '{{ item.private_ip }}'
    overwrite: yes
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Wait for the instances to boot by checking the ssh port
  wait_for:
    host: '{{ item.private_ip }}'
    port: 22
    delay: 60
    timeout: 320
    state: started
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode
  • ミドルウェアからデプロイまで

webアプリはrailsで組まれているので、よくある組み合わせのnginx、unicornで構築しました。
DBはインフラで管理するので、database.ymlを作成して配置するrolesを別途作成しています。
あとは、デプロイしただけでmigrationまで行ってあげる親切設計。

ちなみにrelease-testのrolesは疎通確認、ヘルスチェックを行うrolesです。
もしうまくデプロイからnginx,unicornの起動までできなかった場合はansibleが落ちます。
これでデプロイしたけど、見てみたら動いていないなんてことはない。

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
  • デプロイ後

デプロイ後にターゲットグループの切り替えを行います。
ここでBlue-Greenデプロイを実現しています。ターゲットグループに登録して、削除するまで行いました。

ただ、このrolesは本番だけは行わず、手動で切り替えています。なぜなら、削除を行ってしまうと問題が起きた時にロールバックしにくくなる、と言うのが一番の理由です。うまくやればできるとは思うので、残論点です。

開発環境はそんなの関係ないので、利便性を取っています。

    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prduction"}

いざ、運用!!

ここまでで無事、Blue-Greenデプロイが実現できました。
当初の予定通り、ターゲットグループが自動で切り替わり、外部からの接続も確認できました。
そして、ansibleで構成管理をしているためサーバーを作っては壊すこともできます。
つまり、Immutable Infrastructureも実現できており、データを作っては壊す開発環境にはもってこいなものができました。

追加で実装に組み込んだテストrolesによって、「ansibleが通ったけれどサービスが動いていない!」なんてぬか喜びもなくすことができました。

まさに、いいことずくめ!!
自動デプロイにひとつ近づいた!

と思っていたのですが、ここで更なる問題が、、、

切り替え後にterminate処理をいれたら、アプリのdaemonなどのプロセスが強制KILLされる。

たとえばアプリ側でデータ出力や取込に数時間かかるという話はよくある話です。
そんな時にターゲットグループ切り替えだけならいざしらず、
サーバーを落としてしまったのならプロセスが強制的にKILLされ、結果処理停止となります。

自動デプロイを実装した結果、アプリ側に影響を出してしまいました。
この辺りはBlue-Greenデプロイを行う上で考慮すべき項目でしたね。

このことからプロセス監視を行うことが、今後の課題かなと思っています。
AWSにはスポットインスタンスもあるので、terminateしても動き続けられるようアプリ側にサルベージできる機能か何かも欲しいですね。

マイクロサービスだったことを忘れていた

運用に載せてからしばらくたつと、サービスも増え立てるサーバーも増えてきます。
つまり、、、デプロイの回数が単純に増えていきます。
更に自動化を進めないと、工数が増加の一途をたどることに・・・

最後に

自動デプロイ、Blue-Greenデプロイをansibleで実装してみました。
サービスのリリーススピードはデプロイの手間に依存する」が私の持論です。
なので、デプロイ作業をいかに自動化しつつ安定化させるかが肝だと思っています。

まだまだ改善できるところは多いので、今週末もansibleと一緒に過ごします~

続きを読む