MariaDBでBLOBを取扱時の注意事項

BLOBでバイナリデータをPHPから書き込みするときにはまったポイントをいくつか。。。MariaDBでの発生事例だが、MySQLでも同様だと思う。

・BLOBは65535Bytesしか格納できない。BLOBよりも大きなサイズの格納が必要であれば、MEDIUMBLOB、LONGBLOBを利用する必要がある。
・BLOB型にデータをinsertするときにはバイナリデータをストリームのまま登録することはできないので、PHPであればbin2hex関数でいったん16進数に変換してinsertした後、selectするときにhex2bin関数で戻す必要がある。また、BLOB型で65,535byteを超えるデータをinsertすると先頭65,535byteだけが登録されてしまう(insert時にエラーにならない)ので、insert前にデータサイズのチェックが必要。
・パケットサイズを大きくする必要がある。max_allowed_packet という設定値だが、デフォルトでは1MBなので、設定値を超えるデータをinsertしようとするとエラーが発生する。mysqlで大きなファイルを保存するための設定 を参考にログファイルサイズの設定値も変更したほうが良いかもしれない。
・PHPの設定項目値として、php.ini でupload_max_filesize設定値を確認する。この値を超えたデータがPOSTされても、$_FILES[name][name]でファイル名は設定されるが、データは一時ディレクトリに保存されず、
$_FILES[name][tmp_name] は値がセットされない。また、環境によってはset_time_limit関数を使ってタイムアウト時刻を調整したほうが良い。

GAとは

昨年のvForum Tokyo2018に参加した際にGAという表現があったが、GAの意味が理解できなかった。

GoogleでGAを検索してもよくわからず。。。。

つい先日、 Kubernetes完全ガイド を参照する機会があり、本文でGAに触れられており、Generally available(一般ユーザに利用可能な状態になること)であることが分かった。

最近略語が多くて本当に分からないことが多い。。。

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の両方のゾーンを登録したほうが良いと思われる。

Non-Recurring Expense

開発依頼者が開発依頼先に開発費として一括で支払う方法のこと。
エンジニアリング・リソースや設計ツールの費用、デバイスを製造するための加工技術の費用など、デバイスの設計・製造にかかる経費の総計を指す。
⇔ Recurring Cost

スクラムガイド

2017年11月版より

スプリントプランニング
スプリントの作業計画。

スプリントレトロスペクティブ
スクラムチームの検査と次のスプリントの改善計画を作成する機会。
平成30年度システムアーキテクト試験では、スクラムチームで何がうまくいき、何がうまくいかなかったのかを議論し、継続的なプロセス改善を促進するアクティビティとなっている。

スプリントレビュー
スプリントの終了時にインクリメントの検査と、必要であればプロダクトバック ログの適応を行うもの。

デイリースクラム
開発チームのための 15 分間のタイムボックスのイベント。