AWS NATインスタンスを作成したメモ

AWSでEC2とWorkspacesを1つのVPCに入れて NATを構成しました。
昔とった杵柄でちょろいと思いきや、色んなところにハマりまくったので、記録しておきます。

要件

  • VPC内にEC2数台と、Workspaces数台を構築
  • VPC内でEC2とWorkspacesは相互に通信する
  • EC2とWorkspacesからVPC外(インターネット)にアクセス可能
  • VPC外から一部のEC2 Webサーバーにアクセスできる(ポート転送)

完成形

以下、アドレスやポート番号、IDなどはダミーです。
また、設定する順番通りには記載していないので、手順については以下のサイトの通りです。

http://docs.aws.amazon.com/ja_jp/AmazonVPC/latest/UserGuide/VPC_NAT_Instance.html

構成図

Qiita.png

NAT instanceの設定

インスタンスの作成

今回は、「amzn-ami-vpc-nat-hvm-2015.03.0.x86_64-gp2」というAMIから作成しました。
コミュニティAMIで「amzn-ami-vpc-nat」で検索して一番上にあったのを素直に選択しました。

送信元/送信先チェックを無効にする

EC2 コンソールで NATインスタンスを選択して、「ネットワーキング」→「送信元/送信先の変更チェック」で無効にしておく。

ポート転送

デフォルトだと無効になってます。

$ sysctl net.ipv4.conf.all.forwarding
net.ipv4.conf.all.forwarding = 0

有効にします。

$ sudo sysctl net.ipv4.conf.all.forwarding=1
net.ipv4.conf.all.forwarding = 1 

NATの設定

iptablesを書きます。

*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
COMMIT
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
-A POSTROUTING -s 192.168.0.0/16 -o eth0 -j MASQUERADE
-A PREROUTING  -i eth0 -d 192.168.10.100 -p tcp -m tcp --dport 80   -j DNAT --to 192.168.32.101
-A PREROUTING  -i eth0 -d 192.168.10.100 -p tcp -m tcp --dport 8080 -j DNAT --to 192.168.32.102:8080
-A POSTROUTING -o eth0 -d 192.168.32.101  -p tcp -m tcp --dport 8080 -j SNAT --to-source 192.168.10.100
-A POSTROUTING -o eth0 -d 192.168.32.102  -p tcp -m tcp --dport 80   -j SNAT --to-source 192.168.10.100
COMMIT

サービス再起動すると反映されます。

$ sudo service iptables restart

VPCの設定

IPv4 CIDRブロック 192.168.0.0/16

サブネット

名前 CIDR
Public 192.168.0.0/20
Private1 192.168.32.0/21
Private2 192.168.48.0/21

ルートテーブル

サブネット 送信先 ターゲット
Public 192.168.0.0/16 local
0.0.0.0/0 igw-xxxxxxxx(*1)
Private1 192.168.0.0/16 local
0.0.0.0/0 eni-xxxxxxxx(*2)
Private2 192.168.0.0/16 local
0.0.0.0/0 eni-xxxxxxxx(*2)

(1) ゲートウェイを指定する
(
2) NAT Instanceを指定する

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

AWSの推奨を参考にしつつこんな感じ。

ポート範囲 ソース
すべてのTCP 192.168.0.0/16
すべてのICMP – IPv4 192.168.0.0/16
80 (参照元Global IP)
8080 (参照元Global IP)
22 (管理者のGlobal IP)

ハマったこと

起動済のEC2のVPCを変更できない

オンプレだと、ネットワークケーブル引っこ抜いて挿し直せば良いだけなので、意外と罠。
一度、AMIを作成し、設定しておいたVPCでイメージからインスタンスを起動すればOKです。

EC2を作り直したらSSH接続できなくなった

NAT関係ないですが。
AMIからEC2インスタンスを作り直すと finger printが変わるため、sshするとエラーになります。

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
(後略)

すいません、NASTYなことしたのは、私です。
ローカルの .ssh/known_hostsを開いて、IPアドレスで接続先を特定し、その行を削除すればOKです。

Workspacesには、subnetが3つ必要

Public 1つと、Private 2つが必要でした。
NAT Instanceのドキュメントを見て Subnet 2つで進めてたら、Workspaces立てるところでドボンしました。
ちなみに、Workspacesのドキュメントには明記されていましたので、計画性のある向きはハマらないと思う。

DockerホストもIP転送を有効にする必要あり

これも単に再起動したから発生した問題ですが、ちょっとハマった。
EC2上でDockerコンテナを立てていたインスタンスで、IP転送が無効になっていたため、コンテナから通信できなくなっていました。

NAT instanceにはVPC内のローカルIPで入ってくる

当初iptablesでこんな風に書いていたのですが、ポート転送されずに???となりました。

-A PREROUTING  -i eth0 -d 118.123.123.123 -p tcp -m tcp --dport 80   -j DNAT --to 192.168.32.101

tcpdumpで見てみて、NAT instanceに入ってきた時点で、VPC内のローカルIPになっていることが判りました。
結果として、前記の設定でポート転送に成功しました。

感想

AWSが良きに計らってくれるところと、自分でケアしなければいけないところの境界線がハマりポイントでした。

続きを読む

EBS学習メモ

この文書について

AWS EBSに関する学習メモ

https://aws.amazon.com/jp/ebs/details/

EBS

簡単なまとめ

  • SSD(io1,gp2)とHDD(st1,sc2) に大別される
  • 読み書きの瞬発力(IOPS)がほしいなら io1
  • そこそこで良いなら gp2
  • 安さと最大スループットを求めるなら st1sc1
  • io1でないEBSを使っていて、IOPSで問題が発生したら BurstBalance を確認
    • Burstしていなければ、EBS以外に問題がある
    • Burstしていれば、EBSをio1に変え、EC2もEBS最適化インスタンスにする

性能比較

ボリューム当たりの最大IOPS

io1(2万) > gp2(1万) >>> st1(500) > sc1(250)

ボリューム当たり最大スループット

st1(500MB/s) > io1(320MB/s) > sc1(250MB/s) > gp2(160MB/s)

I/O サイズとボリュームのスループット制限

  • EBSにはスループット制限がある
  • 小さいデータのI/O操作が原因で、スループット制限よりも低いデータ量でも制限が発動することがある
    • 特にSSDの場合、単一I/O とみなされるデータ量が小さい
  • スループット制限を超えてもバーストバケットを使ってカバーされる
    • バーストバケットがなくなると本当に制限される

      • CloudWatchなどでBurstBalanceを見ると状況がわかる
  • EC2 インスタンスの帯域幅が制限要因になることがある
    • 対策はEBS最適化インスタンスの利用

IPOS

  • IOPS = 1 秒あたりの入出力操作数を表す測定単位
  • 操作は KiB 単位で測定される
  • 単一I/O とみなされるデータ量はEBSがSSD/HDDによって異なる
    • 最大I/Oサイズ

      • SSD: 256 KiB
      • HDD: 1,024 KiB
    • SSDでは1つのIOとみなさるサイズが小さい
      = I/O やランダム I/O の処理が HDDより効率が良い

EBS最適化EC2インスタンス

http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/EBSOptimized.html

  • io1 は EBS最適化EC2との併用が推奨
  • EBS最適化EC2インスタンスの特徴
    • Amazon EC2 と Amazon EBS の間の専用スループットがある

      • Amazon EBS I/O と EC2 インスタンスからの他のトラフィックとの競合を最低限に抑える
      • 専用スループットは500Mbps~10,000 Mbps
        • インスタンスタイプに応じて変化
  • EBS最適化インスタンスが使えるEC2 (2017年10月現在)
    • c系 (c1, c3, c4)
    • d系 (d2)
    • f系 (f1)
    • g系 (g2, g3)
    • i系 (i2, i3)
    • m系 (m1, m2, m3, m4)
    • p系 (p2)
    • r系 (r3, r4)
    • x系 (x1, x1e)

続きを読む

Webアプリのスマホテストを自動化する

Webブラウザアプリケーションのスマホテストを外部サービスを使い自動化します。
AWS Device FarmRemote TestKit等のサービスが対象になりますが、今回はAWS Device Farmを利用します。
なお、CIツールはプラグインが存在するJenkinsを用います。

必要なもの

Jenkinsのインストール

terraformを用いてEC2上に、Jenkinsをセットアップします。
スクリプトでは、ユーザデータを用いて、Jenkinsのセットアップを実施しています。

参考レポジトリ

bash
$ git clone https://github.com/Thirosue/devicefarm-sample.git
$ cd devicefarm-sample/provisioning/
$ terraform apply -var-file=~/.aws/terraform.tfvars -var 'key_name=[keypair]'
ec2_jenkins.tf
variable "key_name" {}

provider "aws" {
  region = "ap-northeast-1"
}

data "aws_ami" "amazon_linux" {
  most_recent = true
  owners = ["amazon"]

  filter {
    name   = "architecture"
    values = ["x86_64"]
  }

  filter {
    name   = "root-device-type"
    values = ["ebs"]
  }

  filter {
    name   = "name"
    values = ["amzn-ami-hvm-*"]
  }

  filter {
    name   = "virtualization-type"
    values = ["hvm"]
  }

  filter {
    name   = "block-device-mapping.volume-type"
    values = ["gp2"]
  }
}

resource "aws_instance" "jenkins" {
  ami           = "${data.aws_ami.amazon_linux.id}"
  instance_type = "t2.micro"
  key_name      = "${var.key_name}"
  user_data = <<EOF
IyEvYmluL2Jhc2gKCndnZXQgLU8gL2V0Yy95dW0ucmVwb3MuZC9qZW5raW5zLnJl
cG8gaHR0cDovL3BrZy5qZW5raW5zLWNpLm9yZy9yZWRoYXQvamVua2lucy5yZXBv
CnJwbSAtLWltcG9ydCBodHRwOi8vcGtnLmplbmtpbnMtY2kub3JnL3JlZGhhdC9q
ZW5raW5zLWNpLm9yZy5rZXkKCnl1bSBpbnN0YWxsIC15IGdpdCBqZW5raW5zIGph
dmEtMS44LjAtb3BlbmpkawphbHRlcm5hdGl2ZXMgLS1zZXQgamF2YSAvdXNyL2xp
Yi9qdm0vanJlLTEuOC4wLW9wZW5qZGsueDg2XzY0L2Jpbi9qYXZhCgpjaGtjb25m
aWcgamVua2lucyBvbgovZXRjL2luaXQuZC9qZW5raW5zIHN0YXJ0CgpleGl0IDA=
EOF
}
userdata.sh
#!/bin/bash

wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
rpm --import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key

yum install -y git jenkins java-1.8.0-openjdk
alternatives --set java /usr/lib/jvm/jre-1.8.0-openjdk.x86_64/bin/java

chkconfig jenkins on
/etc/init.d/jenkins start

exit 0

Jenkinsの管理コンソールセットアップ

EC2のPublicIPを確認し、以下にアクセス

http://[IPv4 Public IP]:8080

アクセス後、Jenkinsがロックされているので、指示通り/var/lib/jenkins/secrets/initialAdminPasswordを確認し入力します。

Unlock Jenkins

Device Farm Plugin インストール

Jenkins-プラグインマネージャよりaws-device-farm-pluginをインストール。

DeviceFarmPlugin

DeviceFarm AccessKey/SecretKey設定

Jenkinsの管理画面に用意したIAMユーザのAccessKey/SecretKeyを設定

DeviceFarmIAMSetting

テストコード作成

以下を参考にテストコードを作成する。
参考レポジトリ はAppium+JUnitをgradleでbuildしている。

SampleTest.java
import com.codeborne.selenide.Configuration;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.Platform;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.remote.DesiredCapabilities;
import org.openqa.selenium.remote.RemoteWebDriver;

import java.io.File;
import java.net.URL;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class SampleTest {

    private RemoteWebDriver driver;

    @Before
    public void setUp() throws Exception {
        DesiredCapabilities capabilities = new DesiredCapabilities();
        capabilities.setPlatform(Platform.IOS);
        capabilities.setBrowserName("safari");
        driver = new RemoteWebDriver(new URL("http://127.0.0.1:4723/wd/hub"),
                capabilities);
        driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
    }

    @After
    public void tearDown() throws Exception {
        driver.quit();
    }

    public boolean takeScreenshot(final String name) {
        String screenshotDirectory = System.getProperty("appium.screenshots.dir", System.getProperty("java.io.tmpdir", ""));
        System.out.println(screenshotDirectory);
        File screenshot = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        return screenshot.renameTo(new File(screenshotDirectory, String.format("%s.png", name)));
    }

    @Test
    public void runTest() throws Exception {
        driver.get("https://www.google.co.jp/");
        Thread.sleep(1000);

        System.out.println(driver.getTitle());
        System.out.println(driver.getPageSource());

        driver.findElement(By.id("lst-ib")).sendKeys("AWS DeviceFarm");
        driver.findElement(By.id("tsbb")).click();
        assertTrue(takeScreenshot("index"));

        assertEquals("AWS DeviceFarm", driver.findElement(By.id("lst-ib")).getAttribute("value"));
        assertTrue(takeScreenshot("result"));
    }
}

build設定

参考レポジトリ はカスタムタスクのinstallZipでDeviceFarmへのuploadファイルを生成する。

スクリーンショット 2017-09-14 時刻 20.28.51.png

task installZip(dependsOn: ["clean", "packageTests", "installDist"]) << {
    new File("build/work").mkdir()
    new File('build/zip.sh') << 'cd build/work; zip -r zip-with-dependencies.zip .'
    copy{
        from "build/install/test/lib/"
        into "build/work/dependency-jars/"
    }
    copy{
        from "build/libs/test-1.0-SNAPSHOT-tests.jar"
        into "build/work/"
    }
    "chmod 755 build/zip.sh".execute().waitFor()
    "build/zip.sh".execute().waitFor()
    copy{
        from "build/work/zip-with-dependencies.zip"
        into "build/"
    }
}

DeviceFarmテスト設定

作成済みのDeviceFarm Project及びDeviceFarm Device Poolを選択して、buildで固めたzipファイルを指定する。

スクリーンショット 2017-09-14 時刻 20.34.19.png

テスト実行

Jenkinsでテスト実行後、DeviceFarmマネジメントコンソールへのレポートリンク等がJenkinsのテスト結果画面に表示されます。

スクリーンショット 2017-09-14 時刻 20.39.01.png

その他(開発中のテストについて)

54.244.50.32~54.244.50.63. の IP範囲をホワイトリストに登録 すれば、開発中資源もテストできそうです。

終わりに

利用事例が少なそうですが、かなり使えそうなサービスなので、今後積極的に利用していきたいと思います。

続きを読む

AWS CLI から ELB ~ Auto Scaling グループの作成・設定を行う

AWS CLI から ELB ~ Auto Scaling グループの作成を行ってみた時のメモ

ELB の作成

以下のコマンドを実行する
(aws cli の region が ap-northeast-1 に設定された状態で試しました)

ELBの作成

始めにELBの作成を行う
ヘルスチェックなどいくつかの設定は別のコマンドで設定しなければいけないよう

aws elb create-load-balancer \
        --load-balancer-name sample-elb \
        --listeners 'Protocol=HTTP,LoadBalancerPort=80,InstanceProtocol=HTTP,InstancePort=80' \
        --subnets 'subnet-123456' 'subnet-456789' \
        --security-groups 'sg-123456' \
        --scheme internal

今回は内部向けロードバランサー
http://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/classic/elb-internal-load-balancers.html
を作成しようと思うので
--scheme internal
をつけているが、内部向けでなければこのオプションを外す

クロスゾーン負荷分散・接続のストリーミングの有効化

今回はクロスゾーン負荷分散と接続のストリーミングを有効化したいので以下のコマンドを追加で実行する

aws elb modify-load-balancer-attributes \
  --load-balancer-name sample-elb \
  --load-balancer-attributes '{
    "CrossZoneLoadBalancing":{
        "Enabled":true
    },
    "ConnectionDraining":{
        "Enabled":true,
        "Timeout":300
    }
  }'

ヘルスチェック設定を追加

更にヘルスチェックの設定を行うために以下のコマンドも実行する

aws elb configure-health-check \
  --load-balancer-name sample-elb \
  --health-check Target=HTTP:80/target/ping/path,Interval=30,UnhealthyThreshold=2,HealthyThreshold=2,Timeout=3

上記で設定は完了

値の確認

正しく作成・設定されたか確認する
(値は適当な値に書き換えてます)

 aws elb describe-load-balancers --load-balancer-name sample-elb
{
    "LoadBalancerDescriptions": [
        {
            "Subnets": [
                "subnet-123456",
                "subnet-456789"
            ],
            "CanonicalHostedZoneNameID": "CanonicalHostedZoneNameID",
            "VPCId": "vpc-123456",
            "ListenerDescriptions": [
                {
                    "Listener": {
                        "InstancePort": 80,
                        "LoadBalancerPort": 80,
                        "Protocol": "HTTP",
                        "InstanceProtocol": "HTTP"
                    },
                    "PolicyNames": []
                }
            ],
            "HealthCheck": {
                "HealthyThreshold": 2,
                "Interval": 30,
                "Target": "HTTP:80/target/ping/path",
                "Timeout": 3,
                "UnhealthyThreshold": 2
            },
            "BackendServerDescriptions": [],
            "Instances": [],
            "DNSName": "internal-sample-elb-123456.ap-northeast-1.elb.amazonaws.com",
            "SecurityGroups": [
                "sg-123456"
            ],
            "Policies": {
                "LBCookieStickinessPolicies": [],
                "AppCookieStickinessPolicies": [],
                "OtherPolicies": []
            },
            "LoadBalancerName": "sample-elb",
            "CreatedTime": "2017-08-27T04:54:54.290Z",
            "AvailabilityZones": [
                "ap-northeast-1b",
                "ap-northeast-1c"
            ],
            "Scheme": "internal",
            "SourceSecurityGroup": {
                "OwnerAlias": "123456",
                "GroupName": "default"
            }
        }
    ]
}

起動設定 ~ Auto Scaling グループを作成

起動設定を作成

以下のコマンドで起動設定を作成

試した時の状況としてオプションとして行ってることとして

  • 詳細モニタリングをOFFにしてる
  • インスタンスプロファイルを設定してる
  • ブロックデバイスを手動で設定してる

あたりが入ってる

aws autoscaling create-launch-configuration \
  --launch-configuration-name launch-configuration-for-sample-elb \
  --image-id ami-123456 \
  --instance-type m4.large \
  --security-groups sg-123456 \
  --instance-monitoring Enabled=false \
  --iam-instance-profile sample-instance-profile \
  --key-name keyname \
  --block-device-mappings '[
    {
        "DeviceName": "/dev/sdx",
        "Ebs": {
          "VolumeSize": 100,
          "VolumeType": "gp2",
          "DeleteOnTermination": true
        }
      },
      {
        "DeviceName": "/dev/xvda",
        "Ebs": {
          "VolumeSize": 8,
          "VolumeType": "gp2",
          "DeleteOnTermination": true
        }
      }
    ]'

Auto Scaling グループを作成

上記で作成した ELB と 起動設定を利用して Auto Scaling グループを作成

注意点として一旦台数固定の Scaling Policy は無しで作成してる

aws autoscaling create-auto-scaling-group \
    --auto-scaling-group-name auto-scaling-group-for-sample-elb \
    --launch-configuration-name launch-configuration-for-sample-elb \
    --min-size 2 \
    --max-size 2 \
    --desired-capacity 2 \
    --load-balancer-names sample-elb \
    --health-check-type EC2 \
    --health-check-grace-period 300 \
    --availability-zones ap-northeast-1b ap-northeast-1c \
    --vpc-zone-identifier subnet-123456,subnet-456789 \
    --tags '[
      {
        "PropagateAtLaunch": true,
        "Key": "Name",
        "Value": "sample-elb-instance"
      }
    ]'

Auto Scaling グループの通知設定を作成

先程作成した Auto Scaling グループに通知設定を追加

aws autoscaling put-notification-configuration \
  --auto-scaling-group-name auto-scaling-group-for-sample-elb \
  --topic-arn arn:aws:sns:ap-northeast-1:XXXXXX:XXXXXX \
  --notification-type autoscaling:EC2_INSTANCE_LAUNCH autoscaling:EC2_INSTANCE_LAUNCH_ERROR autoscaling:EC2_INSTANCE_TERMINATE autoscaling:EC2_INSTANCE_TERMINATE_ERROR

値の確認

正しく作成・設定されたか確認する
(値は適当な値に書き換えてます)

$ aws autoscaling describe-launch-configurations --launch-configuration-names launch-configuration-for-sample-elb
{
    "LaunchConfigurations": [
        {
            "UserData": "",
            "IamInstanceProfile": "intent-recommend-search",
            "EbsOptimized": false,
            "LaunchConfigurationARN": "arn:aws:autoscaling:ap-northeast-1:XXXXXX:launchConfiguration:XXXXXX:launchConfigurationName/launch-configuration-for-sample-elb",
            "InstanceMonitoring": {
                "Enabled": false
            },
            "ClassicLinkVPCSecurityGroups": [],
            "CreatedTime": "2017-08-27T06:31:16.558Z",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/xvda",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "VolumeSize": 8,
                        "VolumeType": "gp2"
                    }
                },
                {
                    "DeviceName": "/dev/sdx",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "VolumeSize": 100,
                        "VolumeType": "gp2"
                    }
                }
            ],
            "KeyName": "keyname",
            "SecurityGroups": [
                "sg-123456"
            ],
            "LaunchConfigurationName": "launch-configuration-for-sample-elb",
            "KernelId": "",
            "RamdiskId": "",
            "ImageId": "ami-123456",
            "InstanceType": "m4.large"
        }
    ]
}


$ aws autoscaling describe-auto-scaling-groups --auto-scaling-group-name auto-scaling-group-for-sample-elb
{
    "AutoScalingGroups": [
        {
            "AutoScalingGroupARN": "arn:aws:autoscaling:ap-northeast-1:XXXXXXX:autoScalingGroup:XXXXXX:autoScalingGroupName/auto-scaling-group-for-sample-elb",
            "TargetGroupARNs": [],
            "SuspendedProcesses": [],
            "DesiredCapacity": 2,
            "Tags": [
                {
                    "ResourceType": "auto-scaling-group",
                    "ResourceId": "auto-scaling-group-for-sample-elb",
                    "PropagateAtLaunch": true,
                    "Value": "sample-elb-instance",
                    "Key": "Name"
                }
            ],
            "EnabledMetrics": [],
            "LoadBalancerNames": [
                "intent-recommend-search-ja-JP"
            ],
            "AutoScalingGroupName": "auto-scaling-group-for-sample-elb",
            "DefaultCooldown": 300,
            "MinSize": 2,
            "Instances": [
                ...
            ],
            "MaxSize": 2,
            "VPCZoneIdentifier": "subnet-123456,subnet-456789",
            "HealthCheckGracePeriod": 300,
            "TerminationPolicies": [
                "Default"
            ],
            "LaunchConfigurationName": "launch-configuration-for-sample-elb",
            "CreatedTime": "2017-08-27T06:31:16.558Z",
            "AvailabilityZones": [
                "ap-northeast-1b",
                "ap-northeast-1c"
            ],
            "HealthCheckType": "EC2",
            "NewInstancesProtectedFromScaleIn": false
        }
    ]
}


$ autoscaling describe-notification-configurations --auto-scaling-group-name auto-scaling-group-for-sample-elb
{
    "NotificationConfigurations": [
        {
            "AutoScalingGroupName": "auto-scaling-group-for-sample-elb",
            "NotificationType": "autoscaling:EC2_INSTANCE_LAUNCH",
            "TopicARN": "arn:aws:sns:ap-northeast-1:XXXXXX:XXXXXX"
        },
        {
            "AutoScalingGroupName": "auto-scaling-group-for-sample-elb",
            "NotificationType": "autoscaling:EC2_INSTANCE_LAUNCH_ERROR",
            "TopicARN": "arn:aws:sns:ap-northeast-1:XXXXXX:XXXXXX"
        },
        {
            "AutoScalingGroupName": "auto-scaling-group-for-sample-elb",
            "NotificationType": "autoscaling:EC2_INSTANCE_TERMINATE",
            "TopicARN": "arn:aws:sns:ap-northeast-1:XXXXXX:XXXXXX"
        },
        {
            "AutoScalingGroupName": "auto-scaling-group-for-sample-elb",
            "NotificationType": "autoscaling:EC2_INSTANCE_TERMINATE_ERROR",
            "TopicARN": "arn:aws:sns:ap-northeast-1:XXXXXX:XXXXXX"
        }
    ]
}

参考:

続きを読む

Amazon WorkMail – WEBインフラに不要な穴を開けないために

概要

Amazon EC2 でインターネット向けにWEBサーバーを運用している場合、よくある仕様追加「このドメイン名でメール送受信したい」に対応。
Amazon EC2 からのメール送受信は公式では非推奨 1
「安いは正義」に「WEBインフラにメールサービスを上乗せするなんて」で立ち向かう方への処方箋。

前提

  • ドメイン名(例えば example.tld)は既に取得済み、利用可能状態と仮定します。
  • 実現までのプロセスはすべて AWS リソースを使います。
  • ディレクトリーサービスの既存利用なしと仮定します。(お金なくて検証できてない)

AWS リソース

AWS 製品

既存のディレクトリーサービスが存在せず Amazon WorkMail をミニマムスタート、かつ、AWS サービス内で完結する場合には以下が必要になります。
1. Amazon VPC
2. AWS Directory Service
3. AWS Identity and Access Management (IAM)
4. Amazon WorkMail
5. Amazon Route53

AWS リージョン

Amazon WorkMail は、2017年8月現在、以下のリージョンで提供されています。

Code Name
us-east-1 US East (N.Virginia)
us-west-2 US West (Oregon)
eu-west-1 EU (Ireland)

Amazon WorkMail を利用するリージョンで Amazon VPC ならび AWS Directory Service を利用開始する必要があります。

費用計算

請求金額見積もり

  1. Amazon VPC
    今回のケースでは費用発生しません。
  2. AWS Directory Service
    Amazon WorkMail にて Simple AD を利用する場合に限り無料となります。2
  3. AWS Identity and Access Management (IAM)
    今回のケースでは費用発生しません。
  4. Amazon WorkMail
    1ユーザーあたり1ヶ月につき 4 USD。
  5. Amazon Route53
    1ホストゾーン(ドメイン名)につき 0.50 USD/月
    100 万クエリにつき 0.400 USD

損益分岐点

Amazon WorkMail と Amazon EC2 (t2.medium) を12ヶ月運用で比較してみます。(US East (N.Virginia))

Products type price
Amazon EC2 t2.medium のリザーブドインスタンス(1年全額前払) 約 22.91 USD/月
Amazon EBS gp2 50GB 5 USD/月
Amazon WorkMail 6ユーザー 24 USD/月
Amazon WorkMail 7ユーザー 28 UDS/月

6ユーザーより大きなユーザー数で運用は Amazon EC2 リザーブドインスタンスの方が費用的に安価になりますが、これには冗長性等、考慮されていません。
耐障害性、可用性から考えても、この要件が発生した際には Amazon WorkMail を選択がベストプラクティスと言えます。

オペレーション

ドメイン名 example.tld によるメール送受信を実現するために以下を行います。

  1. Amazon VPC
    • Simple AD 稼働のための Subnet を用意
  2. AWS Directory Service
    • Simple AD によるディレクトリー作成
  3. AWS Identity and Access Management (IAM)
    • 保管メールデータ暗号化に使用するキー管理のために AWS Key Management Service (KMS) を設定
  4. Amazon WorkMail
  5. Amazon Route53
    • ドメイン名 example.tld のDNSレコード管理

Amazon VPC

デフォルトで用意されている VPC と Subnet でも開始可能ですが、Simple AD で利用する Subnet は public subnet である必要はありません。
private subnet (Route Table に Destination: 0.0.0.0/0 が存在しない、またはそれ以外)で可能です。
以下成果物です。要望あればキャプチャー添付。TBC。

Name IPv4 CIDR Availability Zone
simplead-a 10.0.0.0/28 us-east-1b
simplead-b 10.0.0.16/28 us-east-1e

AWS Directory Service

AWS Management Console より AWS Directory Service を選択し、Simple AD をセットアップします。
年々 Simple AD と AD Connector の取り扱いがざつにn……

セットアップ情報は例えば下記です。要件に合わせて読み替えます。

Directory details

Name Value remarks
Directory type Simple AD 固定値
Directory DNS ad.example.tld
NetBIOS name オプション・未記入
Default administrative user Administrator 固定値
Administrator password ********
Description オプション・未記入
Directory size Small

VPC Details

Name Value
VPC 10.0.0.0/16
Subnets simplead-a, 10.0.0.0/28, us-east-1b
simplead-a, 10.0.0.16/28, us-east-1e

Access URL

セットアップ完了後、Status: Creating から Active になれば利用可能です。
この時、Access URL を定義します。
これは Amazon WorkMail の Web メールのURLで利用します。
例えば example.awsapps.com です。

AWS Identity and Access Management (IAM)

保存されるメール暗号化のため、暗号キーを指定します。
ここではデフォルトの aws/workmail を使います。

Amazon WorkMail

上記までに事前準備が完了しているはずですので、ここでは Standard setup でセットアップします。

Organizations

セットアップ情報は例えば下記です。要件に合わせて読み替えます。

Name Value remarks
Available Directories ad.example.tld セットアップした Simple AD
Master keys aws/workmail セットアップした KMS

Create 押下後、Status が Creating から Active に変われば利用可能です。

Domains

セットアップした Organization のドメイン名を定義します。
Organization のナビゲーション Domains を押下してドメイン名を定義します。
ここで先に設定した Access URL(example.awsapps.com)も確認できます。

Amazon SES と同様、ドメイン名の確認(Verify domain)が必要になります。
次項の Amazon Route53 の Hosted Zone (example.tld)に表示される下記のDNSレコードを追加します。

Recode type Hostname Value remarks
TXT _amazonses.example.tld. (指定された文字列) Domain verification で利用します
MX example.tld. 10 inbound-smtp.us-east-1.amazonaws.com. 3
CNAME autodiscover.example.tld. 3
CNAME (指定された文字列)._domainkey.example.tld. (指定された文字列) DKIM セットアップ4

Users

同じく Organization のナビゲーション Users よりユーザー定義を行います。
ユーザー名、パスワード、メールアドレスを入力してセットアップします。
特記する箇所はないため、ここでは割愛します。

Amazon Route53

Amazon WorkMail のセットアップ、Domains で得たDNSレコード情報を Hosted Zone example.tld に登録します。
割愛します。

上記にて username@example.tld によるメール送受信が可能です。

クライアント

デスクトップ、モバイル

Microsoft Exchange ActiveSync プロトコルをサポートするので、以下のデスクトップ、モバイルのクライアントに対応。
– Mac Mail.app
– iPhone
– Outlook
その他要検証。TBC。

ウェブアプリケーション

AWS Management Console, Amazon WorkMail のナビゲーション Organization settings で用意されている Web Application https://(文字列).awsapps.com/mail よりログイン可能。5

その他留意事項

申請関連

サービス上限

以下、気をつけるべき箇所の抜粋です。

  • 1ユーザーのメールボックスは 50GB
  • 送信・受信ともに1メールは 25MB
  • 1 日のユーザーあたりのメッセージ送信数 宛先に関係なく、1,000 件のメッセージ
  • ユーザーのエイリアス(アドレス)は 100(ハードリミット)

Amazon SES

メール送信は Amazon SES 経由で送信されますが、WorkMail からの送信は課金対象となりません。
また API 経由では必要な送信制限解除申請(サンドボックスから取り外す)も必要ありません。

未記入事項

以下の機能については記載してません。to be continued.

  • 移行
  • ジャーナリング
  • フロールール
  • グループ、リソース
  • 既存AD連携

出典

いつの日も AWS ドキュメントと FAQ は最強説。

続きを読む

SpotFleet(diversified/lowest)で起動するインスタンスを常にELBにアタッチする

目的

  • SpotFleetで起動しているインスタンスを常に同一のELBにアタッチする
  • 生存戦略はdiversified/lowestで機能する

前提

本構成ではインスタンス1台の運用を想定しているのでインスタンスが停止後、他プールからインスタンスが起動するまで〜2分かかるので、可用性を保つためにはオンデマンドを入れるなり、工夫が必要

方法

スポットインスタンスリクエスト時にユーザーデータを利用する

 イメージ

Untitled (5).png

設定

  1. ELBに対する権限を持つIAMRoleを作成
  2. IAMRoleを使用するスクリプト作成
  3. EC2Resourcesのaws_spot_fleet_requestの引数にuser_dataを記述
spot_fleet_request.tf
# Request a Spot fleet
resource "aws_spot_fleet_request" "aws-simple-console-spotfleet" {
  iam_fleet_role                      = "arn:aws:iam::xxxxxx:role/xxxxxx"
  spot_price                          = "0.0127"
  allocation_strategy                 = "diversified"
  target_capacity                     = 2
  terminate_instances_with_expiration = false

  launch_specification {
    instance_type               = "m3.medium"
    ami                         = "ami-xxxxxx"
    availability_zone           = "ap-northeast-1c"
    subnet_id                   = "subnet-xxxxxx"
    iam_instance_profile        = "xxxxxx"
    key_name                    = "xxxxxx"
    vpc_security_group_ids      = ["sg-xxxxxx"]
    monitoring                  = false
    associate_public_ip_address = true
    user_data                   = "${file("userdata.sh")}"

    root_block_device {
      volume_size = "10"
      volume_type = "gp2"
    }
  }
}
userdata.sh
#!/bin/bash
export INSTANCEID=`curl http://169.254.169.254/latest/meta-data/instance-id`
aws elb register-instances-with-load-balancer --load-balancer-name xxxxxx --instances $INSTANCEID --region ap-northeast-1

続きを読む

terraformでASGを作る時の注意点

概要

起動設定を更新した際に相関関係にあるASGで起動しているインスタンスが作り直されてしまうので、これを回避する。

方法

EC2Resourcesaws_launch_configurationを使用時、引数にnameではなく name_prefixを使用する。

launch_configuration.tf
resource "aws_launch_configuration" xxxxxxxx-autoscaling-conf" {
  name_prefix                 = "xxxxxxxx-autoscaling"
  image_id                    = "ami-xxxxx"
  instance_type               = "t2.medium"
  iam_instance_profile        = "xxxxxxxx"
  key_name                    = "xxxxxxxx"
  security_groups             = ["sg-xxxxxxxx"]
  associate_public_ip_address = true
  user_data                   = "${file("xxxxxxxx.sh")}"
  root_block_device  {
    volume_type           = "gp2"
    volume_size           = "50"
    delete_on_termination = "true"
  }
  lifecycle  {
    create_before_destroy = true
  }
}
  • IDのみがユニークなものに置換され、ASGは影響を受けない
aws_launch_configuration.xxxxxxxx-autoscaling-autoscaling-conf:
  id = xxxxxxxx-autoscalingxxxxxxxxxxxxxxxx

続きを読む

[JAWS-UG CLI] Amazon Kinesis Firehose re:入門 (1) 事前準備(Source(Kinesis Agent)およびDestination(S3)の作成)

この記事について

JAWS-UG CLI専門支部 #90 Kinesis Firehose 復習編で実施するハンズオン用の手順書です。

前提条件

必要な権限

作業にあたっては、以下の権限を有したIAMユーザもしくはIAMロールを利用してください。

  • 以下のサービスに対するフルコントロール権限

    • Kinesis Firehose
    • IAM
    • EC2
    • S3
    • CloudWatch Logs
    • STS
    • (Lambda)
      • データの変換を行う場合
    • (KMS)
      • データの暗号化を行う場合

0. 準備

0.1. リージョンを指定

オレゴンリージョンで実施します。(東京マダー?)

コマンド
export AWS_DEFAULT_REGION="us-west-2"

0.2. 資格情報を確認

コマンド
aws configure list

インスタンスプロファイルを設定したEC2インスタンスでアクセスキーを設定せずに実行した場合、以下のようになります。

結果
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   profile                <not set>             None    None
access_key     ****************QSAA         iam-role
secret_key     ****************c1xY         iam-role
    region                us-west-2              env    AWS_DEFAULT_REGION

0.3. バージョン確認

コマンド
aws --version
結果
aws-cli/1.11.129 Python/2.7.12 Linux/4.9.38-16.33.amzn1.x86_64 botocore/1.5.92

0.4. バージョンアップ(必要に応じて)

コマンド
sudo pip install -U awscli

1. 管理対象の構築

CloudFormationを利用して、Source(Kinesis AgentをインストールしたEC2インスタンス)とDestination(S3バケット)を作成します。

1.1. KeyPairの作成

EC2インスタンス用にKeyPairを作成します。

KeyPairの名前を指定

コマンド
AWS_ID=$(aws sts get-caller-identity 
    --query "Account" 
    --output text) 
    && echo ${AWS_ID}
コマンド
KEY_PAIR_NAME="${AWS_ID}_firehose_jawsug_cli"
KEY_MATERIAL_FILE_NAME=${KEY_PAIR_NAME}.pem

同名KeyPairの不存在を確認

コマンド
aws ec2 describe-key-pairs 
    --query "KeyPairs[?KeyName==`${KEY_PAIR_NAME}`]"
結果
[]

KeyPairの作成

コマンド
aws ec2 create-key-pair 
    --key-name ${KEY_PAIR_NAME} 
    --query "KeyMaterial" 
    --output text 
    > ~/.ssh/${KEY_MATERIAL_FILE_NAME} 
    && cat ~/.ssh/${KEY_MATERIAL_FILE_NAME}

KeyPairの存在を確認

コマンド
aws ec2 describe-key-pairs 
    --query "KeyPairs[?KeyName==`${KEY_PAIR_NAME}`]"
結果
[
    {
        "KeyName": "XXXXXXXXXXXX_firehose_jawsug_cli",
        "KeyFingerprint": "xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx:xx"
    }
]

秘密鍵のPermissionを変更

コマンド
chmod 600 ~/.ssh/${KEY_MATERIAL_FILE_NAME}
ls -al ~/.ssh/${KEY_MATERIAL_FILE_NAME}
結果
-rw------- 1 ec2-user ec2-user 1671 Aug  5 18:33 /home/ec2-user/.ssh/788063364413_firehose_jawsug_cli.pem

1.2. CloudFormation テンプレートの生成

テンプレートの作成

コマンド
CF_TEMPLATE_FILE_NAME="firehose_jawsug_cli.yml"
コマンド
cat << EOF > ${CF_TEMPLATE_FILE_NAME}
AWSTemplateFormatVersion: "2010-09-09"
Description: JAWS-UG CLI Kinesis Firehose Hands-on

Parameters: 
  VPCNetworkAddress: 
    Type: String
    Description: "Network Address on AWS"
    MinLength: 9
    MaxLength: 18
    # AllowedPattern: "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})"
    Default: "10.0.0.0/16"
  PublicSubnetAddr: 
    Type: String
    Description: "Network Address on AWS"
    MinLength: 9
    MaxLength: 18
    # AllowedPattern: "(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})/(\d{1,2})"
    Default: "10.0.0.0/24"
  KeyPairName:
    Type: AWS::EC2::KeyPair::KeyName


Resources:
  S3Bucket: 
    Type: "AWS::S3::Bucket"
  IAMRole: 
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: "service-role-firehose"
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "firehose.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
  IAMPolicy:
    Type: "AWS::IAM::Policy"
    Properties: 
      PolicyName: "service-policy-firehose"
      PolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Action: 
              - "s3:AbortMultipartUpload"
              - "s3:GetBucketLocation"
              - "s3:GetObject"
              - "s3:ListBucket"
              - "s3:ListBucketMultipartUploads"
              - "s3:PutObject"
            Resource:
              - !GetAtt S3Bucket.Arn
              - Fn::Join: 
                - "/"
                - 
                  - !GetAtt S3Bucket.Arn
                  - "*"
          - 
            Effect: "Allow"
            Action: 
              - "logs:PutLogEvents"
            Resource: "*"
      Roles:
        - Ref: IAMRole

  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VPCNetworkAddress
      Tags: 
        - 
          Key: "Name"
          Value: "KinesisFirehoseClient"
  IGW:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags: 
        - 
          Key: "Name"
          Value: "KinesisFirehoseClient"
  AttachIGW:
      Type: AWS::EC2::VPCGatewayAttachment
      Properties:
          VpcId:
              Ref: VPC
          InternetGatewayId:
              Ref: IGW
  PublicSubnet:
    Type: AWS::EC2::Subnet
    Properties: 
      AvailabilityZone: 
        Fn::Select: 
          - 0
          - Fn::GetAZs: ""
      CidrBlock: !Ref PublicSubnetAddr
      MapPublicIpOnLaunch: true
      VpcId: 
        Ref: VPC
      Tags: 
        - 
          Key: "Name"
          Value: "Public"
  PublicRT:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: VPC
      Tags: 
        - 
          Key: Name
          Value: Public
  PublicDefaultRoute:
    Type: AWS::EC2::Route
    Properties: 
      DestinationCidrBlock: "0.0.0.0/0"
      GatewayId: 
        Ref: IGW
      RouteTableId: 
        Ref: PublicRT
  PublicSubnetARouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      SubnetId:
        Ref: PublicSubnet
      RouteTableId:
        Ref: PublicRT
  SecurityGroup:
    Type: "AWS::EC2::SecurityGroup"
    Properties: 
      GroupDescription: WebServer
      SecurityGroupEgress:
        - 
          IpProtocol: "-1"
          CidrIp: "0.0.0.0/0"
      SecurityGroupIngress:
        - 
          IpProtocol: "tcp"
          FromPort: 80
          ToPort: 80
          CidrIp: "0.0.0.0/0"        
        - 
          IpProtocol: "tcp"
          FromPort: 22
          ToPort: 22
          CidrIp: "0.0.0.0/0"
      VpcId:
        Ref: VPC    
  InstanceRole: 
    Type: "AWS::IAM::Role"
    Properties: 
      RoleName: "instance-role"
      AssumeRolePolicyDocument: 
        Version: "2012-10-17"
        Statement: 
          - 
            Effect: "Allow"
            Principal: 
              Service: 
                - "ec2.amazonaws.com"
                - "ssm.amazonaws.com"
            Action: 
              - "sts:AssumeRole"
      Path: "/"
      ManagedPolicyArns: 
        - "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM"
        - "arn:aws:iam::aws:policy/AmazonKinesisFirehoseFullAccess"
  InstanceProfile: 
    Type: "AWS::IAM::InstanceProfile"
    Properties: 
      Path: "/"
      Roles: 
        - !Ref InstanceRole
  Instance:
    Type: "AWS::EC2::Instance"
    Properties:
      KeyName: 
        Ref: KeyPairName
      ImageId: ami-6df1e514
      InstanceType: t2.micro
      SecurityGroupIds: 
        - Ref: SecurityGroup
      SubnetId:
        Ref: PublicSubnet
      IamInstanceProfile:
        Ref: InstanceProfile
      BlockDeviceMappings: 
        - DeviceName: "/dev/sdm"
          Ebs: 
            VolumeType: "gp2"
            DeleteOnTermination: "true"
            VolumeSize: "8"
      UserData:
        Fn::Base64: |
          #!/bin/bash
          cd /tmp
          sudo yum install -y https://s3.amazonaws.com/ec2-downloads-windows/SSMAgent/latest/linux_amd64/amazon-ssm-agent.rpm
          sudo yum install -y aws-kinesis-agent
          sudo chkconfig aws-kinesis-agent on
          sudo service aws-kinesis-agent start
          sudo yum install -y httpd
          sudo chkconfig httpd on
          sudo service httpd start
          sudo yum install npm --enablerepo=epel -y
          sudo npm install -g jsonlint

Outputs:
  S3BucketName:
    Value:
      Ref: S3Bucket
  IAMRoleARN:
    Value: !GetAtt IAMRole.Arn
  PublicIP:
    Value: !GetAtt Instance.PublicIp
EOF

cat ${CF_TEMPLATE_FILE_NAME}

CloudFormation テンプレートの検証

コマンド
aws cloudformation validate-template 
    --template-body file://${CF_TEMPLATE_FILE_NAME}
結果
{
    "CapabilitiesReason": "The following resource(s) require capabilities: [AWS::IAM::InstanceProfile, AWS::IAM::Role]",
    "Description": "JAWS-UG CLI Kinesis Firehose Hands-on",
    "Parameters": [
        {
            "NoEcho": false,
            "ParameterKey": "KeyPairName"
        },
        {
            "DefaultValue": "10.0.0.0/16",
            "NoEcho": false,
            "Description": "Network Address on AWS",
            "ParameterKey": "VPCNetworkAddress"
        },
        {
            "DefaultValue": "10.0.0.0/24",
            "NoEcho": false,
            "Description": "Network Address on AWS",
            "ParameterKey": "PublicSubnetAddr"
        }
    ],
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ]
}

1.3. CloudFormation Stackの作成

CloudFormation Stack名の指定

コマンド
CF_STACK_NAME="firehose-jawsug-cli"

同名CloudFormation Stackの不存在を確認

コマンド
aws cloudformation describe-stacks 
    --query "Stacks[?StackName==`${CF_STACK_NAME}`]"
結果
[]

CloudFormation Stackの作成

コマンド
aws cloudformation create-stack 
    --stack-name ${CF_STACK_NAME} 
    --template-body file://${CF_TEMPLATE_FILE_NAME} 
    --capabilities "CAPABILITY_NAMED_IAM" 
    --parameters ParameterKey=KeyPairName,ParameterValue=${KEY_PAIR_NAME},UsePreviousValue=false
結果
{
    "StackId": "arn:aws:cloudformation:us-west-2:XXXXXXXXXXXX:stack/firehose-jawsug-cli/8812e540-7a0e-11e7-aac3-50a68d01a68d"
}

CloudFormation Stackの作成完了を待機

5分程度で作成が完了すると思います。

コマンド
aws cloudformation wait stack-create-complete 
    --stack-name ${CF_STACK_NAME}
結果
(返値無し)

CloudFormation Stackの存在を確認

“StackStatus”が”CREATE_COMPLETE”になっていることを確認します。

コマンド
aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME}
結果
{
    "Stacks": [
        {
            "StackId": "arn:aws:cloudformation:us-west-2:XXXXXXXXXXXX:stack/firehose-jawsug-cli/4043bde0-7a16-11e7-8701-50a686be73ba",
            "Description": "JAWS-UG CLI Kinesis Firehose Hands-on",
            "Parameters": [
                {
                    "ParameterValue": "XXXXXXXXXXXX_firehose_jawsug_cli",
                    "ParameterKey": "KeyPairName"
                },
                {
                    "ParameterValue": "10.0.0.0/16",
                    "ParameterKey": "VPCNetworkAddress"
                },
                {
                    "ParameterValue": "10.0.0.0/24",
                    "ParameterKey": "PublicSubnetAddr"
                }
            ],
            "Tags": [],
            "Outputs": [
                {
                    "OutputKey": "PublicIP",
                    "OutputValue": "54.191.102.113"
                },
                {
                    "OutputKey": "IAMRoleARN",
                    "OutputValue": "arn:aws:iam::XXXXXXXXXXXX:role/service-role-firehose"
                },
                {
                    "OutputKey": "S3BucketName",
                    "OutputValue": "firehose-jawsug-cli-s3bucket-134czh3hcofqz"
                }
            ],
            "CreationTime": "2017-08-05T19:42:44.440Z",
            "Capabilities": [
                "CAPABILITY_NAMED_IAM"
            ],
            "StackName": "firehose-jawsug-cli",
            "NotificationARNs": [],
            "StackStatus": "CREATE_COMPLETE",
            "DisableRollback": false
        }
    ]
}

1.4. パラメータの確認

以降の手順で必要になるパラメータを抽出します。

  • IAMロールARN
  • S3バケット名
  • パブリックIPアドレス
コマンド
OUTPUTKEY_ROLE_ARN="IAMRoleARN"
OUTPUTKEY_BUCKET_NAME="S3BucketName"
OUTPUTKEY_PUBLIC_IP_ADDRESS="PublicIP"
コマンド
ROLE_ARN=$(aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME} 
    --query "Stacks[].Outputs[?OutputKey==`${OUTPUTKEY_ROLE_ARN}`].OutputValue[]" 
    --output text) 
    && echo ${ROLE_ARN}
結果
arn:aws:iam::XXXXXXXXXXXX:role/service-role-firehose
コマンド
BUCKET_NAME=$(aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME} 
    --query "Stacks[].Outputs[?OutputKey==`${OUTPUTKEY_BUCKET_NAME}`].OutputValue[]" 
    --output text) 
    && echo ${BUCKET_NAME}
結果
firehose-jawsug-cli-s3bucket-134czh3hcofqz
コマンド
PUBLIC_IP_ADDRESS=$(aws cloudformation describe-stacks 
    --stack-name ${CF_STACK_NAME} 
    --query "Stacks[].Outputs[?OutputKey==`${OUTPUTKEY_PUBLIC_IP_ADDRESS}`].OutputValue[]" 
    --output text) 
    && echo ${PUBLIC_IP_ADDRESS}
結果
54.191.***.***

動作確認

パブリックIPアドレスにブラウザでアクセスします。

以上

続きを読む

AutoScaleを設定し、lambdaでAMIとAutoScaleの設定を自動更新する

今回はAuto Scaleの設定のまとめです。

・概要

負荷分散のためにAutoScaleを検討することはよくあることです。

ゲームのイベントなどでは負荷がかかる時間帯が決まっているので、常時起動しているサーバーを減らしAutoScaleのスケージュールで設定することで費用を抑えることが出来ます。

※AutoScaleは起動に時間がかかるので、スケーリングポリシーでは突発的な負荷に対応できないのでスケジュールを使っています。

・問題点

AutoScaleを設定した場合、設定しているAMIのassetやsourceが起動中のインスタンスと違うといった事が起こります。

そういった場合にAMIを更新し、自動的AutoScaleに割り当てる設定を行います。

・AutoScaleグループの作成

今回の設定は元々あるAutoScaleグループの設定を置き換えるので先にAutoScaleグループを作成しておきます。

・AWSコンソールの[AUTO SCALING]→[起動設定]→[起動の作成]

※ここの起動設定は最終的に使わないので適当でOKです

・AWSコンソールの[AUTO SCALING]→[AUTO SCALINGグループ]→[AUTO SCALINGグループの作成]

上記の起動設定を選択、下記を設定

ネットワーク
グループ名
ロードバランシング

※今回はスケジュールなのでスケーリングポリシーは設定しない
※通知を使用するとauto scaleの情報をlambda等に引き渡して色々出来ます。
※タグを使いauto scaleで作成されたインスタンスに適用できます。

※ここで作成したAUTO SCALINGグループの名前を控えておきます。

lambdaを設定

・[Blank Function]→リストから[CloudWatch Events]を選択
・ルール:新規のルール
・ルール名:任意
・ルール説明:任意
・ルールタイプ:スケジュール
・スケジュール式:cron(0 * * * ? *)
※cronの設定と同じ、上記は1時間毎
・トリガーの有効化にチェック

・名前:任意
・説明:任意
・ランタイム:python

・ロール:[カスタムロールの作成]を選択、IAMに下記をアタッチし作成、再度[既存のロールを選択]で作成したIAMを選択

・コード:下記を貼り付けて***の部分を自分の環境に置き換える

import boto3
import time
from botocore.client import ClientError
from datetime import datetime, timedelta, tzinfo
import logging
logger = logging.getLogger()
logger.setLevel(logging.INFO)

ec2 = boto3.client('ec2')
autoscaling = boto3.client('autoscaling')

instance = "***" #コピー元のec2インスタンスID(例:i-22a9edc2ted27d2a1)
device_name = "***" #コピー元のec2のブロックデバイス(例:/dev/sda1)
image_prefix = "***" #amiの名前の識別子(任意※他のAMIで使っていない文字)
launch_prefix = "***" #auto scaleの起動設定の名前の識別子(任意※他の起動設定で使っていない文字)
ec2_volume_size = *** #ebsのボリュームサイズ(単位:GB)
ec2_volume_type = "***" #ebsのボリュームタイプ(例:gp2)
secrity_group = [***] #ec2インスタンスのセキュリティグループ
ec2_instance_type = "***", #ec2インスタンスタイプ(例:c4.2xlarge)
ec2_key_name = "***", #ec2インスタンスのキーペア名
auto_scaling_group_name ="***",#作成したAutoScaleGroup名


def lambda_handler(event, context):
    try:
        dstr = datetime.now().strftime('_%Y-%m-%d-%H-%M-%S_') + instance
        logger.warning("create ami:%s" % (dstr))
        new_ami = ec2.create_image(Name=image_prefix + dstr ,InstanceId=instance,NoReboot=True,DryRun=False)
        recv = ec2.describe_images(Owners=['self'])
        list=[]
        target = image_prefix
        for img in recv['Images']:
            if target in img['ImageLocation']:
                list.append(img)
        s_list = sorted(list,key=lambda h: h['CreationDate'])
        if len(s_list) > 0:
            del_target = s_list[0]
            logger.info("delete ami:%s" % (del_target['Name']))
            ec2.deregister_image(ImageId=del_target['ImageId'],DryRun= False)
        dstr = datetime.now().strftime('_%Y-%m-%d-%H-%M-%S')
        logger.info("create auto scale launch config:%s" % (dstr))
        device = {}
        device['DeviceName'] = device_name
        ebs = {}
        ebs['VolumeSize'] = ec2_volume_size
        ebs['VolumeType'] = ec2_volume_type
        ebs['DeleteOnTermination'] = True
        device['Ebs'] = ebs
        device_mapping = [device]
        launch_name = launch_prefix + dstr

        res = autoscaling.create_launch_configuration(
            LaunchConfigurationName = launch_name,
            ImageId = new_ami['ImageId'],
            InstanceType = ec2_instance_type,
            SecurityGroups = secrity_group, 
            KeyName = ec2_key_name,
            BlockDeviceMappings = device_mapping,
            AssociatePublicIpAddress = True
        );
        logger.info("update auto scale group name:%s" % (launch_name))
        res = autoscaling.update_auto_scaling_group(
            AutoScalingGroupName=auto_scaling_group_name 
            LaunchConfigurationName = launch_name
        );  
        target = launch_prefix
        recv = autoscaling.describe_launch_configurations()
        list=[]
        for launch in recv['LaunchConfigurations']:
            if target in launch['LaunchConfigurationName']:
                list.append(launch)
        s_list = sorted(list,key=lambda h: h['CreatedTime'])
        if len(s_list) > 2:
            del_target = s_list[0]
            logger.info("delete launch config:%s" % (del_target['LaunchConfigurationName']))
            autoscaling.delete_launch_configuration(
                LaunchConfigurationName=del_target['LaunchConfigurationName'],
            )
        logger.info("end ami update")

    except ClientError as e:
        logger.error( e )
    return 'end'

・auto scaleのスケジュールを登録する

負荷の時間に合わせて自由に

・注意事項

※最初はelbのhealthチェックだけじゃなくwebサーバーのログやawsコンソールで正常な起動を確認すること

※デプロイがAMIの更新と被らないようにすること

続きを読む