okochangの馬鹿でありがとう

ふらふら適当に世間を生きる日々でございます

awscliを使ってIAMユーザーを作成・削除する

AWSのIAMアカウントを複数作るときにManagement Consoleで作業をすると時間もかかってしまいますし、ミスも多くなります。
そういうときはコマンドラインツールを使ったりSDKを使った方が楽が出来るので、IAMユーザーの作成手順を残しておきます。

環境

aws-cli 1.2.10

作成手順

ユーザーを作成する

$ aws iam create-user --user-name foo
{
    "User": {
        "UserName": "foo", 
        "Path": "/", 
        "CreateDate": "2014-01-22T15:37:34.673Z", 
        "UserId": "AIDAJDSTS6NKEGLHA53L4", 
        "Arn": "arn:aws:iam::12345678910:user/foo"
    }
}

IAMユーザーにパスワードを作成

$ aws iam create-login-profile --user-name foo --password foo-password
{
    "LoginProfile": {
        "UserName": "foo", 
        "CreateDate": "2014-01-22T15:40:44.953Z"
    }
}

アクセスキーとシークレットキーを作成

$ aws iam create-access-key --user-name foo
{
    "AccessKey": {
        "UserName": "foo", 
        "Status": "Active", 
        "CreateDate": "2014-01-22T15:43:33.530Z", 
        "SecretAccessKey": "*******************************", 
        "AccessKeyId": "AKIAJWBY7Z4AUQQEOHPA"
    }
}

グループを作成

$ aws iam create-group --group-name foo-group
{
    "Group": {
        "Path": "/", 
        "CreateDate": "2014-01-22T15:44:57.967Z", 
        "GroupId": "AGPAIFQSBSRZLEC4Q3JVK", 
        "Arn": "arn:aws:iam::12345678910:group/foo-group", 
        "GroupName": "foo-group"
    }
}

グループにポリシーを割り当てる

$ aws iam put-group-policy --group-name foo-group --policy-document file://./user_foo_policy.json --policy-name foo-policy

グループにユーザーを割り当てる

$ aws iam add-user-to-group --user-name foo --group-name foo-group

削除

グループからユーザーを外す

$ aws iam remove-user-from-group --user-name foo --group-name foo-group

グループポリシーの削除

$ aws iam delete-group-policy --group-name foo-group --policy-name foo-policy

グループの削除

$ aws iam delete-group --group-name foo-group

アクセスキーとシークレットキーの削除

$ aws iam delete-access-key --user-name foo --access-key AKIAJWBY7Z4AUQQEOHPA

ログインパスワードの削除

$ aws iam delete-login-profile --user-name foo

ユーザーの削除

$ aws iam delete-user --user-name foo

おまけ

アカウントエイリアスを作成する

$ aws iam create-account-alias --account-alias okochang

AMIを作成し、インスタンスの情報をSimpleDBに保存するスクリプト

こんにちは@oko_changです。
年始のブログでAMIを作成時にインスタンスの情報をタグに保存するというのを書いていました。
さらに、前回AWS SDK for RubyでSimpleDBを操作する方法をまとめたので、今回はインスタンスの情報をSimpleDBに保存するのを書いておきます。

環境

f:id:okochang:20140113160305p:plain

スクリプト

こんな感じのスクリプトを作りました。
ENIとかSecondary IPをどうしたものかという感じ。

# -*- coding: utf-8 -*-
require 'net/http'
require 'aws-sdk'

## 自分自身の情報を取得する
ec2_region  = 'ec2.' + Net::HTTP.get('169.254.169.254', '/latest/meta-data/placement/availability-zone').chop + '.amazonaws.com'
sdb_region  = 'sdb.' + Net::HTTP.get('169.254.169.254', '/latest/meta-data/placement/availability-zone').chop + '.amazonaws.com'
instance_id = Net::HTTP.get('169.254.169.254', '/latest/meta-data/instance-id')
image_name  = instance_id + '-' + Time.now.strftime("%Y%m%d%H%M")
domain_name = 'ami_backup'
description = 'automatically generated image'
generation  = 3

class Ec2Base

  def initialize(ec2_region)
    @ec2 = AWS::EC2.new(ec2_endpoint: ec2_region).client
  end
 
  def get_instance_datas(instance_id)
    @ec2.describe_instances(instance_ids: ["#{instance_id}"])[:instance_index][instance_id]
  end

  def get_api_termination(instance_id)
    @ec2.describe_instance_attribute(
      :instance_id => instance_id,
      :attribute   => "disableApiTermination"
    )[:disable_api_termination][:value]
  end

  def get_shutdown_behavior(instance_id)
    @ec2.describe_instance_attribute(
      :instance_id => instance_id,
      :attribute   => "instanceInitiatedShutdownBehavior"
    )[:instance_initiated_shutdown_behavior][:value]
  end

  def create_image(instance_id, image_name, description)
    puts "Start image creation"
    @ec2.create_image(
      :instance_id => instance_id,
      :name        => image_name,
      :description => description,
      :no_reboot   => true
    )[:image_id]
  end

  def image_status(image_id)
    30.times do
      if @ec2.describe_images(image_ids: ["#{image_id}"])[:images_set][0][:image_state] == "available"
        puts "Image status became availabled"
        break
      else
        sleep 10
      end
    end
  end

  def get_deleted_image(instance_id, generation)
    image_list = @ec2.describe_images(
      :owners => ["self"],
      :filters => [{name: 'name', values: ["#{instance_id}" + "-*"]}]
    )[:images_set].sort { |a,b| b[:name] <=> a[:name] }
    delete_target = image_list[generation.to_i, image_list.length]
  end

  def delete_image(deleted_image_ids)
    unless deleted_image_ids.nil?
      puts "Remove old images"
      deleted_image_ids.each do |image|
        @ec2.deregister_image(image_id: image[:image_id])
        image[:block_device_mapping].each do |device|
          @ec2.delete_snapshot(snapshot_id: device[:ebs][:snapshot_id])
        end
      end
    else
      puts "No image to be deleted"
      exit 0
    end
  end

end

class SimpledbBase

  def initialize(sdb_region)
    @sdb = AWS::SimpleDB.new(simple_db_endpoint: sdb_region).client
  end
  
  def put_data_to_simpledb(image_id, insert_datas, domain_name)
    @sdb.put_attributes(
      :domain_name => domain_name,
      :item_name => image_id,
      :attributes => [
        { name: "instance_type",                        value: insert_datas[:instance_type]},
        { name: "subnet_id",                            value: insert_datas[:subnet_id]},
        { name: "local_ip_address",                     value: insert_datas[:local_ip_address] },
        { name: "key_name",                             value: insert_datas[:key_name] },
        { name: "security_group_ids",                   value: insert_datas[:security_group_ids].join(" ") },
        { name: "source_dest_check",                    value: insert_datas[:source_dest_check].to_s},
        { name: "disable_api_termination",              value: insert_datas[:disaable_api_termination].to_s },
        { name: "monitoring",                           value: insert_datas[:monitoring] },
        { name: "iam_instance_profile",                 value: insert_datas[:iam_instance_profile] },
        { name: "tag_set",                              value: insert_datas[:tag_set].join(" ") },
        { name: "instance_initiated_shutdown_behavior", value: insert_datas[:instance_initiated_shutdown_behavior].to_s },
        { name: "vpc_id",                               value: insert_datas[:vpc_id] }
      ])
    puts "Putted tags to simpledb"
  end
  
  def delete_data_from_simpledb(domain_name, deleted_image_ids)
    unless deleted_image_ids.nil?
      puts "Removed old image's item from simpledb"
      deleted_image_ids.each do |image|
        @sdb.delete_attributes(domain_name: domain_name, item_name: image[:image_id])
      end
    else
      puts "No image to be deleted"
      exit0
    end
  end
  
end

# AMIのタグに必要な情報を取得する
ec2  = Ec2Base.new(ec2_region)
insert_datas = Hash.new
instance_datas = ec2.get_instance_datas(instance_id)
security_group_ids = []

insert_datas[:instance_type]            = instance_datas[:instance_type]
insert_datas[:availability_zone]        = instance_datas[:placement][:availability_zone]
insert_datas[:iam_instance_profile]     = instance_datas[:iam_instance_profile][:arn]
insert_datas[:instance_type]            = instance_datas[:instance_type]
insert_datas[:key_name]                 = instance_datas[:key_name]
insert_datas[:local_ip_address]         = instance_datas[:private_ip_address]
insert_datas[:monitoring]               = instance_datas[:monitoring][:state]
insert_datas[:security_group_ids]       = instance_datas[:group_set].each {|group_set| security_group_ids << group_set[:group_id]}
insert_datas[:source_dest_check]        = instance_datas[:source_dest_check]
insert_datas[:subnet_id]                = instance_datas[:subnet_id]
insert_datas[:tag_set]                  = instance_datas[:tag_set]
insert_datas[:vpc_id]                   = instance_datas[:vpc_id]
insert_datas[:disaable_api_termination] = ec2.get_api_termination(instance_id)
insert_datas[:shutdown_behavior]        = ec2.get_shutdown_behavior(instance_id)

## AMIを作成
image_id = ec2.create_image(instance_id, image_name, description)

ec2.image_status(image_id)

## SimpleDBにインスタンスの情報を入れる
sdb = SimpledbBase.new(sdb_region)
sdb.put_data_to_simpledb(image_id, insert_datas, domain_name)

## 不要なAMIとItemを削除
deleted_image_ids = ec2.get_deleted_image(instance_id, generation)
ec2.delete_image(deleted_image_ids)
sdb.delete_data_from_simpledb(domain_name, deleted_image_ids)

まとめ

タグを使うよりは少しマシになった感はあるけど、ENIとかSecondary IPとか考えるとイマイチな感じ。

AWS SDK for RubyでSimpleDBを操作する

こんにちは@oko_changです。
自分ブログにもSimpleDBの爪痕を残しておこうと思ったので、AWS SDK for RubyでのSimpleDBの使い方をまとめておきます。
SimpleDBとは?というのは以下の記事などが参考になると思います。

環境

使い方

以下のような感じDomainを作ったりItemを追加したり削除したりが出来ました。
スクリプトとかではありません

# -*- coding: utf-8 -*-
require 'net/http'
require 'aws-sdk'
sdb_region  = 'sdb.' + Net::HTTP.get('169.254.169.254', '/latest/meta-data/placement/availability-zone').chop + '.amazonaws.com'
domain_name = "milan_no_10"

@sdb = AWS::SimpleDB.new(:simple_db_endpoint => sdb_region).client

## Domainを作成する
@sdb.create_domain(:domain_name => domain_name)

## 作成されているDomainの一覧を見る
@sdb.list_domains

## Domainのメタデータを確認する
@sdb.domain_metadata(:domain_name => domain_name)

## Itemを追加する
@sdb.put_attributes(
  :domain_name => domain_name,
  :item_name => "Keisuke Honda",
  :attributes => [{ :name => "Country", :value => "Japan"}, { :name => "Number", :value => "10"}]
  )

## 追加したItemを見る
@sdb.get_attributes(:domain_name => domain_name, :item_name => "Keisuke Honda")

## Attributeを削除する
@sdb.delete_attributes(:domain_name => domain_name, :item_name => "Keisuke Honda", :attributes => [{ :name => "Number", :value => "10" }])

## 追加したItemを削除する
@sdb.delete_attributes(:domain_name => domain_name, :item_name => "Keisuke Honda")

## 一度に複数のItemを追加する
@sdb.batch_put_attributes(:domain_name => domain_name,
  :items => [
    { :name => "Keisuke Honda", :attributes => [{ :name => "Country", :value => "Japan"}, { :name => "Number", :value => "10"}] },
    { :name => "Zvonimir Boban", :attributes => [{ :name => "Country", :value => "Croatia" }, { :name => "Number", :value => "10" }] },
    { :name => "Rui Costa", :attributes => [{ :name => "Country", :value => "Portugal" }, { :name => "Number", :value => "10" }] }
    ])

## ドメイン内のアイテムを取得する
@sdb.select(:select_expression => "select Country from #{domain_name}")

## 一度に複数のAttributeを削除する
@sdb.batch_delete_attributes(:domain_name => domain_name,
  :items => [
    { :name => "Keisuke Honda", :attributes => [{ :name => "Country", :value => "Japan" }] },
    { :name => "Rui Costa", :attributes => [{ :name => "Number", :value => "10" }] }
  ])

## 一度に複数のItemを削除する
@sdb.batch_delete_attributes(:domain_name => domain_name,
  :items => [
    { :name => "Keisuke Honda"},
    { :name => "Rui Costa"}
  ])

## Domainを削除する
@sdb.delete_domain(:domain_name => domain_name)

Clientクラスを使わずに以下のような感じもOKです。

# -*- coding: utf-8 -*-
require 'net/http'
require 'aws-sdk'
sdb_region  = 'sdb.' + Net::HTTP.get('169.254.169.254', '/latest/meta-data/placement/availability-zone').chop + '.amazonaws.com'
domain_name = "milan_no_10"

@sdb = AWS::SimpleDB.new(:simple_db_endpoint => sdb_region)
## ドメインを作成する
mydomain = @sdb.domains.create(domain_name)

## 作成したドメインを確認する
mydomain.exists?

## 作成したドメインにアイテムを登録する
mydomain.items.create("Keisuke Honda", {
  "Country" => "Japan",
  "Number" => "10"})

## 作成したドメインを削除する
mydomain.delete!

まとめ

僕が個人レベルでちょっとしたものを保存するにはお手軽でとてもイイ。

CentOS6.5でDockerを少しだけ使ってみた

試してみたいなと思いつつ手をつけられていなかったDockerを少しだけ触ってみたので、ログを残しておきたいと思います。
DockerDocker inc.社がオープンソースで公開しているコンテナ型の仮想化ソフトウェアですが、以下の記事などを見たらイメージがしやすかったです。

環境

VagrantでVMを起動したり、OSX上で直接使ったりなど色々方法があると思いますが、私の環境は以下のとおりです。

リポジトリの追加

Cent OSでDockerをインストールするためにEPELのリポジトリを追加します。

[root@centos.okochang.com ~]# rpm --import http://dl.fedoraproject.org/pub/epel/RPM-GPG-KEY-EPEL-6
[root@centos.okochang.com ~]# yum install -y http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm

Dockerのインストールと起動

リポジトリの追加が出来たら以下のようにインストールが出来ます。

[root@centos.okochang.com ~]# yum install docker-io
[root@centos.okochang.com ~]# docker -v
Docker version 0.7.2, build 28b162e/0.7.2
[root@centos.okochang.com ~]# service docker start
[root@centos.okochang.com ~]# chkconfig docker on

動作確認

起動直後はもちろん特にコンテナやイメージは存在しません。

[root@centos.okochang.com ~]# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
[root@centos.okochang.com ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE

新しいコンテナを作成してでbashを使ってみます。

[root@centos.okochang.com ~]# docker run -t -i centos /bin/bash 
bash-4.1# echo "Hello World"
Hello World
bash-4.1# yum install tree
# tree /home/
/home/

0 directories, 0 files
bash-4.1# exit
exit

再度同じコマンドでコンテナにアクセスした場合の動作を確認します。

[root@centos.okochang.com ~]# docker run -t -i centos /bin/bash
bash-4.1# tree
bash: tree: command not found
bash-4.1# exit
exit

先ほどのリンクの記事にあるように、同じコマンドでアクセスしていても新しいコンテナにアクセスするようです。
以下のコマンドでコンテナの一覧が確認出来ます。

# docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES
3625e3064dc3        centos:6.4          /bin/bash           28 seconds ago      Exit 127                                suspicious_brown    
267c7bb6fa21        centos:6.4          /bin/bash           2 minutes ago       Exit 0                                  boring_euclide 

コンテナを再利用したい場合は、リポジトリにコミットしたうえで新しくコンテナを作成するようです。
コミットをする場合はコンテナIDを指定して以下のように実行します。

[root@centos.okochang.com ~]# docker commit 267c7bb6fa21 okochang/centos
c1896ddfe6e49977edbdd60d18c0c404a66be6ed6bba0257e1a038b57109578c

コミットされるとその状態がイメージとして保存されます。

# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
okochang/centos     latest              c1896ddfe6e4        32 seconds ago      337.1 MB
centos              6.4                 539c0211cd76        9 months ago        300.6 MB
centos              latest              539c0211cd76        9 months ago        300.6 MB

以下のようにコンテナを再利用します。

[root@centos.okochang.com ~]# docker run -t -i okochang/centos /bin/bash
bash-4.1# tree /home/
/home/

0 directories, 0 files
# exit
exit

コンテナの削除を行う場合はrmコマンドにコンテナIDを指定します。

[root@centos.okochang.com ~]# docker rm 03e8a848d495
03e8a848d495

イメージの削除をする場合はrmiコマンドにイメージIDを指定します。
※イメージを使用しているコンテナが削除されている必要があります。

[root@centos.okochang.com ~]# docker rmi c1896ddfe6e4
Untagged: c1896ddfe6e49977edbdd60d18c0c404a66be6ed6bba0257e1a038b57109578c
Deleted: c1896ddfe6e49977edbdd60d18c0c404a66be6ed6bba0257e1a038b57109578c

Dockerfileというテキストファイルを作成して自分用にカスタマイズしたイメージを作成することも出来ます。
書き方はこちらに記載されていますが、そんなに難しいものではないですね。
Dockerfileを作成したらテキストファイルがあるディレクトリでビルドすればOKです。

[root@centos.okochang.com ~]# cat Dockerfile 
FROM centos

RUN yum install -y openssh openssh-client openssh-server emacs tree
EXPOSE 22
[root@centos.okochang.com ~]# docker build -t okochang/base
[root@centos.okochang.com ~]# docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             VIRTUAL SIZE
okochang/base       latest              040477d29dd1        2 minutes ago       536.5 MB
centos              6.4                 539c0211cd76        9 months ago        300.6 MB
centos              latest              539c0211cd76        9 months ago        300.6 MB

EC2インスタンスを使ってAuto Scalingを設定する

こんにちは、@oko_changです。
Twitterを眺めていたら新年早々Auto Scalingに新しい機能がリリースされているようなので、少し触ってみました。
リリース内容の詳細はこちらのリンクに記載されているので、リンクをご覧ください。

環境

Auto Scaling Command Line Toolはすでに新しい機能に対応しているので、以下の環境でテストをしました。

  • Auto Scaling Command Line Tool 1.0.61.4

Auto Scalingの基本的な使い方はこちらの記事をご覧ください。

動作確認

Auto Scalingリソース上限の確認

以下のコマンドで、Auto Scalingに関連するリソースの上限値を知ることが出来るようになりました。
私のアカウントでは以下のようにAuto Scaling Groupが20個、Launch Configurationが100個が上限である事が分かります。

$ as-describe-account-limits --headers
MAX-NUMBER-OF-AUTO-SCALING-GROUPS  MAX-NUMBER-OF-LAUNCH-CONFIGURATIONS
20                                 100

EC2インスタンスを使ったLaunch Configurationの作成

稼働中のEC2インスタンスから新しいLaunch Configurationを作成する事が出来るようになりました。
以下のようなコマンドで簡単にLaunch Configurationを作成出来ます。

$ as-create-launch-config okochang-lc --instance-id i-26c1a923
OK-Created launch config

次のコマンドで作成されたLaunch Configurationを見ると、作成元インスタンスが使用しているAMIやSecurity Groupなどと同じ設定で作成されている事が分かります。
作成元インスタンスが使用しているAMIを使ってLaunch Configurationを使用しているので、AMIから起動後にインスタンスの設定が変更されている場合は注意が必要です。

$ as-describe-launch-configs okochang-lc --headers --show-long

また、作成元インスタンスの一部を変更したい場合は、以下のようにパラメータを指定してLaunch Configurationを作成します。
例)インスタンスタイプを変更する場合

$ as-create-launch-config okochang-lc --instance-id i-26c1a923 --instance-type m1.medium
OK-Created launch config

EC2インスタンスを使ったAuto Scaling Groupの作成

先ほどはEC2インスタンスからLaunch Configurationを作成しましたが、Auto Scaling Groupを作る事も出来ます。

$ as-create-auto-scaling-group okochang-asg --instance-id i-26c1a923 --min-size 1 --max-size 4 --desired-capacity 1
OK-Created AutoScalingGroup

以下のコマンドで作成されたAuto Scaling Groupによって新しいインスタンスが作成元と同じゾーンやサブネットなどに配置されている事が分かります。

$ as-describe-auto-scaling-groups okochang-asg --headers --show-long

また、Auto Scaling Group名と同じLaunch Configurationも同時に作成されています。
以下のコマンドを実行すると、Launch Configurationも作成元インスタンスと同じ設定がされている事が分かります。

$ as-describe-launch-configs okochang-asg --headers --show-long

Auto Scaling GroupへEC2インスタンスをアタッチ

すでに作成済みのAuto Scaling GroupにEC2インスタンスをアタッチ出来るようにもなっています。
アタッチするインスタンスは以下のような点を注意します。

  • アタッチするEC2インスタンスのステータスがrunning状態であること
  • アタッチするEC2インスタンスがAuto Scaling Groupと同じAZであること
  • アタッチするEC2インスタンスがAuto Scaling Groupと同じAWSアカウントであること
  • アタッチするEC2インスタンスが他のAuto Scaling Groupのメンバーでないこと
  • Auto Scaling GroupがELBを使用している場合、アタッチするEC2インスタンスとELBが同じVPCやEC2クラシックに存在すること
  • Auto Scaling Groupへアタッチすることによって起動プロセスが停止したりはしない

実際にAuto Scaling Groupへインスタンスをアタッチするには以下のようなコマンドを実行します。

$ as-attach-instances i-26c1a923 --auto-scaling-group okochang-asg
OK-Instance(s) will be attached

以下のコマンドを実行すると、Auto Scaling Groupにアタッチされたことが分かります。

$ as-describe-auto-scaling-groups okochang-asg --headers

Launch Configurationでのブロックデバイスマッピング

Launch Configuration作成時にDeleteOnTermination、provisioned IOPs、block device mappingの設定が出来るようになっています。
これらの設定はas-create-launch-configコマンドの--block-device-mappingオプションを使用します。
オプションはkey1=value1のような形で指定し、valueの値は[snapshot-id]:[volume-size]:[true|false]:[standard|io1[:iops]]の順番で指定します。
/dev/sdcにsnap-16cba833のスナップショットを使用して100GBのEBS(DeleteOnTerminationをtrue)をProvisioned IOPSを100で作成する場合は以下のコマンドを実行します。

$ as-create-launch-config okochang-lc \
--image-id ami-79711578 \
--instance-type m1.large \
--key okochang-key \
--group sg-b72d31db \
--block-device-mapping "/dev/sdc=snap-16cba833:100:false:io1:200"

まとめ

細かいところのリリースとなりますが結構便利に使える機能が増えた印象があります。
Auto Scalingを使ってない環境からAuto Scalingの環境への移行もスムーズに出来るようになったかなーと思いました。
はやくPython版でも使用したいですね。

あけましておめでとうございます。

昨年は達成できた目標よりも出来なかった目標の方が目立ってしまうという結果でしたので、今年はより精進が必要そうです。
このブログももう少し幅広い内容に出来たら良いなと思います。



さてさて昨年末に書きかけになっていたスクリプトを放置していたので、こちらに残しておこうかと。
またまたEC2インスタンスのバックアップスクリプトですが、取得元のインスタンスに関連する情報をAMIのタグに残しておくといったものです。

# -*- coding: utf-8 -*-
require 'net/http'
require 'aws-sdk'

## 自分自身の情報を取得する
ec2_region  = 'ec2.' + Net::HTTP.get('169.254.169.254', '/latest/meta-data/placement/availability-zone').chop + '.amazonaws.com'
instance_id = Net::HTTP.get('169.254.169.254', '/latest/meta-data/instance-id')
image_name  = instance_id + '-' + Time.now.strftime("%Y%m%d%H%M")
description = "automatically generated image"
generation  = 3

class InstanaceDatas
 
  def initialize(ec2_region, instance_id)
    @ec2 = AWS::EC2.new(:ec2_endpoint => ec2_region).client
    @instance_datas = @ec2.describe_instances(
      :instance_ids => [instance_id]
    )[:instance_index][instance_id]
  end

  def get_instance_type
    @instance_datas[:instance_type]
  end

  def get_availability_zone
    @instance_datas[:placement][:availability_zone]
  end

  def get_vpc_id
    @instance_datas[:vpc_id]
  end

  def get_subnet_id
    @instance_datas[:subnet_id]
  end

  def get_local_ip_address
    @instance_datas[:private_ip_address]
  end

  def get_security_group_ids
    security_group_ids = []
    @instance_datas[:group_set].each do |group_set|
      security_group_ids << group_set[:group_id]
    end
    return security_group_ids
  end

  def get_source_dest_check
    @instance_datas[:source_dest_check]
  end

  def get_monitoring
    @instance_datas[:monitoring][:state]
  end

  def get_iam_instance_profile
    @instance_datas[:iam_instance_profile][:arn]
  end

  def get_tag_set
    @instance_datas[:tag_set]
  end

  def get_key_name
    @instance_datas[:key_name]
  end
end

class InstanceAttributes

  def initialize(ec2_region)
    @ec2 = AWS::EC2.new(:ec2_endpoint => ec2_region).client
  end

  def get_api_termination(instance_id)
    @ec2.describe_instance_attribute(
      :instance_id => instance_id,
      :attribute   => "disableApiTermination"
    )[:disable_api_termination][:value]
  end

  def get_shutdown_behavior(instance_id)
    @ec2.describe_instance_attribute(
      :instance_id => instance_id,
      :attribute   => "instanceInitiatedShutdownBehavior"
    )[:instance_initiated_shutdown_behavior][:value]
  end

end

class AmiBackup

  def initialize(ec2_region)
    @ec2 = AWS::EC2.new(:ec2_endpoint => ec2_region).client
  end

  def create_image(instance_id, image_name, description)
    @ec2.create_image(
      :instance_id => instance_id,
      :name        => image_name,
      :description => description,
      :no_reboot   => true
    )[:image_id]
  end

  def image_status(image_id)
    30.times do
      if @ec2.describe_images(:image_ids => [image_id])[:images_set][0][:image_state] == "available"
        puts "done"
        break
      else
        sleep 10
      end
    end
  end

  def create_tags(image_id, tag_datas)
    @ec2.create_tags(
      :resources => [image_id],
      :tags => [
        { :key => "instance_type", :value => tag_datas[:instance_type] },
        { :key => "subnet_id", :value => tag_datas[:subnet_id] },
        { :key => "local_ip_address", :value => tag_datas[:local_ip_address] },
        { :key => "key_name", :value => tag_datas[:key_name] },
        { :key => "security_group_ids", :value => tag_datas[:security_group_ids].join(" ") },
        { :key => "source_dest_check", :value => tag_datas[:source_dest_check].to_s },
        { :key => "disable_api_termination", :value => tag_datas[:disaable_api_termination].to_s },
        { :key => "monitoring", :value => tag_datas[:monitoring] },
        { :key => "iam_instance_profile", :value => tag_datas[:iam_instance_profile] },
        { :key => "tag_set", :value => tag_datas[:tag_set].join(" ") }
        ## 上限で以下のものはタグにつけられなかった
        # { :key => "instance_initiated_shutdown_behavior", :value => tag_datas[:instance_initiated_shutdown_behavior] }
        # { :key => "vpc_id", :value => tag_datas[:vpc_id] }
      ]
    )
  end

  def delete_images(instance_id, generation)
    image_list = @ec2.describe_images(
      :owners => ["self"],
      :filters => [{:name => 'name', :values => [instance_id + '-*']}]
    )[:images_set]
    sorted_list = image_list.sort { |a,b| b[:name] <=> a[:name] }
    delete_target = sorted_list[generation.to_i, sorted_list.length]
    unless delete_target.nil?
      puts "Remove old backup"
      delete_target.each do |image|
        @ec2.deregister_image(:image_id => image[:image_id])
        image[:block_device_mapping].each do |device|
          @ec2.delete_snapshot(:snapshot_id => device[:ebs][:snapshot_id])
        end
      end
    else
      puts "No image to be deleted"
      exit 0
    end
  end
 
end

# AMIのタグに必要な情報を取得する
ec2_instance_datas  = InstanaceDatas.new(ec2_region, instance_id)
ec2_instance_attributes = InstanceAttributes.new(ec2_region)
tag_datas = Hash.new
tag_datas[:instance_type]            = ec2_instance_datas.get_instance_type
tag_datas[:availability_zone]        = ec2_instance_datas.get_availability_zone
tag_datas[:iam_instance_profile]     = ec2_instance_datas.get_iam_instance_profile
tag_datas[:instance_type]            = ec2_instance_datas.get_instance_type
tag_datas[:key_name]                 = ec2_instance_datas.get_key_name
tag_datas[:local_ip_address]         = ec2_instance_datas.get_local_ip_address
tag_datas[:monitoring]               = ec2_instance_datas.get_monitoring
tag_datas[:security_group_ids]       = ec2_instance_datas.get_security_group_ids
tag_datas[:source_dest_check]        = ec2_instance_datas.get_source_dest_check
tag_datas[:subnet_id]                = ec2_instance_datas.get_subnet_id
tag_datas[:tag_set]                  = ec2_instance_datas.get_tag_set
tag_datas[:vpc_id]                   = ec2_instance_datas.get_vpc_id
tag_datas[:disaable_api_termination] = ec2_instance_attributes.get_api_termination(instance_id)
tag_datas[:shutdown_behavior]        = ec2_instance_attributes.get_shutdown_behavior(instance_id)

## AMIの作成
ami = AmiBackup.new(ec2_region)
image_id = ami.create_image(instance_id, image_name, description)
ami.image_status(image_id)
ami.create_tags(image_id, tag_datas)

## 不要なAMIの削除
ami.delete_images(instance_id, generation)

今年も一年よろしくお願いします。

Termination Protection 対 Auto Scaling

こんにちは@okoc_changです。
Auto Scalingを使っていると、必要なタイミングでインスタンスが起動することが出来ますし、必要がなくなったタイミングでインスタンスを削除することが出来ます。
また、EC2にはTermination Protection(disable-api-termination)というインスタンスの削除を防止する機能があります。
今回はこれを同時に使った場合の挙動を確認します。
以下のリンクには ”Auto Scaling overrides the instance termination protection attribute, if enabled”と書かれてあります。

また、以下のリンクには”Instances that are part of an Auto Scaling group are not covered by termination protection”と書かれています。

どうやらTeminate ProtectionはAuto Scalingによって上書きされてしまうようですが、実際にどんな挙動になるかを確認します。(2013年12月時点の結果です)

テスト環境構築構成

今回は構成をシンプルにするために、Auto Scalingで起動するインスタンスは一つのAZに固定しています。

f:id:okochang:20131205095329p:plain

テスト環境構築

作業前の状態確認

当然ですが、最初はELBもAuto Scalingも存在しません。

$ aws elb describe-load-balancers --region ap-northeast-1
{
    "LoadBalancerDescriptions": []
}
$ aws autoscaling describe-auto-scaling-groups --region ap-northeast-1
{
    "AutoScalingGroups": []
}

ELBの作成

最初にELBの作成します。

$ aws elb create-load-balancer \
--load-balancer-name okochang-lb \
--listeners Protocol=http,LoadBalancerPort=80,InstanceProtocol=http,InstancePort=80 \
--subnets "subnet-zzzzzzzz" \
--security-groups "sg-yyyyyyyy" \
--region ap-northeast-1
{
    "DNSName": "okochang-lb-1512932827.ap-northeast-1.elb.amazonaws.com"
}

先ほど作成したELBにヘルスチェックの設定をします。

$ aws elb configure-health-check \
--load-balancer-name okochang-lb \
--health-check Target=TCP:80,Interval=30,Timeout=10,UnhealthyThreshold=2,HealthyThreshold=5 \
--region ap-northeast-1
{
    "HealthCheck": {
        "HealthyThreshold": 5, 
        "Interval": 30, 
        "Target": "TCP:80", 
        "Timeout": 10, 
        "UnhealthyThreshold": 2
    }
}

Auto Scalingの作成

Launch Configurationを作成します

$ aws autoscaling create-launch-configuration \
--launch-configuration-name okochang-lc \
--image-id ami-xxxxxxxx \
--key-name okochang-key \
--security-groups sg-b72d31db \
--instance-type t1.micro \
--associate-public-ip-address \
--region ap-northeast-1

Auto Scaling Groupを作成します

$ aws autoscaling create-auto-scaling-group \
--auto-scaling-group-name okochang-asg \
--launch-configuration-name okochang-lc \
--min-size 2 \
--max-size 4 \
--load-balancer-names okochang-lb \
--health-check-type ELB \
--health-check-grace-period 300 \
--vpc-zone-identifier subnet-zzzzzzzz \
--region ap-northeast-1

Scale outのポリシーを作成します

$ aws autoscaling put-scaling-policy \
--auto-scaling-group-name okochang-asg \
--policy-name okochang-scale-out \
--scaling-adjustment 1 \
--adjustment-type ChangeInCapacity \
--cooldown 300 \
--region ap-northeast-1
{
    "PolicyARN": "arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:36df9388-8dce-4c18-afcd-14f79ff88636:autoScalingGroupName/okochang-asg:policyName/okochang-scale-out"
}

Scale Inのポリシーを作成します

$ aws autoscaling put-scaling-policy \
--auto-scaling-group-name okochang-asg \
--policy-name okochang-scale-in \
--scaling-adjustment -1 \
--adjustment-type ChangeInCapacity \
--cooldown 300 \
--region ap-northeast-1
{
    "PolicyARN": "arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:856b384d-495d-4d54-8df2-d03b25e86301:autoScalingGroupName/okochang-asg:policyName/okochang-scale-in"
}

Scale Outのアラームを作成します

$ aws cloudwatch put-metric-alarm \
--alarm-name okochang-scale-out-alarm \
--metric-name CPUUtilization \
--namespace AWS/EC2 \
--statistic Average \
--period 180 \
--alarm-actions "arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:36df9388-8dce-4c18-afcd-14f79ff88636:autoScalingGroupName/okochang-asg:policyName/okochang-scale-out" \
--evaluation-periods 1 \
--threshold 70 \
--comparison-operator GreaterThanThreshold \
--region ap-northeast-1

Scale Inのアラームを作成します

$ aws cloudwatch put-metric-alarm \
--alarm-name okochang-scale-in-alarm \
--metric-name CPUUtilization \
--namespace AWS/EC2 \
--statistic Average \
--period 180 \
--alarm-actions "arn:aws:autoscaling:ap-northeast-1:123456789012:scalingPolicy:856b384d-495d-4d54-8df2-d03b25e86301:autoScalingGroupName/okochang-asg:policyName/okochang-scale-in" \
--evaluation-periods 1 \
--threshold 30 \
--comparison-operator LessThanThreshold \
--region ap-northeast-1

検証スタート

現時点でのAuto Scaling Groupの状態とELBからのヘルスチェックは以下の通りです。
※Auto Scaling Group内のEC2インスタンスはどちらも正常な状態

$ aws autoscaling describe-auto-scaling-groups --region ap-northeast-1 | jq ".AutoScalingGroups[] | {Instances}"
{
  "Instances": [
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "InService",
      "HealthStatus": "Healthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-f59493f0"
    },
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "InService",
      "HealthStatus": "Healthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-0270d307"
    }
  ]
}

$ aws elb describe-instance-health --load-balancer-name okochang-lb --region ap-northeast-1 | jq ".InstanceStates"
[
  {
    "Description": "N/A",
    "State": "InService",
    "ReasonCode": "N/A",
    "InstanceId": "i-0270d307"
  },
  {
    "Description": "N/A",
    "State": "InService",
    "ReasonCode": "N/A",
    "InstanceId": "i-f59493f0"
  }
]

ここでi-0270d307のTermination Protectionを有効にしてみます

$ aws ec2 modify-instance-attribute --instance-id i-0270d307 --disable-api-termination "{\"Value\": true}" --region ap-northeast-1
{
    "return": "true"
}

$ aws ec2 describe-instance-attribute --instance-id i-0270d307 --attribute disableApiTermination --region ap-northeast-1 | jq ".DisableApiTermination"
{
  "Value": true
}

次にTermination Protectionを有効にしたインスタンスApacheを停止してから、Auto Scaling GroupとELBの分散対象となるインスタンスを確認します。
ELBからの監視が失敗してOutOfServiceとなり、HealthStatusもUnhealthyになります。

$ aws elb describe-instance-health --load-balancer-name okochang-lb --region ap-northeast-1 | jq ".InstanceStates"
[
  {
    "Description": "Instance has failed at least the UnhealthyThreshold number of health checks consecutively.",
    "State": "OutOfService",
    "ReasonCode": "Instance",
    "InstanceId": "i-0270d307"
  },
  {
    "Description": "N/A",
    "State": "InService",
    "ReasonCode": "N/A",
    "InstanceId": "i-f59493f0"
  }
]

$ aws autoscaling describe-auto-scaling-groups --region ap-northeast-1 | jq ".AutoScalingGroups[] | {Instances}"
{
  "Instances": [
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "InService",
      "HealthStatus": "Healthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-f59493f0"
    },
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "InService",
      "HealthStatus": "Unhealthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-0270d307"
    }
  ]
}

しばらくするとAuto Scaling Groupには新しいインスタンス(i-23f99926)が起動し、先ほどのインスタンスがAuto Scaling Groupから外れています。
また、ELBにも新しいインスタンスが分散対象に登録されている事が分かります。

$ aws autoscaling describe-auto-scaling-groups --region ap-northeast-1 | jq ".AutoScalingGroups[] | {Instances}"
{
  "Instances": [
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "InService",
      "HealthStatus": "Healthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-f59493f0"
    },
    {
      "LaunchConfigurationName": "okochang-lc",
      "LifecycleState": "Pending",
      "HealthStatus": "Healthy",
      "AvailabilityZone": "ap-northeast-1c",
      "InstanceId": "i-23f99926"
    }
  ]
}

$ aws elb describe-instance-health --load-balancer-name okochang-lb --region ap-northeast-1 | jq ".InstanceStates"
[
  {
    "Description": "Instance has failed at least the UnhealthyThreshold number of health checks consecutively.",
    "State": "OutOfService",
    "ReasonCode": "Instance",
    "InstanceId": "i-23f99926"
  },
  {
    "Description": "N/A",
    "State": "InService",
    "ReasonCode": "N/A",
    "InstanceId": "i-f59493f0"
  }
]

しかし以下のように先ほどのEC2インスタンスはrunningのままでTerminateはされていません。
さすがはTermination Protectionといったところでしょうか。

$ aws ec2 describe-instances --instance-id i-0270d307 --region ap-northeast-1 | jq ".Reservations[] | .Instances[] | {State}"
{
  "State": {
    "Name": "running",
    "Code": 16
  }
}

と、思いきやしばらく待っているとAuto ScalingによってTerminateされてしまいました。

$ aws ec2 describe-instances --instance-id i-0270d307 --region ap-northeast-1 | jq ".Reservations[] | .Instances[] | {State}"
{
  "State": {
    "Name": "terminated",
    "Code": 48
  }
}

まとめ

一瞬、Termination Protectionの勝利か!と思いましたが、ドキュメントに記載されているように、Auto Scalingを使っている場合はTermination Protectionすらも上書きされてしまいます。
最強の盾(Termination Protection)よりも最強の矛(Auto Scaling)が強いという結果となりました。