Amazon OpenSearch Service(旧AWS ElasticSearch)の手動スナップショットをコンパネポチポチで取れるようにする

自分用のメモをブログ用に再編しただけなので、特にアドベントカレンダーは関係ない。一応今年も一人アドベントカレンダー2021 Advent Calendar 2021 - Adventarに登録だけしようか思っていたけど、悩んでいたら枠が全部埋まったので出せる要素がない。

本題

AWS ElasticSearchことAWS OpenSearch Service(以下ES)のスナップショットは自動で取られる分も手動で取る分もコンパネではほぼ制御できない。前者は現在1時間おきに取る以外の選択肢がなく、後者はそういうインターフェイスを提供していない(おそらく、Kibanaを使えということだろうが...)。

じゃあ公式ドキュメントにはどう書いてあるかというと、スナップショットを取るようESに直接HTTPSリクエストを送る、という形になる。しかもリクエストはAWS4Authを通して認証する必要があって、これはcurlではできないのでそれができるライブラリを使え、ということになっている: [Python] Boto3以外でV4署名リクエストを行う | DevelopersIO

書くコード自体はたいしたことないけど、スナップショットリポジトリを作成するのに、「S3関連の操作を許可するRoleをPassRoleして付与できるRole」の必要がある。そのためにIAM Userを発行してローカルでコードを動かすならRoleを作成してAWS内で動く方がいいなー、ということで、雑にLambda SAMを作ってなんとかした。

できたもの : mi-24v/aws-es-snapshot-helper: Lambda簡易ESクライアントによるAWS ESの手動スナップショットヘルパ

IAM ロールの PassRole と AssumeRole をもう二度と忘れないために絵を描いてみた | DevelopersIO

Lambdaの作成

以降は先述したリポジトリの中身を想像しながら書いてるので、わけがわからない場合はソース読んで貰ったほうが早い。

AWS4Authの使い方を見ているとIAM UserのAccess KeyとSecret Access Keyが必要になる。これはLambdaだとLambdaのExecution Roleから付与される一時キーから取得できて、実行時に勝手に環境変数(AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEYAWS_SESSION_TOKEN)に入る。

この辺はBoto3(AWS SDK for Python)の利用する認証情報 - Qiitaのように、boto3が勝手にやっていることでもあるので、boto3.Session().get_credentials()で出てくるっぽい(試していない)。

credentialsの取得ができたらAWS4Authの準備ができるので、公式ドキュメント(再掲)のコードを適当にコピーして整形する。

def lambda_handler(event, context):
    host = event["domain_url"]  # include https:// and trailing /
    repository_name = event["repo_name"]
    access_key = os.environ["AWS_ACCESS_KEY_ID"]
    secret_key = os.environ["AWS_SECRET_ACCESS_KEY"]
    region = os.environ["AWS_REGION"]
    service = 'es'
    session_token = os.environ["AWS_SESSION_TOKEN"]
    awsauth = AWS4Auth(access_key, secret_key, region, service, session_token=session_token)
    # requestsでよしなにする(以下省略)
    # action = event["action"] # のようにして、処理を分岐

ESへ実際にリクエストを投げるのは、requestsを使わずに本家ElasticSearchの公式クライアントelastic/elasticsearch-pyを使う手もあるけど、スナップショット周りができるかどうかは知らない。

AWS Lambda (Python 3.8)から Amazon Elasticsearchを使う(LambdaはSAMで) – 或る阿呆の記

なお、今回はコンパネポチポチで操作するのが目標なので、ESドメインURL等はLambdaコンソールの「Test」欄から投げることを想定して、イベントに必要なパラメータは揃ってるものとして扱った。必要なイベントは次の感じのJSON。

{
    "domain_url": "https://vpc-example-123456.ap-northeast-1.es.amazonaws.com/", # ESのドメインURL(末尾に/を入れる)
    "repo_name": "manual_snapshot_repo", # スナップショットレポジトリ名
    "register_repository_role": "arn:aws:iam::123456:role/exampleRole-12345", # PassRole対象になるRoleのARN(レポジトリ登録時のみ)
    "index_name": ".kibana_1", # index単位で処理するときに使うindex名(delete等のときのみ)
    "action": "register_repository" # このLambdaが処理の分岐に使うキー。ESの操作とは直接関係しないのでよしなに実装。
}

時刻のformatstring

pythonのformat stringには日付フォーマットもある。ってだけのメモ。

日付フォーマット(datetime⇔文字列) | Python Snippets

SAM Templateの作成

前述したRoleを含むテンプレートを作っていく。

今回相手にするESインスタンスはVPCアクセスにしているので、LambdaをESと同じ(もしくは、ESインスタンスの属するSubnetに到達可能な)VPCにつなげておく必要がある。VPCとセキュリティグループのARNを控えて代入するか、テンプレートで定義してARNを渡せばOK。

また、LambdaをVPCアクセスにするとENIの操作が必要になるので、前述したRoleにENI操作のIAM権限をつける必要がある。

結局どのIAM権限がいるかを、作ったテンプレートのうち、Roleに関する部分を抜粋して示す。

 SnapshotRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
          - Effect: Allow
            Principal:
              Service: es.amazonaws.com
            Action: "sts:AssumeRole"
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonESFullAccess"
      Policies: # 本当は自身のArnをPassRoleするPolicyも必要なのだが自己参照できないので手動で足す
        - PolicyName: "AllowTakeElasticSearchSnapshotPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "s3:ListBucket"
                Resource: !GetAtt SnapshotBucket.Arn
              - Effect: Allow
                Action:
                  - "s3:GetObject"
                  - "s3:PutObject"
                  - "s3:DeleteObject"
                Resource: !Sub "${SnapshotBucket.Arn}/*"
        - PolicyName: "LambdaWithVPCPolicy"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: Allow
                Action:
                  - "logs:CreateLogGroup"
                  - "logs:CreateLogStream"
                  - "logs:PutLogEvents"
                Resource: "arn:aws:logs:*:*:*"
              - Effect: Allow
                Action:
                  - "ec2:CreateNetworkInterface"
                  - "ec2:DescribeNetworkInterfaces"
                  - "ec2:DetachNetworkInterface"
                  - "ec2:DeleteNetworkInterface"
                Resource: "*"

注意として、公式ドキュメント(再掲)に乗ってるPassRole(以下の部分)に関しては、Roleの記述を分けるか、手動で足すかしないといけなくて、自身のRole ARNは参照できないっぽい(IDEAのSAM Syntaxチェックがそう怒ってきたから、そうに違いない)。

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": "iam:PassRole",
      "Resource": "arn:aws:iam::123456789012:role/TheSnapshotRole"
    }
  ]
}

CloudFormation組み込み関数の記述をなんとなくつかっていたので、その辺のメモも残す。

動かしていく

ローカルでのテストはめんどくさかったのでしていない(ええ)。トリガーもないし、直接デプロイしてコンパネでポチポチ試していった。

で、実際にKibanaでインデックスを確認するには、プライベートVPCを超える何かが必要。一時的な用途なら踏み台で十分なので踏み台を建ててローカルポートフォワーディングで手元のPCでアクセスできるようにした。

今考えるとSSHポートフォワーディングでいけた気がするけど、踏み台からESへはリバースプロキシを建ててアクセスした。一時的な用途だと、NGINXを建てるのはだるくて....と思っていたら、cortesi/devdというコマンドラインリバースプロキシがあるのを見つけた。Go製なのでReleaseバイナリを解凍するだけですぐ使える。本来は開発サーバ用っぽいが、求めていたものと完全に合致していて良かった。

アクセスできるようになったら、curlとかkibanaのコンソールでスナップショットのデータがリストアされているか試していく。

これで確認できたら踏み台は閉じて、Lambdaでイベントをいじってコンパネからスナップショットを取ったり取らなかったりしておわり。

おわりに

AWS関連の操作やmiwkeyの管理はまだまだやることはいっぱいある。けど、時間とやる気と調査が足りねえ...

WSLgをArchWSL環境で使えるようにする

wslではsystemdが工夫しないと動かないとかで、今回のwslgも工夫しないと動かないのかと思いきや、割と適当でなんとかなる。やることはこれだけ:

  1. Windows 11にしろ
  2. wsl2系へ更新しろ
  3. xorg-xeyesとか入れてテストして

が、微妙な点もあるのでメモ。

iPadの音声をWindows 10で聴く

最近メモ書きをしていないので更新がサボり気味だけど、本当はしたほうがいいんだよな....

前置き

スマホゲームの類は前までPixel 3aでやっていたけど、ウマ娘を始めるあたりで流石にスペック的にしんどくなってきたので、今年4月に購入したiPad Air(4th Gen.)に任せるようになった。

当時の写真
https://miwkey.miwpayou0808.info/notes/8kx9ttjtb7
https://twitter.com/miwpayou0808/status/1385635128743927808

Pixelのときからそうだったけど、地味に気になるのが音声で、普段はWindows PCのヘッドホンを使っているのでそちら側で音声を聴きたい(身体は一つしかない)。

方法としては

  1. Windows PCをiPadのワイヤレススピーカーとして認識させる
  2. VB-Audio VBANでWi-Fi経由で転送する

があって、なんとなく1を選んだ。

WindowsでBluetoothを用意する

ハード面

ここでいうWindows PCがラップトップを指すなら、おそらくBluetoothはbuilt-inだろうけど、デスクトップ、こと自作PCではWi-Fi付きマザーボードを選んでない限りは内蔵されていないことが多いと思う。

そういうわけで拡張カードもしくはドングルを用意する。

で、買ったのがIntel AX210。IntelのWi-FiカードにはBluetoothが付いているものがあり、将来的にWi-Fi 6Eネットワークを組むことを想定して、Wi-Fiカードを更新するついでにBluetoothも増設しようというお気持ち。

TD-AX210H WiFi 6E PCI-E Intel AX210 3000Mbps Bluetooth 5.2WifiアダプターワイヤレスPCI Express Wi-fiカード802.11ax / ac 2.4Ghz / 5Ghz / 6Ghz MU-MIMO OFDMA for Windows 10 Linux

Primeデーセールのときに買い物金額10000円を埋めるために買った。当時(2021/6/22)はAX200の方がメーカの選択肢が広かったけど、AX210はこの中華カードしかぱっと出てこなかった。Yanwenで発送されて、届くまで1週間?掛かった気がする。

で、こいつのUSBコネクタ(普通のUSB-A端子ではなく、マザーボードのUSBソケットに刺さるコネクタ)とPCIカードを刺せばOKだけど、うちの環境ではGPUと同じx16/x8に刺すと起動できなくなって口座残高みたいに虚無画面を見つめるはめになったので、カードのサイズ通りx1に落ち着けておいた。

ソフト面

AX210のドライバは、Bluetoothに関しては自動取得されるけど、Wi-Fiに関しては取得されなかったので、両方をIntelのサイトから取り直した。

WindowsではBluetooth A2DP Sinkによるオーディオサポートが出たり入ったりして暴れてるけど、Windows 10 2004から今の記事を書いているときまで搭載されているので、3rd partyのドライバを用意する必要はない。

Enable and Use A2DP Sink for Bluetooth in Windows 10

が、なぜかA2DPオーディオの設定をするGUIは存在しないらしく、そこだけを3rd partyに頼って用意する必要がある。上記の記事でも紹介されているが、その手のMicrosoft Storeアプリがいくつかある。

  1. Get Bluzic - Microsoft Store
  2. Get Bluetooth Audio Receiver - Microsoft Store
  3. ysc3839/AudioPlaybackConnector: Bluetooth audio playback (A2DP Sink) connector for Windows 10 2004+

で、オープンソースで(ソースコードを読めば)挙動がわかる3を選ぶことにした。リリースバイナリを実行するだけでタスクトレイに出現し、ペアリングしたBluetooth機器をつなげるものになる。

接続ができるとiPad側でもBluetoothオーディオが認識される。

少し気になるのは、音量があまり大きくないことで、iPad側の音量は最大にしてしまってもよいかもしれない。

これで終わり。

Avatar3.0のBase Layerで遊ぶ(コライダージャンプ)

最初は普通に色変えしてUTSのマニュアル読みながらうなってたんだけどやっぱ動くと嬉しいおもちゃがほしくなってしまってた。

VRChat(というかUnity?)ではコライダージャンプなるものがあって、空中のコライダーに一旦着地することによって連続ジャンプする飛行めそっどがある。実際一部のパブリックアバターやワールド環境で飛ぶことができる。

で、これを自アバターでもやってみたいなあと思って、Googleしたら何か一発で記事見つかったのでこれを突っ込んだりいろいろして遊んだ。

コライダージャンプ搭載

参考記事 : そうだ、空を飛ぼう。VRChatで空を飛ぶもっとも簡単な方法|ブルージー|note

ほんとにこれの通り。

基本的にはコライダーをアバターにアタッチして前の記事みたいにオブジェクト出し入れを実装するだけなんだけど、現行VRCSDKはBoolパラメータが使用可能なのと、今回は衣装のように必ず何か着てないと困る状況ではないのでIntでスイッチせずにBoolでスイッチする。

ステートマシン

Write Defaultsオンなのでアイテムオフの挙動は素のアバターの状態を使ってくれる分アニメーションクリップは一つ少なくなる(手抜きですいません...)けど、ここでは参考記事通り図のようなステートマシンにする。

モーションを付ける

飛行(跳躍か?)機能自体は上記でokなんだけど、参考記事にもあるとおり飛行中にムーンウォークするのは見た目的に微妙かもしれない。

そこでVRC想定移動モーション(無料配布) - 半沢のモーション部 - BOOTHを使って飛行中のモーションを切り替えられるようにする。

サンプルからvrc_AvatarV3LocomotionLayerをコピって図のようにStandingかつIsColliderJumpActiveがtrueだったらFlyingって名前のBlendTree状態に遷移するようにする。

遷移

いろいろ遷移条件を公式のAnimator Parametersをみて試したけど、コライダージャンプ中はコライダーに乗っ掛かってる分Groundedは使えなくて、結局上図のようになった。

そして、この状態で使用するBlendTreeは自作する必要があって少しめんどくさい(適用具合を確認して適宜修正しないといけないので。今回はやらなかったけど...)。

vrc_StandingLocomotionをサンプルからコピペしてきて(かなり値が違うので普通に空っぽから作った方がいいかも)、先にDLしたモーションを次のように割り当てる(ゼロポジションが抜けてるので、これは要修正かも)。

BlendTree割当

ここまでできたらビルド(とアップロード)して確認できる。

TODO できたら動画をここに追記する

おまけ:デスクトップ時の立ちモーションを変える

デスクトップで直立してると腕が真下にぶら下がってて衣装や素体によっては貫通して見た目が微妙になってしまう。これもStandingのBlendTreeを一部変更することで改善できる。

モーションはVRChat向けIDLEアニメーション - Imaginary Caravan by Coquelicotz - BOOTHが身長的にも近かったのでこれを使ってみた。

具体的には(もう一回)コピーしてきたvrc_StandingLocomotionproxy_stand_stillを任意のもので置き換える。

置き換え

実際に立ってみるとこんな感じ。

図

ちなみに今回使ったものとは別に持ってるオリジナル3Dモデル 『Izul -イズール-』 - bikiTadpole - BOOTHは立ちモーションも入っていて直立にならなかったので、アバター作者さんがどれだけ実装してたかによると思います(当たり前体操)。

Avatar3.0の着せ替えギミックとかを入れる

Avatar3.0(以下AV3)対応アバターの着せ替え+VirtualLens2を購入したので、AV3の機能を使って着せ替えてみよう!というやつ。UnityのAnimatorを素直に(?)使えるのはAV3の魅力って感じがする。

AV3復習とか

以下のものがすごく参考になる。というか参考にした。

VRC公式のやついまいち分かりにくくないですか?

基本的な処理の流れとしては、

  1. Action MenuでExMenuを操作
  2. Menuに対応するParameterが変化
  3. AnimatorがParameter変化に従ってStateを変更
  4. State遷移によって(VRCが許容する)任意のアニメーションが駆動

って感じ...のはず。

なおWrite DefaultsVRC公式ではOFF推奨らしい。
すべてのアニメーションプロパティにDefaultを書き込む挙動らしいけどこれが謎でよくわからない(公式もなんかおかしいみたいな旨が書いてるっぽい)。またこの影響で、Write DefaultsがONのものとOFFのものが混ざるとギミック全体がぶっ壊れる。ONでもOFFでもいいけど統一した方が無難っぽい。

着せ替え

基本的な(AV3の機能を使わない)着せ替え方法はVRChat 衣装変更方法 | テトラログで、Radial Inventory System V2を使う方法では【VRChat】Avatar3.0 アクションメニューからワンタッチで衣装などの着せ替えを楽しむ方法! | こはろぐでできる。

この手の外部で用途を管理して実際には1つか少しのParameterを使うAV3ギミックシステムを使ったほうが、Expression Parametersの使用数を節約できるので制限に収まりやすくなってお得...
なんだけど、何故かデフォルトの状態が全裸のアニメーションになってしまったので勉強も兼ねて今回は手動ですることに。

今回の環境

作業

BlueSummerの導入手順を参考にしてかつ、Skinned Mesh RendererがあるオブジェクトをまとめてRadial Inventory Sysytemに入れてやってみると腕だけが追従しなかった。原因はボーンオブジェクトを全部対応させそこねてたこと。

失敗

原因

ちゃんと全部対応させるとしっかり動いた。

成功

もう片方の着せ替えに関しては、マテリアル(とアクセサリー)のみだったので、マテリアル入れ替えするアニメーションクリップを参考通り作成して手動で入れたらできた。

そもそもUnity上でのアバターの扱いどうなってんの

単にFBX上のオブジェクト構造をUnityのGameObjectにインスタンス化して、そのPrefabを生成しているっぽい。知りたいのは標準的なHumanoidの作り方だったかもしれない。

ただ、HumanoidはUnityが解釈できるボーン構造になってないと自動でRigが合わないので、手動で合わせるかモデルを変更する必要がある。

Skinned Mesh Renderはボーンオブジェクトの姿勢を参考にメッシュを変形させてレンダリングするっぽく、服のようなボーンに追従する必要があるオブジェクトは何かしらのボーンをRoot Boneとして設定しないといけない。

また、Anchor Overrideはリフレクションプローブやライトプローブで補完される位置って書いてるけど、スクリーンスペース・リフレクション(SSR) - Qiitaの衝突判定みたいなものなのかな。VRだとスクリーンスペース系のものは使えなさそうだけど...
参考画像

たぶん[Unity] Box Projectionで屋内シーンの反射による映り込みの品質を向上する - Qiitaの方がそれっぽそう。

リフレクションプローブ - Unity マニュアル : よくわかんねー

VirtualLens2の導入

セットアップガイド | VirtualLens2通り入れる。手ブレ補正(stabilizer)は最初指定していたけど下記画像みたいにカメラ本体の位置がぶっ飛んでしまったので一旦なしにした。

カメラ本体がぶっ飛んだやつ

カメラのワールド固定(drop)とかはconstraintを使って実現しているっぽく、このconstraintのoffsetがおかしかったみたいで、CameraMeshに入ってるParent ConstraintをActivateし直してLockを外したら解決してしまった。

ConstraintはTransform.LookAtみたいなC#スクリプトでできることをコンポーネントポチポチで実現できるので、アニメーションでカスタム遷移を書くことが多いVRChatと相性がいいのかも。実際みんな使ってるらしい 使ってないの私だけ。

試しにDropを使って自分で撮ったものが下の写真。

撮影サンプル