ようへいの日々精進XP

よかろうもん

2020 年 07 月 02 日 (木)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • 夕方, 奥さんと山王公園を 25 分ラン
  • 今日の通院でランニングの許可が出たので喜びを噛み締めつつ走った

ギョーム

  • プロジェクト管理ツールのハンズオン, 初めてちゃんと使うツールだったので, 食わず嫌いだったなー感があったけど, 実際に触ってみると「おおっ」って感じだった

ディナー

  • 鮭の塩焼き, 一番搾りを飲んだし芋焼酎も少々
  • ゴーヤの塩麹和えと豆苗とちくわの和え物, いずれもお酒のあてに最高でおいしゅうございました

Elasticsearch 検索周りTutorial (2) 〜 シェルスクリプトで scroll API を操作する 〜

tl;dr

inokara.hateblo.jp

前回からの続きです. 検索ということではありませんが, 念願のシェルスクリプトで scroll API を利用して検索結果の全件を取得してみました.

Abema TV の番組データが登録されている Elasticsearch

「M 愛すべき人がいて」が既にドキュメントとして登録されていることが判ります.

f:id:inokara:20200621225232p:plain

scroll API を利用して検索結果の全レコードを取得する

シェルスクリプト

以下のようなシェルスクリプトを用意しました.

#!/usr/bin/env bash

# refer to: https://gist.github.com/cb372/4567f624894706c70e65
# require: jq

# 環境に応じて Elasticsearch のエンドポイントとインデックス名を設定する
ES_URL='http://localhost:9200'
INDEX='abema-channel-*'

CMDNAME=$(basename $0)
if [ ! $# -eq 1  ]; then
  echo "Usage: ${CMDNAME} [size]" 1>&2
  exit 1
fi

SIZE=${1}
if [ ${1} -gt 1000 ];then
  echo 'size cannot be more than 1000.'
  exit 1
fi

# ニーズに応じて関数を実装する
function get_title {
  echo ${1} | jq -r .hits.hits[]._source.title
}

echo "=== initial request"
response=$(curl -H "Content-Type: application/json" -s "${ES_URL}/${INDEX}/_search?scroll=1m&size=${SIZE}" -d @query.json)
scroll_id=$(echo ${response} | jq -r ._scroll_id)
hits_count=$(echo ${response} | jq -r '.hits.hits | length')
hits_so_far=hits_count
# echo "Got initial response with ${hits_count} hits and scroll ID ${scroll_id}."

# TODO process first page of results here
get_title "${response}"

i=1
while [ "${hits_count}" != "0" ]; do
  echo "=== request: $i"
  response=$(curl -H "Content-Type: application/json" -s ${ES_URL}/_search/scroll -d "{ \"scroll\": \"1m\", \"scroll_id\": \"$scroll_id\" }")
  scroll_id=$(echo ${response} | jq -r ._scroll_id)
  hits_count=$(echo ${response} | jq -r '.hits.hits | length')
  hits_so_far=$((hits_so_far + hits_count))
  # echo "Got response with ${hits_count} hits (hits so far: ${hits_so_far}), new scroll ID ${scroll_id}."

  # TODO process page of results
  get_title "${response}"

  # DO NOT REMOVE
  i=$(expr $i + 1)
done
echo "Done!"

echo "Remove scroll snapshot."
curl -XDELETE -H "Content-Type: application/json" "${ES_URL}/_search/scroll" -d "
{
    \"scroll_id\" : \"$scroll_id\"
}"

このシェルスクリプトは, 以下のコードを参考にさせて頂きました.

Using the Elasticsearch scroll API · GitHub

シェルスクリプトと一緒に, 以下のような検索クエリ用の JSON ファイル (ファイル名: query.json) を用意します.

{
  "query": {
    "bool": {
      "must": [
        {
          "query_string": {
            "query": "title: \"/愛すべき人\"",
            "analyze_wildcard": true,
            "default_field": "*"
          }
        }
      ]
    }
  }
}

動かしてみる

シェルスクリプトに適当な名前 (今回は scroll.sh ) に設定, 実行権限を付与してシェルスクリプトを実行します.

$ chmod 755 scroll.sh
$ ./scroll.sh 1
=== initial request
M ~愛すべき人がいて~ #1~3ダイジェスト+#4
=== request: 1
M ~愛すべき人がいて~ #1~3ダイジェスト+#4
=== request: 24月新ドラマ】M 愛すべき人がいて #6
=== request: 3
M 愛すべき人がいて / #5 これが神様の答えだ!
=== request: 44月新ドラマ】 #5 これが神様の答えだ! / M 愛すべき人がいて
=== request: 5
【地上波みながらアベマで語ろう!】M 愛すべき人がいて#5※終了後アベマ独占配信
=== request: 6
【地上波みながらアベマで語ろう!】M 愛すべき人がいて#6※終了後アベマ独占配信
=== request: 7
Done!
Remove scroll snapshot.
{"succeeded":true,"num_freed":50}

以上

これまでは, 検索結果を全件取得する際には Python + Elasticsearch ライブラリを用意していましたが, シュッと検索結果が欲しい時に役立ちそうです.

参考

www.elastic.co

2020 年 07 月 01 日 (水)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • 完全休養
  • 夕方, 奥さんと山王公園を散歩 (一周
  • 引続き, 背中の左側に突っ張りが気になる

ギョーム

  • 引続き, 朝から異常なアクセス対応に追われて, ちっきしょーって感じ

ディナー

奥さんお得意の麻婆茄子. ホタテのカルパッチョ. 美味しゅうございました.

2020 年 06 月 30 日 (火)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • 完全休養
  • 引続き, 背中の左側に突っ張りが気になる
  • いつになったら走れるようになるんだろうか...

ギョーム

  • 朝から異常なアクセス対応に追われて, なんだか落ち着かない一日だった
  • 空いた時間に ECS Service イベントを CloudWatch Events で受けて Lambda で Slack に通知するようにした
  • コードを書いて問題解決していく感じは気持ちいいなあ...案件としては極小だけど

ディナー

ひろみのトンカツ. 今回もおろしポン酢で. ジューシーなんだけどクドくなくていくらでも食べることが出来るトンカツ. 美味しゅうございました.

あっと言う間に 6 月が終わった

  • 明日から 7 月だよ... どうしよう

CircleCI で checkout がコケる場合 (1)

tl;dr

勉強用の Github リポジトリと連携していた CircleCI プロジェクトで, 以下のようなエラーが出てソースコードのチェックアウトが失敗していた.

...
Using SSH Config Dir /home/circleci/.ssh
Cloning into '.'...
Warning: Permanently added the RSA host key for IP address 'xxx.xxx.xxx.xxx' to the list of known hosts.

Permission denied (publickey).

fatal: Could not read from remote repository.

Please make sure you have the correct access rights
and the repository exists.

exit status 128

何が起こっていたのか

Deploy Key が未登録だった.

f:id:inokara:20200629234626p:plain

なぜ, 未登録になってしまったのか解らない.

どうしたか

Projects リストから Set up Project をクリック.

f:id:inokara:20200629235137p:plain

Add Manually をクリック.

f:id:inokara:20200629235349p:plain

Start Building をクリック.

f:id:inokara:20200629235539p:plain

この状態で Github リポジトリの Deploy Key を見てみると..., 以下のように CircleCI の SSH キーが登録されていました.

f:id:inokara:20200629235821p:plain

やったー.

Using SSH Config Dir /home/circleci/.ssh
Cloning into '.'...
Warning: Permanently added the RSA host key for IP address 'xxx.xxx.xxx.xxx' to the list of known hosts.

remote: Enumerating objects: 22, done.        
remote: Counting objects: 100% (22/22), done.        
remote: Compressing objects: 100% (13/13), done.        
remote: Total 114 (delta 5), reused 20 (delta 4), pack-reused 92        
Receiving objects: 100% (114/114), 10.84 KiB | 3.61 MiB/s, done.
Resolving deltas: 100% (44/44), done.
HEAD is now at ab35411 test
HEAD is now at ab35411 test

ちゃんとチェックアウトされた.

以上

ほんとにちょっとしたことだけど, また一つ学びを得た.

2020 年 06 月 29 日 (月)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • 完全休養
  • 背中の左側に突っ張りが気になる

ギョーム

  • 検証環境に手を入れたり
  • プルリクエストした CircleCI の設定がバグってて迷惑をかけてしまってごめんなさい (改めて動作確認しなおした
  • 古いシステムの撤去

ディナー

イカと小松菜のオイスターソース炒め. イカの下処理がしっかりしていたし, 小松菜のシャキシャキ感があってビール (ノンアルコール) にピッタリだった. 美味しゅうございました.

2020 年 06 月 28 日 (日)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • 山王公園をウォーキング 5 周
  • 若干, 熱中症気味になって頭痛が...

ギョーム

  • 勉強したいことが多々あるんだけど, 終日ダラダラしていたので反省

シン・エヴァンゲリオン

  • アマゾンプライムで三作目まで見ることが出来るということで, とりあえず二作目までは見た
  • 面白そうな感じがしたけど, 年寄りに展開が早すぎて, 一作あたりあと三回くらい見たいと思う

ディナー

生姜ご飯に桜えびとジャコが入っていて, 他におかずが要らないくらいだったし, オクラの塩麹和えとかゴーヤのツナタマゴ和えも美味しゅうございました.

2020 年 06 月 27 日 (土)

アクティビティ (今までの走行 (歩行) 距離)

https://pixe.la/v1/users/inokappa/graphs/fitbit-activity

Fitibit Charge2 のアクティビティから走行 (歩行) 距離を Fitbit Web API で取得して Pixela で草生やしている. 色が濃くなれば濃くなる程強度が高い (歩行, 走行距離が長い) ということで. 実装の詳細はこちら.

ジョギング

  • おやすみ

ギョーム

  • もちろん, おやすみ

最後のアクトネル

  • もらっている分のアクトネルが今日で最後
  • 終日, ぼやーっとした頭痛があって, 夕方には強い頭痛でちょっと辛かった

父の PC サポート

  • Zoom を使えばだいたいのことは出来るつもりだったけど, 今日のサポートは難航した

ディナー

たこ焼き. ダイレックスで購入したタコが思ったよりも固くて残念だった.

AutoScaling Group のパラメータをいじるコマンドラインツール asg を更新しました

ti;dr

業務に asg を導入しようと思っていたら, 足りない機能があったので追加実装したのでメモ. まさに, 業務ドリブン OSS 開発.

github.com

バージョンは 0.0.6 です.

追加要件

1. 稼働中のインスタンス台数 + N 台を追加したい

一時的にスケーリンググループ内のインスタンス台数を N 台増やしたいという要件が発生しました.

asg には --desired オプションがあり, --desired=3 を指定して実行すると, スケーリンググループ内のインスタンスを 3 台に調整します.

$ asg --group=my-autoscaing-group --desired=3

この --desired オプションはスケーリンググループ内のインスタンス数を指定した数に調整するオプションであり, 例えば, 4 台で稼働しているスケーリンググループ内に対して, --desired=3 を指定すると 3 台に台数が減ることになります.

[ EC2 ] [ EC2 ] [ EC2 ] [ EC2 ]
    ↓
    ↓ asg --group=my-autoscaing-group --desired=3
    ↓
[ EC2 ] [ EC2 ] [ EC2 ]

ところが, 単純にインスタンスの台数を増やしたい場合, 現在稼働しているインスタンスの台数が解らないと --desired オプションを使ってインスタンスの数を調整することは出来ません.

( EC2 ) ( EC2 ) ( EC2 ) ※  EC2 が何台起動しているのか解らない...
    ↓
    ↓ 2 台追加したいんだけど...
    ↓ asg --group=my-autoscaing-group --desired=???
    ↓
( EC2 ) ( EC2 ) ( EC2 )  [ EC2 ] [ EC2 ]

バッチ処理等で, 自動的に台数を増やしたい場合等, 現在稼働しているインスタンスの台数を取得してから --desired オプションを利用してインスタンスを増やすという処理を書かなければいけない... ということで, --append オプションを追加しました.

AutoScaling Group my-autoscaing-groupインスタンスを 2 台追加したい場合, 以下のように実行します.

$ asg --group=my-autoscaing-group --append=2

実行すると, 現時点のインスタンス台数をチェックして, --append で指定した値を加算して内部的に --desired オプションに渡してインスタンスの台数を調整します.

2. スケーリンググループにインスタンスの追加が完了するまで待機する

スケーリンググループ内のインスタンス台数が Desired Capacity の値と同値になるまで待機するオプションを追加しました.

$ asg --group=my-autoscaing-group --wait=60

--wait オプションには秒数を指定します. 指定した秒数待機します. 待機している間, 以下のように出力されます.

$ asg --group=my-autoscaing-group -wait=60
Waiting...59
  Desired Capacity : 0
  Instances        : 1

状態の変化を待機する場合, 以下のように WaitUntilGroupInService のような関数がちゃんと用意されています.

docs.aws.amazon.com

しかし, 今回はこの関数が自分の要件にあっているのか判らず, 且つ, 自分の実装力が足りないので, 今回は利用しておらず, DescribeAutoScalingGroups をぐるぐる回して Desired Capacity の値とスケーリンググループ内のインスタンス台数を比較しています.

...
        waitTime, _ := strconv.Atoi(*argWait)
        writer := uilive.New()
        writer.Start()
        for i := 0; i < waitTime; i++ {
            asgGroups = getGroups(*argGroup)
            _, _, desired, instances := getDetectedSize(asgGroups)
            fmt.Fprintf(writer, "Waiting...%d\n  Desired Capacity : %d\n  Instances        : %d\n", i, desired, instances)
            if int(desired) == instances {
                fmt.Fprintln(writer, "Launched the specified number of instances.")
                writer.Stop()
                os.Exit(0)
            }
            time.Sleep(time.Second * 1)
        }
        fmt.Println("Wait time out!")
        os.Exit(1)
...

ここで, ちょっとした工夫で gosuri/uilive を利用して待機中の出力をループごとに書き換えるようにしています. 実際に以下のように出力されてます. これは, CircleCI 等の環境で実行結果の出力で画面が一杯にならないようにしたいという意図があります.

f:id:inokara:20200627202714p:plain

ちなみに, gosuri/uilive は以下のようにプログレスバーも簡単に実装出来るので, 今後, コマンドラインツールを作る際には是非利用したいなあと考えています.

https://github.com/gosuri/uilive/blob/master/doc/example.gif?raw=true

以上

この記事を書きながら, もう少し待機系の処理をちゃんと実装した方が良さそうな気がしてきました.

以上, もし, よろしければ asg をお試し頂けると幸いです.

シェルスクリプトを書く時には set -e をつけた方がいいのかな...どうなんだろう

tl;dr

とあるプロジェクトのデプロイを CircleCI で実行していて, いくつかのコマンドをラップしたBash スクリプト (以後, シェルスクリプトと記載) を実行していたんだけど, コマンドのエラーを意図せずハンドル出来ていない現象に気づいて修正したメモです. 結局のところ, シェルスクリプトを書く時には set -e をつけた方がいいのかなという結論に至った次第です.

for example

以下のようなシェルスクリプトを動かそうとした場合...

#!/bin/bash

echo "イメージをビルドします"
docker build -t foo/bar:build .
echo "イメージタグを設定します"
docker tag foo/bar:build foo/bar:xxxxxxx

docker build で失敗した場合, CI/CD 環境に限らず, シェルスクリプトとしては即座に終了してもらたいのですが, 意図に反して終了せずに次のステップ (docker tag) の処理に進んでしまいます.

解決策

その 1 〜 がんばってエラーハンドリングを実装する 〜

docker build において, Dockerfile に不備があったり, ビルド時に実行されるコマンドにエラーが発生した場合にステータスコード 1 で終了します. これをハンドリングするような処理を入れることを検討としました. ステータスコード 1 以外で終了する場合も想定して, 正常終了 (ステータスコード 0) 以外は全てエラーとしてハンドリングしたいと思います.

#!/bin/bash

echo "イメージをビルドします"
docker build -t foo/bar:build .
[ $? -eq 0 ] && echo "次の処理に進みます" || exit 1
echo "イメージタグを設定します"
docker tag foo/bar:build foo/bar:xxxxxxx
[ $? -eq 0 ] && echo "次の処理に進みます" || exit 1

echo "引続きの処理を行います"

悪くはないですが, 同じような内容を繰り返し記述する必要があり, 可読性が下がりますし, メンテナンスもし辛いと思います.

その 2 〜 set -e を付与する 〜

インターネット上を検索すると, シェルスクリプトShebang-e を付与したり, set -e を付与したりすることで, スクリプト内で実行されるコマンドが正常に終了しなければ, スクリプトを即座に終了することが出来ることが判りました.

パイプライン (1 つの 単純なコマンド からなるものでもよい)、 括弧で囲まれた サブシェル のコマンド、 ブレース (前述の シェルの文法 を参照) で囲まれたコマンドのリストの一部として実行されたコマンドの 1 つ が 0 でないステータスで終了した場合、即座に終了します... (以下, 略)

以下のように書くことで, docker builddocker tag のコマンドが失敗した時点でシェルスクリプトも終了します.

#!/bin/bash

set -e

echo "イメージをビルドします"
docker build -t foo/bar:build .
echo "イメージタグを設定します"
docker tag foo/bar:build foo/bar:xxxxxxx

echo "引続きの処理を行います"

ちょっと, 以下のようなシェルスクリプトで動作確認してみたいと思います.

#!/bin/bash

echo "step 1"
ech "step 1 が終了"

echo "step 2"
echo "step 2 が終了"

echo "step 3"
cho "step 3 が終了"

シェルスクリプトは見ての通り, echotypo した雑なスクリプトですが, これをこのまま実行すると...

$ ./test.sh
step 1
./test.sh: line 4: ech: command not found
step 2
step 2 が終了
step 3
./test.sh: line 10: cho: command not found

上記のように step 1step 3 の分まで実行されていることが判ります. これはこれで良い状況もあるかもしれませんが, CI/CD で実行する場合には, エラーが発生した段階で即座に終了して欲しいです. ということで, set -e を付与してみます.

#!/bin/bash

set -e

echo "step 1"
ech "step 1 が終了"

echo "step 2"
echo "step 2 が終了"

echo "step 3"
cho "step 3 が終了"

実行すると, 以下のように step 1 の段階でエラーとして終了しています.

$ ./test.sh
step 1
./test.sh: line 6: ech: command not found

良い感じです. ちなみに, shebang-e オプションを付与しても同じ結果を得ることが出来ます.

#!/bin/bash -e

echo "step 1"
ech "step 1 が終了"

echo "step 2"
echo "step 2 が終了"

echo "step 3"
cho "step 3 が終了"

実行すると, 結果は先述と同様に step 1 の段階で終了します.

$ ./test.sh
step 1
./test.sh: line 6: ech: command not found

注意点

再掲

パイプライン (1 つの 単純なコマンド からなるものでもよい)、 括弧で囲まれた サブシェル のコマンド、 ブレース (前述の シェルの文法 を参照) で囲まれたコマンドのリストの一部として実行されたコマンドの 1 つ が 0 でないステータスで終了した場合、即座に終了します。 ただし、失敗したコマンドが、キーワード while または until の直後のコマンドの一部である場合、予約語 if または elif に続く条件式の一部である場合、 && または || によるコマンドのリストの一部である場合 (最後の && や || の後のコマンドを除く)、 パイプラインの中の最後のコマンド以外である、 コマンドの返り値が ! で反転されている場合、のいずれかであれば、シェルは終了しません。 ERR に対するトラップが設定されていれば、シェルが終了する前に実行されます。 このオプションはシェルの環境と各サブシェルの環境に別々に適用され (前述の コマンド実行環境 を参照)、 サブシェルはサブシェル内の全てのコマンドを実行する前に終了するかもしれません。

set -e オプションの挙動について, man ページの抜粋を再掲させて頂きます.

個人的に分かりづらい (読みにくい) ですが, set -e オプションが付与されていても, 幾つかの条件では, 実行されるコマンドがエラーとなっても, シェルスクリプトを終了しないようです.

実例 (1)

以下のようなシェルスクリプトだと, set -e が付与されていても最後まで処理が行われました.

#!/bin/bash

set -e

if [ "$(cho 'test')" == "0" ];then
  echo "success"
else
  echo "failure"
fi

例のごとく, echochotypo しています. 実際に実行してみると, 以下のように出力されて, シェルスクリプト全体の挙動としては正しいのかなと思います.

$ ./test.sh
./test.sh: line 5: cho: command not found
failure

実例 (2) 〜 grep コマンド 〜

例えば, シェルスクリプト内で, 何かしらの条件判断に grep を使っている場合, set -e を利用していると意図しない結果になることがあります.

grep コマンドにおいて, 引数で指定した文字列が含まれている場合, ステータスコード 0 が返ってきます. 逆に含まれていない場合には, ステータスコード 1 が返ってきます.

$ bash -c 'echo "foo" | grep "foo"; echo $?'
foo
0
$ bash -c 'echo "foo" | grep "bar"; echo $?'
1

この挙動を利用して, 以下のようなシェルスクリプトを書きました.

#!/bin/bash

echo "foofoo" | grep "$1"
if [ $? == 0 ];then
  echo "処理を継続します..."
else
  echo "処理を終了します"
fi

これを, このまま実行すると, 以下のように「処理を終了します」が出力されます.

bash -c './test.sh bar; echo $?'
処理を終了します
0

set -e オプションを付与すると, 前回とは異なる結果となります.

$ bash -c './test.sh bar; echo $?'
1

これを回避する為 (set -e が付与されていても, 正しく条件判断が行われるようにする) には, 以下のようにシェルスクリプトを書きます.

set -e

if echo "foo" | grep -q "$1" ;then
  echo "処理を継続します..."
else
  echo "処理を終了します"
fi

実行すると, 以下のように出力されました.

$ bash -c './test.sh foo; echo $?'
処理を継続します...
0
$ bash -c './test.sh bar; echo $?'
処理を終了します
0

尚, set -e を使っている際の意図しない挙動への対処については, 以下のブログ記事が参考になりました. ありがとうございます.

sousaku-memo.net

以上

set -e#!/bin/bash -e について, ちゃんと使ったことなかったのですが, 実際にトラブったり, 触ってみたりすることで挙動を把握出来て良かったです. また, bash の奥深さの少しだけ垣間見ることが出来た気がします.

以上, ドキュメントをちゃんと読め案件でした.