2013年11月16日土曜日

re:Inventってなんじゃ?(CloudTrail)

re:Invent2013に来ています。
そこでCloudTrailの発表があったので触ってみました。

CloudTrailは、AWSのAPIコールの記録をS3にログとして保存するサービスです。
これによってセキュリティ分析や運用チェックなどを行うことが可能です。

AWSコンソールをみるとCoudTrailが追加されているので、開いてみます。


ログの保存先のS3バケットを指定して、必要であればその他のオプションも設定します。


Subscribeボタンを押すと、設定完了です。



しばらく、AWSの操作をしたあと、出力先に設定したS3バケットを確認します。
すると、以下のように、json形式のログファイルが圧縮された状態で日ごとに保存されるようになります。



このうちの一つをダウンロードして中身を見てみます。

{
    "Records": [
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeRouteTables", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:00Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "routeTableIdSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeSubnets", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:07:59Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "subnetSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeVpcs", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:07:58Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "vpcSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeSecurityGroups", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:03Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "securityGroupIdSet": {}, 
                "securityGroupSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeNetworkAcls", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:01Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "networkAclIdSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeDBInstances", 
            "eventSource": "rds.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:06Z", 
            "eventVersion": "1.0", 
            "requestParameters": null, 
            "responseElements": null, 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeVolumes", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:04Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "volumeSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeAddresses", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:03Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "allocationIdsSet": {}, 
                "filterSet": {}, 
                "publicIpsSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }, 
        {
            "awsRegion": "us-east-1", 
            "eventName": "DescribeInstances", 
            "eventSource": "ec2.amazonaws.com", 
            "eventTime": "2013-11-15T18:08:02Z", 
            "eventVersion": "1.0", 
            "requestParameters": {
                "filterSet": {}, 
                "instancesSet": {}
            }, 
            "responseElements": "<responseOmitted>", 
            "sourceIPAddress": "54.249.240.229", 
            "userAgent": "aws-sdk-php/1.6.2 PHP/5.4.20 curl/7.19.7 openssl/1.0.0-fips", 
            "userIdentity": {
                "accessKeyId": "xxxxxxxxxxxxxxxxxx", 
                "accountId": "821635308497", 
                "arn": "arn:aws:iam::821635308497:root", 
                "principalId": "821635308497", 
                "type": "Root"
            }
        }
    ]
}


このように呼び出したAPIの内容が表示されるため、jqやログ解析ツールなどと併用してAWSアカウントに対してどういった操作があったかを簡潔に知ることが可能になります。

以上です。

2013年9月26日木曜日

EBSってなんじゃ?(cryptsetup + S3 + IAM Roleでディスク暗号化鍵をS3で管理)

EBSの暗号化の方法のひとつとしてcryptsetupという技術があります。cryptsetupはパスフレーズで暗号化されたディスクにアクセス(マウント)しますが、今回は鍵ファイルを使用してアクセスする手順をまとめました。
また、鍵ファイルを同じインスタンス内に置かないためにS3から鍵ファイルを取得し、マウントした後に削除するようにします。

それでは手順を追ってみます。

まずcryptsetupで/dev/xvdfにアタッチされたEBSボリュームの暗号化を行い、マウントします。
# yum install -y cryptsetup
# yum install xfsprogs -y
# cryptsetup luksFormat -c aes -h sha256 /dev/xvdf
# cryptsetup luksOpen /dev/xvdf encrypted
# ls -l /dev/mapper/
# mkfs.xfs /dev/mapper/luks
# mkdir /mnt/vol
# mount -t xfs /dev/mapper/luks /mnt/vol
# mkdir /mnt/vol/data


次に鍵ファイルを作成し、cryptsetupに登録します
# dd if=/dev/urandom of=/root/encrypted_key bs=1 count=1024
# cryptsetup luksAddKey /dev/xvdf /root/encrypted_key


S3バケットを作成し、そこへ鍵を保管します




自動で暗号化+マウントするために起動スクリプトをつくり、S3から鍵ファイルを取得しマウントするようにします。終わったらAMIを作成します。
# cat /etc/init.d/cryptmount
-----------
#!/bin/sh
#
#
# chkconfig: 345 60 16

aws s3 get-object --region ap-northeast-1 --bucket luks-key --key xvdf_luks_key /boot/xvdf_luks_key
cryptsetup luksOpen /dev/xvdf luks --key-file /boot/xvdf_luks_key
mount -t xfs /dev/mapper/luks /mnt/vol
rm -rf /boot/xvdf_luks_key
-----------

# rm -rf /root/xvdf_luks_key



IAM Roleを作成し、S3へのアクセスを許可します



作成したAMIの起動時にIAM Roleを指定します




これで、鍵がない状態ではEBSをマウントすることができなくなりました。
以上です。

2013年6月15日土曜日

Spiderってなんじゃ?(SUBPARTITIONで負荷分散しながらストレージ容量を無限追加)

Spiderシリーズです。Spiderではパーティションを使ってシャーディングしますが、パーティションの定義によって分散の特性が変わります。

1. サロゲートキーなどのHASHパーティション
  • データは均等に分散
  • 更新負荷は均等に分散
  • 増設はデータ全体の再ハッシュが必要

2. 日付の年月などのHASHパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しい年月を追加

3. サロゲートキーなどのRANGEパーティション
  • データは順次格納
  • 更新負荷は常に1データノードにのみかかる
  • 増設は新しいキー範囲を追加

理想は均等に負荷分散しながら、データ容量を後から増やせるのがベストですが、1,2,3とも負荷分散と容量追加のどちらかにしか親和性がありません。
そこで、作者の斯波さん(@kentokushiba)に伺ったところ、 SpiderでもSUBPARTITIONが使えるとのことでRANGEとHASHを組み合わせることで実現できると教えて頂きました。



早速やってみます。
CREATE TABLE `spdoc` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `created_at` datetime DEFAULT NULL,
  `content` text,
  `docname` varchar(255),
  INDEX(created_at) ,
  PRIMARY KEY (`id`)
) ENGINE=SPIDER DEFAULT CHARSET=utf8 CONNECTION=' table "spdoc", user "xxxxxxxx", password "yyyyyyyyy" auto_increment_mode "1" '
PARTITION BY RANGE (id)
SUBPARTITION BY HASH(id) (
  PARTITION p1 VALUES LESS THAN (1000000) (
    SUBPARTITION db1 COMMENT = 'host "10.0.1.141", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db2 COMMENT = 'host "10.0.1.142", port "3306"' ENGINE = SPIDER
  ),
  PARTITION p2 VALUES LESS THAN (2000000) (
    SUBPARTITION db3 COMMENT = 'host "10.0.1.143", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db4 COMMENT = 'host "10.0.1.144", port "3306"' ENGINE = SPIDER
  )
);


まずRANGEパーティションをidの値の範囲で定義します。試しに1000000,2000000に設定します。
次に、SUBPARTITIONでidでHASHパーティションを定義し、ここにデーターノードを指定します。

すると、idが1000000以下のデータについてdb1, db2のデータノードに均等に分散され、
1000000〜2000000の範囲では、db3, db4に均等に分散されます。

idが2000000を超えそうになった場合は、後から3つめのパーティションを追加することができます。



ALTER TABLE spdoc ADD PARTITION
(
  PARTITION p3 VALUES LESS THAN (3000000) (
    SUBPARTITION db5 COMMENT = 'host "10.0.1.145", port "3306"' ENGINE = SPIDER,
    SUBPARTITION db6 COMMENT = 'host "10.0.1.146", port "3306"' ENGINE = SPIDER
  )
);

これでデータ容量はほぼ無限に増やすことができます。

パーティションごとにSUBPARTITIONの数を変えることはできないので、負荷の分散数は同じデータノード数で賄わなければいけませんが、その場合はパーティションごとのデータノードのインスタンスタイプやPIOPSを増やすことで処理能力をアップさせることで対応できるかと思います。

斯波さんありがとうございました!

以上です。

2013年5月28日火曜日

Fluentdってなんじゃ?(設定ファイルをリモートから読み込む)

fluentdのドキュメントに以下のような記載がありました。

Configuration File - (3) Include Directive
# absolute path
include /path/to/config.conf

# if using a relative path, the directive will use 
# the dirname of this config file to expand the path
include extra.conf

# glob match pattern
include config.d/*.conf

# http
include http://example.com/fluent.conf

最後の行、、、HTTPから設定ファイルを取得してインクルードできるみたいです。

ということで試してみました。

common.conf
<source>
  type tail
  format apache
  pos_file /var/log/td-agent/httpd-access.log.pos
  path /var/log/httpd/access_log
  tag apache.access
</source>

<match apache.access>
  type s3

  aws_key_id xxxxxxxxxxxxxxxxxxx 
  aws_sec_key yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
  s3_bucket hoge-bucket
  path logs/
  buffer_path /var/log/fluent/s3
  flush_interval 10s 
</match>

これをS3にアップロードしてWEBホスティングしてみます。


そして、EC2では以下のように設定します。

/etc/td-agent/td-agent.conf
include http://s3-ap-northeast-1.amazonaws.com/hoge-bucket/conf/fluentd/common.conf
# /etc/init.d/td-agent start


httpdのコンテンツにアクセスしてしばらくたつと、、、


無事出力されていました!

これの何がいいかというと、

  • 各サーバーに共通の設定ファイルなどを外部化しておき、一括変更をかけられる (再起動は必要ですが、定期的に再起動をかけるようにしておくという手もあります)
  • いっそ全設定を外出しして完全に外部で管理する

などが可能になります。

以上です。

2013年5月25日土曜日

spiderってなんじゃ?(spider自身の冗長化)

いままではSpiderがひとつに対して、データノードが複数ある構成をとってきましたが、実戦ではSpiderが単一障害点になるため好ましくありません。ここではSpider自身の冗長化を行なってみます。

いままでのSpider x 1 + mroonga x 2 の構成に対してデータを入れ込むためのappノードを用意します。

SpoderのSPOF





mroonga

 CREATE TABLE doc (
  id INT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = mroonga; 


spider

 CREATE TABLE doc (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  created_at datetime,
  content text,
  FULLTEXT INDEX (content)
 ) ENGINE = spider CHARSET utf8
 CONNECTION ' table "doc", user "memorycraft_user", password "memorycraft_pass" '
 PARTITION BY KEY() (
  PARTITION db1 comment 'host "10.0.1.136", port "3306"',
  PARTITION db2 comment 'host "10.0.1.137", port "3306"'
 ); 

app

# yum install -y php php-mbstring php-pdo php-mysql
$ vim links.php
<?php

//ランダム文字列
function getRandomString($size = 8){
    $chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    mt_srand();
    $char = "";
    for($i = 0; $i < $size; $i++) {
        $char .= $chars{mt_rand(0, strlen($chars) - 1)};
    }
    return $char;
}

//XMLテンプレート
$tmpl =<<< END
<?xml version="1.0" encoding="UTF-8" ?>
<data>
  <info>
    <datetime>%s</datetime>
    <address>%s</address>
    <content>%s</content>
  </info>
</data>
END;

//XMLテンプレートに流しこむcontent要素の内容を読み込む
$contents = array();
for($i=0;$i<10;$i++){
  $contents[] = file_get_contents(dirname(__FILE__)."/dummy".$i.".txt");
}

//spiderホストは引数から取得する
$host = $argv[1];
//タイムゾーン
date_default_timezone_set('Asia/Tokyo');

//無限ループ
while(true){
  //XMLテンプレートに各要素を割り当てる
  $xml = sprintf($tmpl, date('Y/m/d H:i:s'), getRandomString(), $contents[rand(0,9)]);
  echo ".";
  //DB接続
  $pdo = new PDO(
    "mysql:dbname=memorycraft;host=$host;",
    "memorycraft_user",
    "memorycraft_pass",
    array(
     PDO::MYSQL_ATTR_INIT_COMMAND => "SET CHARACTER SET `utf8`"
   ));
  $pdo->query("SET NAMES utf8;");
  //投入
  $st = $pdo->prepare("INSERT INTO doc VALUES(NULL,NOW(),?);");
  $rslt = $st->execute(array($xml));
}
?>
実行します
$ php links.php 10.0.1.20
.......................................


Spiderノードで投入されていることを確認します。

mysql> select id, created_at from doc limit 10;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 | |    2 | 2013-05-25 11:27:47 | |    3 | 2013-05-25 11:27:47 | |    4 | 2013-05-25 11:27:47 | |    5 | 2013-05-25 11:27:47 | |    6 | 2013-05-25 11:27:48 | |    7 | 2013-05-25 11:27:48 | |    8 | 2013-05-25 11:27:48 | |    9 | 2013-05-25 11:27:48 | |   10 | 2013-05-25 11:27:48 | +------+---------------------+ 10 rows in set (0.06 sec)


これで、spider x 1 + mroonga x 2 + app x 1になりました。


Spiderの冗長化


次は、Spider x 2 + mroonga x 2 + internal ELB + app x 4 にしてみます。




Spiderノードをもう1台(spider2)追加します。

テーブルはspider1と同じ内容にします。
/etc/my.cnfでauto_increment_increment = 100でoffsetを1,2でずらします。
これについては、別の記事で触れています。

mysqlってなんじゃ?(マルチマスタでauto_increment)
http://memocra.blogspot.jp/2013/05/mysqlautoincrement.html

また、spiderを複数にした場合、appからの接続は1つにまとめます。
Proxy系のソフトウェアを入れるのも1つの方法ですが、ここでは内部ELBを利用してみます。

ヘルスチェックは3306ではなく別のポート経由でMySQL監視プロセスに接続スべきですが、ここではとりいそぎhttpdを立ちあげ80番に対してヘルスチェックします。


それではappで投入プログラムを実行します。
対象のホストは内部ELBのDNSNameになります。

$ php links.php internal-spider-2040122974.ap-northeast-1.elb.amazonaws.com
........................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................


spider1でデータを見てみます。

mysql> select id, created_at from doc limit 20;
+------+---------------------+
| id   | created_at          |
+------+---------------------+
|    1 | 2013-05-25 11:27:47 |
|  101 | 2013-05-25 11:27:47 |
|  201 | 2013-05-25 11:27:47 |
|  202 | 2013-05-25 11:27:47 |
|  601 | 2013-05-25 11:27:47 |
|  701 | 2013-05-25 11:27:48 |
| 1101 | 2013-05-25 11:27:48 |
| 1201 | 2013-05-25 11:27:48 |
| 1601 | 2013-05-25 11:27:48 |
| 1701 | 2013-05-25 11:27:48 |
| 2101 | 2013-05-25 11:27:49 |
| 2201 | 2013-05-25 11:27:49 |
| 2301 | 2013-05-25 11:27:49 |
| 2601 | 2013-05-25 11:27:49 |
| 2701 | 2013-05-25 11:27:49 |
| 2802 | 2013-05-25 11:27:49 |
| 3101 | 2013-05-25 11:27:49 |
| 3201 | 2013-05-25 11:27:49 |
| 3301 | 2013-05-25 11:27:49 |
| 3601 | 2013-05-25 11:27:50 |
+------+---------------------+

mysql> select count(*) from doc;
+----------+
| count(*) |
+----------+
|   111433 |
+----------+


順調に入っていってるみたいです。
あとはSpiderノードを必要に応じて追加していき、my.cnfでoffsetをずらして起動するだけです。
offsetずらしの処理はcloud-initなどで行うと、AutoScalingすることもできそうです。

今回は以上です。