funasaki memo

このブログ上の投稿は個人のものであり、所属する企業を代表する投稿ではありません。所属:AWSのSolutions Architect

AWS OpsWorksでのカスタムAMIの利用について

AWS OpsWorksで2013年10月現在サポートされているAMIは、Amazon LinuxUbuntu 12.04 LTSベースです。

これらを素のまま使うこともできますし、事前にEC2コンソールで起動して、必要なパッケージをインストールしておいて、それをOpsWorksで使うこともできます。

そこで、実際にOpsWorksでインスタンスを起動するときに、必要となるOpsWorks Agentをどうやってインストールさせているんだろう?と疑問に思っていたのですが、解決しました!

Cloud-initを使ってインストールさせてるんですね。

OpsWorksで起動したインスタンスを、EC2 ConsoleでUser Dataをみたところ、OpsWorks Agentをインストールさせるためのスクリプトが書かれていました。
f:id:kenjifunasaki:20131017154453p:plain

なるほど!これで納得。

逆にCloud-initを使ってOpsWorks Agentをインストール・動作できるのであれば、他のOSのイメージででも技術的には動作可能かもしれないですね。(サポートの有無はまた別の話として。)

詳細はこちら。
http://docs.aws.amazon.com/opsworks/latest/userguide/workinginstances-custom-ami.html

OpsWorksで起動されるインスタンスは複数のレイヤーに属することができる

OpsWorksで作成したインスタンスは複数のレイヤーに属することができます。

例えばStatic Web Serverレイヤーに属するインスタンスを作成した場合、それにMySQLレイヤーも属させることで、1インスタンスで2つのレイヤーの役割をこなすことができます。

ただし、その設定が行えるのはインスタンスがstopの状態にあるときです。

具体的には以下のようなインスタンスのEdit画面で、レイヤーを追加してあげるだけです。
f:id:kenjifunasaki:20131016164816p:plain
これで追加したレイヤーのChefレシピも動作するということですね。

これでより多くの構成をカバーできますね。

ただ、レイヤー間の互換性があるため、非互換のものは同居させることは出来ないようです。以下のように表示され、非互換のものは選択できません。
f:id:kenjifunasaki:20131016165654p:plain

以下はその例です。

  • PHP App Serverレイヤーが互換性のあるレイヤー:custom, db-master, memcached, monitoring-master, and rails-app.
  • MySQLレイヤーが互換性のあるレイヤー:custom, lb, memcached, monitoring-master nodejs-app, php-app, rails-app, and web.
  • HAProxyレイヤーが互換性のあるレイヤー:custom, db-master, and memcached

詳細はこちら。
http://docs.aws.amazon.com/opsworks/latest/userguide/layers.html

aws-cliを使ってAutoScaling Groupを作成してみる。

launch-configurationを作成

$ aws autoscaling create-launch-configuration --launch-configuration-name config1 --image-id ami-39b23d38 --instance-type t1.micro --key-name ap-northeast

作成結果を確認

$ aws autoscaling describe-launch-configurations --output text

        ap-northeast    arn:aws:autoscaling:ap-northeast-1:XXXXXXXXXXXX:launchConfiguration:0ad52a06-34d4-4c70-940c-b4b5a23adbd9:launchConfigurationName/config1        ami-39b23d38            False   config1 2013-08-23T10:44:36.777Z                t1.micro
INSTANCEMONITORING      True
RESPONSEMETADATA        5d980e12-0be1-11e3-a812-5b0041b22e4e

auto-scaling-groupを作成

$ aws autoscaling create-auto-scaling-group --auto-scaling-group-name group1 --launch-configuration-name config1 --min-size 2 --max-size 2 --vpc-zone-identifier subnet-d8de6cb0,subnet-96d96bfe

作成結果の確認

$ aws autoscaling describe-auto-scaling-groups --output text

arn:aws:autoscaling:ap-northeast-1:XXXXXXXXXXXXXX:autoScalingGroup:3cffd102-8a3e-4756-8a20-3343b39ba977:autoScalingGroupName/group1       0       2       group1  300     2       2       subnet-d8de6cb0,subnet-96d96bfe config1 2013-08-23T10:58:55.700Z        EC2
i-02215307      ap-northeast-1c Healthy InService       config1
i-9fe2059a      ap-northeast-1a Healthy InService       config1
RESPONSEMETADATA        45062903-0be3-11e3-84d9-670da55b233a

インスタンス2つが、異なるサブネット(異なるAZ)で起動しているのが分かりますね。

ついでに削除処理もメモとして残しておきます。

$ aws autoscaling update-auto-scaling-group --auto-scaling-group-name group1 --min-size 0 --max-size 0
$ aws autoscaling delete-auto-scaling-group --auto-scaling-group-name group1
$ aws autoscaling delete-launch-configuration --launch-configuration-name config1
以上で削除完了です。AutoScaling groupの削除は、インスタンスが完全にterminateされてからでないと削除はできませんので、ご注意を。

Amazon Linux 2013.03にデフォルトでインストールされているAWS CLIにhelpが入ってない。

https://forums.aws.amazon.com/thread.jspa?messageID=449307
こちらのディスカッションフォーラムにもあるように、Amazon Linux 2013.03にはaws-cliのhelp(manual entry)が入っていないです。

$ aws --version
aws-cli/0.9.3 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

以下のようにhelpコマンドを実行しても表示されません。

$ aws help
No manual entry for aws

ので、aws-cliを一旦削除して再インストールし直すことにしました。

$ sudo yum remove  aws-cli
$ sudo yum -y update
$ sudo easy_install pip
$ sudo pip install awscli

インストール後、helpを実行すると、

$ aws help
AWS()                                                                    AWS()



NAME
       aws -

DESCRIPTION
       The  AWS  Command Line Interface is a unified tool that provides a con-
       sistent interface for interacting with all parts of AWS.

SYNOPSIS
           aws [options] <service_name> <operation> [parameters]

       Use aws service help for information on a specific service.

OPTIONS
       --debug (boolean)

       Turn on debug logging.

       --endpoint-url (string)

正しく表示されました。よかったよかった。
ちなみに新しく入れたaws-cliのバージョンは

$ aws --version
aws-cli/0.14.1 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

元々入っていたのが0.9.3だったので、元々のは結構古かったみたいですね。

AWS CLIを使ってTagの値のみを取得する

AWS CLIを使って、以下のように割り当てられたインスタンスのTagの値のみ(今回はWordPress)を取得します。
f:id:kenjifunasaki:20130814155254p:plain
今回はAmazon Linuxを使うので、AWS CLIのインストール方法は割愛します。

# Amazon Linux 2013.03に入っているデフォルトのaws-cliを使いました。バージョンは以下です。

$ aws --version
aws-cli/0.9.3 Python/2.6.8 Linux/3.4.43-43.43.amzn1.x86_64

#バージョンが異なると、オプションの指定が異なる可能性があります。

まず以下の環境変数を設定します。

export AWS_DEFAULT_REGION=ap-northeast-1
export AWS_CREDENTIAL_FILE=/opt/aws/credential-file

credential-fileを作成します。

$ sudo cp /opt/aws/credential-file-path /opt/aws/credential-file
$ sudo vi /opt/aws/credential-file
AWSAccessKeyId=XXXXXXXXXXXXXXXXXX
AWSSecretKey=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

以下コマンドを実行して動作確認。

$ aws ec2 describe-instances
{
    "Reservations": [
        {
            "OwnerId": "000000000000",
            "ReservationId": "r-883ce98b",
            "Groups": [
(以下省略)

ちゃんと出力されることを確認。

$ aws ec2 describe-tags
{
    "ResponseMetadata": {
        "RequestId": "b98ff86b-24e7-4950-9161-c864e11c20a7"
    },
    "Tags": [
        {
            "ResourceType": "snapshot",
            "ResourceId": "snap-6d0d4c4d",
            "Key": "Name",
            "Value": "dev-urandom-1G"
        },
        {
            "ResourceType": "security-group",
            "ResourceId": "sg-958dff94",
            "Key": "aws:cloudformation:stack-name",
            "Value": "WordpressSite"
        },
(以下省略)

通常describe-tagsを実行すると、全部のTagが出てきてしまうので、絞り込みをします。

$ aws ec2 describe-tags --filter '{"name":"resource-id","values":"i-06672203"}'
{
    "ResponseMetadata": {
        "RequestId": "8c60bc2e-16bc-4882-8bfb-de5889eb84c3"
    },
    "Tags": [
        {
            "ResourceType": "instance",
            "ResourceId": "i-06672203",
            "Key": "Name",
            "Value": "WordPress"
        }
    ]
}

これをさらに絞り込むためにjqを使います。jqは入力内容を元に出力を絞り込むためのプログラムです。
まずは、jqをインストール。

$ sudo yum install jq

そして、上記出力をjqを使って絞り込みます。

$aws ec2 describe-tags --filter '{"name":"resource-id","values":"i-06672203"}' | jq '.Tags[] | .Value'
"WordPress"

以上で、WordPressの値のみを抽出することができました!

もちろん、Name以外のTagも取得できますので、いろいろ使いまわせそうですね。

AWS Security Token Serviceを使ってみる

AWS SDKやAWSコマンドラインツールを使う場合、Security Credentialsが必要になります。Security Credentialsの中にはアクセスキーとシークレットアクセスキーが含まれています。AWSのサービスやリソースをAPIを使って管理するときには、この2つのキーを使って認証します。

この2つのキーをどうやって保管するか?が本記事の目標とするところです。

2つのキーをソースコードやもしくはテキストファイル、DB等に格納して使うこともできますが、これだとキーの置き換えをするのが大変です。そこでAWSから提供されているSecurity Token Serviceというサービスを使って、キーの管理を安全に、かつ効率的に行うことを試してみました。

Security Token Serviceとは?
一時的な、かつ制限された特権を持つAWSアカウント、またはIAMユーザのCredential情報を取得可能にする機能です。Security Token Serviceは、以下の3つのアクションが可能です。

AWS SDKでSecurity Token Serviceより権限を取得できる。対応しているSDKは2013/04/16時点では以下。

http://docs.aws.amazon.com/STS/latest/UsingSTS/AccessingSTS.html

AWS SDK for Javaを使って、GetSessionTokenした例が以下。今回は取得した権限でS3のバケットリストを表示させている。

import java.io.IOException;
import java.util.List;

import com.amazonaws.AmazonWebServiceClient;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.s3.model.ObjectListing;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest;
import com.amazonaws.services.securitytoken.model.GetSessionTokenResult;


public class MySecurityTokenSample {

	public static void main(String[] args) throws IOException {
		
		//STSからトークンを取得するために必要なCredential情報
		AWSCredentials credentials = new PropertiesCredentials(
                MySecurityTokenSample.class.getResourceAsStream("AwsCredentials.properties"));
		
		//SecurityTokenServiceClientの設定
		AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(credentials);
		GetSessionTokenRequest req = new GetSessionTokenRequest();
		GetSessionTokenResult res = sts.getSessionToken(req);
		Credentials tmpCredentials = res.getCredentials();
		String accessKeyId = tmpCredentials.getAccessKeyId();
		String secretAccessKeyId = tmpCredentials.getSecretAccessKey();

		//取得したアクセスキー、シークレットアクセスキーでS3のバケットリストを表示してみる。
		BasicSessionCredentials c = new BasicSessionCredentials(tmpCredentials.getAccessKeyId(),
				tmpCredentials.getSecretAccessKey(), tmpCredentials.getSessionToken());
		AmazonS3Client s3Client = new AmazonS3Client(c);
		s3Client.setEndpoint("s3-ap-northeast-1.amazonaws.com");
					
		List<Bucket> list = s3Client.listBuckets();
		for(Bucket b : list) {
			System.out.println(b.getName());
		}
	}
}

こちらを実行すると、バケット名のリストが表示された。
トークンを取得するためには、やはりSecurity Credentialsの情報が必要。なので、システムの中に1台だけトークン管理サーバなるものを立てて、そこの中では唯一Credentials情報を持つ。他のサーバで一時的にCredentials情報が必要な場合は、このサーバが何かしらの安全な方法で配ってあげる形が良さそう。取得したトークンは、デフォルトで12時間まで有効。その時間が過ぎると無効になる。以下のように実行することで、その時間も変更できる。

getSessionTokenRequest.setDurationSeconds(7200); 

上記例では、AWSアカウントの権限を取得していたが、以下のようにgetFederatedTokenを使って指定したIAM Userの権限を取得もできる。

import java.io.IOException;
import java.util.List;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.BasicSessionCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.Bucket;
import com.amazonaws.services.securitytoken.AWSSecurityTokenServiceClient;
import com.amazonaws.services.securitytoken.model.Credentials;
import com.amazonaws.services.securitytoken.model.GetFederationTokenRequest;
import com.amazonaws.services.securitytoken.model.GetFederationTokenResult;
import com.amazonaws.services.securitytoken.model.GetSessionTokenRequest;
import com.amazonaws.services.securitytoken.model.GetSessionTokenResult;


public class MySecurityTokenSample2 {

public static void main(String[] args) throws IOException {
		
		//STSからトークンを取得するために必要なCredential情報
		AWSCredentials credentials = new PropertiesCredentials(
                MySecurityTokenSample.class.getResourceAsStream("AwsCredentials.properties"));
		
		//SecurityTokenServiceClientの設定
		AWSSecurityTokenServiceClient sts = new AWSSecurityTokenServiceClient(credentials);
		GetFederationTokenRequest req = new GetFederationTokenRequest();
		req.setName("TVMUser1");
		req.setPolicy("{\"Statement\": [{\"Effect\": \"Allow\",\"Action\": \"s3:*\",\"Resource\": \"*\"}]}");
		GetFederationTokenResult res = sts.getFederationToken(req);
		Credentials tmpCredentials = res.getCredentials();
		
		//取得したアクセスキー、シークレットアクセスキーでS3のバケットリストを表示してみる。
		BasicSessionCredentials c = new BasicSessionCredentials(tmpCredentials.getAccessKeyId(), 
				tmpCredentials.getSecretAccessKey(), tmpCredentials.getSessionToken());
		AmazonS3Client s3Client = new AmazonS3Client(c);
		s3Client.setEndpoint("s3-ap-northeast-1.amazonaws.com");
					
		List<Bucket> list = s3Client.listBuckets();
		for(Bucket b : list) {
			System.out.println(b.getName());
		}
	}
}

TVMUser1というIAM Userで、S3のみにアクセス可能なポリシーをJSONで書いたものを使ってGetFederationTokenしている。
req.setPolicyでは以下のJSONデータを引数に入れている。

{
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "s3:*",
      "Resource": "*"
    }
  ]
}

このように、Security Token Serviceを使えば、一時的なCredentials情報を取得できることがわかった!

リンク

DynamoDBでscanを実行してみる

DynamoDBのテーブル名が SampleTable, AttributeがIdとNameのみのテーブルに対してScanを実行した。

AWS SDK for Javaで以下のように実行してみた。

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.PropertiesCredentials;
import com.amazonaws.services.dynamodb.AmazonDynamoDBClient;
import com.amazonaws.services.dynamodb.model.AttributeValue;
import com.amazonaws.services.dynamodb.model.ComparisonOperator;
import com.amazonaws.services.dynamodb.model.Condition;
import com.amazonaws.services.dynamodb.model.GetItemRequest;
import com.amazonaws.services.dynamodb.model.GetItemResult;
import com.amazonaws.services.dynamodb.model.Key;
import com.amazonaws.services.dynamodb.model.PutItemRequest;
import com.amazonaws.services.dynamodb.model.PutItemResult;
import com.amazonaws.services.dynamodb.model.ScanRequest;
import com.amazonaws.services.dynamodb.model.ScanResult;


public class DynamoDBLoadGenerator {

	static AmazonDynamoDBClient dynamoDB;
	
	public static void main(String[] args) throws IOException {
		//Credentialsの設定
		AWSCredentials credentials = new PropertiesCredentials(
                DynamoDBSample.class.getResourceAsStream("AwsCredentials.properties"));

		//リクエストのパラメータの設定
                dynamoDB = new AmazonDynamoDBClient(credentials);
                dynamoDB.setEndpoint("http://dynamodb.ap-northeast-1.amazonaws.com");
                String tableName = "SampleTable";
        
         

                //ScanFilterConditionの設定
                Condition scanFilterCondition = new Condition()
    	               .withComparisonOperator(ComparisonOperator.LT.toString())   //〇〇よりも小さい
    	               .withAttributeValueList(new AttributeValue().withN("500")); //500
        
                Map<String, Condition> conditions = new HashMap<String, Condition>();
                conditions.put("Id", scanFilterCondition);
        
    	        ScanRequest scanRequest = new ScanRequest()
    		       .withTableName(tableName)
    		       .withScanFilter(conditions)
    		       .withAttributesToGet(Arrays.asList("Id")); //Idに対してScanFilterを実行

    	       //Scan実行
    	       ScanResult scanResult = dynamoDB.scan(scanRequest);
    
               //結果表示
               for (Map<String, AttributeValue> item : scanResult.getItems()){
        	     System.out.println(item.get("Id"));
               }
	}
}

実行結果は以下のように出力される。

{N: 251, }
{N: 187, }
{N: 154, }
{N: 7, }
{N: 115, }
{N: 286, }
{N: 361, }
{N: 380, }
{N: 117, }
{N: 401, }
{N: 47, }
{N: 184, }
{N: 403, }
{N: 304, }
{N: 156, }
{N: 122, }
{N: 273, }
以下省略

IDが500以下のデータが出力された。
また今回使用したScanFilterConditionでComparisonOperatorで、BETWEENやBEGIN WITHやCONTAINなどいろいろ指定できる。今回使ったのはLT(おそらくはLess Thanのことだと思う)。これでscanするフィルターをいろいろ設定できそう。

試しにNameが"Book 500"とイコールであるIdをFilterConditionで指定したのが以下。

        //ScanFilterConditionの設定
        Condition scanFilterCondition = new Condition()
    	.withComparisonOperator(ComparisonOperator.EQ.toString())
    	.withAttributeValueList(new AttributeValue().withS("Book 500"));


以下はComparisonOperatorのJavaDoc
[http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodb/model/ComparisonOperator.html:title=
http://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodb/model/ComparisonOperator.html]