RTX1200をAWS VPCとVPN接続する

AWSのVPCとVPNで接続するためには、Direct Connect接続するか、IPSec接続する方法の2通りがある。
Direct Connectはお手軽にVPNを試すことが出来ないので、IPSec接続を試してみることにした。
フレッツ光からYAMAHA RTX1200を使いAmazon VPCにハードウェアVPN接続する手順
の通りに実施すると後述する問題に突き当たったが、解決することで接続することができた。
紹介サイトでは、RTX1200でのPPPoE接続の方式がとられていたが、PPPoEはONUのルータにて実施しているため、設定しなかった。
また、ONUルータに割り当てされるグローバルIPアドレスはプロバイダより割り当てされる可変アドレスであり、そのアドレスをカスタマゲートウエイのアドレスに指定した。

なお、設定作業において、IPSecのトンネルが確立できなかった点が3つあったので、共有しておきたい。
1.サイト間VPN確立時にASNが重複していて確立に失敗する
→カスタマーゲートウエイ、仮想プライベートゲートウエイの2つにASNを設定するが、同一のASNを誤って設定していた。紹介サイトと同様に、 カスタマーゲートウエイのASNには64512、
仮想プライベートゲートウエイ のASNには10124を設定することで設定に成功した。
2.500/udpポートがフィルタリングされていて接続に失敗する
→ip filter (フィルタ番号) pass * (ルータの内側のIPアドレス) udp * 500
にてフィルタを許可する
3.1分間隔で接続が切れる。
VPN接続後1分でVPNが切断されますが、切断されないようにする方法は?
同一機種の対応方法ではなかったが、事象が同一だったので、試してみたところ、切断されなくなった。
AWSよりサイト間のVPN接続を作成する際にダウンロードできる設定ファイルに記述されている
ipsec ike keepalive use (ipsecトンネル番号) on dpd 10 3

ipsec ike keepalive use (ipsecトンネル番号) off
に変更することで接続できる。

以前PPTP方式によるVPN接続を試したが、この方法であれば、安定してたVPNを確立することが出来るようになる。ただ、サイト間VPNは、1時間あたり約0.05USD(月額約4,000円)が必要となるため、少しでも安くVPNを構築したいという用途であれば、PPTP方式が良いかと思う。

S3へのアクセスを制限する

 S3のバケットに対して、特定のIPアドレスからのみ通信を受け付けるようにするためには、バケットポリシーに定義すればよい。
EC2インスタンスからのS3のアクセスについては、エンドポイントがない場合には、インターネットに一度パケットが出て行ってからS3にアクセスすることになってしまうため、IPアドレスでの制限が出来なくなってしまう。
 解決方法としては、エンドポイントを設置して、通信をエンドポイント経由にすることでエンドポイントを使った制限とすることができる。
 エンドポイントを介することによって、インターネットを経由しない通信にできるので、パケットがインターネットに出て行ってはいけないというセキュリティポリシーに準拠でき、EC2→S3への通信料課金が発生しなくなる。
 下記バケットポリシーは、NNN.NNN.NNN.NNNとvpce-XXXXXXXXXXXXXからのみ通信を受け付ける定義になる。また、Principalには、*を定義しているが、厳密に権限定義が必要な場合には、AWS JSON ポリシーの要素:Principalを参考にして、記述する必要がある。

 テストしている時には、 エンドポイントID(vpce-xxxxxx)ではなく、間違ってVPCID(vpc-xxxxxx)を指定してしまっていてハマってしまった。指定するIDがvpceから始まることを確認するとよい。

{
    "Version": "2012-10-17",
    "Id": "PolicyXXXXXXXXXXXX",
    "Statement": [
        {
            "Sid": "XXXXXXXXXXXXX",
            "Effect": "Deny",
            "Principal": {
                "AWS": "*"
            },
            "Action": "s3:*",
            "Resource": "arn:aws:s3:::(バケット名)",
            "Condition": {
                "NotIpAddress": {
                    "aws:SourceIp": "NNN.NNN.NNN.NNN"
                },
                "StringNotEquals": {
                    "aws:SourceVpce": "vpce-XXXXXXXXXXXXX"
                }
            }
        }
    ]

Amazon Linux 2にAppiumを導入する

AppeiumはAndoroidやiOSのアプリケーションのシステムテストを行うためのテストツールとして紹介しているサイトもあるが、Windowsアプリケーションのテストにも利用できる。
Linuxbrewに従って、下記を実行。

sh -c "$(curl -fsSL https://raw.githubusercontent.com/Linuxbrew/install/master/install.sh)"
test -d ~/.linuxbrew && eval $(~/.linuxbrew/bin/brew shellenv)
test -d /home/linuxbrew/.linuxbrew && eval $(/home/linuxbrew/.linuxbrew/bin/brew shellenv)
test -r ~/.bash_profile && echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.bash_profile
echo "eval \$($(brew --prefix)/bin/brew shellenv)" >>~/.profile
sudo yum install jre
yum install **/stdint.h
brew install npm
npm install --global appium appium-doctor wd
appium &

Windows Application DriverでWindowsアプリケーションのテストを自動化しよう を参照してWindows Applicationのシステムテストを行う

IAMユーザで請求画面が表示されない

チュートリアル: 請求コンソールへのアクセス権の委任 に従って登録しても請求画面を表示しようとするとエラーメッセージが表示されてしまって悩んだ件について

なお、上記チュートリアルでは、
[Visual editor (ビジュアルエディタ)] タブで、まず [Choose a service (サービスの選択)] を選択します。次に、[請求] を選択します。
と記載があるが、請求を選択することができない。代わりにBillingを選択しなければならない。単純に翻訳されてしまっているためだろうか。。。。ちなみに、サービスから検索ボックスで「請求」を入力するとBilling Managerが選択できる。(履歴タブにも「請求」が表示されるようになる)

【表示されるエラーメッセージパターン1】
You Need Permissions
You don’t have permission to access billing information for this account. Contact your AWS administrator if you need help. If you are an AWS administrator, you can provide permissions for your users or groups by making sure that (1) this account allows IAM and federated users to access billing information and (2) you have the required IAM permissions.

【表示されるエラーメッセージパターン2】
アクセス権限が必要です。
このアカウントの請求情報にアクセスするためのアクセス権限がありません。サポートが必要な場合は、AWS 管理者に連絡してください。AWS 管理者は、(1) このアカウントが IAM およびフェデレーティッドユーザーに対して請求情報へのアクセスを許可できること]、および (2) 必要な IAM アクセス権限を持っていること]を確認して、ユーザーまたはグループにアクセス権限を付与できます。

原因は、IAM ユーザー/ロールによる請求情報へのアクセスは無効になっているためである。下記手順にてIAMユーザによるアクセスを有効化する必要がある。

1.マスターアカウントにて、右上のアカウント名が表示されているところをクリックして「アカウント」を選択する。

2.IAM ユーザー/ロールによる請求情報へのアクセス の編集をクリックする

3.IAM アクセスのアクティブ化 のチェックを入れて更新ボタンを押下する

“IAMユーザで請求画面が表示されない” の続きを読む

支払方法のクレジットカードにJCBを設定すると日本円で支払いができない

AWSで課金された利用料を
クレジットカードで 支払する方法は2通りある。
1.USドルで請求を受けて、クレジットカード会社から円換算されて請求を受ける方法
2.日本円で請求を受ける方法

いずれも課金自体はUSドルなので、どこかでレート換算されることになるのだが、1の方法ではクレジットカード会社での手数料が発生するので(AWSからの請求された日が月中の換算レートよりも著しく円高にならない限りは)不利になることが多い。
2の方法を選択することによって、1日あたり表示される換算レートによって課金されていくことになるので、為替レートは日ごとで適用されることとなるので、こちらを選択することがベターである。しかしながら、登録したクレジットカードがJCBである場合には、

お客様のデフォルトのお支払方法は、現地支払通貨では使用できません。設定した支払通貨を使用するには、デフォルトのお支払方法として Visa または MasterCard を選択してください。

と表示されてしまい選択できない。
AWSでのクレジットカード登録の際にはカードブランドにも気を付けておきたい。

EC2のダイナミックDNS運用

EC2で割り当てできるパブリックIPアドレスには、固定のパブリックIPアドレス(Elastic IP)とAWS Public IPの2種類がある。Elastic IPは割り当てしたインスタンスが稼働している間は課金は発生しないが、インスタンスに割り当てしない場合や、インスタンスが停止している間は1時間あたり0.005USDの課金が発生してしまう。1月あたり1EIPで約300円程度(0.005×(16時間×22日+24時間×8日)×110円)になる。一方でAWS Public IPを利用した場合には、再起動するたびにIPアドレスが変わってしまう問題がある。

DDNSを運用できるようになると、Elastic IPが必要なくなり、EC2インスタンス、RDSインスタンスを指定時間に起動及び停止する にてインスタンスのコストを最小化しようとする場合に効果を発揮する。

Building a Dynamic DNS for Route 53 using CloudWatch Events and Lambda を参考に実施してみた。最小限のコストで利用したい意図からLambda関数の内容は引用元と異なっている。詳細は後述を参照してください。

1.IAMポリシー「ddns-lambda-policy」を追加

{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Action": "ec2:Describe*",
    "Resource": "*"
  }, {
    "Effect": "Allow",
    "Action": [
      "dynamodb:*"
    ],
    "Resource": "*"
  }, {
    "Effect": "Allow",
    "Action": [
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents"
    ],
    "Resource": "*"
  }, {
    "Effect": "Allow",
    "Action": [
      "route53:*"
    ],
    "Resource": [
      "*"
    ]
  }]
}

2.IAMロールでこのロールを使用するサービスを選択欄でLambdaを選択し、1で作成したポリシーを選択し、「ddns-lambda-role」の名前を付けて作成する。

3.Lamda関数を作成する。名前は「ddns_lambda」、ランタイムは「Python 2.7」、ロールには2で作成した
「ddns-lambda-role」 を選択し、下記関数を登録する。

import json
import boto3
import re
import uuid
import time
import random
from datetime import datetime

print('Loading function ' + datetime.now().time().isoformat())
route53 = boto3.client('route53')
ec2 = boto3.resource('ec2')
compute = boto3.client('ec2')
dynamodb_client = boto3.client('dynamodb')
dynamodb_resource = boto3.resource('dynamodb')

def lambda_handler(event, context):
    """ Check to see whether a DynamoDB table already exists.  If not, create it.  This table is used to keep a record of
    instances that have been created along with their attributes.  This is necessary because when you terminate an instance
    its attributes are no longer available, so they have to be fetched from the table."""
    tables = dynamodb_client.list_tables()
    if 'DDNS' in tables['TableNames']:
        print 'DynamoDB table already exists'
    else:
        create_table('DDNS')

    # Set variables
    # Get the state from the Event stream
    state = event['detail']['state']

    # Get the instance id, region, and tag collection
    instance_id = event['detail']['instance-id']
    region = event['region']
    table = dynamodb_resource.Table('DDNS')

    if state == 'running':
        instance = compute.describe_instances(InstanceIds=[instance_id])
        # Remove response metadata from the response
        instance.pop('ResponseMetadata')
        # Remove null values from the response.  You cannot save a dict/JSON document in DynamoDB if it contains null
        # values
        instance = remove_empty_from_dict(instance)
        instance_dump = json.dumps(instance,default=json_serial)
        instance_attributes = json.loads(instance_dump)
        table.put_item(
            Item={
                'InstanceId': instance_id,
                'InstanceAttributes': instance_attributes
            }
        )
    else:
        # Fetch item from DynamoDB
        instance = table.get_item(
        Key={
            'InstanceId': instance_id
        },
        AttributesToGet=[
            'InstanceAttributes'
            ]
        )
        instance = instance['Item']['InstanceAttributes']

    try:
        tags = instance['Reservations'][0]['Instances'][0]['Tags']
    except:
        tags = []
    # Get instance attributes
    private_ip = instance['Reservations'][0]['Instances'][0]['PrivateIpAddress']
    private_dns_name = instance['Reservations'][0]['Instances'][0]['PrivateDnsName']
    private_host_name = private_dns_name.split('.')[0]
    try:
        public_ip = instance['Reservations'][0]['Instances'][0]['PublicIpAddress']
        public_dns_name = instance['Reservations'][0]['Instances'][0]['PublicDnsName']
        public_host_name = public_dns_name.split('.')[0]
    except BaseException as e:
        print 'Instance has no public IP or host name', e

    # Get the subnet mask of the instance
    subnet_id = instance['Reservations'][0]['Instances'][0]['SubnetId']
    subnet = ec2.Subnet(subnet_id)
    cidr_block = subnet.cidr_block
    subnet_mask = int(cidr_block.split('/')[-1])

    reversed_ip_address = reverse_list(private_ip)
    reversed_domain_prefix = get_reversed_domain_prefix(subnet_mask, private_ip)
    reversed_domain_prefix = reverse_list(reversed_domain_prefix)

    # Set the reverse lookup zone
    reversed_lookup_zone = reversed_domain_prefix + 'in-addr.arpa.'
    print 'The reverse lookup zone for this instance is:', reversed_lookup_zone

    # Get VPC id
    vpc_id = instance['Reservations'][0]['Instances'][0]['VpcId']
    vpc = ec2.Vpc(vpc_id)

    # Are DNS Hostnames and DNS Support enabled?
    if is_dns_hostnames_enabled(vpc):
        print 'DNS hostnames enabled for %s' % vpc_id
    else:
        print 'DNS hostnames disabled for %s.  You have to enable DNS hostnames to use Route 53 private hosted zones.' % vpc_id
    if is_dns_support_enabled(vpc):
        print 'DNS support enabled for %s' % vpc_id
    else:
        print 'DNS support disabled for %s.  You have to enabled DNS support to use Route 53 private hosted zones.' % vpc_id

    # Create the public and private hosted zone collections.  These are collections of zones in Route 53.
    hosted_zones = route53.list_hosted_zones()
    public_hosted_zones = filter(lambda x: x['Config']['PrivateZone'] is False, hosted_zones['HostedZones'])
    public_hosted_zones_collection = map(lambda x: x['Name'], public_hosted_zones)
    # Wait a random amount of time.  This is a poor-mans back-off if a lot of instances are launched all at once.
    time.sleep(random.random())

    # Loop through the instance's tags, looking for the zone and cname tags.  If either of these tags exist, check
    # to make sure that the name is valid.  If it is and if there's a matching zone in DNS, create A and PTR records.
    for tag in tags:
        if 'ZONE' in tag.get('Key',{}).lstrip().upper():
            if is_valid_hostname(tag.get('Value')):
                if tag.get('Value').lstrip().lower() in public_hosted_zones_collection:
                    print 'Public zone found', tag.get('Value')
                    public_hosted_zone_name = tag.get('Value').lstrip().lower()
                    public_hosted_zone_id = get_zone_id(public_hosted_zone_name)
                    # create A record in public zone
                    if state =='running':
                        try:
                            create_resource_record(public_hosted_zone_id, public_host_name, public_hosted_zone_name, 'A', public_ip)
                        except BaseException as e:
                            print e
                    else:
                        try:
                            delete_resource_record(public_hosted_zone_id, public_host_name, public_hosted_zone_name, 'A', public_ip)
                        except BaseException as e:
                            print e
                else:
                    print 'No matching zone found for %s' % tag.get('Value')
            else:
                print '%s is not a valid host name' % tag.get('Value')
        # Consider making this an elif CNAME
        else:
            print 'The tag \'%s\' is not a zone tag' % tag.get('Key')
        if 'CNAME' in tag.get('Key',{}).lstrip().upper():
            if is_valid_hostname(tag.get('Value')):
                cname = tag.get('Value').lstrip().lower()
                cname_host_name = cname.split('.')[0]
                cname_domain_suffix = cname[cname.find('.')+1:]
                cname_domain_suffix_id = get_zone_id(cname_domain_suffix)
                for cname_public_hosted_zone in public_hosted_zones_collection:
                    if cname.endswith(cname_public_hosted_zone):
                        cname_public_hosted_zone_id = get_zone_id(cname_public_hosted_zone)
                        #create CNAME record in public zone
                        if state == 'running':
                            try:
                                create_resource_record(cname_public_hosted_zone_id, cname_host_name, cname_public_hosted_zone, 'CNAME', public_dns_name)
                            except BaseException as e:
                                print e
                        else:
                            try:
                                delete_resource_record(cname_public_hosted_zone_id, cname_host_name, cname_public_hosted_zone, 'CNAME', public_dns_name)
                            except BaseException as e:
                                print e

def create_table(table_name):
    dynamodb_client.create_table(
            TableName=table_name,
            AttributeDefinitions=[
                {
                    'AttributeName': 'InstanceId',
                    'AttributeType': 'S'
                },
            ],
            KeySchema=[
                {
                    'AttributeName': 'InstanceId',
                    'KeyType': 'HASH'
                },
            ],
            ProvisionedThroughput={
                'ReadCapacityUnits': 4,
                'WriteCapacityUnits': 4
            }
        )
    table = dynamodb_resource.Table(table_name)
    table.wait_until_exists()

def create_resource_record(zone_id, host_name, hosted_zone_name, type, value):
    """This function creates resource records in the hosted zone passed by the calling function."""
    print 'Updating %s record %s in zone %s ' % (type, host_name, hosted_zone_name)
    if host_name[-1] != '.':
        host_name = host_name + '.'
    route53.change_resource_record_sets(
                HostedZoneId=zone_id,
                ChangeBatch={
                    "Comment": "Updated by Lambda DDNS",
                    "Changes": [
                        {
                            "Action": "UPSERT",
                            "ResourceRecordSet": {
                                "Name": host_name + hosted_zone_name,
                                "Type": type,
                                "TTL": 60,
                                "ResourceRecords": [
                                    {
                                        "Value": value
                                    },
                                ]
                            }
                        },
                    ]
                }
            )

def delete_resource_record(zone_id, host_name, hosted_zone_name, type, value):
    """This function deletes resource records from the hosted zone passed by the calling function."""
    print 'Deleting %s record %s in zone %s' % (type, host_name, hosted_zone_name)
    if host_name[-1] != '.':
        host_name = host_name + '.' 
    route53.change_resource_record_sets(
                HostedZoneId=zone_id,
                ChangeBatch={
                    "Comment": "Updated by Lambda DDNS",
                    "Changes": [
                        {
                            "Action": "DELETE",
                            "ResourceRecordSet": {
                                "Name": host_name + hosted_zone_name,
                                "Type": type,
                                "TTL": 60,
                                "ResourceRecords": [
                                    {
                                        "Value": value
                                    },
                                ]
                            }
                        },
                    ]
                }
            )
def get_zone_id(zone_name):
    """This function returns the zone id for the zone name that's passed into the function."""
    if zone_name[-1] != '.':
        zone_name = zone_name + '.'
    hosted_zones = route53.list_hosted_zones()
    x = filter(lambda record: record['Name'] == zone_name, hosted_zones['HostedZones'])
    try:
        zone_id_long = x[0]['Id']
        zone_id = str.split(str(zone_id_long),'/')[2]
        return zone_id
    except:
        return None

def is_valid_hostname(hostname):
    """This function checks to see whether the hostname entered into the zone and cname tags is a valid hostname."""
    if hostname is None or len(hostname) > 255:
        return False
    if hostname[-1] == ".":
        hostname = hostname[:-1]
    allowed = re.compile("(?!-)[A-Z\d-]{1,63}(?<!-)$", re.IGNORECASE)
    return all(allowed.match(x) for x in hostname.split("."))

def reverse_list(list):
    """Reverses the order of the instance's IP address and helps construct the reverse lookup zone name."""
    if (re.search('\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}',list)) or (re.search('\d{1,3}.\d{1,3}.\d{1,3}\.',list)) or (re.search('\d{1,3}.\d{1,3}\.',list)) or (re.search('\d{1,3}\.',list)):
        list = str.split(str(list),'.')
        list = filter(None, list)
        list.reverse()
        reversed_list = ''
        for item in list:
            reversed_list = reversed_list + item + '.'
        return reversed_list
    else:
        print 'Not a valid ip'
        exit()

def get_reversed_domain_prefix(subnet_mask, private_ip):
    """Uses the mask to get the zone prefix for the reverse lookup zone"""
    if 32 >= subnet_mask >= 24:
        third_octet = re.search('\d{1,3}.\d{1,3}.\d{1,3}.',private_ip)
        return third_octet.group(0)
    elif 24 > subnet_mask >= 16:
        second_octet = re.search('\d{1,3}.\d{1,3}.', private_ip)
        return second_octet.group(0)
    else:
        first_octet = re.search('\d{1,3}.', private_ip)
        return first_octet.group(0)

def json_serial(obj):
    """JSON serializer for objects not serializable by default json code"""
    if isinstance(obj, datetime):
        serial = obj.isoformat()
        return serial
    raise TypeError ("Type not serializable")

def remove_empty_from_dict(d):
    """Removes empty keys from dictionary"""
    if type(d) is dict:
        return dict((k, remove_empty_from_dict(v)) for k, v in d.iteritems() if v and remove_empty_from_dict(v))
    elif type(d) is list:
        return [remove_empty_from_dict(v) for v in d if v and remove_empty_from_dict(v)]
    else:
        return d

def is_dns_hostnames_enabled(vpc):
    dns_hostnames_enabled = vpc.describe_attribute(
    DryRun=False,
    Attribute='enableDnsHostnames'
)
    return dns_hostnames_enabled['EnableDnsHostnames']['Value']

def is_dns_support_enabled(vpc):
    dns_support_enabled = vpc.describe_attribute(
    DryRun=False,
    Attribute='enableDnsSupport'
)
    return dns_support_enabled['EnableDnsSupport']['Value']

タイムアウト時間を1分30秒にして、CloudWatchEventsは
イベントパターン
サービス名:ec2
イベントタイプ:EC2 Instance State-change Notification
特定の状態:running, shutting-down, stopped
として、「ec2_lambda_ddns_rule」の名前で登録すればよい。

Lambda関数の実行にそれなりに時間がかかることから、タイムアウト時間は30秒程度でよいとなっているが、ソースコードがアップロードされていたGitHubでは1分30分で登録されていたので、1分30秒とした。

4.route53でDDNSで登録したいドメイン名を登録する。なお、route53ではゾーン情報を1つ登録するごとに月額0.50USDがかかり、無料枠の対象にはならない。

5.DDNSを運用したいEC2インスタンスのタグにCNAMEを登録し、値としてFQDNを登録する。最後に”.”が入っているかを確認してほしい。
例)blog.development-network.net.

参考元の手順で実施した場合、Privateのゾーンに対して登録される仕組みになっていることと、逆引きが登録される仕様になっていたので、Privateのゾーン登録が不要な場合には上記でよいこととなる。

インスタンス間通信をRoute53で名前解決させる場合には、プライベート接続にしないとトラフィックがインターネットを経由してしまうので課金されることから、オリジナルのソースコードを採用し、Route53にはprivateとpublicの両方のゾーンを登録したほうが良いと思われる。

EC2インスタンス、RDSインスタンスを指定時間に起動及び停止する

下記を参考にして、CloudWatch+Lambdaの組み合わせを用いて、タグ指定でインスタンスの開始、停止を実装してみた。
できるだけシンプルな仕組みで簡単にEC2の自動起動・停止を実現したい!
AWS Lambda+Python3で複数のRDSを起動停止

開発環境など業務時間内のみインスタンスを稼働させ、コストを最小化させたいときに役に立つ。

EC2インスタンスの場合
Lambda関数に設定する実行ロール

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeInstances",
                "ec2:StartInstances",
                "ec2:StopInstances"
            ],
            "Resource": "*"
        }
    ]
}

Lambda関数(ec2.describe_instances()の戻りデータがそれなりに大きいので、タイムアウトをデフォルトの3秒より少し長めにしておかないとタイムアウトしてしまう)

import boto3

"""
  Request 4Parameters
    Region
    Action(start or stop)
    TagName
    TagValue
"""

def lambda_handler(event, context):
    region = event['Region']
    action = event['Action']
    tagname = event['TagName']
    tagvalue = event['TagValue']
    ec2 = boto3.client('ec2', region_name=region)
    try:
        reservations = ec2.describe_instances()
    except Exception as e:
        print(e)
        return { 'status': -1 }

    if action == 'start':
        for instances in reservations["Reservations"]:
            for instance in instances['Instances']:
                tags = instance['Tags']
                tag = next(iter(filter(lambda tag: tag['Key'] == tagname and tag['Value'] == tagvalue, tags)), None)
                if tag:
                    if instance['State']['Name'] == 'stopped':
                        response = ec2.start_instances(InstanceIds=[instance["InstanceId"]])
                        print(instance['InstanceId'] + " is starting!")
                    else:
                        print(instance['InstanceId'] + " is already started!")
    elif action == 'stop':
        for instances in reservations["Reservations"]:
            for instance in instances['Instances']:
                tags = instance['Tags']
                tag = next(iter(filter(lambda tag: tag['Key'] == tagname and tag['Value'] == tagvalue, tags)), None)
                if tag:
                    if instance['State']['Name'] == 'running':
                        response = ec2.stop_instances(InstanceIds=[instance["InstanceId"]])
                        print(instance['InstanceId'] + " is stopping!")
                    else:
                        print(instance['InstanceId'] + " is already stopped!")
    return { 'status': 0 }

CloudWatchEventで設定するターゲットの入力値(定数 (JSON テキスト))

{
  "Action": "start",
  "Region": "ap-northeast-1",
  "TagName": "AutoStart",
  "TagValue": "true"
}

上記を設定すると、東京リージョン(ap-northeast-1)にあるAutoStart=Trueとタグ設定されたEC2インスタンスが起動するようになります。Actionをstopとすると停止になります。

RDSインスタンスの場合
Lambda関数に設定する実行ロール

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:CreateLogGroup",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "rds:DescribeDBInstances",
                "rds:StopDBInstance",
                "rds:StartDBInstance",
                "rds:ListTagsForResource"
            ],
            "Resource": "*"
        }
    ]
}

Lambda関数

import boto3

"""
  Request 4Parameters
    Region
    Action(start or stop)
    TagName
    TagValue
"""

def lambda_handler(event, context):
    region = event['Region']
    action = event['Action']
    tagname = event['TagName']
    tagvalue = event['TagValue']
    rds = boto3.client('rds', region_name=region)
    try:
        dbs = rds.describe_db_instances()
    except Exception as e:
        print(e)
        return { 'status': -1 }

    if action == 'start':
        for db in dbs['DBInstances']:
            instance_arn = db['DBInstanceArn']
            instance_tags = rds.list_tags_for_resource(ResourceName=instance_arn)
            tags = instance_tags['TagList']
            tag = next(iter(filter(lambda tag: tag['Key'] == tagname and tag['Value'] == tagvalue, tags)), None)
            if tag:
                if db['DBInstanceStatus'] == 'stopped':
                    response = rds.start_db_instance(DBInstanceIdentifier=db["DBInstanceIdentifier"])
                    print(db['DBInstanceIdentifier'] + " is starting!")
                else:
                    print(db['DBInstanceIdentifier'] + " is already started!")
    elif action == 'stop':
        for db in dbs['DBInstances']:
            instance_arn = db['DBInstanceArn']
            instance_tags = rds.list_tags_for_resource(ResourceName=instance_arn)
            tags = instance_tags['TagList']
            tag = next(iter(filter(lambda tag: tag['Key'] == tagname and tag['Value'] == tagvalue, tags)), None)
            if tag:
                if db['DBInstanceStatus'] == 'available':
                    response = rds.stop_db_instance(DBInstanceIdentifier=db["DBInstanceIdentifier"])
                    print(db['DBInstanceIdentifier'] + " is stopping!")
                else:
                    print(db['DBInstanceIdentifier'] + " is already stopped!")
    return { 'status': 0 }

参考)EC2とRDSのAPIに差異がある点に注意が必要。
例:EC2のstart_instancesは、リストで渡せる(Type: Array of strings)一方で、RDSのstart-db-instanceは、単一のIDしか渡せない(Type: String/リストで渡せない)。

re:Invent 2018 Second Day

明日はセミナー本番

受講予定のセミナーについて少し調べてみる。

ANT322-R – [REPEAT] High Performance Data Streaming with Amazon Kinesis: Best Practices

Amazon Kinesis makes it easy to collect, process, and analyze real-time, streaming data so you can get timely insights and react quickly to new information. In this session, we dive deep into best practices for Kinesis Data Streams and Kinesis Data Firehose to get the most performance out of your data streaming applications. Our customer NICE inContact joins us to discuss how they utilize Amazon Kinesis Data Streams to make real-time decisions on customer contact routing and agent assignments for its Call Center as a Service (CCaaS) Platform. NICE inContact walks through their architecture and requirements for low-latency, accurate processing to be as responsive as possible to changes.

終わり次第、
IOT314-R – [REPEAT] IoT Analytics Workshop
を見てみようかな。

In this workshop, you learn about the different components of AWS IoT Analytics. You have the opportunity to configure AWS IoT Analytics to ingest data from AWS IoT Core, enrich the data using AWS Lambda, visualize the data using Amazon QuickSight, and perform machine learning using Jupyter Notebooks. Join us, and build a solution that helps you perform analytics on appliance energy usage in a smart building and forecast energy utilization to optimize consumption.

DVC304 – Red Team vs. Blue Team on AWS

Red teamers, penetration testers, and attackers can leverage the same tools used by developers to attack AWS accounts. In this session, two technical security experts demonstrate how an attacker can perform reconnaissance and pivoting on AWS, leverage network, AWS Lambda functions, and implementation weaknesses to steal credentials and data. They then show you how to defend your environment from these threats.

This session is part of re:Invent Developer Community Day, a series led by AWS enthusiasts who share first-hand, technical insights on trending topics.

IOT322-R – [REPEAT] Machine Learning Inference at the Edge

Training ML models requires massive computing resources, so it is a natural fit for the cloud. But, inference typically takes a lot less computing power and is often done in real time when new data is available. So, getting inference results with very low latency is important to making sure your IoT applications can respond quickly to local events. AWS Greengrass ML Inference gives you the best of both worlds. You use ML models that are built and trained in the cloud and you deploy and run ML inference locally on connected devices. For example, you can build a predictive model in Amazon SageMaker for scene detection analysis and then run it locally on an AWS Greengrass enabled security camera device where there is no cloud connectivity to predict and send an alert when an incoming visitor is detected. We show you some examples of image recognition models running on edge devices.

場所がAriaで移動時間がかなり厳しいので、
CMP368-R – [REPEAT] Scalable Multi-Node Deep Learning Training in the Cloud

Developing and optimizing machine learning (ML) models is an iterative process. It involves frequently training and retraining models with new data and optimizing model and training parameters to increase prediction accuracy. At the same time, to drive higher prediction accuracy, models are getting larger and more complex, thus increasing the demand for compute resources. In this chalk talk, AWS and Fast.ai will share best practices on how to optimize AWS infrastructure to minimize deep learning training times by using distributed/multi-node training.

このセッションを聞くことにした。
CON308-R – [REPEAT] Building Microservices with Containers

Microservices are minimal function services that are deployed separately, but can interact together to function as a broader application. Microservices can be built, changed, and deployed quickly with a relatively small impact, empowering developers to speed up the rate of innovation. In this session, we show how containers help enable microservices-based application architectures, discuss best practices for building new microservices, and cover the AWS services that allow you to build performant microservices applications.

GENM201 – Monday Night Live

Want to enjoy live entertainment while learning about Amazon’s infrastructure updates? Be sure to attend Monday Night Live with Peter DeSantis, Vice President, AWS Global Infrastructure and Customer Support, on Nov. 26, at 7:30 PM at The Venetian, Level 2, Hall A.

AWS 機械学習 勉強会 in 福井に参加してきた

Amazon SageMakerを利用した機械学習のハンズオン。
内容は、AWSの亀田さんが講師をされ、オンラインで公開されているハンズオンの57スライド目からを利用して実際にハンズオンをやるというもの。

スライドの資料を利用すると大まかな内容が理解できる。

ハンズオンでは実際にzipファイルをダウンロードして展開されたJupyter Notebookよりここで進められるようになっているのだが、最初は進め方がよくわからず困惑した。[ ]をクリックすると[*]になってしばらくする(実際にデプロイしているので時間がかかるものがある)と[1]となるのだが、この部分を理解せずにやっていると途中で進められなくなったりして本質の理解(どのような原理で動いているか)を進めるのに時間を要してしまった。

受講した感想としては、zipファイルを実際に見てみないことには分かったような気にしかならないので、ここから理解をしていこうと思う。

機械学習のセミナー出ていたキーワード

男子の身長と体重のデータより女子の身長と体重の相関は導けない
→重要なことは目的に対応した学習用データの準備が必要

線形回帰
転移学習

実際に体験したアルゴリズム
XGBoost

RandomCutForest
波があるところに異常値をみつける