レタスのかわをぜんぶむく

ぜんぶむきます

2020年振り返り

心を2020年に置いてきてしまって、未だに年末の気分が抜けていない。
2020年ときちんとお別れをするために色々思い出しながら振り返りと今後についてまとめる。

副業

2020年は1月から4月にかけて本業で色々考えさせられることがあって、今と違う環境や違うタイプの仕事に携わってみたいな〜と考えていた折に、タイミングよく友人の伝手で仕事の紹介がありそのうちいくつかの案件に参加させてもらった。

あらかじめ決まった範囲の対応を行って完了とするようなスポットタイプの案件を3件と、業務委託契約を結んで時間報酬で継続的にコミットするような副業を1社関わることができて、ちょうどよいくらいの割合で普段使わないインフラや開発言語、その他ツールに関わることができて知的好奇心や技術的な視野の広さや深さを得ることができて非常に良かった。

一方で副業の内容や契約にもよるものの、本業だけをやっている場合と比べて確実に思考リソースや時間がとられてしまうため、諸々コントロールが難しくなるのと本業と副業で忙しいタイミングの波が重なると非常に厳しい状態になるため注意が必要と感じた。

今現在受けている内容についてキリのよいところまで対応が終わったら、受ける仕事を絞って読書や趣味の開発など自己研鑽方向にある程度時間を割けるようにしたいと考えている。

コーヒー

春頃に古くからの友人に手動のコーヒーミルをもらって、そこから諸々買い揃えて自分で豆を挽いてコーヒーを飲むようになった。
最初はなんとなく挽いた豆で抽出して、おいしい!たのしい!大好き!
のような状態だったのが、抽出時の温度に再現性を持たせるために温度調節機能つきのケトルを買ったり、手動のミルだと挽く度に少しずつ挽目が変わってしまうので電動のミルを買ったりと分かりやすく沼に浸かってしまった。

f:id:uskey:20210113014829j:plain:w500

料理と同様に工夫したり変更した部分のフィードバックが自分の味覚や嗅覚で戻ってくるため、うまくいったときの満足度が非常に高いのと、ある程度のところまで行くと既製品との競争が始まるのでやりこみの幅が圧倒的に広い。スタバに勝ちたい。

開発

本業だと開発というよりドキュメントをわっと作ったり、調査系タスクが多かったためがっつり手を動かしてコードを書いたのは副業と検証系のコードがメインだった。
その過程でいくつかブログ記事にまとめたり、OSSにも小粒なPRを出してマージされた。

github.com

github.com

github.com

ただ、例によって細かな不具合修正であったりドキュメント追加みたいなものが多かったため、2021年は小さくても完結した機能を提供するようなツールを作ったりしていこうと思う。

引っ越し

コロナ禍で2020/02頃からほぼフルリモートで在宅勤務になっていたものの、住んでた家が1Kで流石に厳しくなってきたので夏頃からゆるゆると物件探しを開始した。
予算に収まってかつ条件が良いところを探すと全然見つからないため11月までずれ込んだ上、引越し業者を探したり粗大ゴミを新居に一部持っていったりと超バタバタになったものの12月に引っ越しが終わった。
(写真は旧居)

f:id:uskey:20210113014835j:plain:w500

面積が倍以上になったため端的にめちゃめちゃ過ごしやすく、あと2~3年くらいは在宅勤務で戦えそうな感じになった。早くやればよかったですねとは思うものの、副業である程度蓄えができたから踏み切れた部分もあるので難しい。
部屋が広くなったのに合わせて家具家電の追加が必要になって出費がすごいことになったため、意識的に生活レベルを落としている。つらい。

まとめ

他にも自炊に目覚めてある日いきなり野菜を5kgとか買って持て余したり、ハチャメチャにかわいい甥っ子が生まれて名実ともにおじさんになったり、尊敬していた先輩・同僚が2連続で辞めちゃったり、気の迷いで某仮想通貨に突っ込んだチョット万円が3倍になった後にもとに戻ったのを見てゲラゲラ笑ったり、思い返すと割と色々なことがあってコロナ禍でこれまで通りに外出できなくなった割には濃く充実した1年だった。

それで、色々なことをする機会や縁に恵まれた1年で実際色々なことに挑戦してみた反面、何かこれといったひとかたまりの進捗や成果は出なかったように感じるので、2021年は色々な挑戦をしつつも進捗を発散させるのではなく指向性を持って積み上げるような1年にしたいと思う。

がんばるぞ。

AmazonLinuxでカーネルバージョンをダウングレードする

脆弱性の検証に利用する場合や、そこまで積極的な用途でなくとも
一部ソフトウェアでは互換性のあるカーネルバージョンが明確に指定されているケースがある。

その場合現在より古いカーネルバージョンを明示的に指定して利用したい。
手順を調べたのでまとめる。

手順

  • 古いカーネルを導入する
  • デフォルトカーネルを差し替えて再起動する
  • (Optional) アップデート対象からカーネル関連モジュールを除外する

古いカーネルを導入する

まずは現時点でのカーネルバージョンを確認する。

# uname -a
Linux ip-172-31-23-7.ap-northeast-1.compute.internal 4.14.193-149.317.amzn2.x86_64 #1 SMP Thu Sep 3 19:04:44 UTC 2020 x86_64 x86_64 x86_64 GNU/Linu

# grubby --default-kernel
/boot/vmlinuz-4.14.193-149.317.amzn2.x86_64

現状では4.14.193-149.317.amzn2が導入されているようだ。

今回はカーネルバージョンkernel-4.9.85-47.59.amzn2を指定して導入する。

# yum install kernel-4.9.85-47.59.amzn2
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                          | 3.7 kB  00:00:00
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ kernel.x86_64 0:4.9.85-47.59.amzn2 を インストール
--> 依存性解決を終了しました。

依存性を解決しました

===========================================================================================================================================================================
 Package                              アーキテクチャー                     バージョン                                       リポジトリー                              容量
===========================================================================================================================================================================
インストール中:
 kernel                               x86_64                               4.9.85-47.59.amzn2                               amzn2-core                                17 M

トランザクションの要約
===========================================================================================================================================================================
インストール  1 パッケージ

総ダウンロード容量: 17 M
インストール容量: 75 M
Is this ok [y/d/N]: y
Downloading packages:
Delta RPMs disabled because /usr/bin/applydeltarpm not installed.
kernel-4.9.85-47.59.amzn2.x86_64.rpm                                                                                                                |  17 MB  00:00:00
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  インストール中          : kernel-4.9.85-47.59.amzn2.x86_64                                                                                                           1/1
  検証中                  : kernel-4.9.85-47.59.amzn2.x86_64                                                                                                           1/1

インストール:
  kernel.x86_64 0:4.9.85-47.59.amzn2

完了しました!

(必要であれば) デフォルトカーネルを差し替える

AMIの仮想化タイプによってはカーネルをインストール後に手動でデフォルトカーネルを変更する必要がある。
(HVMではなくPVと呼ばれるタイプの仮想化タイプでは手動での変更が必要)

# grubby --default-kernel
/boot/vmlinuz-4.9.85-47.59.amzn2.x86_64

今回はインストールされたカーネルがデフォルトカーネルになっているため、そのまま再起動。

# reboot now

(Optional) アップデート対象からカーネル関連モジュールを除外する

検証などに利用するために短期的に利用するだけであれば以下の作業は不要。
本来であれば脆弱性などの問題があるため推奨されない。あくまで自己責任で。

現状は4.14.193-149.317.amzn24.9.85-47.59.amzn2がインストール済みとなっている。

最新カーネルのバージョンが上がった際にyum updateでカーネルが更新されないように設定を行う。
検証のために新しい方(4.14.193-149.317.amzn2)を削除する。

# uname -a
Linux ip-172-31-23-7.ap-northeast-1.compute.internal 4.9.85-47.59.amzn2.x86_64 #1 SMP Wed Mar 14 21:51:55 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

# grubby --default-kernel
/boot/vmlinuz-4.9.85-47.59.amzn2.x86_64

# ls -al /boot/
合計 75976
dr-xr-xr-x  4 root root     4096 10月 23 18:29 .
dr-xr-xr-x 18 root root      257 10月 23 18:21 ..
-rw-r--r--  1 root root      174  9月  3 19:06 .vmlinuz-4.14.193-149.317.amzn2.x86_64.hmac
-rw-r--r--  1 root root      170  3月 14  2018 .vmlinuz-4.9.85-47.59.amzn2.x86_64.hmac
-rw-------  1 root root  2829664  9月  3 19:06 System.map-4.14.193-149.317.amzn2.x86_64
-rw-------  1 root root  2900199  3月 14  2018 System.map-4.9.85-47.59.amzn2.x86_64
-rw-r--r--  1 root root   120243  9月  3 19:06 config-4.14.193-149.317.amzn2.x86_64
-rw-r--r--  1 root root   104187  3月 14  2018 config-4.9.85-47.59.amzn2.x86_64
drwxr-xr-x  3 root root       17  9月 21 21:10 efi
drwx------  5 root root       79 10月 23 18:29 grub2
-rw-------  1 root root 31826017  9月 21 21:12 initramfs-4.14.193-149.317.amzn2.x86_64.img
-rw-------  1 root root 28205290 10月 23 18:29 initramfs-4.9.85-47.59.amzn2.x86_64.img
-rw-r--r--  1 root root   669043  9月 21 21:12 initrd-plymouth.img
-rw-r--r--  1 root root   235508  9月  3 19:07 symvers-4.14.193-149.317.amzn2.x86_64.gz
-rw-r--r--  1 root root   202798  3月 14  2018 symvers-4.9.85-47.59.amzn2.x86_64.gz
-rwxr-xr-x  1 root root  5727184  9月  3 19:06 vmlinuz-4.14.193-149.317.amzn2.x86_64
-rwxr-xr-x  1 root root  4936688  3月 14  2018 vmlinuz-4.9.85-47.59.amzn2.x86_64

# yum remove kernel-4.14.193-149.317.amzn2
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
依存性の解決をしています
--> トランザクションの確認を実行しています。
---> パッケージ kernel.x86_64 0:4.14.193-149.317.amzn2 を 削除
--> 依存性解決を終了しました。

依存性を解決しました

===========================================================================================================================================================================
 Package                             アーキテクチャー                    バージョン                                           リポジトリー                            容量
===========================================================================================================================================================================
削除中:
 kernel                              x86_64                              4.14.193-149.317.amzn2                               installed                              101 M

トランザクションの要約
===========================================================================================================================================================================
削除  1 パッケージ

インストール容量: 101 M
上記の処理を行います。よろしいでしょうか? [y/N]y
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
  削除中                  : kernel-4.14.193-149.317.amzn2.x86_64                                                                                                       1/1
  検証中                  : kernel-4.14.193-149.317.amzn2.x86_64                                                                                                       1/1

削除しました:
  kernel.x86_64 0:4.14.193-149.317.amzn2

完了しました!

yum list updatesで削除した最新カーネルがリストされることを確認する。

# yum list updates
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
更新したパッケージ
awscli.noarch                                                                         1.18.147-1.amzn2.0.1                                                       amzn2-core
ec2-net-utils.noarch                                                                  1.4-3.amzn2                                                                amzn2-core
kernel.x86_64                                                                         4.14.198-152.320.amzn2                                                     amzn2-core
kernel-tools.x86_64                                                                   4.14.198-152.320.amzn2                                                     amzn2-core
p11-kit.x86_64                                                                        0.23.21-2.amzn2.0.1                                                        amzn2-core
p11-kit-trust.x86_64                                                                  0.23.21-2.amzn2.0.1                                                        amzn2-core
pam.x86_64                                                                            1.1.8-23.amzn2.0.1                                                         amzn2-core
python2-botocore.noarch                                                               1.18.6-1.amzn2.0.1                                                         amzn2-core
python2-rpm.x86_64                                                                    4.11.3-40.amzn2.0.5                                                        amzn2-core
rpm.x86_64                                                                            4.11.3-40.amzn2.0.5                                                        amzn2-core
rpm-build-libs.x86_64                                                                 4.11.3-40.amzn2.0.5                                                        amzn2-core
rpm-libs.x86_64                                                                       4.11.3-40.amzn2.0.5                                                        amzn2-core
rpm-plugin-systemd-inhibit.x86_64                                                     4.11.3-40.amzn2.0.5                                                        amzn2-core

初期に入っていた4.14.193-149.317.amzn2から4.14.198-152.320.amzn2に上がってしまっているが
古いカーネルを利用している状態だと、kernel関連のモジュールがアップデート対象に含まれることが確認できた。

このままだとyum updateしたタイミングでカーネルのバージョンが上がってしまうため
/etc/yum.confの[main]配下にexclude=kernel*を追加し、アップデート対象から除外する。

[main]
cachedir=/var/cache/yum/$basearch/$releasever
keepcache=0
debuglevel=2
logfile=/var/log/yum.log
exactarch=1
obsoletes=1
gpgcheck=1
plugins=1
installonly_limit=3
distroverpkg=system-release
timeout=5
retries=7
exclude=kernel* # ←ここ

#  This is the default, if you make this bigger yum won't see if the metadata
# is newer on the remote and so you'll "gain" the bandwidth of not having to
# download the new metadata and "pay" for it by yum not having correct
# information.
#  It is esp. important, to have correct metadata, for distributions like
# Fedora which don't keep old packages around. If you don't like this checking
# interupting your command line usage, it's much better to have something
# manually check the metadata once an hour (yum-updatesd will do this).
# metadata_expire=90m

# PUT YOUR REPOS HERE OR IN separate files named file.repo
# in /etc/yum.repos.d

更新後再度確認を行う。

# yum list updates
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
amzn2-core                                                                                                                                          | 3.7 kB  00:00:00
amzn2extra-docker                                                                                                                                   | 3.0 kB  00:00:00
更新したパッケージ
awscli.noarch                                                                          1.18.147-1.amzn2.0.1                                                      amzn2-core
ec2-net-utils.noarch                                                                   1.4-3.amzn2                                                               amzn2-core
p11-kit.x86_64                                                                         0.23.21-2.amzn2.0.1                                                       amzn2-core
p11-kit-trust.x86_64                                                                   0.23.21-2.amzn2.0.1                                                       amzn2-core
pam.x86_64                                                                             1.1.8-23.amzn2.0.1                                                        amzn2-core
python2-botocore.noarch                                                                1.18.6-1.amzn2.0.1                                                        amzn2-core
python2-rpm.x86_64                                                                     4.11.3-40.amzn2.0.5                                                       amzn2-core
rpm.x86_64                                                                             4.11.3-40.amzn2.0.5                                                       amzn2-core
rpm-build-libs.x86_64                                                                  4.11.3-40.amzn2.0.5                                                       amzn2-core
rpm-libs.x86_64                                                                        4.11.3-40.amzn2.0.5                                                       amzn2-core
rpm-plugin-systemd-inhibit.x86_64                                                      4.11.3-40.amzn2.0.5                                                       amzn2-core

kernel.x86_64,kernel-tools.x86_64がリストされなくなった。
(本来は良くないが)これで古いカーネルを利用し続けることができる。

Redashでクエリにタイムアウトを設定する

要約

  • Redashのクエリ実行にはタイムアウトを設定できる
    • 手動実行 : REDASH_ADHOC_QUERY_TIME_LIMIT (v3.0.0~)
    • 定期実行 : REDASH_SCHEDULED_QUERY_TIME_LIMIT (v8.0.0~)
  • 本家ドキュメント側の記載が漏れていたのでprを出した

環境

  • macOS 10.15.5
  • docker desktop community 2.4.0.0 (48506)
  • Redash 8.0.0

手順

github.com

例によって、カック先生(id:kakku22)のredash-hands-onを利用する。
ただ、今回はクエリのタイムアウトについて検証を行いたいため下記について編集する。

環境変数の設定

docker-compose.ymlを参照すると分かるがRedash向けの環境変数がenvファイルにまとめられている。
今回追加する設定値はすべてRedashに関係する環境変数なのでここに追加すればOK。

追加するのは下記の3行

REDASH_ADDITIONAL_QUERY_RUNNERS=redash.query_runner.python
REDASH_ADHOC_QUERY_TIME_LIMIT=5
REDASH_SCHEDULED_QUERY_TIME_LIMIT=10

1行目でデータソースにPythonを追加し
2行目,3行目でそれぞれ手動実行, 定期実行クエリのタイムアウトを設定している。

起動時の設定

README.mdにはdocker-compose up -dと記載されているが
今回は実行時に各コンテナが出力するログを確認したいのでdocker-compose upと実行する。

データソースの追加

初回ログインまでチュートリアルにそって確認後はデータソースにPythonを追加する。
Modules to import prior to running the scriptには、datetime, timeを追加する必要がある。
(前に書いた記事で少し触れたが、RedashのPythonには何かと制限が多い)

実行するクエリは以下

import time
from datetime import datetime, timedelta
  
td_jst = timedelta(hours=9)
sleep  = 4
  
print("start")
time.sleep(sleep)
result = {}
add_result_column(result, 'finished_at', '', 'string')
add_result_row(
    result,
    {'finished_at' : (datetime.now() + td_jst).strftime('%Y-%m-%d %H:%M:%S')}
)
print("finish")

実行すると下記のような結果が得られた

クエリの実行時間からもsleepが効いていることが確認できる。

ADHOC_QUERY

まずは手動実行クエリのタイムアウト動作を確認する。
手動実行クエリのタイムアウトは5秒なのでsleepを6秒に設定して実行する。
実行後ブラウザには下記のようなエラーが表示されクエリが中断されたことが分かる。

同じタイミングのdocker-compose側のログは下記。 adhoc_worker_1で例外が発生していることがわかる。

...
adhoc_worker_1      | [2020-10-23 02:59:45,953][PID:1][WARNING][MainProcess] Soft time limit (5s) exceeded for redash.tasks.execute_query[6a76c71c-5a52-4605-b27a-ae5d570e617b]
adhoc_worker_1      | [2020-10-23 02:59:45,954][PID:24][INFO][ForkPoolWorker-3] task_name=redash.tasks.execute_query task_id=6a76c71c-5a52-4605-b27a-ae5d570e617b task=execute_query query_hash=60a9cad8d5384755b62d1971f9d914f0 data_length=None error=[<class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()]
adhoc_worker_1      | [2020-10-23 02:59:45,957][PID:24][ERROR][ForkPoolWorker-3] Task redash.tasks.execute_query[6a76c71c-5a52-4605-b27a-ae5d570e617b] raised unexpected: QueryExecutionError("<class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()",)
adhoc_worker_1      | Traceback (most recent call last):
adhoc_worker_1      |   File "/usr/local/lib/python2.7/site-packages/celery/app/trace.py", line 385, in trace_task
adhoc_worker_1      |     R = retval = fun(*args, **kwargs)
adhoc_worker_1      |   File "/app/redash/worker.py", line 84, in __call__
adhoc_worker_1      |     return TaskBase.__call__(self, *args, **kwargs)
adhoc_worker_1      |   File "/usr/local/lib/python2.7/site-packages/celery/app/trace.py", line 648, in __protected_call__
adhoc_worker_1      |     return self.run(*args, **kwargs)
adhoc_worker_1      |   File "/app/redash/tasks/queries.py", line 436, in execute_query
adhoc_worker_1      |     scheduled_query).run()
adhoc_worker_1      |   File "/app/redash/tasks/queries.py", line 382, in run
adhoc_worker_1      |     raise result
adhoc_worker_1      | QueryExecutionError: <class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()
...

SCHEDULED_QUERY

次には定期実行クエリの動作を確認する。
画面左下のRefresh Scheduleから定期実行の頻度を設定できる。
まずは設定時間が6秒のまま定期実行クエリのインターバルを毎分に設定して動作することを確認する。

...
server_1            | [2020-10-23 02:59:46,163][PID:13][INFO][metrics] method=GET path=/api/jobs/6a76c71c-5a52-4605-b27a-ae5d570e617b endpoint=job status=200 content_type=application/json content_length=198 duration=1.07 query_count=2 query_duration=2.51
nginx_1             | 172.23.0.1 - - [23/Oct/2020:02:59:46 +0000] "GET /api/jobs/6a76c71c-5a52-4605-b27a-ae5d570e617b HTTP/1.1" 200 183 "http://localhost/queries/1/source" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.111 Safari/537.36" "-"
scheduler_1         | [2020-10-23 03:00:00,308][PID:22][INFO][Beat] Scheduler: Sending due task refresh_queries (redash.tasks.refresh_queries)
scheduler_1         | [2020-10-23 03:00:00,310][PID:1][INFO][MainProcess] Received task: redash.tasks.refresh_queries[543a01a0-ec5e-4708-b191-1f171619d3e5]
scheduler_1         | [2020-10-23 03:00:00,311][PID:33][INFO][ForkPoolWorker-12] task_name=redash.tasks.refresh_queries task_id=543a01a0-ec5e-4708-b191-1f171619d3e5 Refreshing queries...
scheduler_1         | [2020-10-23 03:00:00,315][PID:33][INFO][ForkPoolWorker-12] task_name=redash.tasks.refresh_queries task_id=543a01a0-ec5e-4708-b191-1f171619d3e5 Done refreshing queries. Found 0 outdated queries: []
scheduler_1         | [2020-10-23 03:00:00,317][PID:33][INFO][ForkPoolWorker-12] Task redash.tasks.refresh_queries[543a01a0-ec5e-4708-b191-1f171619d3e5] succeeded in 0.0063627999989s: None
scheduler_1         | [2020-10-23 03:00:30,305][PID:22][INFO][Beat] Scheduler: Sending due task sync_user_details (redash.tasks.sync_user_details)
scheduler_1         | [2020-10-23 03:00:30,307][PID:1][INFO][MainProcess] Received task: redash.tasks.sync_user_details[e830e682-bb32-4266-b87f-bd84183f1a2c]   expires:[2020-10-23 03:01:15.305506+00:00]
scheduler_1         | [2020-10-23 03:00:30,308][PID:22][INFO][Beat] Scheduler: Sending due task refresh_queries (redash.tasks.refresh_queries)
scheduler_1         | [2020-10-23 03:00:30,317][PID:33][INFO][ForkPoolWorker-12] Task redash.tasks.sync_user_details[e830e682-bb32-4266-b87f-bd84183f1a2c] succeeded in 0.00900940000065s: None
scheduler_1         | [2020-10-23 03:00:30,319][PID:1][INFO][MainProcess] Received task: redash.tasks.refresh_queries[81cb8e71-918d-4fa5-8950-03fe44bcfc31]
scheduler_1         | [2020-10-23 03:00:30,320][PID:33][INFO][ForkPoolWorker-12] task_name=redash.tasks.refresh_queries task_id=81cb8e71-918d-4fa5-8950-03fe44bcfc31 Refreshing queries...
scheduler_1         | [2020-10-23 03:00:30,330][PID:33][INFO][ForkPoolWorker-12] Inserting job for 60a9cad8d5384755b62d1971f9d914f0 with metadata={'Username': 'Scheduled', 'Query ID': 1}
scheduler_1         | [2020-10-23 03:00:30,347][PID:33][INFO][ForkPoolWorker-12] [60a9cad8d5384755b62d1971f9d914f0] Created new job: 255c632f-b198-4018-883c-bc295996bb7b
scheduled_worker_1  | [2020-10-23 03:00:30,347][PID:1][INFO][MainProcess] Received task: redash.tasks.execute_query[255c632f-b198-4018-883c-bc295996bb7b]
scheduler_1         | [2020-10-23 03:00:30,348][PID:33][INFO][ForkPoolWorker-12] task_name=redash.tasks.refresh_queries task_id=81cb8e71-918d-4fa5-8950-03fe44bcfc31 Done refreshing queries. Found 1 outdated queries: [1]
scheduled_worker_1  | [2020-10-23 03:00:30,352][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=255c632f-b198-4018-883c-bc295996bb7b task=execute_query state=load_ds ds_id=1
scheduler_1         | [2020-10-23 03:00:30,353][PID:33][INFO][ForkPoolWorker-12] Task redash.tasks.refresh_queries[81cb8e71-918d-4fa5-8950-03fe44bcfc31] succeeded in 0.0330446000007s: None
scheduled_worker_1  | [2020-10-23 03:00:30,356][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=255c632f-b198-4018-883c-bc295996bb7b task=execute_query state=executing_query query_hash=60a9cad8d5384755b62d1971f9d914f0 type=python ds_id=1  task_id=255c632f-b198-4018-883c-bc295996bb7b queue=scheduled_queries query_id=1 username=Scheduled
scheduled_worker_1  | [2020-10-23 03:00:36,366][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=255c632f-b198-4018-883c-bc295996bb7b task=execute_query query_hash=60a9cad8d5384755b62d1971f9d914f0 data_length=213 error=[None]
scheduled_worker_1  | [2020-10-23 03:00:36,368][PID:22][INFO][ForkPoolWorker-1] Inserted query (60a9cad8d5384755b62d1971f9d914f0) data; id=None
scheduled_worker_1  | [2020-10-23 03:00:36,378][PID:22][INFO][ForkPoolWorker-1] Updated 1 queries with result (60a9cad8d5384755b62d1971f9d914f0).
scheduled_worker_1  | [2020-10-23 03:00:36,380][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=255c632f-b198-4018-883c-bc295996bb7b task=execute_query state=checking_alerts query_hash=60a9cad8d5384755b62d1971f9d914f0 type=python ds_id=1  task_id=255c632f-b198-4018-883c-bc295996bb7b queue=scheduled_queries query_id=1 username=Scheduled
scheduled_worker_1  | [2020-10-23 03:00:36,382][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=255c632f-b198-4018-883c-bc295996bb7b task=execute_query state=finished query_hash=60a9cad8d5384755b62d1971f9d914f0 type=python ds_id=1  task_id=255c632f-b198-4018-883c-bc295996bb7b queue=scheduled_queries query_id=1 username=Scheduled
scheduler_1         | [2020-10-23 03:00:36,382][PID:1][INFO][MainProcess] Received task: redash.tasks.check_alerts_for_query[e25e7ceb-4d64-4cc6-9925-93d476cc9b97]
scheduled_worker_1  | [2020-10-23 03:00:36,383][PID:22][INFO][ForkPoolWorker-1] Task redash.tasks.execute_query[255c632f-b198-4018-883c-bc295996bb7b] succeeded in 6.0339485s: 230
...

最後の行の succeeded in 6.0339485s から分かる通り問題なく動作しているようだ。

次にタイムアウトの10秒を超えるようにsleepを15秒に設定して待機する。
手動実行ではないためブラウザ上にはエラーは表示されないものの
docker-compose側では下記のようなエラーが表示されているためタイムアウトが動作していることがわかる。

...
scheduled_worker_1  | [2020-10-23 03:05:30,343][PID:1][INFO][MainProcess] Received task: redash.tasks.execute_query[a266bd40-5abe-4515-ad69-f2f756142b7b]
scheduler_1         | [2020-10-23 03:05:30,346][PID:35][INFO][ForkPoolWorker-14] Task redash.tasks.refresh_queries[a9cd4248-7f95-434f-9b76-d4de42ab34b1] succeeded in 0.0338508000023s: None
scheduled_worker_1  | [2020-10-23 03:05:30,349][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=a266bd40-5abe-4515-ad69-f2f756142b7b task=execute_query state=load_ds ds_id=1
scheduled_worker_1  | [2020-10-23 03:05:30,354][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=a266bd40-5abe-4515-ad69-f2f756142b7b task=execute_query state=executing_query query_hash=115c0c43431f5ba5c1284589e62738a4 type=python ds_id=1  task_id=a266bd40-5abe-4515-ad69-f2f756142b7b queue=scheduled_queries query_id=1 username=Scheduled
scheduled_worker_1  | [2020-10-23 03:05:40,347][PID:1][WARNING][MainProcess] Soft time limit (10s) exceeded for redash.tasks.execute_query[a266bd40-5abe-4515-ad69-f2f756142b7b]
scheduled_worker_1  | [2020-10-23 03:05:40,348][PID:22][INFO][ForkPoolWorker-1] task_name=redash.tasks.execute_query task_id=a266bd40-5abe-4515-ad69-f2f756142b7b task=execute_query query_hash=115c0c43431f5ba5c1284589e62738a4 data_length=None error=[<class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()]
scheduled_worker_1  | [2020-10-23 03:05:40,357][PID:22][ERROR][ForkPoolWorker-1] Task redash.tasks.execute_query[a266bd40-5abe-4515-ad69-f2f756142b7b] raised unexpected: QueryExecutionError("<class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()",)
scheduled_worker_1  | Traceback (most recent call last):
scheduled_worker_1  |   File "/usr/local/lib/python2.7/site-packages/celery/app/trace.py", line 385, in trace_task
scheduled_worker_1  |     R = retval = fun(*args, **kwargs)
scheduled_worker_1  |   File "/app/redash/worker.py", line 84, in __call__
scheduled_worker_1  |     return TaskBase.__call__(self, *args, **kwargs)
scheduled_worker_1  |   File "/usr/local/lib/python2.7/site-packages/celery/app/trace.py", line 648, in __protected_call__
scheduled_worker_1  |     return self.run(*args, **kwargs)
scheduled_worker_1  |   File "/app/redash/tasks/queries.py", line 436, in execute_query
scheduled_worker_1  |     scheduled_query).run()
scheduled_worker_1  |   File "/app/redash/tasks/queries.py", line 382, in run
scheduled_worker_1  |     raise result
scheduled_worker_1  | QueryExecutionError: <class 'billiard.exceptions.SoftTimeLimitExceeded'> SoftTimeLimitExceeded()
...

タイムアウトが設定出来るのでうまく設定してやれば定期実行クエリがキューに詰まることは無くすことができそう。
その一方で、定期実行クエリが失敗した場合に結果は更新されず一見しただけでは失敗したかどうか分からない。
QueriesからLast Executed Atを参照する、ないしは結果内に最後に実行した時間を含めるようにしないと
古いデータと気づかずに利用してしまう可能性があるのでそこは注意が必要。

雑記

手動実行クエリについて調査していた時に__init__.pyを眺めていて
ドキュメント化されてないけど定期実行にもタイムアウトあるやんけと思い

みたいなことを投稿したのだけど
おそらく追記漏れだろう、ということで本家ドキュメントにもprを出した。
マージされるといいですね。おわり。

RedashのPythonデータソースで自作の再帰関数が動作しない

そもそもRedashのPythonデータソースで再帰関数を書くな。

Redashではデータソースとして各種DBやDWH以外にPythonで記述したスクリプトも利用することができる。
検証のために実行時間が数十秒かかるようなスクリプトを書いた際にハマったので、調べて分かったことをまとめる。

要約

  • Redash内で記述出来るPythonスクリプトはRestrictedPythonを利用して実行されている
  • RestrictedPythonを利用してる都合上様々な制限がある
  • 利用したい関数をglobalとすれば利用できる

環境

  • Redash v8.0.0 (dev)

経緯

Redashで時間がかかりすぎているクエリを特定した時にどう対応するかを確認するために
あえて、数十秒程度実行に時間がかかるような処理をクエリとして実行したかった。

一定時間かかる処理かつある程度負荷をパラメータで調整できるような処理をSQLでは
すぐには思いつかなかったのでPythonスクリプトでフィボナッチ数を計算するスクリプトを書いた。

以下のソースコードがまず動かない

print "start"
def fibonacci(n):
    if n <= 2:
        return 1
    else:
        return fibonacci(n - 2) + fibonacci(n - 1)
print fibonacci(5)
print "finish"

実行すると
Error running query: <type 'exceptions.NameError'> global name 'fibonacci' is not defined
とエラーが出力されてスクリプトが動作しない。

自分のPython力が不安になりpaiza.ioで試すと問題なく動作する。
次に関数定義が制限されてるのかと考えて下記の処理を書いた。

def hello():
  print "hello"

hello()

問題なく動作する。
次に自作関数から別の自作関数を定義するような処理を書いた。

def a():
  print "hello"

def b():
  a()

b()

Error running query: <type 'exceptions.NameError'> global name 'a' is not defined
で最初のコード例と同様に動作しない。

自作関数から自作関数をコールすると死ぬ?なぜ?
と思って調べたら以下のようなIssueが公式にあった

github.com

まさに同じような問題に直面している。
globalキーワードをつければ動くよとのことなので最初のコードでは
下記のようにfibonacciにglobalをつけて動作を確認することができた。

print "start"
def fibonacci(n):
    if n <= 2:
        return 1
    else:
        return fibonacci(n - 2) + fibonacci(n - 1)
global fibonacci
print fibonacci(5)
print "finish"

このコードだと動作する。
めでたしめでたし。

以下蛇足

なぜメソッド名が見つからない(制限されているのか)?

先程のIssueコメントのリンク内では

https://github.com/getredash/redash/blob/master/redash/query_runner/python.py#L263

でExceptionを吐くと言ってるが当時のmasterを指しているので正確ではなく
コメント時期から推測するにRedash v2.0.1の

https://github.com/getredash/redash/blob/22aa64571a2e827672d0a68f7daebd3516e3f6f8/redash/query_runner/python.py#L263

...
            exec(code) in restricted_globals, self._script_locals
...

この部分でExceptionが発生している。

前段のcompile_restricted で生成されているバイトコードを実行しようとしているので RestrictedPython.compile_restrictedを調べると

pypi.org

RestrictedPythonというものがあることがわかる。

RestrictedPythonとは

ユーザーから受け取ったPythonスクリプトを安全に実行するためのライブラリ
Pythonのビルトイン関数のcompileを置き換え、利用可能なモジュール、メソッドを制限することで
バイトコードの段階で怪しい処理をチェックする、とのこと。

定義済みの利用可能なモジュール、メソッドのセットを使用したり、自前で追加削除が可能な模様。
RedashではPythonクエリランナーのこのあたりで処理していそう。

具体的にどこで制限しているの

わかりませんでした。

このあたりで入ってきたバイトコードを見つつ
not_allowedなどで使用できないキーワードを決めたりしているところまでは確認できるものの
明確に辞書から外す、入れるみたいな処理を確認できなかった。

メソッド定義を発見した時の処理で、色々とやっているように見えるけれど
そもそも例外が送出されるタイミングがバイトコードを実行するexec側なので
戻りのバイトコードが壊れてる(または意図的に壊している)のか判断つかなかった。

似たような言及を探してみると
以下の記事ではPythonスクリプトの先頭でimportしたライブラリが使えないので
メソッド内でimportしろという記述もある

www.ehfeng.com

RestrictedPythonのドキュメントを見てもどの部分がこの機能に相当するのかわからない…
Pythonの言語仕様自体に対して理解が浅いだけかもしれないしこれ以上はハマりそうなので撤退

ウオーなんでやと思って調査しはじめたら3時間くらい溶けた…
オチとしてはよくわかりませんでしたで締まらないけど
初見のソースコードをガッツリ掘っていくのもたまには楽しい。

Redashでユーザーにクエリの閲覧と実行のみを許可する

Redashから出力したデータを監査等の別プロセスで利用する場合や、利用部門のリテラシが低い場合など
全ユーザーがなんでも出来てしまうと問題があるものの、ある程度制限した状態でRedashを利用してほしい場合がある。

そこで、用意されたパラメータつきクエリの閲覧と実行のみができるユーザーを作成したい。

tl;dr

  • Redashの権限管理は2種類ある
    • データソース毎の設定 (Full Access / View Only)
    • 実行できるアクションの設定
  • アクションへの設定は管理画面からは行えないのでCLIで行う必要がある
  • パラメータを都度取るようなクエリへの閲覧実行権限は後者でないと設定できない

環境

  • MacOS 10.14.6
  • docker desktop 2.1.0.5
  • Redash 8.0.0+b32245

検証にあたっては、id:kakku22さんのredash-hands-onリポジトリを利用した。
Dockerだけ入れていれば手元で試せるので、本家サイトの導入例よりとても手軽で最高。

github.com

解決したい課題

クエリ編集権限を与えずに、パラメータ付きクエリを閲覧実行できる権限をユーザーに設定したい。

Redash上から行える権限管理ではデータソースに対するFull AccessかView Onlyのどちらかしか設定できない。
単純にView Onlyになっている状態だと、パラメータ付きクエリにパラメータを渡して結果の絞り込みが行えない。
逆にFull Accessを与えてしまうとクエリの編集削除、その他諸々のことが行えてしまう。

そのため、新規クエリ発行・既存クエリ編集などをさせない状態で
既存のパラメータつきクエリを利用できる状態を用意したい。

権限モデル

Redashではグループに紐づく2種類の権限モデルが存在する。
https://redash.io/help/user-guide/users/permissions-groups

  • データソース毎の設定 (Full Access / View Only)
  • 実行できるアクションの設定

データソース毎の設定については右上のメニューGroups > [グループ名] > Data Sourcesから 以下のような画面で設定が行える。
f:id:uskey:20200229191922p:plain Full Accessでは全機能が利用でき、View Onlyでは既に実行された結果のみが閲覧できる。

内部的にはdata_source_groupsというテーブル内に情報が格納されている。
adminグループへの設定レコードはdata_source_groupsに存在しない。

postgres=# select * from data_source_groups;
id | data_source_id | group_id | view_only
----+----------------+----------+-----------
  1 |              1 |        2 | f

後者のアクションへの権限はRedash上からは確認できないため
付属されているCLIツールのmanage.pyを利用するか、直接groupsテーブルを更新する必要がある。

postgres=# select id, org_id, type, name, permissions from groups;
 id | org_id |  type   |   name   |                                                                              permissions
----+--------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  1 |      1 | builtin | admin    | {admin,super_admin}
  2 |      1 | builtin | default  | {create_dashboard,create_query,edit_dashboard,edit_query,view_query,view_source,execute_query,list_users,schedule_query,list_dashboards,list_alerts,list_data_sources}

permissionsカラムで列挙されている内容が、そのグループで実行できるアクションとなっている。
指定できるpermissionは10種類近くあるようだが、対応するドキュメントが公式にはないので試行錯誤する必要がある。

今回設定をしたかったクエリの閲覧実行のみを許可したい場合は
データソースへをFull Access, 許可するアクションを{view_query,execute_query}と設定することで実現出来た。

それぞれ、ViewOnly, ExecuteOnlyを用意して設定した後はこちら。

postgres=# select * from data_source_groups;
 id | data_source_id | group_id | view_only
----+----------------+----------+-----------
  1 |              1 |        2 | f
  2 |              1 |        3 | t
  3 |              1 |        4 | f
postgres=# select id, org_id, type, name, permissions from groups;
 id | org_id |  type   |   name   |                                                                              permissions
----+--------+---------+----------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------
  1 |      1 | builtin | admin    | {admin,super_admin}
  2 |      1 | builtin | default  | {create_dashboard,create_query,edit_dashboard,edit_query,view_query,view_source,execute_query,list_users,schedule_query,list_dashboards,list_alerts,list_data_sources}
  3 |      1 | regular | ViewOnly | {create_dashboard,create_query,edit_dashboard,edit_query,view_query,view_source,execute_query,list_users,schedule_query,list_dashboards,list_alerts,list_data_sources}
  4 |      1 | regular | ExecuteOnly | {view_query,execute_query}

後はユーザーをそれぞれのグループに所属させればグループに指定された権限を持つようになる。

なお、クエリはデータソースに紐付いているため
事前に利用したいクエリが存在するデータソースをグループに追加しておく必要がある。

設定毎のRedash上でのクエリの見え方

View Onlyのみ

固定クエリ

f:id:uskey:20200229195350p:plain 問題なく実行できる

f:id:uskey:20200229195414p:plain クエリ自体の編集は保存時にエラー

パラメータ付きクエリ

f:id:uskey:20200229195443p:plain 表示するだけでエラー

Full Access かつ {view_query, execute_query}

固定クエリ

f:id:uskey:20200229195431p:plain 問題なく表示されているものの
許可されないアクションはリンク自体表示されていないため編集画面を出せない

パラメータ付きクエリ

f:id:uskey:20200229195834p:plain 問題なく表示される

f:id:uskey:20200229195852p:plain パラメータを変更して再実行も可能