FastAPI環境構築

前回の投稿にてDjangoの置き替えとしてFastAPIの勉強を始めてかなり時間が経ってしまいましたが、まずはDjangoの時と同じようにシングルページWEBアプリケーションのテンプレートのような形で勉強しようと思い、同様にDockerの環境構築からの覚え書きです。

  • PythonイメージベースのDockerfile
    • nodejsをインストールして、モジュールとしてTypescript、Bootstrap、SASSを追加
    • Pythonライブラリとしてfastapi関連ライブラリをインストール
    • 実行ユーザーappを作成してsudo権限を与える
  • nginxプロキシを追加したdocker-compose.yml
    • /codeをマウントしてここにアプリを配置する
    • startupスクリプトを実行させる(環境変数設定とtypescript、sassコンパイラの常駐設定、APIサーバー:uvicornのデーモン起動)

上記プロジェクトを用いてdocker-compose up -dすると、サーバーが起動してブラウザからhttp://localhost:8080/でアクセス出来るようになります。

WEBアプリケーションの実体はapp/main.pyです。サンプルではある程度サンプルページが表示されるまで作り込んでしまっていますが、覚え書きとして一番単純なアプリケーションから

from fastapi import FastAPI

app = FastAPI()

@app.get('/')
async def index():
    return {'detail': 'Hello FastAPI World.'}

として、保存。ブラウザからhttp://localhost:8080/で読み込むと、

一番単純なFastAPIアプリ

こんな感じで表示されます。

app/main.pyの1行目でライブラリを読み込んで、アプリケーションインタンスの生成(3行目)、5行目はHTTPリクエストメソッドを表す関数デコレータです。例ではルートパスに対するGETリクエスト。その下のdefが関数定義で、名前は分かりやすければ何でも良いです。この関数の戻り値がHTTPのレスポンスとしてブラウザに返されます。

見て分かるように、このアプリケーションで返されるレスポンスはHTMLではないため、結果の見た目が変です。

FastAPIはDjangoと異なりAPIが主体なのでデフォルトではJSONで返すのが基本のようです。Ajaxなどでデータのやりとりをするのに向いている感じ。

HTTP通信を使用したアプリケーションでデータのやりとりを行い、クライアント側で見た目を準備するような用途ではこのままで良さそうですが、普通のWEBアプリのようにブラウザから利用したい場合はひと工夫が必要です。

少し調べた感じでは、二通りの方法があるようです。

  1. Jinja2Templatesテンプレートライブラリを利用する
    • FastAPIのもととなるStarletteやFlaskでも使用されており、Djangoのテンプレートと同様に変数埋込で動的なHTML文書を返すことが出来る。
  2. スタティックマウントしたディレクトリでhtmlを有効にする
    • from astapi.staticfiles import StaticFiles
    • app.mount(“/html”, StaticFiles(directory=”/code/html”, html=True))
    • 上記のように追加することで http://localhost:8080/html/ で/code/htmlに保存されたindex.htmlが表示されるのでそのページに仕込んだjavascriptからFastAPIのAPIを利用することで、シングルページアプリケーションを実現することが出来る。

サンプルでは1の方法を採用して、ルートページを表示させるようにしている。2の方法をにするには@app.get(‘/’)の部分を他のパスに変更するか、レスポンスごと削除し、スタティックマウント先を’/’ルートにすると良い。

サンプルのようにHTMLテンプレートを利用したレスポンスがこちら。

Bootstrapでチョッピリレイアウトしてみた。

最初は2の方法が良いなと思ってみたけど、サンプルにあるようにエラーベージをHTMLでちゃんと表示させるにはテンプレートを使用した方が便利だし、どうせテンプレート使うならルートページもテンプレートで行こう!という結論となりました。

FastAPIの導入

前回記事より一ヶ月ほど経過してしまいましたが、また思いつきで調査勉強の没頭していたという適当な感じで休んでいました。やっぱりこういうものを習慣化するのは難しい……。

現在の作業項目としてdjangoによるがWEBアプリテンプレートがあるが、シングルページアプリケーションへの移行を考慮するなど次第にバックエンド側の比重が軽くなってきた。このためもう少し軽いバックエンドフレームワークをと言うことで検討を行いFastAPIの採用を検討することにした。

まだ不勉強なので、間違った比較かも知れないが下記のように比較できると思う。

djangoFastAPI
バックエンド主体の比較的規模の大きなWebアプリケーションに向いている
主にHTMLレスポンスで動的にWebページを表示できる
フロントエンド主体のWebアプリケーションに向いている
主にJSONレスポンスでWebAPIに特化。ブラウザに表示するためには別途テンプレートや静的HTML文書を用意し、API利用によるWebアプリを実現する
ユーザー認証、データベース利用など一般的なバックエンド機能が一通り揃っている状態。基本状態ではリクエストに応じてJSONオブジェクトを返すだけのAPIサーバー。
バックエンド機能はチュートリアルに沿って進めればある程度のものが出来る。
機能追加は容易ではあるが、ある程度の調査が必要なのと、複雑なライブラリ構造の理解がないと敷居が高い印象構造が比較的単純で分かりやすいため、機能追加は容易。ただしそれなりの調査も必要。
簡単な比較

例えば、Djangoではユーザー認証や管理画面が最初から用意されているが、それのデザインや振る舞いを変えたり認証方法をカスタムしたりするためには、フレームワークの約束事に沿ったカスタム方法を調査するなど、かなりの労力が必要になるが、FastAPIはそもそも準備されていないので、自分で作らなくてはならない。そのため後から機能追加などのカスタムは非常に容易という事になる(ただし出来上がりもそれなりのものになってしまうのは自己責任ということで)。また、フレームワーク的な約束もあまり細かくないので、作っていくうちにコードの構成が分かりにくくなってしまう危険もあるのではと思う。

実験用のサーバー構成は、色々解説があるが、こちらではもともとのDjangoテンプレートのDocker環境があるのでそちらをベースにDocker環境の構築を行う。

次回はFastAPI導入の準備としてDocker環境構築のメモを残しておきたいと思う。

FastAPIでDjangoのテンプレートアプリと同等のものを実現しようとすると、認証の仕組み、データベースアクセスの仕組みなどDjangoでは組み込まれていたものを個別に調査・勉強・実装を行わないといけない。
大変そうだけど、汎用的な仕組みの理解なので色々役に立ちそうと、前向きに考えておこう。

WEBアプリテンプレート(管理機能)

前回紹介したdjangoWEBアプリテンプレートに関して、個別の機能について気が向いたらメモを残しておきます。

ユーザー(www-data)の設定

djahgoのサービスはwww-dataというユーザー(debian-slimイメージに最初から居る)で起動するので、/code以下のファイル所有権もwww-dataとし、また実際のアプリ開発のために起動したコンテナにVScodeにアタッチして編集したりgitでソース管理出来るようにwww-dataユーザーでログインすることとし、念のためsudo権限を与えておく事とした。

# Dockerfile
RUN apt update; apt install -y git sudo
......
# usermod for www-data & sudo group
RUN usermod -d /code www-data; \
    usermod -s /bin/bash www-data; \
    echo "%www-data ALL=(ALL) NOPASSWD: ALL" > /etc/sudoers.d/www-data

# docker-compose.yml
services:
  django:
    build: .
    image: django_image:test
    user: www-data
    command: /code/startup
.......

こうすることで/code/startupはwww-dataの権限で実行されてテンプレートの構築が行われる。ただし、コンテナでマウントしたディレクトリの扱いがMacとそれ以外では異なるようで、Linuxなどの場合コンテナとホストのUIDが一致していない場合、ホストのユーザーで作成した/codeにwww-data権限で書込が出来ない問題がある。ここでは/code/startupの初期の段階で/codeの所有権をコンテナのwww-dataに取得させている(その為にパスワード無しのsudo権限を与えてしまった・・・・)

adminユーザーの設定

コンテナ側のユーザー管理とは別に、django WEBアプリにも認証があるため、少なくとも管理ユーザーの作成が必要(認証する気がなければ無くても良いかも知れない)。これもサイト構築毎に毎回は面倒なので、/code/startupのテンプレート作成時に自動生成させている。

ユーザーデータベースの更新は本来インタラクティブモードで動作するが、全自動で行うために下記のようにshellでコマンドを食わせている。

# startup
python manage.py migrate # データベースの初期化

cat <<CREATESUPER | sed -r "s/^\s*//g" | python manage.py shell
    from django.contrib.auth import get_user_model
    User = get_user_model()
    User.objects.create_superuser('admin' ,'', 'admin')
CREATESUPER

本テンプレートでは認証画面への遷移や管理画面へのリンクはnavbarに配置しており、テンプレートのcommon.tsでDOM構築している。

テンプレートのDOM構造は先日投稿のものをTypescript対応にしたもので、少し冗長になる感じだけど上手く動いています。これも気が向いたらメモを残しておこうかと・・・・

WEBアプリテンプレート(django+javascript)

code
code
nginx
nginx
Dockerfile
Dockerfile
docker-compose.yml
docker-compose.yml
conf.d
conf.d
uwsgi_params
uwsgi_params
project.conf
project.conf
project.conf
project.conf
/
/
[PROJECT]
[PROJECT]
docker
マウントポイント
docker…
サイト構築
ポイント
サイト構築 ポイント
プロジェクト構成
プロジェクト構成
Text is not SVG – cannot display
# 通常起動
$docker-compose up -d
# 再構築
CLEARSITE=1 docker-compose up -d
テンプレート表示状態

WEBアプリの勉強を始めた所、djangoを用いて同じ構成のサイトを何度も構築するのが面倒でテンプレートを作ることにした。しかしコレばっかりに夢中で肝心の勉強の方がおろそかになってきているのは、いつものお約束。

テンプレート構成

  • docker image 仕様(Dockerfile)
    • debianベースのpythonスリムイメージをベースとする
    • pythonライブラリインストールステージ:dbアクセス用ライブラリの為に一時的に開発ツール一式をインストール(後にスリム化処理)
    • nodejsは一旦aptパッケージインストールした後、npmで最新のバージョンをインストール
    • npmでtypescript、sass、bootstrapをインストール
    • その他チョットしたツール:gitやwget、アプリ実行用ユーザー(user)の作成とsudo権限設定
    • 実行コマンドは/code/startupシェルスクリプト:イメージにはダミーのシェルスクリプトを格納しているが、実際はサイト構築テンプレートとwusgi起動を含むシェルスクリプトを/code/startupとして作成しマウントして実行(後述)。
  • 起動:docker-compose構成
    • djangoは/codeをマウントしてサービスを実行
    • 環境変数CLEARSITE=1とすることでサイトを削除してテンプレートで再構築する(危険コマンド)
    • 構築用のstartupスクリプトが長いため、djangoサービスが始める前にnginxからのサービス要求がありロックすることがあったので、ヘルスチェックを行うようにしている(uwsgiのプロセスチェック)。
  • startupスクリプト(テンプレート構築&サービス起動)
    • 環境変数でサイト名アプリ名を定義し、サイト名のディレクトリが存在しない場合、またはCLEARSITEでアプリを削除している場合、テンプレートを用いてアプリケーション雛形を構築する。
    • Django-admin startupprojectでサイトを構築、settings.pyの必要な部分を順次編集。
    • python manage.py startappで最初のアプリケーションを作成(これを用いてシングルページアプリケーションの雛形とする)
    • アプリケーションの設定部分をsettings.pyに追記編集。bootstrapなどのミドルウェアの設定、LOGGING設定、環境変数読み込み設定。
    • ルート用urls.py、フロント側にデフォルト変数を送る機構context_processors.pyの雛形作成
    • アプリケーション定義用urls.py、views.pyの雛形作成、indexページとajax待ち受けの最低限構成、およびテンプレートHTMLのindex.html、base.html雛形作成。
    • python manage.py migrate :sqliteデータベースの初期化、管理者ユーザーの作成
    • スタティック領域の構成
      • bootstrapはnpmのグローバルエリアからローカルコピー(これは最新版に対応するため常に行う)
      • サイト用カスタムscssのテンプレート作成(再構築時)
      • sass常駐コンパイラの起動(上記scssファイルの更新と同時に自動コンパイル)
      • bootstrap関連その他js、およびtinymce設定
      • DOM構築用javascriptモジュールテンプレート(TSソース)設置、およびTypeScript初期化と設定ファイル初期編集(再構築時)
      • tsc常駐コンパイラ起動

テンプレートに凝りすぎてほぼほぼ自由度が下がってしまっている件。前回のDOM構文もTypescriptに移植して使える状態にして用意してます。細かい解説は折りを見て追記します。

続:ES6とDOMの件

前回はなるべく素のjavascriptでDOMっぽい構文を考えてみようということで、下記のような即時関数を使用して子要素を生成して内容を定義しつつ親要素にアペンドするような形で、文書構造に則したコーディングが出来るのではないかという感じで投稿しました。

((body) => {                            // bodyを引数とした矢印関数①
  body.className='hogehoge';
  body.innerHTML = '';
  body.appendChild(((title) => {        // bodyタグへのappendChildを同様に矢印関数で記述②
    title.innerHtml = "page title";
    return title;
  })(document.createElement('h1')));
  body.appendChild(((content) => {      // 同様に本文ダグも矢印関数で生成と同時にappendChild
    content.innerHtml = "page content";
    return content;
  })(document.createElement('p')));
  return body;                          // ①関数の戻り値(これが無いと結果が空になる)
})(document.querySelector('body'))      // ①関数の引数として body = document.querySelector('body')

結果↓

<body>
<!--
   空のHTML文書
-->
</body>

=>

<body class="hogehoge">
  <h1>page title</h1>
  <p>page content</p>
</body>

慣れてしまえばサクサク書けてしまうが、やっぱり文書構造が大きくなると即時関数の入れ子だらけになって見にくくなってくるのと、即時関数のretrun文を入れ忘れて文書が真っ白になりがちという欠点も見えてくる。

というか、そもそもappendChildなどのメソッドが自身を返してくれればメソッドチェインでもっと便利に使えるのに・・・ということで、あまり大規模にならない程度に下記の拡張メソッドをHTMLElementに追加してみた。

/**
 * ① 自身を引数とする関数を引数に取り、処理を行った後、自身を返す
 */
HTMLElement.prototype.editElement = function(func) {
  func(this);
  return this;
}

/**
 * ② appendChild(文字列引数の場合はappend)処理を行った後、自身を返す
 */
HTMLElement.prototype.appendChain = function(child) {
  if(child instanceof HTMLElement){
    this.appendChild(child);
  } else if (typeof(child) === "string") {
    this.append(child);
  }
  return this;
}

/**
 * ③ 子要素をクリアしたのち自身を返す
 */
HTMLElement.prototype.clearElement = function() {
  this.innerHTML = "";
  return this;
}

これらを定義することで、先の構文は下記の様に書き換えられる(思った以上にスッキリ)

document.querySelector("body")
  .clearElement()
  .editElement(body => body.className = "hogehoge")
  .appendChain(document.createElement("h1").appendChain("page title"))
  .appendChain(document.createElement("p").appendChain("page content"));

良い感じになってきたー。と思ったけど、良く考えたら最近Web製作に関してはTypescriptに移行しつつあり、こんな型の曖昧な仕様を実装するのとても面倒な気がする・・・かといってanyの多用は避けたい。。。ということで次のネタに続く…と。

室内灯自動点灯システム

システムなんて言うほど大した物ではないが、先日室内灯の蛍光灯が切れたのを機会に、LEDシーリングライトに交換した(日立LEC-AH08U)。お安い割に明るさ調整や色も白から電球色まで変えられるです。

付属のリモコンでON/OFF出来るのだが、リモコンが小さく、さらにボタンも小さくて押しにくい感じ、暗い部屋に来ていざ点けようとするとマゴマゴ感が否めない。そこでせっかくのリモコン受信付きの室内灯ということで人感センサーで自動で点灯する仕組みを作ることにした。

計画内容

  • 基本的にリモコンは使用せず、光量や色合い設定の為だけとする。
  • Arduinoなどのワンボードマイコンを使用して学習リモコンの要領でIR LEDを発光させる
  • 人感センサーでONさせるがOFFはしない(トイレの人感センサーライトで散々経験しているが、人がじっとしていると消灯してしまうので)。OFFの仕組みは別に用意する(フォトレジスタに手をかざしてOFFにする仕様)。
  • センサとLEDを仕込んだ適当なケースを3Dプリンタで製作

学習リモコン仕様

  • Arduino Nano(安い互換品)を使用
  • 人感センサー:‎ARCELI00595
  • IR LED:uxcell IRLED
  • IR受光素子:HX1838(リモコンコード取得用)
  • フォトレジスタ uxcell GL5528

右が配線図。受光素子はブレッドボード環境で実験的にリモコンコードを読み込むために使用することとし、実際の装置には組み込まない(装置としての学習機能は無し)

学習リモコン配線図

リモコン信号の学習

Arduinoのライブラリとして、ライブラリマネージャから赤外線リモコンのためのIRremoteをインストールすると、サンプルで付いてくる「ReceiveDump」を使用すると、使用したいリモコン信号を取得することが出来る。Arduino開発環境からシリアルモニタを起動しておくと下記のようにリモコン受信データの詳細がレポートされる。下記の例は今回ターゲットとしているシーリングライトのリモコン信号(ON)の詳細。

Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

Raw result in internal ticks (50 us) - with leading gap
rawData[180]: 
・・・・・
Raw result in microseconds - with leading gap
rawData[180]: 
・・・・・
Result as internal ticks (50 us) array - compensated with MARK_EXCESS_MICROS=20
uint8_t rawTicks[179] = {65,33, 10,24, 9,7, 9,8, 8,9, 8,9, 8,8, 8,9, 8,9, 8,9, 8,8, 8,9, 8,9, 8,25, 8,9, 8,8, 8,9, 8,9, 8,9, 8,8, 9,8, 9,24, 9,24, 9,8, 9,8, 9,8, 8,8, 9,8, 9,8, 8,25, 9,8, 8,25, 8,9, 8,25, 9,24, 9,24, 9,25, 8,8, 9,24, 9,8, 9,25, 8,8, 9,8, 9,8, 8,9, 8,8, 9,8, 8,9, 8,9, 8,25, 8,25, 8,25, 9,24, 9,24, 9,25, 8,25, 8,25, 9,8, 9,8, 8,25, 8,9, 8,8, 9,8, 9,8, 8,9, 8,25, 8,25, 9,8, 8,25, 9,25, 8,25, 8,25, 8,25, 9,8, 8,25, 9,8, 8,9, 8,9, 8,8, 8,25, 9,25, 8,25, 8,9, 8,25, 8,25, 8,25, 8,25, 9,8, 8,9, 8};  // Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

Result as microseconds array - compensated with MARK_EXCESS_MICROS=20
uint16_t rawData[179] = {3230,1670, 480,1220, 430,370, 430,420, 380,470, 380,470, 380,420, 380,470, 380,470, 380,470, 380,420, 380,470, 380,470, 380,1270, 380,470, 380,420, 380,470, 380,470, 380,470, 380,420, 430,420, 430,1220, 430,1220, 430,420, 430,420, 430,420, 380,420, 430,420, 430,420, 380,1270, 430,420, 380,1270, 380,470, 380,1270, 430,1220, 430,1220, 430,1270, 380,420, 430,1220, 430,420, 430,1270, 380,420, 430,420, 430,420, 380,470, 380,420, 430,420, 380,470, 380,470, 380,1270, 380,1270, 380,1270, 430,1220, 430,1220, 430,1270, 380,1270, 380,1270, 430,420, 430,420, 380,1270, 380,470, 380,420, 430,420, 430,420, 380,470, 380,1270, 380,1270, 430,420, 380,1270, 430,1270, 380,1270, 380,1270, 380,1270, 430,420, 380,1270, 430,420, 380,470, 380,470, 380,420, 380,1270, 430,1270, 380,1270, 380,470, 380,1270, 380,1270, 380,1270, 380,1270, 430,420, 380,470, 380};  // Protocol=PULSE_DISTANCE Address=0x0 Command=0x0 Raw-Data=0x3DC2FB 88 bits LSB first

uint16_t address = 0x0;
uint16_t command = 0x0;
uint32_t data = 0x3DC2FB;
・・・・・

この例ではプロトコルがPULSE_DISTANCEとなっており、サポートしているメーカーフォーマットでは無いっぽいため、rawDataとして直接送信する必要がある。ターミナルに出力された詳細データのrawDataはそのままCのソースに貼り付けられる形式となっている。

// 初期化
IrSender.begin(IR_SEND_PIN);
// 送信部
uint16_t rawData[179] = {
  3230,1720, 380,1270, 380,470, 380,420, 480,370, 430,420, 430,420,
  430,420, 380,420, 430,420, 430,420, 430,420, 380,470, 380,1270,
  380,470, 380,420, 430,420, 430,420, 430,420, 380,470, 380,420,
  430,1270, 380,1270, 430,420, 380,470, 380,420, 430,420, 430,420,
  430,420, 380,1270, 430,420, 380,1270, 430,420, 430,1220, 430,1270,
  380,1270, 380,1270, 430,420, 380,1270, 430,420, 430,1220, 430,420,
  430,420, 430,420, 380,470, 380,420, 430,420, 430,420, 380,470, 380,1270,
  430,1220, 430,1270, 380,1270, 380,1320, 380,1270, 380,1270, 380,1270,
  430,420, 430,420, 380,1320, 380,420, 380,470, 380,420, 430,420, 430,420じ,
  430,1270, 380,1270, 380,470, 380,1270, 380,1270, 430,1270, 380,1270,
  380,1320, 380,420, 430,1270, 380,420, 430,420, 380,470, 380,470, 380,1270,
  380,1270, 430,1270, 380,470, 380,1270, 380,1270, 380,1270, 430,1270,
  380,420, 430,420, 380
};
IrSender.sendRaw(rawData, sizeof(rawData) / sizeof(rawData[0]), 38);

Arduinoのプログラムでは人感センサーの出力に連動してON信号を送信、フォトレジスタのアナログ入力レベルが一定以下になったとき(手をかざすなど)にOFF信号を送信する仕組みとした。

フローチャート(draw.io)
START
START
初期化
初期化
Yes
Yes
No
No
readmode?
readmode?
受信モード初期化
受信モード初期化
送信モード初期化
送信モード初期化
Loop
Loop
Yes
Yes
No
No
readmode?
readmode?
Yes
Yes
受信データあり
受信データあり
受信データ
ターミナル出力
受信データ ターミナル出力
光センサ
光センサ
On
On
Off
Off
LIGHT Mode
LIGHT Mode
LIGHT OFF CMD
LIGHT Mode Off
LIGHT OFF CMD…
Off
Off
Off
Off
LIGHT Mode
LIGHT Mode
5000ms
5000ms
Yes
Yes
No
No
人感センサ
人感センサ
LED インジケータON
LED インジケータON
1500ms
1500ms
Yes
Yes
人感センサ
人感センサ
LIGHT ON CMD
LIGHT ON CMD
1000ms
1000ms
500ms
500ms
LED インジケータOFF
LED インジケータOFF
100ms
100ms
LIGHT Mode On
LIGHT Mode On
Text is not SVG – cannot display

←ソースコードと簡単な資料はこちら

完成した装置全貌

3Dプリンタでザックリ作ったボックスと内部配線。

ボックスの上部にリモコン発信用赤外線LEDとOFF感知用フォトレジストを埋め込んでます。

リモコンが小さすぎて操作しにくいなら、従来通り壁スイッチで良いじゃん!というのはごもっともなお話ですが、それに気づいたのが一通り完成して運用し始めてからでしたw
あと、最近の半導体不足でArduinoが純正品しか手に入らず比較的高価なため、安価なRaspberryPi Picoに移植するべく開発環境など準備中・・・てか次の投稿ネタに保存中。。

FFmpeg on Docker

久々の投稿

dockerイメージでFFmpegをフルコンパイルしてみました。第1の理由は興味本位ですが、あとSVTAV1のライブラリを使用したいのと、tarボールからコンパイル&インストールして通常使用の環境が汚れるのをある程度防ぎたかったというのもあり、実験環境としてのDockerで行うこととした。副次的な効果として、Windows環境やMac環境、ひいてはRaspberryPi環境のどれか空いてるマシンで動画変換作業をさせることが出来る。

Dockerfileの解説

ベースとするイメージはpython:3.10.3-slim-bullseyeでDebianスリムイメージを利用しました。

pythonのビルドと同様に

  1. 現在のaptパッケージを保存(python環境など)
  2. aptで開発環境をドヤ−−−−っとインストール
  3. debianのライブラリにはfdk-aacがパッケージとして存在しないので、sourceforgeからソースコードをダウンロードしてビルド&インストール
  4. 開発環境が一通り完成したところで、SVT-AV1をgithubからcloneしてビルド&インストール
  5. ライブラリが揃ったので、ひとまずldconfigしてから、目的のffmpegをこれもgithubからcloneしてビルド&インストール
  6. このままではDockerイメージサイズが大変なことになっているので、不必要なパッケージを削除します。
  7. apt-markコマンドを使用して、2番以降でインストールしたパッケージをauto-remove対象にします(1番以前のパッケージは対象としない)。
  8. /usr/local 以下にある実行ファイルに関してlddコマンドを使用して関連するダイナミックライブラリをリストアップ、さらにそれらを含むaptパッケージを検索してリストアップしてauto-remove対象外にする。
  9. aptコマンドを使用しauto-removeで不必要なパッケージをゴッソリ削除
  10. イメージのスリム化を行った所でステージを分けて、Python用の必要パッケージをインストールしてffmpeg-pythonなどが利用できるようにする

イメージのスリム化のための仕組みはベースとしたpython:3.10.3-slim-bullseyeを参考に・・・というか殆どパクりで使用しました。1〜9までを一つのRUN文で行うのがミソ。RUN文で分けた方が見やすいけど、ビルドのステージが分かれてしまうのでステージが変わってからパッケージを削除してもイメージサイズが変わらないというの何とも。。

ビルドファイルの開発中などは複数のステージに分かれていた方がデバッグしやすくて良いが、一通り完成したら一つのRUN文にまとめてしまう感じですかね。

Windows11リモート環境構築メモ

プログラミング等の作業用としては現在はM1 MacBookを使用しており、WindowsデスクトップPCは主にゲームなどエンタメ用に使用している。その為置き場所はリビングとなり、作業場と少しばかり離れていたりする。

それに加えて最近WindowsPCの動作が不安定になってきたため、暫くメンテナンスを繰り返すこととなり。最終的にはOS再インストールという結論。そのついでに今まで放置していた問題点など諸々対策することとした。

デスクトップPCは今時少なくなった自作PCで、筐体に至っては15年以上使っている。OSに至ってはVistaから7にアップグレードした際に一度クリーンインストールして以来、全てアップグレードインストールで、M/B&CPU等のハードウェアも2回ほどそのままアップグレード、ハードディスクの交換やSSDへの換装の際もクローンコピーツールを用いて環境を変えずに運用してきた経緯がある。
M/B買ったときにも店員さんに「クリンインストールした方が良い」と散々言われたのに、動くから〜とそのまま結果オーライで運用してきたので、とうとう破綻が来たのかなーくらいの印象。

Windows11をクリーンインストールする際、同じPC(ハードウェア)であればそのまま認証された。またユーザー設定をする際にマイクロソフトアカウントを使用してしまうと、ユーザーフォルダ名が適当な物にされてしまい後で変更するハメとなった。最初はローカルユーザーでセットアップした方が良いかもしれない。

ツールやゲームなどアプリを一通りインストールし直して環境を再現したところで無事終了。

ここからがリモート環境構築

  • Macからのリモートデスクトップ
    • これはMac用のMicrosoft Remote Desktopがあるので使用
  • Macからssh接続
    • Windows側でOpenSSHサーバーをインストール(設定→機能の追加)
    • Windows側でOpenSSHサーバーを起動する(サービスの管理)
    • SSHデフォルトシェルをPowershellにする
New-ItemProperty -Path "HKLM:\SOFTWARE\OpenSSH" -Name DefaultShell -Value "C:\Program Files\PowerShell\7\pwsh.exe" -PropertyType String -Force
  • リモートで電源ON/OFF
    • 電源OFFはリモートデスクトップもしくはSSHからシャットダウンコマンド(PSTOOLS)
    • 電源ONはWindowsPCのBIOS設定でWakeup On LANを有効にする
    • 電源設定で高速起動をOFF
    • デバイスマネージャでネットワークカードの電源管理項目でmagicpacketでの起動を有効にする。また省電力機能をOFFにする
    • magicpacketを発行するスクリプト(Powershell)を作成し、Macから起動することでWindowsPCの電源が入るのを確認(シャットダウン状態、休止状態、スリープ状態それぞれから起動出来る事を確認)
#! /usr/bin/env pwsh
# Wakeup On Lan

# Unicorn
$hostmac="XX-XX-XX-XX-XX-XX"

$mac_addr=@($hostmac)
$header=[byte[]](@(0xff)*6)

foreach ($item in $mac_addr){
    Write-Host "Send magic packet to : " $item
    $addr=[byte[]]($item.split("-") | %{ [Convert]::ToInt32($_,16) });
    $magicpacket = $header + $addr * 16;
    $target = [System.Net.IPAddress]::Broadcast;
    $client = New-Object System.Net.Sockets.UdpClient;
    $client.Connect($target, 2304);
    $client.Send($magicpacket, $magicpacket.Length) | Out-Null
    $client.Close()
    Write-Host "Send magic paket to : " $item -ForegroundColor Green
}

WordPress 5.9

余り使いこなしていないので、イマイチ何が変わったか解らないけど、アナウンスがあったので兎に角アップグレードを行った。サイト改造によりDocker環境での運用のためDockerイメージとしてのwordpressを使用しているが、運用中でもあることなので、普通にダッシュボード画面にてアップグレードで難なく完了。

しかし、ダッシュボードのサイトヘルスステータスを見ると新たなエラーがあった。調べても良く分からないが何やら「intlなるライブラリが足りない」旨のメッセージのようだ。これはwordpress本体の方ではなくサーバー機能の問題ということで、DockerHubへ調査に。wordpress5.9用のfpmイメージがあったのでDockerfileを見てみるとphpのライブラリとしてintlが追加されている。

ということで、Dockerイメージの入れ替え

$ docker-compose down --rmi all

コンテナを停止してイメージ削除、そしてdocker-compose.ymlを編集してをwordpress用phpイメージを5.9に変更

$ docker-compose up -d

にて再起動完了。無事動作することを確認しました。

以前これをやってwordpressのサイトデータがおかしくなった記憶があるので、念のためディレクトリごとバックアップをとっておいたのですが、問題なかったため出番は無し。

mariadbのデータベースも毎日バックアップを取っているので事故があっても面倒なだけで大丈夫な環境にはなっています。

dockerイメージのアップデートは頻繁にあるものもあるので、ときどきはチェックしないといけないと反省。しかもDockerfileやdocker-compose.ymlでのイメージ指定がlatestになっているのが多く、現在のバージョンを調べるのが面倒。しっかりとバージョンを記載したイメージ指定にしないといけないな。。

ゆるキャン△スタンプラリー遠州コース

年末の浜名湖コースに続いて三コース目の遠州コースを回ってきた。

企画の最初の頃は、浜名湖・遠州・富士山麓の三コースだったけど、後半になってHPなどを見てみると浜名湖・遠州コースと、富士山麓コースの2コースになっていた。最初に行った富士山麓でも気づいたが、景品のステッカーが足りなくなったんじゃないかなと邪推してみた。

今回は遠州コースということで前回浜名湖コースの続きとなる。Ⓐ磐田市の見付天神からⒷ掛川の茶屋・きみくら、Ⓒ御前崎渚の交番という施設、で最終のⒹ富士山静岡空港でゴールとなる。

磐田市見付天神

カーナビが上手い具合に神社外周の臨時駐車場に案内してくれたので問題なく駐車。普通に神社の駐車場に向かってしまったら渋滞で大変なことになっていただろうw

掛川市きみくら

ここも聖地巡礼コース、原作でも登場した茶店。2階が喫茶店で昼食時間でもあったのでぜんざいを戴いてきました。メニューをよく見たらお茶漬けもあったので、、それにすれば良かったと少し後悔。あと期間限定のイチゴパフェのボリュームが凄かったです。

昼食ボリューム的にどうかな?とは思ったけど、まぁ年齢的にあまり食べ過ぎない方が良いかと…w

御前崎渚の交番で異常事態発生!!!

続きを読む…