EBS Snapshot Copyしたものを使ってAMIを作成、起動してみる
前回のの「EBS Snapshot Copyを使ってみる」の応用で、リージョン間でコピーしたSnapshotを利用して新たにAMIを作成、起動してみました。本来であれば、こちらのページにありますように、リージョン間のAMIコピー機能がリリースされるとのことなので、それを待ってから使う方が良いかと思っていますが、取り急ぎEBS Snapshot Copy機能で出来るかどうか試してみました。
結果としては、リージョン間でコピーしたSnapshotをベースに、カスタマイズしたAmazon Linux 2012.09 のAMIを新しいリージョンで作成、起動することができました。
注意事項としては、異なるリージョンでは使用されるKernel IDが異なるため、正しいKernel IDを指定する必要があることです。
その時に試した方法が以下です。
1.Tokyo リージョンでAmazon Linux 2012.09 を起動。
2.起動後、Apache HTTP Server をインストール
sudo yum install httpd
3.インストール後、Tokyoリージョン内でAMI 作成
4.作成したAMIに含まれているSnapshot の ID を確認、そのSnapshotをSingaporeリージョンにコピーする。
Management Consoleで、該当するAMIの情報を確認すると、Snapshot IDを確認できます。
このSnapshotをSingaoreリージョンにコピーします。
コピーが開始され、進行中の状態だと以下のように表示されます。
コピーが完了されるまで待ちます。
5.SingaporeリージョンにコピーされたSnapshotを元に新たなAMIを作成する
コピーが無事完了後、該当するSnapshotを選択して、create Image from Snapshotを実行します。
作成ウィンドウが以下のように表示されます。ここでアーキテクチャはx86_64を選択、Kernel ID や Ramdisk ID は Use Defaultを選択しました。
Yes, Create ボタンをクリックすると、AMI 作成が開始され、以下のようなウィンドウが表示されました。
これで新しいAMIのIDが分かりますね。
作成が完了すると、以下のように表示されます。
6.作成したAMIを起動する。
作成したAMIを選択して起動してみます。基本的にはいつもの起動方法と同じなのですが、1点、Kernel IDを指定しない場合には起動に失敗してしまうことがあるようです。以下の起動設定時に、Kernel IDをUse Defaultにして起動すると、起動処理は開始されるのですが、途中のStatus CheckでErrorとなります。
Instance / System Reacheability Check で Error となるようです。その時の起動ログをManagement Console上で見てみると、
md: autorun ... md: ... autorun DONE. EXT3-fs: sda1: couldn't mount because of unsupported optional features (240). EXT2-fs: sda1: couldn't mount because of unsupported optional features (240). Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(8,1)
とKernel panicが起きていました。
今回の場合は、Kernel IDを明示的に指定する必要があるようです。Singaporeリージョンで同じバージョンのAmazon Linuxを起動するときに、どのKernel IDを使用しているか?を確認、そのKernel IDを指定して起動すると、無事インスタンスに接続できました。
今回は、Singaporeリージョン内では aki-fe1354ac を指定すると、起動できました。
最後に
今回の方法で、Amazon Linux 2012.09では、Copy Snapshot機能を使って、別のリージョン内にSnapshotをコピー、それをベースにAMIを作成することができました。
他のOSの場合でも、例えばMarket Placeで取得できるSnapshotの場合でも、コピー先のリージョンでサポートされていれば、コピー自体は可能なようですね。
リンク
http://aws.typepad.com/aws_japan/2012/12/ebs-snapshot-copy-between-regions.html
EBS Snapshot Copyを使ってみる
AWSブログで「リージョン間のスナップショットコピーが可能に!」という記事が出ていましたので、早速試してみました。
今回は、以下の順番で確認してます。
- 東京リージョンで空のEBSボリュームを作成
- EBSボリュームをEC2インスタンスにattach
- EC2インスタンス内でボリュームをマウント
- attachしたボリューム内にtest.txtファイルを作成
- ボリュームをdetach
- ボリュームを元にスナップショット作成
- 作成したスナップショットをシンガポールリージョンにコピー
- シンガポールリージョンに出来たスナップショットを元にボリュームを作成
- 作成したボリュームをEC2インスタンスにattach
- ボリュームをマウントして中身を確認
以上の流れで、正しくスナップショットのコピーが行われたことを確認しました。
1.東京リージョンで空のEBSボリュームを作成
AWS Management Console にログインして、EC2の画面に遷移します。
Create Volumeボタンをクリック、ボリュームを作成します。
今回は1GBの空ボリュームを作成します。
2.EBSボリュームをEC2インスタンスにattach
3.EC2インスタンス内でボリュームをマウント
attachしたボリュームでmkfs実行
[ec2-user@ip-10-162-15-79 ~]$ sudo fdisk -l Disk /dev/xvda1: 8589 MB, 8589934592 bytes 255 heads, 63 sectors/track, 1044 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 Disk /dev/xvdf: 1073 MB, 1073741824 bytes 255 heads, 63 sectors/track, 130 cylinders Units = cylinders of 16065 * 512 = 8225280 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disk identifier: 0x00000000 [ec2-user@ip-10-162-15-79 ~]$ sudo mkfs -t ext3 /dev/xvdf mke2fs 1.42 (29-Nov-2011) Filesystem label= OS type: Linux Block size=4096 (log=2) Fragment size=4096 (log=2) Stride=0 blocks, Stripe width=0 blocks 65536 inodes, 262144 blocks 13107 blocks (5.00%) reserved for the super user First data block=0 Maximum filesystem blocks=268435456 8 block groups 32768 blocks per group, 32768 fragments per group 8192 inodes per group Superblock backups stored on blocks: 32768, 98304, 163840, 229376 Allocating group tables: done Writing inode tables: done Creating journal (8192 blocks): done Writing superblocks and filesystem accounting information: done [ec2-user@ip-10-162-15-79 ~]$
マウント処理実行。
[ec2-user@ip-10-162-15-79 ~]$ sudo mount /dev/xvdf /mnt [ec2-user@ip-10-162-15-79 ~]$ ls /mnt lost+found
4.attachしたボリューム内にtest.txtファイルを作成
[ec2-user@ip-10-162-15-79 ~]$ touch /mnt/test.txt touch: cannot touch `/mnt/test.txt': Permission denied [ec2-user@ip-10-162-15-79 ~]$ sudo touch /mnt/test.txt [ec2-user@ip-10-162-15-79 ~]$ ls /mnt lost+found test.txt [ec2-user@ip-10-162-15-79 ~]$
5.ボリュームをdetach
アンマウント。
[ec2-user@ip-10-162-15-79 /]$ sudo umount /mnt [ec2-user@ip-10-162-15-79 /]$ ls /mnt [ec2-user@ip-10-162-15-79 /]$
detach
6.ボリュームを元にスナップショット作成
7.作成したスナップショットをシンガポールリージョンにコピー
今回の本題。スナップショットをコピーします。右クリックしてcopy Snapshot を選択。
8.シンガポールリージョンに出来たスナップショットを元にボリュームを作成
9.作成したボリュームをEC2インスタンスにattach
省略。
10.ボリュームをマウントして中身を確認
[ec2-user@ip-10-130-67-202 ~]$ sudo mount /dev/xvdf /mnt [ec2-user@ip-10-130-67-202 ~]$ ls /mnt lost+found test.txt
これで、作成したtest.txtがコピーした先のスナップショットにもちゃんと含まれていたことの確認ができました。
MultiRegions Active Standby パターン
2012年のCDPのAdvent Calendarで、12月20日分を担当させていただくことになりました!舟崎と申します。
今回考えたデザインパターンは「MultiRegions Active Standbyパターン」です。
システムをAWS上で構築しようとしたときに、そのシステム要件によっては「日本国内に稼働させておくだけで大丈夫か?」と疑念を持たれる方もいらっしゃると思います。日本全域を襲う震災に備え、海外にシステムを立てておくことで、冗長構成をとる必要があるかもしれません。
そこで考えた構成が以下です。
二つのリージョンで同じ構成を立てておいて、冗長化します。
リージョンA全体で障害が起きた場合は、Route 53でDNSのマッピングを別のリージョンのELBのCNAMEに変更します。
メリット
- リージョンA全体がダウンした場合でも、リージョンBでサービス継続が可能
デメリット
- リージョン間で同じサーバーやデータを生成・取得するのに、ひと手間がかかる。
- リージョン間の通信には、通信量に応じて課金が発生する
このActiveStandby構成をとる上で重要なポイントが二つあると考えてます。
- 1.リージョン間で同じWeb/AP/DBサーバーを構築可能であること
- 2.Active構成側でDBのデータを取得して、Standby構成側のDBに随時反映(インポート)すること
1.リージョン間で同じサーバーを構築するには
Amazon EC2で起動したインスタンスをAMIに保存できます。このAMIは、リージョンごとに保存されます。つい最近までは、東京リージョンで新たにカスタマイズして作成したAMIを、シンガポールリージョンで使うことは出来ませんでした。ですが、このブログ記事を載せる直前に急遽「リージョン間のスナップショットのコピーが可能に」なりました!これにより、近い将来にはAMIのリージョン間コピーも対応してくる予定だそうです。すごいですね!!
リージョン間コピーを使わない方法もあります。例えば以下のような方法です。
異なるリージョンで同じインスタンス / AMI を作成する方法
この方法は、異なるリージョンに立っているサーバーに同じコマンドを同時に実行させる方法で、かなり力技とも言えると思います。
2.Active構成側のDBのデータを、Standby構成側のDBに反映させるには
これを実現するには、Active側のDBのデータをエクスポートして、S3に保存する方法が良いと思います。例えば日次でDBのデータをS3に上げておき、Standby側で随時データをダウンロードして、DBにインポートします。S3 へのファイルのアップロード・ダウンロードは、SSLにより通信を暗号化させることができますので、データの漏洩もこれで防げます。また、アップロードに時間がかかる場合には、S3 Multipart Upload という方法もあり、この方法を使うと大容量のファイルでも、それらを分割して、分割した個々のデータを10スレッドで同時に並行してS3 に高速アップロードができます。分割されたデータは、S3側でくっつけることもできます。
最後に
リージョン間の通信は専用線では現状ありません。インターネットを介した通信となるため、データ通信により課金も発生します。そのため、リアルタイムでのリージョン間の同期は、要件にもよるとは思いますが、コストパフォーマンス上好ましくはないかと考えてます。今回の構成のように、リージョンAでActive構成を、リージョンBでStandby構成にして、定期的に例えば日次でデータをStandby側に同期させることで、たとえリージョンA全体に障害があったとしても、リージョンB側でサービスの復旧・継続ができます。
一昔前でしたら、海外のデータセンターに冗長構成用にシステムを構築するというのは、物理的にもコスト的にも難しかったと思います。それが、AWSのグローバルに展開されているインフラを使えば、AWS Management Consoleを使って画面の操作だけで簡単に構築ができます。より安全に確実にサービスを継続させるために、今回のようなMultiRegions Active Standbyパターンのような構成も、少しずつ重要性が増してくるのではないかと思ってます。
以上、12/20分のCDP 向けAdevent Calendar記事でした!
S3ダウンロード時にRangeを指定する
S3にアップしてあるオブジェクトをダウンロードするときに、0byte から 200 byte までを指定してダウンロードすることができます。
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.URL; import java.util.Calendar; import java.util.Date; import com.amazonaws.auth.PropertiesCredentials; import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.ObjectMetadata; import com.amazonaws.services.s3.model.S3Object; public class S3Downloader { /** * @param args */ public static void main(String[] args) throws IOException { // TODO Auto-generated method stub AmazonS3 s3 = new AmazonS3Client(new PropertiesCredentials( S3Downloader.class.getResourceAsStream("AwsCredentials.properties"))); String bucketName = "bucketname"; String key = "filename"; ObjectMetadata metaData = s3.getObjectMetadata(bucketName, key); long contentLength = metaData.getContentLength(); System.out.println("Content Length: " + contentLength); GetObjectRequest rangeObjectRequest = new GetObjectRequest( bucketName, key); for (int i = 0 ; i < contentLength ; i=i+100) { //現在の位置から100byte先までを指定 rangeObjectRequest.setRange(i, i+100); S3Object objectPortion = s3.getObject(rangeObjectRequest); displayTextInputStream(objectPortion.getObjectContent()); } } //1行ずつ表示 private static void displayTextInputStream(InputStream input) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(input)); while (true) { String line = reader.readLine(); if (line == null) break; System.out.println(line); } } }
こちらの実行結果が以下です。
268byte(今回は非常に小さいですが)を100byteずつ分けて、ダウンロードしたものです。
途中のレンジをダウンロード中に、失敗してしまった場合に、再度同じレンジをダウンロードすることで、接続切れに対処できるかと思います。例えば5MB のレンジで順番にダウンロードする、など。
ただ、並列にダウンロードするわけではないですし、レンジごとに順番に再接続してダウンロードするので、ダウンロード速度は速くはないですね。。。ロジック次第でより安定したダウンロード処理を実現はできそうですが。
Link
http://docs.amazonwebservices.com/AmazonS3/latest/dev/RetrievingObjectUsingJava.html
異なるリージョンで同じインスタンス・AMIを作成する方法
AWS で HA を考えたときに「本当に日本のデータセンターにサーバーを立てておくだけで大丈夫か?」と不安に思われる方がいらっしゃると思います。(もちろん3つのアベイラビリティゾーン全てがダウンするということは、とても考えにくいですが。)
そこで別の国のリージョンにサーバを立てればいいのですが、異なるリージョン間ではAMIをインポートすることが、少なくとも簡単な方法では提供されていません。例えば、別のリージョン間でインスタンスを二つ立てて、ファイルシステムまるごとリモートコピーする手段がありますが、それがサポート対象となるかは、OS 次第ですし、ちゃんと問題なく動くかどうかは要確認だと思います。
そこで、原点に戻って考えたのが、今回の方法。
AMIを構築する初期段階から、複数のリージョンでインスタンスを立てて、同時にコマンド入力オペレーションを複数のリージョンにあるインスタンスに向けて実行すればいいのではないか?
下記のようなイメージです。
これを実際に試してみました。
今回はClient側は、Windows のデスクトップを利用しているので、TeraTerm を使っています。
まずは、Singapore リージョンでAmazon Linuxインスタンスを起動します。
起動後、いつも通りSSHでログインして、コマンド入力できる状態にします。
次に Tokyo リージョンでも同じバージョンのAmazon Linuxを起動します。当然中身が違うOSのAMIを選択すれば、動作は変わってきます。
二つのターミナル画面が表示されたら、ここで、「コントロール」→「ブロードキャストコマンド」を選択します。
これで、以下のような新しいウィンドウがポップアップするので、該当する複数のインスタンスのpublicDNSを選択します。
ls や pwd 等のコマンドをブロードキャストのウィンドウで実行してみると、異なるリージョンのインスタンスに、同じコマンドが実行されていますね。
この方法を使って、必要なソフトウェアのインストール、アプリケーションのデプロイ、アップデートを行います。
作業寛容後に、それぞれのリージョンでAMIに保存すれば、完了です。
この方法はあまり情報が載っていなかったので、掲載しときました。(昔は、複数のサーバーで同じスタックのソフトウェアをインストールするときは、この方法を用いてましたね・・。)
DynamoDBを使ってみる
Amazon DynamoDB を使ってみました。
使ってみた一番最初の印象は、今までのDBとは次元が異なるものだなと。
DBのメンテナンスをユーザーがする必要はなくて、フルマネージドであると。
DBのRead および Write のスループットをManagement Consoleで自由に変更できて、そのスループットの設定が高いと、それだけコストが高くなる。
Youtubeにアップされている DynamoDB の動画を見ると、胸が躍らされるというか、ワクワクしますね!
今回は、DynamoDB 上に SampleTable という名前のテーブルを作成して、AWS SDK for Javaを使って参照Queryを投げてみました。
その手順が以下の通りです。
まずは、Management Console上でDynamoDB を選択して、以下のような画面に遷移します。既に以下の画面ではテーブルがいくつか作成されていますが、お気になさらずに。
Create Tableボタンをクリックします。
Table Nameに SampleTable と入力、以下のように Primary Key を設定します。
とりあえず Read Capacity Units を 10 に、Write Capacity Units を 5 にしました。
Send notification to のところには、自分のメールアドレスを入れます。
テーブルを作成すると、SampleTableがCreating状態であることが確認できます。
作成が完了後、SampleTalbeを選択して、Explore Table をクリックします。
テーブル作成当初は、当然データがないので、データを追加してみます。
New Itemをクリックします。
Primary Key で Id の Value に 1 を入れます。
あと、追加で Name という名前の Attribute を追加して、その Value を Test にします。
Put Itemをクリックします。
画面をリフレッシュすると、データが追加されたのがわかります。
以上で、テーブルの作成とデータの追加が完了です。
Managment Console 上で確認できましたが、これを今度は Java のプログラムを使って参照してみます。
まず事前に AWS API を利用するための Credential 用のアクセスキーを AwsCredentials.properties ファイルに以下のように入力して保存します。
secretKey=XXXXXXXXXXXXXXXXXXXXXXXXXXXX accessKey=XXXXXXXXXXXXXXXXX
こちらは、AWS Managment Console 画面右上の自分のアカウント名をクリックすると出てくる「Security Credentials」リンクをクリックして、ページ遷移、AWS アカウントで再ログインすると、accessKey と secretAccessKey 情報を見ることができます。
これらをコピペして上記ファイルに保存してください。
そして、以下のJavaプログラムを実行します。上記propertiesファイルをプログラムから参照できるようにする必要があります。
import java.io.IOException; import java.util.Arrays; 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.GetItemRequest; import com.amazonaws.services.dynamodb.model.GetItemResult; import com.amazonaws.services.dynamodb.model.Key; public class DynamoDBSample { 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"; String id = "1"; GetItemRequest getItemRequest = new GetItemRequest() .withTableName(tableName) .withKey(new Key() .withHashKeyElement(new AttributeValue().withN(id))) .withAttributesToGet(Arrays.asList("Id", "Name")); //リクエストを送信して、結果を取得 GetItemResult result = dynamoDB.getItem(getItemRequest); //結果の出力 Map<String, AttributeValue> attributeList = result.getItem(); for (Map.Entry<String, AttributeValue> item : attributeList.entrySet()) { String attributeName = item.getKey(); AttributeValue value = item.getValue(); System.out.println(attributeName + " " + (value.getS() == null ? "" : "S=[" + value.getS() + "]") + (value.getN() == null ? "" : "N=[" + value.getN() + "]\n")); } } }
上記を実行した結果が以下になります。
Name S=[Test] Id N=[1]
ちゃんと取得できました!
以上ですー。
同じセキュリティグループに属するインスタンスのリストを取得する
Amazon EC2を使って、インスタンスを起動すると、デフォルトでは固定IPで起動しません。
固定IPでないと、そのインスタンス間で通信するときに、どのIPと通信すればよいかがわかりません。
そこで、今回は同じSecurity Groupに属するインスタンスの Public Dns Name を取得する方法を載せます。
AWS SDK を使えば、これが可能です。
AWS SDK for Javaを使ったサンプルコードが以下です。
public class SameSecurityGroupChecker { private static String SECURITY_GROUP_NAME = "Web"; public static void main(String[] args) throws IOException { //Credentials の設定 AWSCredentials credentials = new PropertiesCredentials(---省略---); //EC2 へのリクエストを作成 AmazonEC2 ec2 = new AmazonEC2Client(credentials); ec2.setEndpoint("ec2.ap-northeast-1.amazonaws.com"); //describeInstances実行 DescribeInstancesResult result = ec2.describeInstances(); List<Reservation> resList = result.getReservations(); List<String> publicDnsNameList = new ArrayList<String>(); //実行結果の取り出し for (Reservation res : resList) { List<String> securityGroupList = res.getGroupNames(); for(String securityGroupName : securityGroupList) { //Web という Security Group と名前が一致する場合は、該当する publicDnsName を追加 if(securityGroupName.equals(SECURITY_GROUP_NAME)) { List<Instance> list = res.getInstances(); for (Instance i : list) { publicDnsNameList.add(i.getPublicDnsName()); } } } } //該当するインスタンスのpublicDnsName出力 for(String publicDnsName : publicDnsNameList) { System.out.println(publicDnsName); } } }
以上のサンプルコードを実行すると、
ec2-54-248-xxx-xx.ap-northeast-1.compute.amazonaws.com ec2-176-32-xx-xx.ap-northeast-1.compute.amazonaws.com
というように出力されます。
もちろん、出力形式はプログラムの中で如何様にでも変えられます。
以上!