AWSCLIではまったこと

awscliを普通のbashとかと同じ感覚で使ってはまったのでメモ。

一般に、bashとかだと . とか .. がパスの間に挟まってもそれを解釈した上で処理してくれる。

これは、srcやdestとして使ってもおんなじ。

# 以下の2文は同じ結果を返す
ls /var
ls /var/./log/..

しかしながら、aws s3のコマンドとして利用する場合、これらは異なる意味となる。

具体的には、S3側のprefixに . を持ち込む場合、それも有効な文字列として扱われる。

S3_BUCKET_NAME=[input-your-bucket]
ARCHIVE_FILE=./archive.zip

# こうすると、指定バケットの下で prefixに "." が付いた状態でアップロードされる
aws s3 cp ${ARCHIVE_FILE} s3://${S3_BUCKET_NAME}/${ARCHIVE_FILE}

awscli(特にs3)を使う場合、ファイルライクに扱えこそするが、ファイルシステムと等価ではないということを忘れてはならないようだ。

DynamoDBのテーブル存在チェック

DynamoDBのテーブルの存在チェックをする方法。

ListTableを利用する

一番簡単なのはListTableを実施して、その中にお目当てのテーブルがあるかどうかを見ればよい。

def exists_table(table_name, connection):
  return table_name in connection.list_tables()

…のだが、残念なことにDynamoDBのテーブルはリージョン単位で存在しており、特定のテーブルにprefixをつけて環境ごとの管理(例: test_, staging_ など)をしている場合に、これらのテーブルへのアクセス権限のみではListTableが失敗してしまう。

ListTable権限を付けれるならこれでも良いのだが、まあ、できれば権限は狭くしたい。

Responseコードを利用する

もう1つの方法として、Table.describe をコールした場合のStatusとして400(=ResourceNotFoundException)が返ってくることを確かめる方法がある。

具体的には以下の通り。

import logging
from boto.exception import JSONResponseError
from boto.dynamodb2.table import Table

def exists_table(table_name, connection):
  try:
    Table(table_name, connection=connection).describe()
    return True
  except JSONResponseError as e:
    if e.status == 400:
      return False
    # また別の例外が返ってきた
    logging.exception(e)
    return False

SQSでメッセージ受信回数による処理切り分けをしたい

SQSのVisibility Timeoutを使って再実行をするのはいいんだけれども、無効な命令が投げられてたりした場合には有限で終了して欲しいので、それを実現するにはどうすればいいのかというお話。

幾つか方法はあるけれども、主に考えられるのは2つ。

MessageのAttributeを利用する

AWS Management ConsoleでQueueのメッセージを見る場合に "count" という文字がでているので、どこかにはデータを持っているっぽかったので、それを探してみた結果、一応あった。

ただし、パラメータ名は count では無く、 ApproximateReceiveCount になっている。

しかもこの要素を使おうとした場合、メッセージ取得時にパラメータを入れないと取れない。

botoの場合は以下のようにしてメッセージを持ってくれば良い。

import boto.sqs

conn = boto.sqs.connect_to_region('ap-northeast-1')
my_queue = conn.get_queue('myqueue')
msgs = my_queue.get_messages(attributes='ApproximateReceiveCount')

if len(msgs) > 0:
  count = int(msgs[0].attributes['ApproximateReceiveCount'])

Approximate とあるので、正しい値ではないのかも知れないが、自分が考えている用途ではこの値で十分だった。

IDで判定

取得したMessageには固有のIDがあるので、これが何回出現したかをカウントする。
回数を自分で数えるので、正確な回数が保証したい場合はこれになるのかな。

FirefoxをverupするとSeleniumが動作しなくなった

Windows7/Firefox36/Python2.7/Selenium2.39で発生。

昨日まで動いていたプログラムがFirefoxのバージョンを上げた結果、以下のようなエラーメッセージを発して失敗するように。

...
File "C:\lib\Python27\lib\site-packages\selenium-2.39.0-py2.7.egg\selenium\webdriver\firefox\firefox_binary.py", line 105, in _wait_until_connectable
self.profile.path, self._get_firefox_output()))
selenium.common.exceptions.WebDriverException: Message: 'Can\'t load the profile.
...

解決策: seleniumのバージョンを上げる。
2.45が出ていたので、それにアップロードすることで問題は解消した。

cf. http://stackoverflow.com/questions/6682009/selenium-firefoxprofile-exception-cant-load-the-profile


結論: ブラウザをバージョンアップした後にseleniumが失敗したら、まずはバージョンを上げる。

matplotlibでうまく行かなかったこと

グラフを生成するライブラリとして、matplotlibを利用してみた。

かなりやりたい事が手軽にできたのだが、1つのプログラム内で大量の画像を生成しようとすると以下のような警告がでる。

/usr/lib/pymodules/python2.7/matplotlib/pyplot.py:412: RuntimeWarning: More than 20 figures have been opened. Figures created through the pyplot interface (`matplotlib.pyplot.figure`) are retained until explicitly closed and may consume too much memory. (To control this warning, see the rcParam `figure.max_num_figures`).

max_open_warning, RuntimeWarning)

これを無視して画像生成を続けていくことはできるにはできるが、どこかのタイミングでメモリを食い尽くしてプログラムが終了してしまう。

調べてみると、以下の通り。
pyplotで作ったデータをcloseせよとの事。

http://stackoverflow.com/questions/21884271/warning-about-too-many-open-figures


ギャラリーのサンプルコードに無いから手間取ったけれど、この手のオープンしたらきちんとクローズしないといけないリソースのことはどこかに書いておいて欲しい……

環境構築中にハマったこと

直近でサーバをセットアップしていてはまったネタを幾つか。
なお、両方ともテスト環境では動作していたので、調査に色々と手間取りました。
両方とも、原因は再構築時に色々バージョンが上がっていたことですが、その問題にぶち当たりそうな人がしばらくでそうなのでメモ代わりに。

fluentdのS3-pluginが動作しない。

IAMの権限や各種キー、endpoint指定は正しいが*1 td-agentサービスを立ち上げてS3に保存しようとするタイミングでエラーになる。

色々探してみると、既存の設定ファイルで指定していた s3_endpointが非推奨*2になっており、s3_regionを指定する仕様に変わっていた模様。

古いサンプルだと、s3_endpoint指定の設定ファイルになっているので、既存の仕様にあった設定を行っているかのチェックが必要。

HttpProxyを経由したaws-cliが動かない。

ネットワーク運用のプラクティスから、Proxyを経由してアクセスするようにサーバーを立てる。
その中からAWSのリソースにアクセスするためにHttpProxyを経由して、以下のコマンドを実行してみたがSSLErrorになる。

aws s3 ls

> raise SSLError(e)
> SSLError: hostname '*******' doesn't match either of 's3-[region].amazonaws.com', ...


もちろん、アクセス権限のあるキーはaws configure済み。
調べてみると、同じ現象と思われるIssueが報告されていた。Python2.6なら動くことも同じ。

https://github.com/aws/aws-cli/issues/864

Proxyの設定とか色々と疑ってみましたが、こちらの原因は、Pythonのhttplib.py*3Issue7776の対応が マージされて、botocoreの内部で使ってる urllib3/connection.py 内 self._tunnel() の呼び出しで、コメントに書いてある set_hostport が実行されなくなったことで予期した動作が変更されていることでした。

urllib3のリポジトリ見る限り該当箇所は多分修正されているので、対策としては2つです。

  • PythonのIssue7776対応前のバージョン(2.7.6以前)を使う
  • botocoreのvendoredに入っているurllib3を更新する

一応、両方試しましたが、両方とも動きました。

これにハマる(ハマった)ユースケースとして、

  • Amazon Linuxでバージョン指定なしにPython27をyumで入れる(2.7.8が入る)
  • インストールしたpython2.7.8でaws-cliを起動する

という、まああり得るかもしれない状態だったので、備忘録として残しておきます。

新しめのPythonでProxy+aws-cliを使ったシステムを構築しようとしている人*4は気をつけてください。

追記 (2015/07/13)

上記の問題については、下記のbotocoreのアップデートでurllib3が更新されたので最新版*5のbotocoreを入れてさえいれば問題は発生しなくなりました。

https://github.com/boto/botocore/commit/0ed96b7a96202b5d6c99ef9c95397395ed30e697

*1:同様の内容でawscliを利用して確認

*2:2014/12/08時点でGithubのmasterブランチから削除されていたので、非推奨というか設定方法が変更になる模様

*3:Python3系だとhttp.client.py

*4:少ない気もする...

*5:細かく言えば、botocoreの0.87以上のようですが、今日では1.1がリリースされているので、最新を入れれば問題ないはずです

PowerShellのechoにつまづいたので忘備録。

さて、以下のPowerShellのコードにどのような結果を期待するだろうか?

function Hoge([int]$num) {
  echo "Debug"
  return ($num + 1)
}

Hoge 10

PowerShellの経験が無ければ、おそらく以下のように考えるはずである。

  • Debugという文字列が標準出力される
  • Hogeは引数に1を足した結果を返す関数である

……残念(?)ながら、この認識は誤りである。
この関数の結果として、Hogeは"Debug"と11を持つ長さ2の配列を返す。


PowerShellにおいては、関数は以下の仕様を持つ(厳格ではないので、問題あれば突っ込んでください)

  • echoはWrite-Outputコマンドレットのエイリアスである
  • 関数内で出力された全ての値を(パイプラインで扱える形で)返す
  • return x は Write-Output x; return; の略記系である (PowerShell イン アクションより)

以上の理由により、上記関数の結果は長さ2の配列となるのである。


ぶっちゃけ、普通に1時間近くハマってました。
標準出力に出すだけならば、[Console]::WriteLineメソッドを使う事で問題を解決できます。
また、不要な値を返す関数を関数内で呼び出す場合は、その命令の結果を逐一 $null 向けにリダイレクトする、あるいは戻り値を変数で受ける必要があります。

関数という名前を持っていながら、単一の値を返す記法が簡単に書けないのはどうにかならなかったのかと、関数型言語を学んだ後に改めて思う今日この頃でした。