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 にはブリッジ接続もあるようなのですそれを試すのもありかもしれません
また今回は無線だったのでネットワーク共有を使いましたが有線ネットワークだと今回の共有設定などはしなくても動くかもしれません

参考サイト

2024年4月17日水曜日

個人でUbuntu Proに申し込む方法

個人でUbuntu Proに申し込む方法

概要

Ubuntu Pro は Ubuntu の商用ライセンスです
ESM (Extended Security Maintenance) パッケージの提供などをしておりよりセキュアな Ubuntu を使うことができます
今回は個人で Ubuntu Pro を登録し Ubuntu に ESM パッケージをインストールしてみました

環境

  • Ubuntu 22.04

登録

まず https://ubuntu.com/pro/subscribe にアクセスします
個人であれば5台までは無料で使えるようです

Ubuntu One のアカウントが必要なのでなければ作成しましょう
すでにあればログインします

トークンの確認

Ubuntu Pro に登録するとサブスクリプションの画面で以下のようなトークン情報が表示されます
コマンドでも登録できるので確認しましょう

Ubuntu を登録する

先ほどのコマンドを Ubuntu 上で実行してみましょう

  • sudo pro attach xxxx
Enabling default service esm-apps
Updating Ubuntu Pro: ESM Apps package lists
Ubuntu Pro: ESM Apps enabled
Enabling default service esm-infra
Updating Ubuntu Pro: ESM Infra package lists
Ubuntu Pro: ESM Infra enabled
Enabling default service livepatch
Installing canonical-livepatch snap
Canonical Livepatch enabled
This machine is now attached to 'Ubuntu Pro - free personal subscription'

SERVICE          ENTITLED  STATUS       DESCRIPTION
anbox-cloud      yes       disabled     Scalable Android in the cloud
esm-apps         yes       enabled      Expanded Security Maintenance for Applications
esm-infra        yes       enabled      Expanded Security Maintenance for Infrastructure
fips-preview     yes       disabled     Preview of FIPS crypto packages undergoing certification with NIST
fips-updates     yes       disabled     FIPS compliant crypto packages with stable security updates
livepatch        yes       enabled      Canonical Livepatch service
realtime-kernel* yes       disabled     Ubuntu kernel with PREEMPT_RT patches integrated
usg              yes       disabled     Security compliance and audit tools

 * Service has variants

NOTICES
Operation in progress: pro attach

For a list of all Ubuntu Pro services and variants, run 'pro status --all'
Enable services with: pro enable <service>

     Account: xxxxx@mail.com
Subscription: Ubuntu Pro - free personal subscription

登録が成功するとサブスクリプション画面の「Active machines」が増えていることが確認できると思います

動作確認

ESM バージョンのパッケージがインストールできるか確認してみます
今回は graphviz に ESM パッケージがあるのでこれがインストールされるか確認します (参考)

インストール前は以下のような状態です

dpkg -l | grep 'graphviz'
ii  libgraphviz-dev:amd64                  2.42.2-6                                amd64        graphviz libs and headers against which to build applications

まずは apt update します
update すると esm のリポジトリが追加されていることが確認できます

  • sudo apt -y update
Hit:1 https://aquasecurity.github.io/trivy-repo/deb jammy InRelease
Hit:2 https://download.docker.com/linux/ubuntu jammy InRelease
Hit:3 https://esm.ubuntu.com/apps/ubuntu jammy-apps-security InRelease
Hit:4 https://esm.ubuntu.com/apps/ubuntu jammy-apps-updates InRelease
Hit:5 https://esm.ubuntu.com/infra/ubuntu jammy-infra-security InRelease
Hit:6 https://esm.ubuntu.com/infra/ubuntu jammy-infra-updates InRelease
Hit:7 https://ppa.launchpadcontent.net/ansible/ansible/ubuntu jammy InRelease
Hit:8 https://ppa.launchpadcontent.net/git-core/ppa/ubuntu jammy InRelease
Hit:9 http://jp.archive.ubuntu.com/ubuntu jammy InRelease
Get:10 http://jp.archive.ubuntu.com/ubuntu jammy-updates InRelease [119 kB]
Hit:11 http://jp.archive.ubuntu.com/ubuntu jammy-backports InRelease
Get:12 http://jp.archive.ubuntu.com/ubuntu jammy-security InRelease [110 kB]
Get:13 http://jp.archive.ubuntu.com/ubuntu jammy-updates/main amd64 Packages [1,558 kB]
Get:14 http://jp.archive.ubuntu.com/ubuntu jammy-updates/restricted amd64 Packages [1,699 kB]
Get:15 http://jp.archive.ubuntu.com/ubuntu jammy-updates/universe amd64 Packages [1,060 kB]
Fetched 4,547 kB in 7s (635 kB/s)
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
12 packages can be upgraded. Run 'apt list --upgradable' to see them.

ではパッケージを更新してみます
更新が始まるので終了するまで待ちます

  • sudo apt -y upgrade

そして再度 graphviz のパッケージを確認すると ESM バージョンのパッケージに更新されていることが確認できます
これにより CVE 対応されたパッケージが使えるようになります

dpkg -l | grep 'graphviz'
ii  libgraphviz-dev:amd64                  2.42.2-6ubuntu0.1~esm1                  amd64        graphviz libs and headers against which to build applications

最後に

個人で Ubuntu Pro に申し込み ESM バージョンのパッケージをインストールしてみました
CVE 対応などの脆弱性緊急対応版のパッケージは結構 ESM として公開されることが多い印象です
5台までなら無料で使えるので登録しておいて損はないかなと思います

参考サイト

2024年4月16日火曜日

Ubuntu のカーネルアップグレード時に No space left on device 対応

Ubuntu のカーネルアップグレード時に No space left on device 対応

概要

apt -y upgrade
でカーネルアップグレードした際に No space left on device でエラーになった場合の対応方法を紹介します

環境

  • Ubuntu 22.04
  • linux kernel generic 5.15.0.92

エラー詳細

Processing triggers for initramfs-tools (0.140ubuntu13.4) ...
update-initramfs: Generating /boot/initrd.img-5.15.0-102-generic
I: The initramfs will attempt to resume from /dev/sda3
I: (UUID=b5504e77-f9b7-468f-9dec-d1b8131f15ca)
I: Set the RESUME variable to override this.
zstd: error 25 : Write error : No space left on device (cannot write compressed block)
E: mkinitramfs failure zstd -q -1 -T0 25
update-initramfs: failed for /boot/initrd.img-5.15.0-102-generic with 1.
dpkg: error processing package initramfs-tools (--configure):
 installed initramfs-tools package post-installation script subprocess returned error exit status 1
Errors were encountered while processing:
 initramfs-tools
needrestart is being skipped since dpkg has failed
E: Sub-process /usr/bin/dpkg returned an error code (1)

対応方法

  • sudo apt autoremove -y

基本はこれで古いカーネルファイルが削除されて新しいファイルが /boot 配下に配置されて成功するはずです

それでもダメな場合は /boot 配下にある古いカーネルファイルを手動で削除して対応しましょう

最後に

カーネルアップグレード時のディスク逼迫エラーについて対処方法を紹介しました
/boot 領域が小さいので古いカーネルファイルを削除してあげましょう

2024年4月15日月曜日

VPN + WSL2 で名前解決できなかったりインターネットに接続できない場合の対処方法

VPN + WSL2 で名前解決できなかったりインターネットに接続できない場合の対処方法

概要

VPN (今回は GrobalProtect 環境) に接続した Windows マシン上で WSL2 を使おうとするとネットワークのルーティングがおかしくなり DNS が参照できずにインターネットに接続できない現象が発生します

環境

  • Windows10 22H2
  • GrobalProtect
  • WSL2 (Ubuntu22.04)

準備

  • Windows マシンで VPN に接続する
  • WSL2 (Ubuntu) を起動する
  • 以下のパワーシェルを実行する

ネットワークのルーティングメトリックを更新する Powershell

このスクリプトは参考サイトにあるにサイトかあそのまま拝借しました
自分も GrobalConnect だったのでネットワークのアダプタ名などはそのまま使えました

Powershell を管理者権限で実行して以下のスクリプトをコピペして実行しましょう

  • vim C:\Users\username\app\connect_vpn_on_wsl2.ps1
$targetIfName = 'PANGP Virtual Ethernet Adapter';
# define function
function Get-NetworkAddress {
    param([Parameter(Mandatory, ValueFromPipelineByPropertyName)][string]$IPAddress, [Parameter(Mandatory, ValueFromPipelineByPropertyName)][int]$PrefixLength);
    process {
        $bitNwAddr = [ipaddress]::Parse($IPAddress).Address -band [uint64][BitConverter]::ToUInt32([System.Linq.Enumerable]::Reverse([BitConverter]::GetBytes([uint32](0xFFFFFFFFL -shl (32 - $PrefixLength) -band 0xFFFFFFFFL))), 0);
        [pscustomobject]@{
            Addr = $IPAddress;
            Prfx = $PrefixLength;
            NwAddr = [ipaddress]::new($bitNwAddr).IPAddressToString + '/' + $PrefixLength;
        };
    }
}
# extend route metric
$targetAddresses = Get-NetAdapter -IncludeHidden | Where-Object InterfaceDescription -Match 'Hyper-V Virtual Ethernet Adapter' | Get-NetIPAddress -AddressFamily IPv4 | Get-NetworkAddress;
$targetIfs = Get-NetAdapter -IncludeHidden | Where-Object InterfaceDescription -Match $targetIfName;
$targetIfs | Set-NetIPInterface -InterfaceMetric 2;
$targetIfs | Get-NetRoute -AddressFamily IPv4 | Select-Object -PipelineVariable rt | Where-Object { $targetAddresses | Where-Object { $_.NwAddr -eq (Get-NetworkAddress $rt.DestinationPrefix.Split('/')[0] $_.Prfx).NwAddr } } | Set-NetRoute -RouteMetric 6000;

やっていることは VPN のルーティングに対してメトリックを 6000 に設定しています (Set-NetRoute -RouteMetric 6000)

デフォルト 5256 が最大優先度なのでそれ以上になっていれば OK です

ダブルクリックで実行する方法

上記をコピペして

C:\Users\username\app\connect_vpn_on_wsl2.ps
に保存します
ショートカットを作成しプロパティからリンク先を以下のように設定すればダブルクリックで実行できます

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -ExecutionPolicy RemoteSigned -File C:\Users\username\app\connect_vpn_on_wsl2.ps1

今回の場合は管理者権限が必要なので右クリックから管理者権限で実行が必要になります

DNS の変更

必要に応じて WSL2 側の resolve.conf を変更しましょう
スクリプトを実行すると VPN 側への通信ができるようになっているので VPN 側で使用している DNS サーバの IP を設定すると良いかなと思います

再起動した際に変わらないようにするには以下のようにします

  • sudo vim /etc/wsl.conf
[boot]
systemd=true

[network]
generateResolvConf=false
  • sudo vim /etc/resolv.conf
nameserver 8.8.8.8
  • sudo chattr +i /etc/resolv.conf

最後に

VPN + WSL2 で WSL2 側から VPN 側への通信をしたい場合は魔法のスクリプトを実行しましょう

参考サイト

Slack の Incomming Webhooks URL を取得する方法

Slack の Incomming Webhooks URL を取得する方法

概要

Legacy App ではなく最近のアプリ作成方法から Incomming Webhooks URL を取得する方法を紹介します

環境

  • Slack (2024/04/09 時点)

アプリの作成

まずはアプリを作成します
ここを参考に作成すれば OK です

Incoming Webhooks の有効化

アプリの設定画面の左メニューにある「Incomming Webhooks」を選択します

有効にすると再度アプリの再インストールを促されるので従います
OAuth の許可画面になるので許可しましょう

あとは少し下に表示される Incomming Webhooks URL を使用すれば OK です

動作確認

curl などで行えば OK です

curl -X POST -H 'Content-type: application/json' --data '{"text":"Hello, World!"}' https://hooks.slack.com/services/T1234567890/B1234567890/xxxxxxxxxxxxxxx

Gitlab の Slack notifications Integration などを使っても良いかなと思います

最後に

Slack 最近のアプリ作成方法から Incomming Webhooks URL を取得する方法を紹介しました
結構簡単に取得はできますがアプリを再度許可する必要があるので注意しましょう

参考サイト

2024年4月14日日曜日

Slack の Python SDK でメッセージに対してリアクションする方法

Slack の Python SDK でメッセージに対してリアクションする方法

概要

reaction_add という API を使います
ポイントはリアクションするメッセージを特定する方法が ID などではなくタイムスタンプだという点です

環境

  • 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"]
    print(ts)
    # 取得したtimestamp情報を元にthumbsupのリアクションする
    response = client.reactions_add(channel=channel_id, name="thumbsup", timestamp=ts)
except SlackApiError as e:
    print(e)

動作確認

以下のようにリアクションが追加になれば OK です

また同じメッセージに対して同じリアクションをしようとすると

The request to the Slack API failed. (url: https://www.slack.com/api/reactions.add)
The server responded with: {'ok': False, 'error': 'already_reacted'}

というエラーになるので注意しましょう

最後に

Slack Python SDK でリアクションする方法を紹介しました
絵文字に対するリアクションでボタンに対するアクションではないので注意しましょう

参考サイト

2024年4月12日金曜日

Slack の Python SDK で指定の日付範囲のメッセージの一覧を取得する方法

Slack の Python SDK で指定の日付範囲のメッセージの一覧を取得する方法

概要

conversations.history で oldest と latest を指定します

環境

  • macOS 11.7.10
  • Python 3.11.6
  • slack-sdk 3.27.1

サンプルコード

oldest と latest はエポックタイム形式で指定します
最終的には文字列として API はコールします   もし指定した範囲内のメッセージが 100 件を超える場合は has_more=true が返ってくるのでページネイトする必要があります

from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

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

try:
    # 会話情報の取得、oldestとlatestで範囲指定、前日分の最新100件を取得する
    now = datetime.now(ZoneInfo("Asia/Tokyo")).replace(
        hour=0, minute=0, second=0, microsecond=0
    )
    oldest = now - timedelta(days=1)
    latest = now - timedelta(days=0)
    # print(oldest)
    # print(latest)
    # メッセージの一覧の取得、oldest,latestのタイムスタンプ(float)は文字列(str)に変換
    result = client.conversations_history(
        channel=channel_id,
        oldest=str(oldest.timestamp()),
        latest=str(latest.timestamp()),
        inclusive=True,
    )
    conversation_history = result["messages"]
    # メッセージの表示
    for msg in conversation_history:
        print(msg["text"])
        print(msg["ts"])
except SlackApiError as e:
    print(e)

最後に

サンプルではページネイトに関しては考慮していないので注意してください
最新100件なので古いものが含まれないケースがあります

参考サイト

2024年4月11日木曜日

Python で Slack に Attachments を送信する方法

Python で Slack に Attachments を送信する方法

概要

Attachments はセカンダリメッセージ用のフォーマットで引用などに使われます
今回は Python sdk で Attachments を送信するサンプルを紹介します

環境

  • macOS 11.7.10
  • Python 3.11.6
  • slack-sdk 3.27.1

サンプルコード

トークンは Bot トークンを使います
Attachments は辞書情報として渡します
指定可能なオプションの説明はコード内のコメントに記載しています

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

client = WebClient(token="xoxb-xxxxxxxx")

try:
    response = client.chat_postMessage(
        channel="#private",
        text="Hello world!",
        # ここからAttachmentsの定義
        attachments=[
            {
                # Attachmentsが表示されない場合の代替テキストを指定
                "fallback": "Plain-text summary of the attachment.",
                # 左のバーのカラーを指定
                "color": "#2eb886",
                # Attachmentsの上部のタイトル情報
                "pretext": "Optional text that appears above the attachment block",
                # Attachmentsの投稿者の名前
                "author_name": "Bobby Tables",
                # Attachmentsの投稿者のリンク先URL
                "author_link": "http://flickr.com/bobby/",
                # Attachmentsの投稿者のアイコンのURL
                "author_icon": "http://flickr.com/icons/bobby.jpg",
                # Attachments内に表示するタイトル
                "title": "Slack API Documentation",
                # Attachments内に表示するタイトルのURL
                "title_link": "https://api.slack.com/",
                # Attachments内に表示する本文
                "text": "Optional text that appears within the attachment",
                # 優先度情報
                "fields": [
                    {
                        "title": "Priority",
                        "value": "High",
                        "short": False,
                    }
                ],
                # フッター情報に掲載する画像情報とサムネイル情報
                "image_url": "https://picsum.photos/200/300",
                # image_urlとthumb_urlは一緒に使用できない、thumb_urlは500kb以下でなければならない
                # "thumb_url": "https://picsum.photos/200",
                # フッター情報
                "footer": "Slack API",
                "footer_icon": "https://platform.slack-edge.com/img/default_application_icon.png",
                # フッターに表示するタイムスタンプ情報
                "ts": 1503435956.000247,
            }
        ],
    )
except SlackApiError as e:
    print(e)

動作確認

サンプルコードの Attachments の場合以下のような表示になります

最後に

Slack Python SDK で Attachments を送信してみました
その他にも blocks という形式でも送信できるようです
ボタンや画像、動画などを送ることができます

参考サイト

2024年4月10日水曜日

docker で MySQL8 を起動してみる

docker で MySQL8 を起動してみる

概要

環境強変数での設定とオプションの指定方法を紹介します

環境

  • Ubuntu 22.04
  • docker 25.03
  • MySQL 8.3.0

起動

認証プラグインと文字コードを指定して起動します
パスワード情報は環境変数経由で設定します
今回はテストなのでデータの永続化はしていません

docker run --rm --name=mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=xxxxxxxx \
-e MYSQL_DATABASE=test \
-e MYSQL_USER=test \
-e MYSQL_PASSWORD=xxxxxxxx \
mysql:8.3.0 --default-authentication-plugin=mysql_native_password --character-set-server=utf8mb4

接続

  • mysql -u test -p test -h 127.0.0.1

ERROR 2002 (HY000): Can’t connect to local MySQL server through socket ‘/var/run/mysqld/mysqld.sock’ (2)

docker で起動した場合は必ず -h を指定する必要があります

プラグイン変更

--default-authentication-plugin=mysql_native_password だと警告が表示されるので別のプラグインを使用します
caching_sha2_password を使います

2024-04-08T01:44:53.610926Z 13 [Warning] [MY-013360] [Server] Plugin mysql_native_password reported: ''mysql_native_password' is deprecated and will be removed in a future release. Please use caching_sha2_password instead'
docker run --rm --name=mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=xxxxxxxx \
-e MYSQL_DATABASE=test \
-e MYSQL_USER=test \
-e MYSQL_PASSWORD=xxxxxxxx \
mysql:8.3.0 --default-authentication-plugin=caching_sha2_password --character-set-server=utf8mb4

docker の場合 --initialize-insecure オプションは使えないっぽい

以下のような警告が表示されるので --initialize-insecure を付与してみたのですがコンテナがうまく起動できませんでした

2024-04-08T01:53:26.376301Z 6 [Warning] [MY-010453] [Server] root@localhost is created with an empty password ! Please consider switching off the --initialize-insecure option.
docker run --rm --name=mysql \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=xxxxxxxx \
-e MYSQL_DATABASE=test \
-e MYSQL_USER=test \
-e MYSQL_PASSWORD=xxxxxxxx \
mysql:8.3.0 --default-authentication-plugin=caching_sha2_password --character-set-server=utf8mb4 --initialize-insecure --user=mysql

証明書対策

以下の警告が表示されます

2024-04-08T01:53:30.653497Z 0 [Warning] [MY-010068] [Server] CA certificate ca.pem is self signed.

デフォルトの場合は独自で証明書を生成するようです
今回はテストなので証明書は自己証明書のままにしています

最後に

docker で mysql8 を動かしてみました
mysqld.cnf などを使っても設定できるようですが引数でもいろいろと設定可能です

参考サイト

2024年4月9日火曜日

Slack の Socket Mode を試してみた

Slack の Socket Mode を試してみた

概要

Python を使いました
Socket Mode は Bolt と呼ばれる SDK を使ったほうが扱いやすいので Bolt を使います
トークンは Bot トークンと App Level トークンを使います

環境

  • macOS 11.7.10
  • Python 3.11.6
  • slack-bolt 1.18.1

準備

  • アプリの作成
  • Bot トークンの権限と取得
    • app_mentions:read
    • アプリの管理ページ -> 左メニュー「Install App」
  • Socket モードの有効化
    • アプリの管理ページ -> 左メニュー「Socket Mode」
  • App Level トークの取得
    • アプリの管理ページ -> 左メニュー「Basic Information」

Event Subscriptions の設定

左メニューの「Event Subscriptions」から使用するイベントを設定します
今回は特定のメッセージに反応するようにしたいので「message.channels」を追加します

インストール

  • pipenv install slack_bolt

サンプルコード

from typing import Callable

from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler

# こっちは Bot トークンを設定
app = App(token="xoxb-xxxxxxxxx")


# メッセージへの反応
# Hello に対して Hi there を返す
@app.message("Hello")
def say_hello(message: dict, say: Callable):
    user = message["user"]
    say(f"Hi there, <@{user}>!")


if __name__ == "__main__":
    # Socket モードをスタートする
    SocketModeHandler(
        app,
        # こっちは App Level トークンを設定
        "xapp-xxxxxxxxx",
    ).start()

動作確認

  • pipenv run python app.py

で起動しましょう
アプリはチャネルに招待しておいてください

そしてメッセージで「Hello」と送信するとアプリが反応してメッセージが返ってくることが確認できると思います

最後に

Slack の Socket Mode を試してみました
最近作成したアプリでは RTM API が使えないので Socket Mode に慣れておくと良いかなと思います

RTM に似たような感じで特定のイベントに反応して返信するという動きができます
またプレーンテキストだけではなくボタンなどを含んだインタラクティブコンポーネントを送信することもできるようです

参考サイト

2024年4月8日月曜日

fastapi にリクエストIDを自動で埋め込む方法 (RequestIdPlugin編)

fastapi にリクエストIDを自動で埋め込む方法 (RequestIdPlugin編)

概要

starlette には RequestIdPlugin という仕組みがありこれを使うと context 経由でリクエストIDを取得できます
アプリコンテキスト配下であれば同一のリクエストIDが取得できるのでロギングなどに使えます

環境

  • macOS 11.7.10
  • Python 3.11.6
    • fastapi 0.110.1
    • starlette 0.37.2
    • starlette-context 0.3.6

サンプルコード

from fastapi import FastAPI
from pydantic import BaseModel
from pydantic_settings import BaseSettings
from starlette.middleware import Middleware
from starlette_context import context
from starlette_context.middleware import RawContextMiddleware
from starlette_context.plugins.correlation_id import CorrelationIdPlugin
from starlette_context.plugins.request_id import RequestIdPlugin

# ミドルウェア定義
# これを定義することで context にリクエスト ID が自動で保持される
middleware = [
    Middleware(RawContextMiddleware, plugins=(RequestIdPlugin(), CorrelationIdPlugin()))
]
# ミドルウェア追加
app = FastAPI(middleware=middleware)


class Settings(BaseSettings):
    message: str = ""


settings = Settings()


class Item(BaseModel):
    message: str


@app.get("/")
async def get_msg():
    # コンテキスト情報からリクエストIDの取得
    return {
        "message": settings.message,
        "context": context,
    }


@app.post("/")
def set_msg(item: Item):
    settings.message = item.message
    return {"message": item.message}

最後に

RequestIdPlugin を使って fastapi にリクエストIDを自動生成する方法を紹介しました
context を使うのでアプリコンテキスト配下のモジュール出ないと参照できないのでそこは注意しましょう

参考サイト

2024年4月5日金曜日

Python で Slack API をコールする

Python で Slack API をコールする

概要

python-slack-sdkを使ってチャネルにメッセージをポストする方法を紹介します

環境

  • macOS 11.7.10
  • Python 3.11.6
    • slack_sdk 3.27.1
  • Slack 2024/04/05 時点の画面

Slack アプリの作成

https://api.slack.com/tutorials/tracks/getting-a-token このあたりから作成すると良いです
アプリをワークスペースにも追加しましょう

Bot トークン

Slack にはトークンの種類がいくつかあり今回は Bot トークンを使います

アプリの左メニューから「Install App」を選択し「Bot User OAuth Token」を使います

URL で言うと https://api.slack.com/apps/xxxxxxx/install-on-team? という形式になります

インストール

  • pipenv install slack_sdk

サンプルコード

先ほど取得した Bot トークンを使用します
サンプルコードは指定したチャネルにメッセージをポストするサンプルで Github にあるサンプルスクリプトをそのまま利用します

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

client = WebClient(token="xoxb-xxxxxxxxxxxxxxxxx")

try:
    response = client.chat_postMessage(channel='#private', text="Hello world!")
    assert response["message"]["text"] == "Hello world!"
except SlackApiError as e:
    # You will get a SlackApiError if "ok" is False
    assert e.response["ok"] is False
    assert e.response["error"]  # str like 'invalid_auth', 'channel_not_found'
    print(f"Got an error: {e.response['error']}")
    # Also receive a corresponding status_code
    assert isinstance(e.response.status_code, int)
    print(f"Received a response status_code: {e.response.status_code}")

チャネルの内容を取得する

ついでにチャネルに流れるメッセージを取得するスクリプトも動かしてみました
トークンは先ほど取得した Bot トークンを使用します

直近100件のメッセージを取得します

ちなみに RTM API は非推奨になっています (参考)
なので今回は conversations.history を使っています

from slack_sdk import WebClient
from slack_sdk.errors import SlackApiError

client = WebClient(token="xoxb-xxxxxxxxxxxxxxxxx")

conversation_history = []
channel_id = "C0123456789"

try:
    result = client.conversations_history(channel=channel_id)
    conversation_history = result["messages"]
    print("{} messages found in {}".format(len(conversation_history), channel_id))
    for msg in conversation_history:
        print(msg["text"])

except SlackApiError as e:
    print("Error creating conversation: {}".format(e))

channel_id はアプリでチャネル名を右クリックし Copy link で取得できます
またボットがチャネルに参加していないと会話は取得できないのでボットを招待してあげましょう (すでにメッセージを送信しているとチャネルに参加しているように見えますがまだいないのでちゃんと /invite してあげましょう)

最後に

Python で Slack のAPI をコールする方法を紹介しました
Slack には 3 つのトークン (User トークン、Bot トークン、アプリレベルトークン) があり今回のようにスクリプトベースの場合には Bot トークンを使います

RTM API が非推奨になり Socket API を変わりに使うようになったようなので機会があれば試してみたいです

参考サイト

2024年4月3日水曜日

pyright で dyld: Library not loaded: /usr/local/opt/icu4c/lib/libicui18n.73.dylib

pyright で dyld: Library not loaded: usr local opt icu4c lib libicui18n.73.dylib

概要

Mac 上の pyright でタイトルのエラーが出たので対応しました

環境

  • Python 3.11.6
  • pyright 1.1.356
  • macOS 11.7.10

対応方法

  • brew reinstall node

To retry an incomplete download, remove the file above.

上位の reinstall 中に xz でエラーが発生したので対応しました

エラー文のその上に表示されているキャッシュファイルを削除する

ex)

  • rm /Users/kakakikikeke/Library/Caches/Homebrew/downloads/9a5d194e1a978ebb3dac37f12deaa4085d4d1016cb31bcbf54b7a60103c3e1e6--xz-5.6.1.tar.gz

そして再度 install する

  • brew update
  • brew reinstall xz

最後に

pyright は node なので node まわりのインストールを見直すと良いでしょう
スペックの低いマシンだと node reinstall にかなり時間がかかります (自分の場合1時間以上かかりました

2024年4月2日火曜日

alpine の最新版で venv を作成した場合に python スクリプトを実行する方法

alpine の最新版で venv を作成した場合に python スクリプトを実行する方法

概要

過去に alpine で venv を作成し pip install する方法を紹介しました
今回は python スクリプトを実行する方法を紹介します

環境

  • Ubuntu 22.04
  • docker 25.0.2
  • lego 4.16.1

dockerfile

docker ファイル内では boto3 をインストールします
このイメージ内で boto3 を import できるようにします

FROM goacme/lego:v4.16.1

RUN apk update
RUN apk add --no-cache --virtual .build-deps python3-dev python3 py3-pip
RUN python3 -m venv ~/test_venv --system-site-packages
RUN ~/test_venv/bin/pip3 install --upgrade pip setuptools
RUN ~/test_venv/bin/pip3 install boto3

python スクリプト

boto3 を import することをテストするスクリプトです
これを先程ビルドしたイメージ内で実行します

import boto3

print("Succeed to import boto3")

起動

ポイントは python3 の実行コマンドのパスです
作成した venv 内にある python3 を使うのがポイントです
また lego の場合ホームディレクトリは /root になっているのでそこに配置していますがスクリプトの配置先はどこでも OK です

  • docker run --rm --entrypoint "" -v $(pwd)/test.py:/root/test.py test /root/test_venv/bin/python3 /root/test.py

最後に

alpine の最新版で python スクリプトを実行する方法を紹介しました
ポイントは venv にインストールした python3 を使う点です

2024年4月1日月曜日

The system-wide python installation should be maintained using the system package manager (apk) only. 対応

The system-wide python installation should be maintained using the system package manager (apk) only. 対応

概要

古い alpine では virtualenv を作成しないと pip install ができないようなので対応策を紹介します

環境

  • Ubuntu 22.04
  • docker 25.0.2
  • lego 4.16.1

対応前 dockerfile

FROM goacme/lego:v4.16.1

RUN apk update
RUN apk add --no-cache --virtual .build-deps python3-dev python3 py3-pip
RUN pip3 install --upgrade pip setuptools
RUN pip3 install awscli
RUN aws configure list

これだと3つ目の RUN のpip3 install でエラーになります

対応版 dockerfile

FROM goacme/lego:v4.16.1

RUN apk update
RUN apk add --no-cache --virtual .build-deps python3-dev python3 py3-pip
RUN python3 -m venv ~/test_venv --system-site-packages
RUN ~/test_venv/bin/pip3 install --upgrade pip setuptools
RUN ~/test_venv/bin/pip3 install awscli
RUN ~/test_venv/bin/aws configure list

一度 venv を作成しそこにインストールされるコマンドを使うことでエラーにならなくなります

active しても OK です

最後に

エラーに記載されている通りに venv を作成するのが一番かなと思います
また alpine のバージョンを下げることで対応もできますが基本は新しいバージョンを使ったほうがいいのでおすすめはしません

参考サイト