2024年4月26日金曜日

RQ のジョブのタイムアウトについて調査した

RQ のジョブのタイムアウトについて調査した

概要

RQ のジョブにはタイムアウトが設定されておりデフォルトでは 180 秒のようです
このタイムアウトは単純にワーカーが実行しているジョブが 180 秒経過しても終了しない場合には強制的にジョブを終了させる設定になります

環境

  • macOS 11.7.10
  • Python 3.11.6
  • rq 1.16.1

挙動を確認するためのスクリプト

ジョブは 190 秒待つようにします
初回はエンキュー時にタイムアウトなしでエンキューして本当にタイムアウトが発生するか確認します

  • vim lib/utils.py
import time


class Message:
    def say(self, **kwargs):
        print("Start say")
        print(kwargs)
        time.sleep(190)
        print("End say")
  • vim app.py
from datetime import timedelta

from redis import Redis
from rq import Queue
from rq_scheduler import Scheduler

from lib.util import Message

queue = Queue("default", connection=Redis(host="192.168.1.2"))
scheduler = Scheduler(queue=queue, connection=queue.connection)


scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello")
# scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello", timeout=3000)

実行

  • pipenv run rq worker
  • pipenv run rqscheduler
  • pipenv run app.py

ログ

実際に実行するとジョブの終了時に表示されるメッセージが表示されずに 180 経過してジョブが矯正終了していることがわかります
なお JobTimeoutException で終了したジョブはリトライなどはされませんでした

08:39:14 default: say(msg='hello') (c97f39f8-c197-43ac-828f-245d78ef78f0)
Start say
{'msg': 'hello'}
08:42:14 [Job c97f39f8-c197-43ac-828f-245d78ef78f0]: exception raised while executing (say)
Traceback (most recent call last):
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/worker.py", line 1431, in perform_job
    rv = job.perform()
         ^^^^^^^^^^^^^
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/job.py", line 1280, in perform
    self._result = self._execute()
                   ^^^^^^^^^^^^^^^
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/job.py", line 1317, in _execute
    result = self.func(*self.args, **self.kwargs)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/Users/user/data/repo/python-try/lib/util.py", line 8, in say
    time.sleep(190)
  File "/Users/user/.local/share/virtualenvs/python-try-aR_k1rUJ/lib/python3.11/site-packages/rq/timeouts.py", line 63, in handle_death_penalty
    raise self._exception('Task exceeded maximum timeout value ({0} seconds)'.format(self._timeout))
rq.timeouts.JobTimeoutException: Task exceeded maximum timeout value (180 seconds)

timeout=3000 にして再度試す

コメントにしていた部分を変更して再度エンキューしてみます
今度はジョブのタイムアウトを 3000 秒にしてエンキューします

すると先ほどタイムアウトしたジョブがタイムアウトせずにちゃんと最後まで実行されることが確認できました

08:52:14 default: say(msg='hello') (91aab157-25a5-44b9-898b-4da6dcb3baff)
Start say
{'msg': 'hello'}
End say
08:55:24 default: Job OK (91aab157-25a5-44b9-898b-4da6dcb3baff)
08:55:24 Result is kept for 500 seconds

最後に

ジョブの実行時間が長くなりそうな場合にはエンキュー時に timeout を設定してあげましょう

参考サイト

2024年4月25日木曜日

RQ のシグナルハンドリングについて調べてみる

RQ のシグナルハンドリングについて調べてみる

概要

RQ でプロセスハンドリングの挙動を確認してみました

環境

  • macOS 11.7.10
  • Python 3.11.6
  • rq 1.16.1

サンプルコード

  • vim app.py
from datetime import timedelta

from redis import Redis
from rq import Queue
from rq_scheduler import Scheduler

from lib.util import Message

queue = Queue("default", connection=Redis(host="192.168.1.2"))
scheduler = Scheduler(queue=queue, connection=queue.connection)


scheduler.enqueue_in(timedelta(seconds=5), Message().say, msg="hello")
  • vim lib/util.py
import time


class Message:
    def say(self, **kwargs):
        print("Start say")
        time.sleep(30)
        print(kwargs)
  • pipenv run rq worker
  • pipenv run rqscheduler
  • pipenv run python app.py

10秒以内にワーカーを Ctrl+c で停止した場合

ワーカーがジョブを掴んでからワーカーのプロセスを停止しようとするとちゃんと最後まで処理が終了してからワーカーが停止しました

11:21:53 default: say(msg='hello') (4d5a4db9-e8c9-4ee3-b64c-1dd266e66068)
Start say
11:21:55 Worker 98f9aac6f73445f685d4f5588abd514f [PID 32100]: warm shut down requested
{'msg': 'hello'}
11:22:03 default: Job OK (4d5a4db9-e8c9-4ee3-b64c-1dd266e66068)
11:22:03 Result is kept for 500 seconds
11:22:03 Worker rq:worker:98f9aac6f73445f685d4f5588abd514f: stopping on request
11:22:03 Unsubscribing from channel rq:pubsub:98f9aac6f73445f685d4f5588abd514f

kill -9 した場合

ワーカーがジョブを実行中に kill -9 を送信してみました
その場合ワーカーは Warm shut down せずにすぐに終了してしまいます

12:05:54 default: say(msg='hello') (b4eac96d-9591-482c-9753-e3a9c150d83b)
Start say
zsh: killed     pipenv run rq worker

redis 外部からアクセスできない場合

Protection mode が有効になっているので一旦無効にしましょう

  • CONFIG SET protected-mode no

docker の場合

docker stop はデフォルトでは SIGTERM -> SIGKILL の順番で信号を送ります
SIGKILL までの時間は time というオプションで変更可能でデフォルトで10秒になります

ワーカーを docker 上で動かし docker stop した際のワーカーの挙動を確認します

  • vim Dockerfile
FROM python:3.11.9-bullseye

COPY . /home
WORKDIR /home

RUN pip install pipenv
RUN pipenv install

CMD ["pipenv", "run", "rq", "worker", "--url", "redis://192.168.1.2:6379"]
  • docker build -t worker .
  • docker create --name worker worker
  • docker start worker
  • docker logs -f worker

でワーカーを起動します
スケジューラとエンキューは python から行います

  • pipenv run rqscheduler
  • pipenv run python app.py

これでワーカーがジョブを実行中にワーカーコンテナを停止します

  • docker stop worker
03:28:54 default: say(msg='hello') (76dbdc1b-5af2-4fa1-b4d2-49793454b235)
03:28:57 Worker 15c934ce3084457da63ade0e89ea14b6 [PID 1]: warm shut down requested

SIGTERM を受取り Warm shut down がスタートしますが10秒後に docker が強制的に SIGKILL を送信するためワーカーはジョブの完了を待たずに終了してしました

また強制終了したジョブは FailedJobRegistry に移動するようです

StartedJobRegistry cleanup: Moving job to FailedJobRegistry (due to AbandonedJobError)

time=60 にしてみる

SITTERM -> SIGKILL の送信にかかる時間を 60 秒に伸ばしてみます

  • docker stop --time=60 worker

すると今度はちゃんとワーカーがジョブの終了を待ってから停止していることが確認できました

03:32:54 default: say(msg='hello') (8e04f278-c5b1-4c4a-9069-3105960129f4)
03:33:14 Worker faa70ceee374410ba9416f55d74f4927 [PID 1]: warm shut down requested
Start say
{'msg': 'hello'}
03:33:25 default: Job OK (8e04f278-c5b1-4c4a-9069-3105960129f4)
03:33:25 Result is kept for 500 seconds
03:33:25 Worker rq:worker:faa70ceee374410ba9416f55d74f4927: stopping on request
03:33:25 Unsubscribing from channel rq:pubsub:faa70ceee374410ba9416f55d74f4927

最後に

RQ のシグナルハンドリングについて調べてみました
基本は RQ 側で実装されているので気にすることはないですが docker などと組み合わせる場合には SIGKILL の送信タイミングについて考慮する必要がありそうです

2024年4月24日水曜日

WSL2 で OneDrive をマウントする方法

WSL2 で OneDrive をマウントする方法

概要

特に設定は不要でした

環境

  • Windows10 22H2
  • WSL2 (Ubuntu22.04)

OneDrive アプリのインストールと設定

Windows 上に OneDrive アプリをインストールしましょう
あとはログインすれば OK です

WSL2 側での設定は特に不要

WSL2 は Windows の領域をデフォルトで参照できます
/mnt/c から参照できます
OneDrive のディレクトリをデフォルトのままにしているのであれば /mnt/c/Users/username/OneDrive でアクセスできます

最後に

OneDrive WSL2 ではすでにマウントされているので設定は不要です

参考サイト

2024年4月23日火曜日

WSL2 の Ubuntu に ssh する方法

WSL2 の Ubuntu に ssh する方法

概要

最近の WSL2 であれば特に何も考えずにデフォルトの設定のままできるようです

環境

  • Windows10 22H2
  • WSL2 (Ubuntu22.04)

openssh-server のインストール

  • sudo apt -y update
  • sudo apt -y install openssh-server

自動起動をONにする

  • sudo systemctl status ssh

動作確認

  • ssh devops@localhost

これが一番のポイントかもしれませんが localhost にアクセスします
WSL2 は localhost にバインドするので vEthernet (WSL) で払い出される IP ではなく localhost にアクセスします

パスワード WSL 初回起動時に設定したパスワードを使用します

最後に

デフォルトの WSL2 のターミナルは Windows コンソールのターミナルなのでキーバインドなどが微妙です
使い慣れたターミナルを使いたい場合には ssh を使いましょう

また ssh にするには当然 WSL を起動しておく必要があるので注意しましょう
Windows コンソールが不要な場合は「wsl」コマンドを実行するだけでも起動させることができます

2024年4月22日月曜日

Python Slack SDK でスレッドとして返信する方法

Python Slack SDK でスレッドとして返信する方法

概要

chat_postMessage で thread_ts を指定します

環境

  • macOS 11.7.10
  • Python 3.11.6
  • slack-sdk 3.27.1

サンプルコード

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

client = WebClient(token="xoxb-xxxxxx")
channel_id = "C0123456789"

try:
    # 会話情報の取得、最新100件
    result = client.conversations_history(channel=channel_id)
    conversation_history = result["messages"]
    # とりあえず最新のメッセージのスレッドに返信する
    ts = result["messages"][0]["ts"]
    # thread_tsを指定することでスレッド返信になる
    response = client.chat_postMessage(
        channel="#private",
        text="Hello world!",
        thread_ts=ts,
        # アイコンやユーザ名をカスタムする場合は以下を設定する
        username="hawksnowlog",
        icon_url="https://pbs.twimg.com/profile_images/712848447569661952/ayfI9-77_400x400.jpg",
    )
except SlackApiError as e:
    print(e)

最後に

as_user オプションは廃止されているので使えません
代わりに username と icon_url を使いますが APP というタグは必ず付与されてしまうようで完全にユーザになり変わって投稿することはできないようです

参考サイト

2024年4月19日金曜日

Ubuntu にインストールされているパッケージの脆弱性チェックをしてみる (OpenScap編)

Ubuntu にインストールサれているパッケージの脆弱性チェックをしてみる (OpenScap編)

概要

Ubuntu 上にインストールされているパッケージの脆弱性をチェックするのに OpenScap というツールを使ってみました
インストールから実際に脆弱性をチェックする方法まで紹介します

環境

  • Ubuntu 22.04
  • oscap 1.2.17

インストール

  • sudo apt -y install libopenscap8

jammy では 1.3 はインストールされないようです

脆弱性定義のダウンロード

OVAL (Open Vulnerability and Assessment Language) 形式の脆弱性定義ファイルをダウンロードします

OpenScap は OVAL 形式のファイルをサポートしているのでこの定義ファイルを元にマシン上にあるパッケージのバージョンと比較して脆弱性があるかどうかチェックします

  • wget https://security-metadata.canonical.com/oval/com.ubuntu.$(lsb_release -cs).usn.oval.xml.bz2
  • bunzip2 com.ubuntu.$(lsb_release -cs).usn.oval.xml.bz2

スキャン実行

結果は html に保存されます

  • oscap oval eval --report report.html com.ubuntu.$(lsb_release -cs).usn.oval.xml
Definition oval:com.ubuntu.jammy:def:52051000000: false
Definition oval:com.ubuntu.jammy:def:51821000000: false
Definition oval:com.ubuntu.jammy:def:51811000000: false
Definition oval:com.ubuntu.jammy:def:1021000000: false
Definition oval:com.ubuntu.jammy:def:1011000000: false
Definition oval:com.ubuntu.jammy:def:1001000000: false
Definition oval:com.ubuntu.jammy:def:100: true
Evaluation done.

上記のようなログが流れ最後に done となれば完了です

html ファイルの確認

方法は何でも OK です

  • mv report.html /var/www/html
  • curl localhost/report.html

以下のような感じで表示されます
オレンジっぽくなっている部分が脆弱性未対応のパッケージを使っている部分になります

対象の使用しているパッケージは一番右の欄を見るか CVE のリンク先に詳細な情報があるのでそこを見るしかありません

最後に

OpenScap と公開されている脆弱性情報ファイルを使ってローカルマシン上のパッケージの脆弱性チェックをしてみました

基本は apt upgrade で対応することになりますが Ubuntu Pro でのみ公開されているパッチもあるのでその場合は手動でパッチを当てるか対象のパッケージを削除するかしかないかなと思います

参考サイト

2024年4月18日木曜日

Windows の WSL2 でてっとり早くインターネットに接続する方法

Windows の WSL2 でてっとり早くインターネットに接続する方法

概要

WSL2 はデフォルトだと NAT 接続になります
またネットワークアダプタは WSL2 専用のネットワークアダプタになるため例えば複数のネットワークアダプタがあり通常作業は別のネットワークアダプタから通信する場合には全くインターネットに接続できなくなります

ネット上の記事だと DNS の接続を変更したりプロキシの設定を変更したりすれば何とかなる記事が多くありますがそもそもネットワークが別で普段使っているネットワークに疎通できないのであれば普段使っている DNS やプロキシへもそもそもアクセスできないので意味がありません

今回はそんなときに簡単に WSL2 からインターネットにアクセスすることができる設定を紹介します

環境

  • WSL2 (Ubuntu 22.04)
  • Windows10 22H2

解決方法: ネットワークアダプタの共有設定を使う

Windows のネットワークアダプタには他のネットワークアダプタからの通信を共有する機能があります
これを使用することで普段使っているネットワークアダプタに対して WSL2 で作成されたネットワークアダプタからの通信を許可することでインターネットに接続できるようにします

まずはネットワークアダプタの設定から行います
普段使っているネットワークアダプタのプロパティを開き「共有」を選択します
するとネットワークを共有するアダプタを選択する画面になるので WSL2 が作成した「vEthernet (WSL)」を選択します

ちなみに自分の通常のネットワークは Wi-Fi アダプタになるので Wi-Fi アダプタに対して共有を設定を行っています

vEthernet (WSL) は以下のような表示です

WSL 側の設定

ネットワークを共有すると vEthernet (WSL) の IP アドレス帯が変わります
なのでこの IP を WSL (Ubuntu) に設定してあげます

  • sudo ip addr add 192.168.137.10/24 dev eth0
  • sudo ip route delete default
  • sudo ip route add default via 192.168.137.1

既存のネットワークに 192.168.137.10 という IP を付与しデフォルトゲートウェイを 192.168.137.1 にしているだけです
おそらくネットワーク共有すると同じ IP 帯になるはずなのでそのままコピペして Ubuntu で実行すれば OK です

DNS の設定

WSL2 の場合デフォルトだと vEthernet (WSL) の共有前のアドレスが DNS に設定されています
これはもう使わないので外部の DNS を使います

  • vim /etc/resolv.conf
nameserver 8.8.8.8

動作確認

これで apt update などもできるようになります

この構成の問題点

今回共有したネットワークは本当に普段使っているネットワークで自宅のマシンやスマホ、Chromecast などが存在しているネットワークになります
自宅のホームルータを抜けてインターネットに接続する経路になります

もしその経路ではなく VPN などを張っていて職場やプライベートな環境に WSL2 から通信したい場合にはこの方法ではできません
なぜなら共有しているネットワークアダプタが VPN 用のネットワークではなくただの自宅ネットワークだからです

もし WSL2 から VPN の経路も使いたい場合には VPN 用のネットワークアダプタにも vEthernet (WSL) からのアクセスを許可してあげれば OK です
ただその場合には外部に出るネットワークと VPN に行くネットワークの2つになるので適切に経路を設定してあげないとうまく動作しないので注意しましょう

Windows のネットワーク共有は1つのアダプタに対してのみから行えないようなのでどちらか一方のネットワークを使うしかないようです

最後に

WSL2 でプロキシなどの設定をせずにインターネットに接続する方法を紹介しました

インターネット以外のネットワークにも接続したい場合には更に工夫が必要なので注意しましょう
WSL2 にはブリッジ接続もあるようなのですそれを試すのもありかもしれません
また今回は無線だったのでネットワーク共有を使いましたが有線ネットワークだと今回の共有設定などはしなくても動くかもしれません

参考サイト