おぎろぐはてブロ

なんだかんだエンジニアになって10年以上

セキュリティグループをつくるいろいろ

アカウントを新しく使いはじめる場合や、新しいサブネットを作成したときに、ベースのセキュリティグループをとりあえずセットアップしたいパターンはよくあると思う。 とりあえず、ログインするために、会社のIPからのアクセスを許可するとか。

以下のシナリオを考えてみる。

  • ELB + WEBサーバ (EC2)+ DB (RDS) の構成
  • Public Subnet, Private Subnetを定義
  • 会社のIPからのSSHアクセスを許可する

これを元に以下の構成を考える。

f:id:i_ogi:20140504193315p:plain

NATインスタンスがいないとか、いろいろあるけど、今回のテーマではないので割愛。

aws cliコマンドライン

まず空のセキュリティグループを作成して、そこにルールを追加する形になる。

gista4f525ebfea05615851c

  • aws ec2 create-security-group コマンドを叩いてセキュリティグループを作成。セキュリティグループIDを取得。
  • aws ec2 authorize-security-group-ingressコマンドでIngressのルールを追加

コマンド実行の戻りを変数に入れて、というのがシェルスクリプトだとちょっと面倒です。楽しようと、上では--queryでGroupIdのみ取得していますが、エラーが起きた時のハンドリングができていません。

セキュリティグループを参照したり、変数の嵐となってつらいので上では1個だけ作成しています。

スクリプト (botoの場合)

gist01f77d9a4959ac1b88f4

セキュリティグループをルールに含めたセキュリティグループをつくるのはスクリプト楽ですね。

botoのboto.ec2.securitygroupは、ingress (inbound)前提となっていて、egress (outbound)も使う場合は、 ec2.connectionのauthorize_security_groupauthorize_security_group_egressあたりを利用することになると思います。

他の各言語のAWS SDKなどでも、Create Security Groupして、そこに対してルールを追加する同様の感じとなります。

CloudFormation

CloudFormerで作成した例。CloudFormerについてはクラスメソッドさんのブログが分かり易い。

gist11554515

上のようなJSONを生成して、例えばAWS CLIでキックする。

aws cloudformation create-stack --stack-name generate-security-group --template-body file://generate-securitygroup.json

このJSONでは、VPC IDが決め打ちになっているが、もちろんVPC自体も作成できる。

問題点としては、リソース名 (Security Group Name)が、CloudFormationによる自動生成となること。タグで代替できるが、タグは重複を許容するのが、場合によってはあんまりよろしくない。 あと、CloudFormationスタックにセキュリティグループが紐付いているので、スタックを削除すると削除しちゃうので、適宜、Stack Policyを設定する。

まとめ

実際に運用する場合には、VPCと組み合わせて、

  • Public Subnet、Private Subnetを定義し、NATインスタンスを起動させる
  • 会社側のIP制限ルールのため、踏み台インスタンスを用意してElastic IPを付与したい

とかいろいろあると思う。

こうなってくると、登場人物が増えてくるのと、案件によって変わってきたりして、ちゃんとがっちり構築しようとすると厳しかったりするが、できるところから、ルールが決まっている部分について自動化しておくと、完全な自動構築までの道のりもクリアになる。

boto.s3で大文字を含むバケットにアクセスする

大文字を含むバケットをUS Standardでは作ることができるのですが、そのバケットにbotoでアクセスしようとすると以下のように怒られます。

Bucket names cannot contain upper-case characters when using either the sub-domain or virtual hosting calling format.

言ってることは解りますが、じゃ、どうしたらアクセスできるのか。というと、calling_formatというオプションを明示的に指定する必要があります。

calling format とは

S3へのアクセススタイル。以下が定義されている。 詳しくは s3/connection.py を参照。

  • SubdomainCallingFormat: サブドメイン。これがデフォルト
  • VHostCallingFormat: 独自ドメインだと思う
  • OrdinaryCallingFormat: パススタイル
  • ProtocolIndependentOrdinaryCallingFormat: Ordinaryだけどscheme (http/https) を明示しない何か

大文字を含んでいる場合は OrdinaryCallingFormat を利用すればよいです。

指定方法

設定ファイル

[s3]
calling_format = boto.s3.connection.OrdinaryCallingFormat

S3Connectionのコンストラクタ

from boto.s3.connection import OrdinaryCallingFormat, S3Connection

conn = S3Connection(calling_format=OrdinaryCallingFormat)

2.13.2 以前のboto

仕様が変わっていて、設定ファイルでの指定は不可。また、クラスもしくはクラス名ではなく、インスタンスを渡す必要がある。

from boto.s3.connection import OrdinaryCallingFormat, S3Connection

conn = S3Connection(calling_format=OrdinaryCallingFormat())

Pythonで整数であることをチェックする

引数が整数であることを確認するのに、こんなコードをよく書いてるんだけど

if type(arg) not in (int, long):
    raise InvalidArgumentError

ちょっと冗長じゃないかな。先日は (int, tuple) とか書いてて危ない思いをしたりも。。

どうにかならんものかとつぶやくと、

if not isinstance(arg, (int, long)):

って教えてもらった。isinstanceでタプル渡せるの知らなかった。(2.2 or later)

なんだが、単語数は減ったけど文字数が減ってない。

あと、bookはintのサブクラスなため、isinstance([bool instance], int) は True となるけど、素直に文字列変換すると (True|False) の文字列になる点が要注意。

>>> arg = True
>>> print type(arg), arg
<type 'bool'> True
>>> type(arg) not in (int, long)
True
>>> not isinstance(arg, (int, long))
False

Python 2系での整数の扱い

int (整数), long (長整数) の違いは、もちろんサイズ。

C言語っぽいが、Pythonのintは「C言語の long 型を使って実装されており、少なくとも 32 ビットの精度があります 」で、long (長整数型)は「精度の制限がありません」。

長整数のレンジの数を扱うようなことは普通のお仕事ではあんまりないと思うのだけど、intで表現できる値でもlongに入っていることはあるのが面倒ですね。longを受け取るようにすると今度は巨大な整数がやってくる可能性がでてくるわけで。。

>>> type(1)
<type 'int'>
>>> type(1L)
<type 'long'>

整数についてはクラスツリーはこうなる

numbers.Number --- Integral -+- int --- bool
                             +- long

Python 3系での扱い

Python 3系では、longが無くなり、intに統一されている。 すっきり。

numbers.Number --- Integral --- int --- bool

なので、こう書ける

if type(arg) is not int:
    raise InvalidArgumentError

リストをn個ずつのサブリストに分割 (Python)

愚直にやればもちろんできるんだけど、パフォーマンスが気になったり、かっこよく書きたいとか思うわけです。

>>> a = range(15)
>>> a
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]
>>> [a[i:i+3] for i in range(0,15,3)]
[[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], [12, 13, 14]]

range(0, 15, 3) が [0, 3, 6, 9, 12] に展開されて、スライスしていく感じ。

zip(*[iter(s)]*n) で n 個ずつ分割

探しているとこんなのがでてくる。

iterable の、左から右への評価順序が保証されます。そのため zip(*[iter(s)]*n) を使ってデータ系列を n 長のグループにするクラスタリングすることができます。

ということで、15個の要素を持つリストを3個ずつのサブリストに分割したい場合、

>>> zip(*[iter(range(15))]*3)
[(0, 1, 2), (3, 4, 5), (6, 7, 8), (9, 10, 11), (12, 13, 14)]

となるのだそうだ。なんで?

続きを読む