awscliのec2 wait instance-status-okコマンドで遭遇した思わぬ落とし穴と回避方法

2017.02.24

AWSCLIを使う上で、waitコマンドは本当に便利です(挨拶

例えば下記のようなコマンドラインでEC2インスタンスを起動すると、プロンプトは数秒で戻ってきます。

$ aws ec2 start-instances \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA
{
    "StartingInstances": [
(以下略)

もちろん、この秒数でEC2インスタンスが起動したわけではないので、
別のコマンドやマネジメントコンソール(MC)を使って状態を確認したり、waitコマンドを使って起動するまで待ったりするわけです。

$ aws ec2 wait instance-status-ok \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA

この時わたしはinstance-status-ok、つまり

「指定したインスタンスIDが 全て ステータス : OK」になるまで待ってくれる、

と思っていたのですが、実際はそうではなかったのでした。

落とし穴 : instance-status-okはrunningのインスタンスしか見てくれない

例えばいま、こういう状態になっていたとして(インスタンスID,ステータスの順に表示)、

$ aws ec2 describe-instances \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA \
    --query Reservations[].Instances[].[InstanceId,State.Name] --output text
i-020b7ddf8FUGAFUGA stopped
i-08e952a8eHOGEHOGE running

wait instance-status-okは速攻で戻ってきてしまいます。

$ time aws ec2 wait instance-status-ok \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA

real    0m1.227s
user    0m0.464s
sys 0m0.358s

実はstoppedのインスタンスは、チェックの対象から外れています。
なので一見、stoppedのインスタンスもステータス : OKと判断されているかのような挙動になってしまいます。

この挙動を理解していないと、
例えば「全てのインスタンスの起動をまって次の処理を行う」というスクリプトを組んだときに、たまたま一つインスタンスの起動に失敗したという場合で想定外のことになりそうです。

$ aws ec2 wait instance-status-ok \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA && \
    echo "ALL OK! DO NEXT!!"

全然okじゃないですよね…

回避策 1 : 二段構え

簡単に思いつくのは、「先にinstance-runningでチェックする」という方法です。

$ aws ec2 wait instance-running \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA && \
  aws ec2 wait instance-status-ok \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA && echo "ok."

これだと、まずrunningステータスになるまで待ってから次に進むため確実です。

わたしも最初、この方法で回避していたのですが、
ただ2回コマンドを打つ必要がある(スクリプトだと2行になる)ので、場合によっては美しくないかもしれません。

回避策 2 : --include-all-instances

この挙動をいろいろ調べているうちに、--include-all-instancesというオプションの存在に気づきました。

$ aws ec2 wait instance-status-ok help
(略)
     --include-all-instances | --no-include-all-instances (boolean)
        When true , includes the health status for all instances. When false,
        includes the health status for running instances only.

        Default: false
(以下略)

つまりこのオプションを指定すれば、指定したインスタンスが全て(runningか否かにかかわらず)対象になるわけです。

$ aws ec2 describe-instances \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA \
    --query Reservations[].Instances[].[InstanceId,State.Name] --output text
i-020b7ddf8FUGAFUGA stopped
i-08e952a8eHOGEHOGE running
$ time aws ec2 wait instance-status-ok \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA

real    0m1.302s
user    0m0.478s
sys 0m0.486s
$ time aws ec2 wait instance-status-ok \
    --include-all-instances \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA
^C

real    0m12.114s
user    0m0.477s
sys 0m0.440s

ちょっと分かりづらいですが、
対象の2つのインスタンスのうち片方がstoppedの場合、
1回目のaws ec2 waitだとすぐにプロンプトが戻ってくるのですが、
2回めの--include-all-instancesを指定した方はCtrl-Cするまで戻ってきません。

もちろんCtrl-Cで中断せずに、そのまま別ターミナルやMCから起動してやれば、 起動が完了し、ステータスチェックが2/2で合格になった時点でプロンプトが戻ってきます。

ちなみに(ちょっとだけ深掘り)

御存知の通り、AWSCLIのwaitコマンドは内部では単に定期的にAPIを叩きにいって、その応答を見て待つ/終了するを判断しているだけです。

helpにも冒頭にこう書いてあります。

NAME
     instance-status-ok -

DESCRIPTION
     Wait   until  JMESPath  query  InstanceStatuses[].InstanceStatus.Status
     returns ok for all elements when polling with describe-instance-status.
     It  will  poll  every  15  seconds  until  a  successful state has been
     reached. This will exit with a return  code  of  255  after  40  failed
     checks.

15秒間隔でdescribe-instance-statusを投げている、とあります。

ということはつまり、describe-instance-statusにも同じようなオプションがあるということですね。

$ aws ec2 describe-instance-status help
(略)
DESCRIPTION
     Describes the status of one or more instances. By default, only running
     instances are described, unless specified otherwise.
(略)
     --include-all-instances | --no-include-all-instances (boolean)
        When true , includes the health status for all instances. When false,
        includes the health status for running instances only.

        Default: false
(以下略)

「同じような」どころか、まったく同じオプションがありました :)

$ aws ec2 describe-instance-status \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA \
    --query InstanceStatuses[].[InstanceId,InstanceState.Name,SystemStatus.Status,InstanceStatus.Status]
[
    [
        "i-08e952a8eHOGEHOGE",
        "running",
        "ok",
        "ok"
    ]
]

$ aws ec2 describe-instance-status \
    --include-all-instances \
    --instance-ids i-08e952a8eHOGEHOGE i-020b7ddf8FUGAFUGA \
    --query InstanceStatuses[].[InstanceId,InstanceState.Name,SystemStatus.Status,InstanceStatus.Status]
[
    [
        "i-020b7ddf8FUGAFUGA",
        "stopped",
        "not-applicable",
        "not-applicable"
    ],
    [
        "i-08e952a8eHOGEHOGE",
        "running",
        "ok",
        "ok"
    ]
]