2025年3月31日月曜日

ローカルで renovate を実行し python のパッケージバージョンの管理をする

ローカルで renovate を実行し python のパッケージバージョンの管理をする

概要

renovate は nodejs で書かれた自動パッケージ更新ツールです
python の pipenv でも使えるのでローカルで試してみました
なおローカルで試す場合には実際にブランチやPRの作成はされません
本来は CI と組み合わせて使います

環境

  • macOS 15.3.2
  • node 20.18.2
    • renovate 39.222.1

インストール

  • nvm use 20.18.2 --save
  • npm i renovate

renovate.json の作成

{
  "extends": [
    "config:recommended"
  ],
  "enabledManagers": [
    "pipenv"
  ],
  "packageRules": [
    {
      "description": "Schedule all packages update on Sunday nights (9 PM - 12 AM)",
      "matchManagers": [
        "pipenv"
      ],
      "groupName": "pip dependencies",
      "schedule": [
        "* 21-23 * * 0"
      ],
      "automerge": true,
      "automergeType": "branch"
    }
  ],
  "pipenv": {
    "lockFileMaintenance": {
      "enabled": true,
      "schedule": [
        "monthly"
      ]
    },
    "fileMatch": [
      "(^|/)Pipfile$"
    ]
  }
}

package.json への追記

{
  "dependencies": {
    "renovate": "^39.222.1"
  },
  "scripts": {
    "renovate": "renovate --platform=local --dry-run=full",
    "renovate_help": "renovate --help"
  }
}

renovate 実行

  • RENOVATE_CONFIG_FILE=renovate.json npm run renovate

ローカルで実行する場合にはいろいろとオプションが必要です
コマンドの実体は以下です

  • RENOVATE_CONFIG_FILE=renovate.json renovate --platform=local --dry-run=full

これで Pipfile に書かれたパッケージが更新されるはずです

npx で直接実行もできます

  • RENOVATE_CONFIG_FILE=renovate.json npx renovate --platform=local --dry-run=full

local 動作では --dry-run=lookup になっており Pipfile.lock が作成されないので上書きします
LOG_LEVEL=debug を指定すると詳細なログが確認できます
またまだ renovate.json がない場合には --require-config=ignore を設定してください

エラーが出なければ OK です

動作確認

通常 renovate は更新があった場合にブランチからのPRが作成されます
ただ platform=local 実行の場合にはブランチの作成が許可されていないので platform=bitbucket などを指定して実際の挙動を確認しましょう

== の場合

当然ですがバージョンが固定されているので更新されません

isort = "==6.0.0"

で renovate を実行しても 6.0.1 には更新されない

~=の場合

この場合は 6.0.1 に更新されます

isort = "~=6.0.0"

で Pipfile.lock が更新され 6.0.1 がインストールされます

最後に

ローカルで renovate を試してみました
ローカルでは設定ファイルの確認くらいしかできないので実際はリモートリポジトリと組み合わせて使います
次回は bitbucket と組み合わせてみたいと思います

参考サイト

2025年3月28日金曜日

emacs で gptel を使ってローカルで起動した ollama と接続してみる

emacs で gptel を使ってローカルで起動した ollama と接続してみる

概要

前回 Openhermes をローカルに構築しました
今回は emacs の gptel を使って Openhermes と通信してみます
なお今回の記事はすでにローカルで Openhermes で起動している前提で話を進めます

環境

  • macOS 15.3.2
  • ollama 0.6.2
  • Openhermes v2.5
  • emacs 30.1
  • gptel 0.9.8

gptel インストール

  • M-x package-list-packages
  • gptel -> i -> x

gptel 設定ファイル作成

  • vim .emacs.d/site-lisp/llm/init.el
;; OPTIONAL configuration
(setq
 gptel-model 'openhermes:latest
 gptel-backend
 (gptel-make-ollama
  "Ollama"
  :host "localhost:11434"
  :stream t
  :models '(openhermes:latest)))

動作確認

gptel

M-x gptel を実行すると Openhermes とやりとりするバッファを選択するので *Ollama* バッファを選択します

すると ### というプロンプトになるのでここに質問を入力します
質問を入力したら C-c RET で質問を送信できます

gptel-send

gptel-send は現在のカーソル位置より上部のテキストを送信します
また返答も現在のバッファにそのまま書き込まれます

gptel-rewrite

gptel-rewrite はリファクタなどに使えます
現在選択している部分を上書きしてくれる機能です
リファクタする際に一行ずつやリファクタする方法などを追加で送信することもできます

最後に

他にもいろいろ機能はありそうです
Tools モードを使うと特定のパスに設定ファイルを作成してくれるタスクをインタラクティブに生成することもできるようです

copilot-mode と混同しているとキーバインドが同じケースがあるので別の割当てをする必要がありそうです

copilot-mode はデフォルトで auto-complete のように自動で補完してくれます
gptel はデフォルトではそうでなく修正したい箇所や新規でコードを追加してい場合で質問して LLM 側に書いてもらう感じになります

おそらくどちらも同じ機能があるかなと思うので使い分けというよりかはどちらか一方を使い倒すほうがいいかなと思います

copilot は無料だと制限があるので無制限にかつ無料で使いたい場合には必然的に gptel になるかなと思います
ただ gptel は当然ですがローカルに LLM を動かす環境が必要になるのでそれなりのマシンスペックが要求される点に注意です

参考サイト

2025年3月27日木曜日

M2 Pro Mac mini で Openhermes を動かす

M2 Pro Mac mini で Openhermes を動かす

概要

Ollama を使ってローカルで動かします

環境

  • macOS 15.3.2
  • ollama 0.6.2
  • Openhermes v2.5

ollama インストール

  • brew install ollama

ollama 起動

  • ollama services run ollama

Openhermes 起動

  • ollama run openhermes

この時点でインタラクティブモードになり質問することもできます

Open-WebUI 起動

  • docker run -d -p 3000:8080 --add-host=host.docker.internal:host-gateway -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

動作確認

localhost:3000 にアクセスすると UI を経由して Openhermes を使うことができます

最後に

精度はそこそこと言った感じです
軽量で日本語にも対応してるのでローカルで動かす LLM としては十分かもしれません
ただそこまで回答に期待はしないほうがいいかもです

参考サイト

2025年3月25日火曜日

eslint を 8 系から 9 系にするメモ

eslint を 8 系から 9 系にするメモ

概要

設定ファイルのマイグレーションが必要です

環境

  • node 20.18.2
  • eslint 8.41.0 -> 9.22.0

マイグレーション

.eslintrc.js を eslint.config.mjs にマイグレーションします

  • npx @eslint/migrate-config .eslintrc.js

バージョンアップ

devDependencies の部分を手動で書き換えます

  • vim package.json
{
  "devDependencies": {
    "eslint": "^9.22.0"
  }
}

ちなみに最新バージョンの確認は npm outdated で確認します
あとはインストールするだけです

  • npm i

ちなみに "eslint": "^8.41.0" という感じで書いている場合に npm update すると 8 系の最新までは自動でアップデートしてくれます

最後に

npm だけを使っている場合には手動でバージョンを書き換えて update する必要があります

2025年3月24日月曜日

treemacs の workspace の設定ファイルがあるパス

treemacs の workspace の設定ファイルがあるパス

概要

複数のワークスペースを手動で追加したい場合には直接設定ファイルを編集しちゃうのが簡単です

環境

  • macOS 15.3.2
  • emacs 30.1

パス

~/.emacs.d/.cache/treemacs-persist

直接編集する

先に emacs を停止しましょう

treemacs-persist とバックアップファイルを直接編集します
例えば以下のように特定のディレクトリ配下のプロジェクトを一括で追加できます

echo "* Default" > ~/.emacs.d/.cache/treemacs-persist && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist
echo "* Default" > ~/.emacs.d/.cache/treemacs-persist\~ && for i in `ls | grep -v update_libs.sh`; do echo "** ${i}\n - path :: ~/data/repo/${i}"; done >> ~/.emacs.d/.cache/treemacs-persist\~

動作確認

これで再度 emacs を開くと treemacs にプロジェクトが追加されています

トラブルシューティング

なぜか .emacs.d/.cache/treemacs-persist が改行されるというなぞの現象になるので修正します
この現象は before-save-hook などで自動フォーマットが設定されている場合に自動的に treemacs-persist もフォーマットしてしまい発生してる可能性が高いです

  • cat ~/.emacs.d/.cache/treemacs-persist-at-last-error
# State when last error occurred on 2025-03-20 10:36:48
# Error was 'First item must be a workspace name' in line '*'

*
Default
**
python-try
-
path
::
~/data/repo/python-try
  • vim ~/.emacs.d/.cache/treemacs-persist
* Default
** python-try
- path :: ~/data/repo/python-try

最後に

面倒な場合は直接キャッシュファイルを編集しちゃいましょう
フォーマットがあるのでそこだけ気をつけてください

2025年3月22日土曜日

Github Actions で elisp の lint をする方法

Github Actions で elisp の lint をする方法

概要

elisp-autofmt を使ってフォーマットした結果 git diff で差分があった場合はエラーにすることで lint チェックします

環境

  • emacs 30.1

format_elisp_files.sh

フォーマットするためのシェルスクリプトです
内部で emacs を使って elisp-autofmt を実行しフォーマットします
ls-files で取得するファイル名は適宜変更してください

for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
  (let ((use-stdout nil))
    (dolist (buf (buffer-list))
      (with-current-buffer buf
        (when (buffer-file-name)
          (setq buffer-undo-list t) ;; Disable undo.
          (if use-stdout
              (progn
                (elisp-autofmt-buffer)
                (princ (buffer-substring-no-properties (point-min) (point-max))))
            (princ buffer-file-name)
            (princ \"\n\")
            (elisp-autofmt-buffer-to-file)))))))"
done

.github/workflows/elisp_lint.yml

ame: Emacs Lisp Format Check

on:
  push:
  pull_request:

jobs:
  format-check:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Install Emacs
        run: sudo apt-get install -y emacs-nox

      - name: Install pyenv
        uses: actions/setup-python@v5
        with:
          python-version: "3.12.9"

      - name: Install elisp-autofmt into emacs
        run: |
          mkdir -p ~/.emacs.d/elpa/elisp-autofmt-latest/
          wget -O ~/.emacs.d/elpa/elisp-autofmt-latest/elisp-autofmt.el https://codeberg.org/ideasman42/emacs-elisp-autofmt/raw/branch/main/elisp-autofmt.el

      - name: Run format_elisp_files.sh
        run: sh ./format_elisp_files.sh

      - name: Check diff
        run: git diff --exit-code

最後に

これで CI 上で elisp の lint ができます

参考サイト

2025年3月21日金曜日

emacs に elisp-autofmt を入れて elisp のフォーマットをしてみる

emacs に elisp-autofmt を入れて elisp のフォーマットをしてみる

概要

emacs の設定ファイルである el ファイルをフォーマットする方法を紹介します

環境

  • macOS 15.3.2
  • emacs 30.1
  • python 3.12.9

インストール

  • package-list-packages
  • elisp-autofmt -> x -> y

フォーマット実行

  • M-x elisp-autofmt-buffer

emacs を実行しているパスで python コマンドが使えないとダメなので注意しましょう

トラブルシューティング

実行時に

elisp-autofmt: error output
Note, built-in variable ‘image-scaling-factor’ not bound

となる場合は再度 elisp-autofmt-buffer を実行すればエラーが解消されます

自動起動

(add-hook 'emacs-lisp-mode-hook 'elisp-autofmt-mode)
(add-hook 'lisp-mode-hook 'elisp-autofmt-mode)

これで el ファイル保存時に自動でフォーマットしてくれます

うまく保存時にフォーマットしてくれない場合は以下でも OK です

(add-hook 'before-save-hook 'elisp-autofmt-buffer)

CLI で実行

emacs が必要ですが CLI からも実行できます
https://codeberg.org/ideasman42/emacs-elisp-autofmt/src/branch/main/elisp-autofmt-cmd.py を使っても OK です

for file_name in `git ls-files files | egrep -e 'el|emacs'`
do
emacs --batch $file_name -l ~/.emacs.d/elpa/elisp-autofmt-*/elisp-autofmt.el --eval "
(progn
  (let ((use-stdout nil))
    (dolist (buf (buffer-list))
      (with-current-buffer buf
        (when (buffer-file-name)
          (setq buffer-undo-list t) ;; Disable undo.
          (if use-stdout
              (progn
                (elisp-autofmt-buffer)
                (princ (buffer-substring-no-properties (point-min) (point-max))))
            (princ buffer-file-name)
            (princ \"\n\")
            (elisp-autofmt-buffer-to-file)))))))"
done

こちらも実行するパスに python が必要になるので注意しましょう

最後に

elisp-autofmt をインストールして elisp ファイルの自動フォーマットをしてみました

次回は CI を組み込んで lint してみたいと思います

参考サイト

2025年3月20日木曜日

emacs で特定のモード起動時にシェルコマンド実行する方法

emacs で特定のモード起動時にシェルコマンド実行する方法

概要

いくつかあるので紹介します

環境

  • macOS 15.3.2
  • emacs 30.1

フォアグラウンドで実行

(add-hook 'python-mode-hook
  (lambda ()
    (shell-command "echo 'Hello, Python!' >> ~/start_python_mode_msg.txt")))

バックグラウンドで実行

(add-hook 'c-mode-hook
  (lambda ()
    (start-process "make-process" nil "make")))

非同期実行

(add-hook 'go-mode-hook
  (lambda ()
    (async-shell-command "go build")))

シェルスクリプトを実行

(add-hook 'js-mode-hook
  (lambda ()
    (shell-command "bash ~/scripts/setup.sh")))

最後に

必要に応じて使い分けましょう

2025年3月19日水曜日

flask-sqlalchemy で pyright 用のスタブファイルを作成してみた

flask-sqlalchemy で pyright 用のスタブファイルを作成してみた

概要

flask-sqlalchemy + pyright を使っているとデータベースのモデルからインスタンスを生成する際に pyright のエラーになります
回避方法はいろいろありますが今回は pyright 用のスタブ情報を手動で作成することで対応してみました

環境

  • Python 3.12.9
    • flask-sqlalchemy 3.1.1
    • pyright 1.1.396

エラーの出るファイル

  • vim my_libs/schema.py
from flask_sqlalchemy import SQLAlchemy

db = SQLAlchemy()


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String(80), nullable=False)


# インスタンス作成
user = User(id=1, name="Alice")

これで pyright を実行すると以下のえらーになります
これをスタブを作成することで回避します

  • pipenv run pyright my_libs/schema.py
/path/to/my_libs/schema.py
  /path/to/my_libs/schema.py:12:13 - error: No parameter named "id" (reportCallIssue)
  /path/to/my_libs/schema.py:12:19 - error: No parameter named "name" (reportCallIssue)
2 errors, 0 warnings, 0 informations

スタブファイルの自動生成

  • pipenv run pyright --createstub flask_sqlalchemy

これで typings 配下に以下のファイルが自動で生成されます

ls -ltR typings                                   
total 0
drwxr-xr-x  12 user01  staff  384  3 18 10:22 flask_sqlalchemy

typings/flask_sqlalchemy:
total 120
-rw-r--r--  1 user01  staff    171  3 18 10:22 track_modifications.pyi
-rw-r--r--  1 user01  staff    710  3 18 10:22 table.pyi
-rw-r--r--  1 user01  staff   1428  3 18 10:22 session.pyi
-rw-r--r--  1 user01  staff   1870  3 18 10:22 record_queries.pyi
-rw-r--r--  1 user01  staff   3024  3 18 10:22 query.pyi
-rw-r--r--  1 user01  staff   5279  3 18 10:22 pagination.pyi
-rw-r--r--  1 user01  staff   5297  3 18 10:22 model.pyi
-rw-r--r--  1 user01  staff  14197  3 18 10:22 extension.pyi
-rw-r--r--  1 user01  staff    321  3 18 10:22 cli.pyi
-rw-r--r--  1 user01  staff    180  3 18 10:22 __init__.pyi

動作確認

スタブファイルを作成した状態で再度 pyright を実行しましょう
先のほどまで出ていた No parameter named "name" などのエラーが出ないことが確認できると思います

おまけ: その他の解決方法

__init__ メソッドを User モデルクラスに実装する

この方法でも pyright のエラー自体は出なくなりますがコンストラクタを生成することで初期化時の処理が変わる可能性があるのであまりおすすめはしないです

dict を使い変数展開する

User 生成時にキーワード引数ではなく辞書の展開を使ってもエラーを回避できます

params = {
  "id": 1,
  "name": "hawk"
}
user = User(**params)

最後に

flask-sqlalchemy の pyright エラーに対応してみました
typings/ ディレクトリは今回は自動生成しているので git には含めないほうがいいです
手動で作成している場合は含めたほうがいいです

CI などで typings が必要な場合は毎回生成してあげるようにしましょう

おまけ: スタブファイルの手動作成(失敗編)

以下試したけどダメだったパターンです

スタブファイルはプロジェクトルートの typings というディレクトリ配下に作成します
またパッケージの階層も同じにする必要がありかつファイル名は pyi ファイルにします

  • mkdir typings
  • mkdir typings/my_libs
  • vim typings/my_libs/schema.pyi
from flask_sqlalchemy import SQLAlchemy

db: SQLAlchemy

class User():
    id: int
    name: str

    def __init__(self, id: int, name: str) -> None: ...

2025年3月18日火曜日

python:3.12.9-slim-bookworm に pyyaml をインストールする方法

python:3.12.9-slim-bookworm に pyyaml をインストールする方法

概要

6.0 だとエラーになるので 6.0.2 をインストールしましょう

環境

  • docker 27.3.1
  • Python 3.12.9
    • pyyaml 6.0.2

Dockerfile

FROM python:3.12.9-slim-bookworm

RUN apt -y update && apt -y install libyaml-dev gcc
RUN pip install pyyaml=="6.0.2"

最後の行が pyyaml=="6.0" だとエラーになります

最後に

理由は不明です
おそらく何かしらのパッケージが足りていないんだと思います

同じように uwsgi も 2.0.22 だと slim-bookworm ではエラーになりました

2025年3月17日月曜日

Github で自分が作成した Issue と Pull-Request をすべて表示する方法

Github で自分が作成した Issue と Pull-Request をすべて表示する方法

概要

Issue および Pull request を一覧ですべて表示します

環境

  • Github 2025/03/14 時点

URL

your-github-account-name に自分のアカウント名を入力すればすべて表示されます

最後に

これまでの活動内容をすべて表示したいときに便利です

2025年3月14日金曜日

iOS アプリを xcodebuild CLI コマンドでビルドする方法

iOS アプリを xcodebuild CLI コマンドでビルドする方法

概要

Xcode を起動しないでとりあえずビルドできるかチェックしたい場合に便利です
あとは CI などに使えます

環境

  • macOS 15.3.1
  • xcodebuild 16.2 (16C5032a)

コマンド

  • cd /path/to/project
  • xcodebuild -workspace project_name.xcworkspace -scheme project_name -destination 'platform=iOS Simulator,name=iPhone 16' clean build

成果物

以下のパスに app ファイルが作成されます

~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphonesimulator/project_name.app

Any iOS Device でビルドする方法

destination オプションで generic/platform=iOS だけを指定します

  • xcodebuild -workspace project_name.xcworkspace -scheme project_name -destination 'generic/platform=iOS' clean build

成果物のパスは以下に変わります

~/Library/Developer/Xcode/DerivedData/project_name-bpovlgogrctpyxcxsxxxxxxxcxon/Build/Products/Debug-iphoneos/project_name.app

トラブルシュート

Cocoapods を使ってサードパーティのライブラリを管理している場合はこちらの方法でサードパーティ側のバージョンも固定しなければいけない場合があります
固定する設定がなく Pods 側のビルドバージョンの変更を毎回 Xcode でやっていた場合には固定化する設定が必要になります

最後に

わざわざ Xcode を開いてビルドする必要がなくなるので Pods をアップデートして問題なくビルドできるかなどの確認作業を CLI のみで行うことができるようになります

参考サイト

2025年3月13日木曜日

OpenTelemetry で celery の非同期タスクでもリクエストと同じトレースをする方法

OpenTelemetry で celery の非同期タスクでもリクエストと同じトレースをする方法

概要

Flask アプリから celery の非同期タスクを呼び出した際にもトレースできるようにします
Flask、Celery で一貫して OLTP にトレース情報を送信するサンプルコードを紹介します

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
    • celery 5.4.0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

追加インストール

前回のに追加で以下をインストールします

  • pipenv install opentelemetry-instrumentation-celery celery

app.py

from celery import Celery
from flask import Flask
from opentelemetry import trace
from opentelemetry.propagate import inject

from lib.tasks import roll_dice_async

# Flask アプリの作成
app = Flask(__name__)

# Celery 設定
app.config["CELERY_BROKER_URL"] = "redis://localhost:6379/0"
app.config["CELERY_RESULT_BACKEND"] = "redis://localhost:6379/0"
celery = Celery(app.name, broker=app.config["CELERY_BROKER_URL"])
celery.conf.update(app.config)

# OpenTelemetry の設定
tracer = trace.get_tracer("diceroller.tracer")


@app.route("/rolldice")
def roll_dice():
    username = get_username()

    # トレースコンテキストを取得して Celery に渡す
    headers = {}
    inject(headers)
    roll_dice_async.apply_async(kwargs={"username": username, "headers": headers})

    return "Rolling dice asynchronously!"


def get_username():
    with tracer.start_as_current_span("get_username") as span:
        username = "hawksnowlog"
        span.set_attribute("username", username)
        return username


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

lib/tasks.py

from random import randint

from opentelemetry import trace
from opentelemetry.propagate import extract

from lib.worker import celery

tracer = trace.get_tracer("diceroller.tracer")


@celery.task
def roll_dice_async(username, headers):
    return roll(username, headers)


def roll(username: str, headers):
    # OpenTelemetry のコンテキストを受け取り、トレースを継続
    with tracer.start_as_current_span("roll", context=extract(headers)) as span:
        res = randint(1, 6)
        span.set_attribute("roll.value", res)
        return res

lib/worker.py

from celery import Celery
from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
from opentelemetry.instrumentation.celery import CeleryInstrumentor
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor

# Celery インスタンスを作成
celery = Celery("tasks", broker="redis://localhost:6379/0")
celery.conf.update(broker_connection_retry_on_startup=True)

# OpenTelemetry の設定
tracer_provider = TracerProvider()
tracer_provider.add_span_processor(
    BatchSpanProcessor(OTLPSpanExporter(endpoint="http://localhost:4317"))
)

# Celery の OpenTelemetry を有効化
CeleryInstrumentor().instrument()

起動

Flask

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py

Celery

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server celery -A lib.tasks worker --loglevel=INFO

Jaeger

  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest

動作確認

localhost:8080/rolldice にアクセスして Jaeger にアクセスすると一つのトレース内に Flask と Celery のタスクが処理が含まれていることが確認できると思います

最後に

Flask + Celery に OLTP のトレースを設定する方法を紹介しました

トレースに間があるのは非同期特有なのかもしれません
もしくは BatchSpanProcessor を使っているのでリアルタイムでなくバッチ処理として送信している影響かもしれません

参考サイト

2025年3月12日水曜日

OpenTelemetry のトレース情報を Jaeger に直接送信する方法

OpenTelemetry のトレース情報を Jaeger に直接送信する方法

概要

これまではアプリ -> コレクタ -> exporter -> Jaeger という経路でトレース情報を送信していました
推奨ではないですがコレクタと exporter をすっ飛ばして直接トレース情報を Jaeger に送信することもできるので紹介します

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

Jaeger 起動

  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 4317:4317 -p 4318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest

4317 で OTLP のトレース情報を直接受けます

アプリ起動

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py

上記の設定では otlp は localhost:4317 に送信されます

app.py

前回と変わりませんが記載しておきます

from random import randint
from flask import Flask

from opentelemetry import trace

# Acquire a tracer
tracer = trace.get_tracer("diceroller.tracer")

app = Flask(__name__)

@app.route("/rolldice")
def roll_dice():
    username = get_username()
    return str(roll(username))

def get_username():
    with tracer.start_as_current_span("get_username") as rollspan:
        username = "hawksnowlog"
        rollspan.set_attribute("username", username)
        return username

def roll(username: str):
    # This creates a new span that's the child of the current one
    with tracer.start_as_current_span("roll") as rollspan:
        res = randint(1, 6)
        rollspan.set_attribute("roll.value", res)
        return res

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

動作確認

Jaeger を確認するとトレース情報が確認できます

最後に

おそらく OpenTelemetry が推奨する方法ではないですがこの方法でもトレース情報を送信できます
アプリのメトリックスを取得しない(トレースだけ)かつ OTLP の情報を Jaeger 以外に送らないのであればこの構成でもいいのかもしれません

参考サイト

2025年3月11日火曜日

OpenTelemetry のトレース箇所を独自で設定する方法

OpenTelemetry のトレース箇所を独自で設定する方法

概要

前回 OpenTelemetry を Flask アプリに適用する方法を紹介しました
前回の方法はトレースやメトリックス情報を自動で送信してくれる設定です
今回は手動でトレース情報を送信する方法を紹介します

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

app.py

自動でトレースする設定は外し自分でトレースしたい箇所に設定します
基本的には tracer.start_as_current_span でトレースしたい箇所にスパンを作成するだけで OK です

以下の設定では2箇所でスパンを作成しています

from random import randint
from flask import Flask

from opentelemetry import trace

# Acquire a tracer
tracer = trace.get_tracer("diceroller.tracer")

app = Flask(__name__)

@app.route("/rolldice")
def roll_dice():
    username = get_username()
    return str(roll(username))

def get_username():
    with tracer.start_as_current_span("get_username") as rollspan:
        username = "hawksnowlog"
        rollspan.set_attribute("username", username)
        return username

def roll(username: str):
    # This creates a new span that's the child of the current one
    with tracer.start_as_current_span("roll") as rollspan:
        res = randint(1, 6)
        rollspan.set_attribute("roll.value", res)
        return res

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

各種起動

アプリ、Jaeger、コレクターを起動します

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py
  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 14317:4317 -p 14318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest
  • docker run -p 4317:4317 -v $(pwd)/otel-collector-config.yaml:/etc/otel-collector-config.yaml otel/opentelemetry-collector:latest --config=/etc/otel-collector-config.yaml

動作確認

localhost:8080/rolldice にアクセスし Jaeger で確認しましょう
以下のように1つのリクエストに対して2つのスパンが作成されていれば OK です

最後に

OpenTelemetry を使って独自のトレース情報を設定する方法を紹介しました
自動の場合はスパンなどが細かく設定できないのでプロダクションでは独自設定を使うことになると思います

参考サイト

2025年3月10日月曜日

OpenTelemetry を使って flask アプリのトレースをしてみる

OpenTelemetry を使って flask アプリのトレースをしてみる

概要

アプリのリクエストがどこ、どのくらいの時間をかけてを通ったかなど可視化することができます

環境

  • macOS 15.3.1
  • Python 3.12.9
    • flask 3.1.0
    • open-telemetry 0.51b0
  • OpenTelemetry Collector 0.121.0
  • Jaeger (all in one) 1.67.0

各種インストール

  • pipenv install flask
  • pipenv install open-telemetry

app.py

from random import randint
from flask import Flask, request
import logging

app = Flask(__name__)
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)


@app.route("/rolldice")
def roll_dice():
    player = request.args.get('player', default=None, type=str)
    result = str(roll())
    if player:
        logger.warning("%s is rolling the dice: %s", player, result)
    else:
        logger.warning("Anonymous player is rolling the dice: %s", result)
    return result


def roll():
    return randint(1, 6)


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

OpenTelemetry 用のログを自動で出力するように設定する

作成したアプリに自動でログを出力してくれるようにします

  • pipenv run opentelemetry-bootstrap -a install

これで再度アプリを起動します
OpenTelemetry 用のログが自動で出力されるようになります

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --traces_exporter console --metrics_exporter console --logs_exporter console --service_name dice-server python app.py

これで起動し localhost:8080/rolldice にアクセスするとログが OpenTelemetry 形式になっているが確認できると思いますの

今回はすべてのログを自動で OpenTelemetry 形式にしているので flask の起動ログも OpenTelemetry 形式になっています

コレクターの起動

標準出力に吐かれているログをコレクターに送ることで可視化できます
まずはコレクターを起動します
コレクターは docker イメージが用意されているのでそれを使います

otel-collector-config.yaml

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
exporters:
  # NOTE: Prior to v0.86.0 use `logging` instead of `debug`.
  debug:
    verbosity: detailed
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]
      processors: [batch]
    metrics:
      receivers: [otlp]
      exporters: [debug]
      processors: [batch]
    logs:
      receivers: [otlp]
      exporters: [debug]
      processors: [batch]

コンテナ起動

  • docker run -p 4317:4317 -v $(pwd)/otel-collector-config.yaml:/etc/otel-collector-config.yaml otel/opentelemetry-collector:latest --config=/etc/otel-collector-config.yaml

これで localhost:4317 にアクセスするとコレクタの画面が確認できます

ログをコレクターに送る

あとは先程標準出力に吐かれていた OpenTelemetry のログを起動したコレクターのエンドポイントに向けます

コレクターに送るためのプラグインが必要になるのでインストールします

  • pipenv install opentelemetry-exporter-otlp

そして再度アプリを OpenTelemetry ログ付きで起動します
console 時にくらべコマンドは短くなっています

  • export OTEL_PYTHON_LOGGING_AUTO_INSTRUMENTATION_ENABLED=true && pipenv run opentelemetry-instrument --logs_exporter otlp --service_name dice-server python app.py

すると今度はアプリ側ではなくコレクター側に OpenTelemetry の情報が表示されるのが確認できると思います

トレース情報を Jaeger で可視化する

OpenTelemetry Collector は集めたデータを更に別の場所にエクスポートすることができます
トレースデータを Jaeger にエクスポートして WebUI で確認できるようにしてみます

まずは Jaeger を起動します

  • docker run --rm --name jaeger -e COLLECTOR_ZIPKIN_HOST_PORT=:9411 -p 16686:16686 -p 14317:4317 -p 14318:4318 -p 14250:14250 -p 14268:14268 -p 14269:14269 -p 9411:9411 jaegertracing/all-in-one:latest

いろいろポートを開いていますが今回必要なのは OTLP 用の 4317 (ホスト側は 14317) と WebUI 用の 16686 になります
4317 と 4318 はすでにコレクターで使っているので同一ホストで動作させる場合は別ポートをしていします

次に OpenTelemetry Collector の設定ファイルを編集して exporters を追加します

  • vim otel-collector-config.yaml
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318
exporters:
  debug:
    verbosity: detailed
  otlp/jaeger:
    endpoint: 192.168.1.88:14317
    tls:
      insecure: true
processors:
  batch:
service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [otlp/jaeger]
      processors: [batch]
    metrics:
      receivers: [otlp]
      exporters: [debug]
      processors: [batch]
    logs:
      receivers: [otlp]
      exporters: [debug]
      processors: [batch]

追加したのは exporters の部分です
ここに起動した Jaeger の OTLP ポートを指定しています
コレクターはコンテナで動いているので localhost ではなく Jaeger コンテナが動作しているホストの IP を指定しましょう
そして service.pipelines.traces.exporters を debug から otlp/jaeger に変更します

そしてコレクターを再起動しましょう

動作確認

Flask アプリ、OpenTelemetry Collector、Jaeger をそれぞれ起動し動作確認します
すべて起動できたら localhost:8080/rolldice にアクセスしましょう
そしてトレース情報を Jaeger で確認するため localhost:16686 にアクセスしてみましょう

今回追加した dice-server のトレースが見れるようになっていれば OK です

最後に

Flask アプリに OpenTelemetry を導入してトレース情報を Jaeger で可視化してみました
OpenTelemetry でトレースを可視化する流れは理解できたかなと思います

直接 OpenTelemetry 情報を Jaeger に送る方法はどうやら廃止されるらしく今後はコレクターを経由して Jaeger など別の可視化アプリなどに連携するのが主流になるようです

今回はすべてのログに自動で OpenTelemetry 化する方法を採用しましたがプロダクションなどであれば必要な箇所でスパンなどを定義して送信する必要があります
そのあたりの個別のスパンの作成方法なども機会があれば紹介したいと思います

参考サイト

2025年3月9日日曜日

Flask + distroless 超入門

Flask + distroless 超入門

概要

Flask を distroless 上で動かしてみました
ビルドは 3.12 ですが distroless の python は 3.11.2 なのでそこのバージョンは合わせたほうがいいかもしれません

環境

app.py

from flask import Flask

app = Flask(__name__)

@app.route("/")
def hello():
    return "Hello, Distroless with Pipenv!"

if __name__ == "__main__":
    app.run(host="0.0.0.0", port=8080)

Pipfile 作成

  • pipenv install flask

Dockerfile

# ビルド用のステージ
FROM python:3.12.9-slim-bookworm AS builder

WORKDIR /app

# Pipenv をインストール
RUN pip install --no-cache-dir pipenv

# Pipfile と Pipfile.lock をコピーしてインストール
COPY Pipfile Pipfile.lock ./
RUN PIPENV_VENV_IN_PROJECT=1 pipenv install --deploy --ignore-pipfile

# Flask アプリをコピー
COPY app.py .

# Distroless を使用する実行環境
FROM gcr.io/distroless/python3

WORKDIR /app

# Pipenv でインストールした仮想環境のライブラリをコピー
COPY --from=builder /app/.venv /app/.venv
COPY --from=builder /app /app

# 環境変数を設定して Python が `.venv` を認識できるようにする
ENV PYTHONUNBUFFERED=1 \
    PIPENV_VENV_IN_PROJECT=1 \
    PYTHONPATH="/app/.venv/lib/python3.12/site-packages"

# Flask アプリを起動
CMD ["app.py"]

ビルド

  • docker build -t flask-distroless-pipenv .

起動

  • docker run --rm -p 8080:8080 flask-distroless-pipenv

動作確認

  • curl localhost:8080
Hello, Distroless with Pipenv!
  • docker images
REPOSITORY                  TAG       IMAGE ID       CREATED         SIZE
flask-distroless-pipenv     latest    9adef9f7e0d0   4 minutes ago   86.2MB

最後に

ビルドしたイメージは軽くなりますがシェルがないのでトラシュは大変そうです
そもそもビルド時には distroless なイメージは使えないのでビルド時のイメージを管理しなければいけないので結局ディストリビューションは必要かもしれません
また distroless で提供している Python のバージョンが 3.11.2 とかなり古いのでそこも微妙な感じはします

参考サイト

2025年3月7日金曜日

Google Cloud Associate Cloud Engineer 合格メモ

Google Cloud Associate Cloud Engineer 合格メモ

概要

勉強方法などを紹介しておきます
基本は過去ゲーです

環境

  • GCP ACE (2025/01/27時点)
  • テストセンターにて受験

料金

  • 137ドル (19702円)

対策

基本的には教科書と過去問です
ただ教科書だけでは合格するのはほぼ不可能だと思います (実務経験がかなりあれば別かも)

過去問を完璧にこなせば基本的には大丈夫かなと思います
教科書にも模擬試験的なのがあるのでお守り程度に持っておくといいかなと思います
過去問の問題も実際の試験で30-50%ほど出題された気がします (もう少しあったかも)
なお問題はすべて選択式で4択から1つ選択するだけでした
複数回答を求める問題は出題されませんでした

教科書

模擬試験

Udemy で 400 問ほど解きました
すべて有料なのでご注意ください
むしろ有料以外の模擬試験はほぼなくあっても微妙な問題集しかなかったです

試験範囲

一応確認しておくといいかもです

https://services.google.com/fh/files/misc/associate_cloud_engineer_exam_guide_japanese.pdf

解き方のポイント

  • 回答から問題が何関するものなのか先に推測する
    • 問題はダラダラ長いが最終的に聞きたいことはただの権限の問題だったりする
  • 消去法
    • 知らない問題でもあり得ない回答がわかればそこから2択などに絞り込む
  • 「実務ではこうする」の考えは完全に消したほうがいい
    • その方法じゃなくてもできるんだけどなー
    • そっちのほうが実際は面倒なんだよなー
    • と言った実務経験は完全にすてたほうがいいです
    • Google Cloud 推奨およびベストプラクティスを探す問題がほとんどなので注意しましょう
    • 実務経験が長い人ほど陥りやすいかもです
  • 定番の問題はおさえる
    • クラウドストレージのライフサイクル
    • ロールの最小原則とグループ割り当て
    • VPC のサブネット内の CIDR の扱い

実際に出題された問題の記憶

  • 過去問(および過去問に似た問題)が30-50%ほど出題された
  • 全く勉強していなかった部分はほぼなかった
    • UDP + ネットワークロードバランサは知らなかったが消去法で対処した
  • クラウドストレージに Cloud Interconnect 経由で接続する方法
    • 内部の DNS に CNAME 貼るやつ
  • サブネットの CIDR の IP レンジ拡張
    • ネットワーク部分の数字を減らして拡張するので間違えそう
  • 日本語がおかしい
    • Coldline -> コールドストレージ
    • それ以外にも問題文章自体の翻訳がおかしい部分が多々ある
  • 時間はかなりあまる
    • 自分は60分で終了

スコアレポートはでない

受験直後でも受験後の certmetrics の履歴でも確認することはできませんでした

暫定合格を確認する方法もない

ネットで調べると受験直後の画面に「暫定合格」なる文言が表示されるらしいのですが自分のときはありませんでした
最新の受験だとなくなったんだと思います
また受験直後に webassessor を確認しても完了した試験の項目がなくそこでも確認できませでした

なので現状だと受験直後に合否を確認する手段はありません

合否通知はいつくるか

自分は翌日の朝6:00にメールで合格通知が来ていました
なのでそんなに焦って合否を確認しなくても大丈夫かなと思います

最後に

スコアレポートが出ないのがかなり不満でした
また模擬試験が有料しかほぼないので個人でかつ実費で受験しなければいけない人には正直向かない試験です

2025年3月5日水曜日

(できなかったメモ) Mac で azw to pdf する

(できなかったメモ) Mac で azw to pdf する

概要

azw9.res + kfx 形式のファイルは以下の方法ではできなかったので一応メモしておきます
azw8 + kfx 形式であれば一応いけました
おそらく azw8 形式は自分で作成した pdf をKindleライブラリに追加した本なので変換できるのだと思います

kindle にダウンロードしたファイルを pdf に復号化する方法を紹介します
calibre を使う方法を紹介します

環境

  • macOS 15.3.1
  • calibre 7.26.0

インストール

  • brew install calibre

起動

アプリケーションの一覧にある calibre を起動します
本のタイプを選択する部分があるので Kindle を選択します

KFX input プラグインの追加

  • 環境設定 -> calibre を拡張するプラグイン取得 -> KFX input を検索してインストール

DeDRM プラグインの追加

これはプラグインの一覧にないので手動でインストールします

https://github.com/noDRM/DeDRM_tools/releases/tag/v10.0.3

zip をダウンロードしたら解凍します
DeDRM_plugin.zip というファイルがあれば OK です

  • 環境設定 -> 高度な設定 -> プラグイン -> ファイルからプラグインを読み込む

で先程の zip ファイルを指定します
インストールできたら calibre を再起動します

本を追加する

kindle でダウンロードした kfx ファイルを追加します
本を追加から kindle でダウンロードした本があるディレクトリにある kfx ファイルを選択します

pdf へ変換

本を変換で書き出します
右上に出力形式があるので PDF を選択しましょう (azw9.res 形式の場合ここで DRM を復号化できずにエラーになります)

変換が成功するとディスクに書き出しができるようになるので「ディスクに保存」で保存するパスを選択すれば OK です

最後に

古い kindle 形式であればこの方法でもいけるっぽいですが最新の kindle 形式 (azw9.res) などはこの方法だとダメなようです

2025年3月3日月曜日

mineoからNuroモバイルVMプランに変更したのでメモ

mineoからNuroモバイルVMプランに変更したのでメモ

概要

流れを簡単にメモしておきます
事務手数料で4000円ほど取られるのでそこは注意しましょう

環境

  • mineo (Dプラン、5G、音声通話あり)
  • Nuroモバイル (VMプラン、D回線、音声通話あり)
  • iPhone14

mineoでMNP予約番号を発行

https://support.mineo.jp/notes_cancel.html の「MNP予約番号発行」から発行します

Nuroモバイルの登録

https://mobile.nuro.jp/ 公式ページの「お申し込み」から申し込みます
キャンペーンなどの専用ページがある場合はそちらから必ず申し込みましょう
でないとキャンペーンが適用になりません

登録に必要になる情報は以下です

  • MNP予約番号
  • 携帯の番号
  • 身分証明書のデータ

あとは自分の好きなプランが決まっていれば OK です
SIM のサイズなども選択する画面があるのでそこは自身の端末に合わせて選択してください
iPhone14 の場合は nanoSIM です

NuroモバイルのSIM到着待ち

自分は登録から2日で届きました

Nuroモバイル切り替え

基本は説明書が入っているのでそれに従うだけですが Wifi 環境必須なのでそこだけ注意しましょう

ログイン

https://mobile.nuro.jp/r/use/mnp にログインします
ログインパスワードは郵送されてきた SIM カードに記載されているのでそれを使ってログインします
初回ログイン時はデフォルトのパスワードを変更するように促されるので変更します

MNPによる回線切替日の予約

ログインできたら「料金、お知らせ」タブから開通予約希望日時を選択します
日時は現在日時から選択できるのですぐに切り替えたい場合には最短を選択すれば OK です
(選択できる日時に深夜がなかったような気がするのでそこはご注意ください)

SIM カード入れ替え

iPhone の SIM カードを入れ替えます

NuroモバイルのAPNインストール

https://mobile.nuro.jp/support/settings/apn 基本的な流れはここにあるのでここから APN をダウンロードしインストールすれば OK です
ポイントは以下の通りです

  • 上記URLのスクショが古いの注意
  • 先に mineo のプロファイルを削除しておく必要がある
  • NuroモバイルのAPNインストール後は再起動はせずにフライトモードのオンオフだけでも OK

動作確認

Wifi をオフにしてモバイル回線だけで通信できることを確認しましょう
https://www.ugtop.com/spill.shtml などにアクセスしてゲートウェイが ap.nuro.jp になっているか確認してもいいかなと思います
また mineo 側にログインすると契約情報が消えていることが確認できます (そのうちログインもできなくなるのだろうか、卒業証書なるものがメールで届いたら eoID 自体を削除して問題なさそう)

5Gオプションの有効化

「ご契約オプション」のタブから5Gオプションを有効化できるので有効にしましょう
これを有効にしないと mineo 同様電話を受け取ることができない可能性があります

最後に

mineo から Nuro モバイルに切り替える方法を紹介しました
マイネ王側にアカウントがある方はそちらも忘れずに退会しておきましょう