Docker: busybox のイメージサイズはなぜ小さい

まあ、このあたりは初心者なので新しい発見が多いわけですが…

この前も使った busybox ですが、サイズがやたらと小さいことに気づきました。

$ docker images
 REPOSITORY    TAG       IMAGE ID       CREATED        SIZE
 busybox       latest    c55b0f125dc6   11 hours ago   1.24MB

えっ、busybox って一通りの基本コマンドが揃っていたような、と思ったわけです。普通 /bin とかこのサイズで収まらないでしょう、と。で、調べてみるとそもそも busybox って十徳ナイフのようなコマンドとして組み込み用に存在していてそれを Docker 用イメージにしたのですね。

Linux の man ページには以下のようにあります。

NAME
        BusyBox - The Swiss Army Knife of Embedded Linux

つまり、複数のコマンドを一つの実行ファイルにし共有できるところを共有・最適化することによりこのサイズを実現しているわけです。そして起動時にどのような名前で呼ばれたかによって動作を変えると。

試しに busybox を起動して中身を確認してみます。

$ docker run -i -t busybox:latest
/ # ls -li /bin
total 449684
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 [
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 [[
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 acpid
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 add-shell
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 addgroup
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 adduser
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 adjtimex
  84754 -rwxr-xr-x  400 root     root       1149184 May  3 21:57 ar
(...省略)

ls に ‘-i’ オプションをつけて inode 番号を表示するようにして /bin の中身を見ました。上の例で一番最初の数字が inode 番号ですが、これはファイルシステム上のどのファイル実体を指しているかを表しています。このように 400 の実行ファイルの inode 番号が同じに表示され (注)、これらの実行コマンドファイルの実体が同じものであることがわかります。ちなみにこの 400 という数はオーナー (root) の前に表示されているリンク数 (400) からもわかりますね。

busybox のサイズの小ささと共に 400 もの機能をひとまとめにしていることに感嘆するわけです。

(注: /bin の中で getconf だけ別の inode 番号を持っていました)

参考: https://busybox.net/

WSL 2 複数インスタンス同一 IP アドレス

WSL 2 で複数の distribution (ここではこれを WSL の「インスタンス」と呼ぶことにします) を導入して同時に起動すると同じ IP アドレスを使うのです。ちょっと直感に反していたのでまとめておきます。

まず前提として WSL の複数インスタンスを使うという話ですが、wsl.exe の export/import 機能を使えば同じバージョン Ubuntu でも複数のインスタンスを作成できます。ここでは元々の名前の Ubuntu というインスタンスとそれを export/import して作った Hetarena というインスタンスがあるものとして説明します。

その 2つ、Ubuntu と Hetarena を同時に起動して ifconfig で IP アドレスを確認すると、あら不思議、同じ IP アドレスを持っています。さらに Ubuntu で Apache を起動して Hetarena で netstat で listening しているポートを確認すると、なんと 80/TCP をリッスンしているではないですか!

さらにさらに Ubuntu で mysql を起動して Hetarena から以下のコマンドを打ってみると何と接続できて Ubuntu のデータベースの内容を確認することができます。

mysql -u myid -D mydb -h 127.0.0.1 -p

ちなみにホスト指定を ‘-h localhost’ とすると Unix ソケット経由の接続を試みこれはエラーとなります。ファイルシステムは明らかにインスタンス毎に独立しているし、ps コマンドを打ってみても他方のプロセスが見えるということはなさそうです。

ググってみると関連した issue が見つかり、そこで関係者により以下のように説明されていました。

All of the WSL 2 distros run on the same virtual machine, which has a singular virtualized networking interface controller.

https://github.com/microsoft/WSL/issues/4304

「WSL 2 の複数ディストロは同一 VM 上で動作する」って訳してもチンプンカンプンになりそうですが、面白い実装ですね。

WSL 2 のデフォルトユーザー指定 (2つめのインスタンス用)

Visual Studio Code で WSL 2 と連携してろくに設定せずに Terminal を開くと root でシェルが起動してしまったりします。それでは困るので以下のコマンドでデフォルトユーザーを設定することになります。

ubuntu config --default-user blogger323

参考: https://docs.microsoft.com/en-us/windows/wsl/wsl-config#change-the-default-user-for-a-distribution

ところが、です。私の場合は以下の記事を参考にしてディストリビューションの位置を変えたり 2つめの WSL インスタンスを作ったりしています。

https://stackoverflow.com/questions/38779801/move-wsl-bash-on-windows-root-filesystem-to-another-hard-drive

私の場合、Ubuntu を export 後、Hetarena という名前で import して 2つめのインスタンスを作りました。さて、この 2つめのインスタンスのデフォルトユーザーを変えるにはどうしたらよいでしょう?

hetarena config --default-user blogger323

の様なことをやっても当然のごとく “Command Not Found” なわけです。

ググっても答えは見つからず、結局レジストリ内を検索して見つけ出しました。以下のキーに Linux uid を設定することでデフォルトユーザーを指定できます。

HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\CurrentVersion\Lxss\{distro-id}\DefaultUid

ここで distro-id はディストリビューション (WSL インスタンス) に割り当てられた ID です。DefaultUid と同じ階層に DistributionName というキーがあるのでこれで見分けることができます。

ちなみにレジストリキーでググって後から以下の Issue を見つけることができました。私は試していないですが、Linux ファイルシステムの設定ファイルに書く方法もあるようです。(追記: Microsoft のドキュメントを改めて確認すると記載があったのでこちらの方が正規の方法ですね!)

https://github.com/microsoft/WSL/issues/3974

Anaconda と Visual Studio Code

Windows で Anaconda を使用して主に Python でコーディングしたりしています。Notebook 中心でスクリプトファイルをいじるときは Windows 用 Python 付属の IDLE を少し使うみたいな感じだったのですが、最近思い立って Visual Studio Code 環境に移行しつつあります。で、このエントリは Anaconda 用に Visual Studio Code を設定したときの覚え書きです。Python Extension をインストールした後あたりからの話です。

Python interpreter の選択

公式 Tutorial にも書いてあるしエディター上でもメッセージが出たりしますが、まずは Python interpreter を選択しなければなりません。ここで Anaconda 環境の python.exe を選択します。

conda.bat のあるところにパスを通しておく

作成したスクリプト実行時はまず conda コマンドが起動され、選択されている python.exe に応じた仮想環境が activate されてスクリプトが実行されるわけですが、この conda が見つからずエラーになってしまいます。Visual Studio Code のドキュメントをみると python.condapath を設定するとあるのですが、これが効きません。

ググってみると、この設定での成功例は見当たらないし、関連ありそうな Issue は Open のままっぽいので、素直にパスを設定することにしました。Windows 「システムの詳細設定」から環境変数で Path に “C:\ProgramData\Anaconda3\Scripts” を追加しています。

conda init powershell を実行する

Visual Studio Code 内で起動するターミナルではデフォルトで PowerShell が起動されるようです。Anaconda プロンプトを起動後、以下のコマンドを実行し Anaconda 関連初期化処理をPowerShell 用初期化スクリプト (profile.ps1) に追加します。

conda init powershell

PowerShell 実行ポリシー変更

デフォルト設定のままだと初期化用の profile.ps1 が実行許可されずエラーとなってしまうため、PowerShell Execution Policy を緩くします。ここではローカルのスクリプトは署名がなくても実行できるようにします。(RemoteSigned: 詳細はこちら)

以下を管理者権限で起動した PowerShell 上で実行します。

 Set-ExecutionPolicy RemoteSigned 

以上で Python スクリプトを Visual Studio Code から起動できるようになったはずです。

ということをもろもろ確認しながら書いていたらここの回答そのままじゃん、と見つけて思ってしまいました。やはり探すべきは stack overflow ですよね。

Docker で WSL 2 エンジン有効化後、既存 Hyper-V ボリュームアクセス不可問題

まあ、当たり前と言えば当たり前なんですけどね。私は Docker に関して一般ユーザーなので、 ‘Use the WSL 2 based engine’ を有効化したとたんにこれまで使用できたボリュームにアクセスできなくなって焦りました。そんなときどうするかという話です。今更という感じの話題かも知れませんが…。

シナリオとしては以下の通りです。

  1. これまで Docker Desktop for Windows を Hyper-V backend で使用してきた。
  2. メリットが多いというので WSL 2 backend に切り替えた。
  3. 切り替えた後は既存ボリュームにアクセスできない!

さて、この時どうするかなのですが、まず焦らず もう一度 Hyper-V backend に戻します。backend を切り替えるだけでは Hyper-V 上の Docker サービス用 VM が削除されたりするわけではないので Docker Desktop で設定を元に戻せばこれまでの image や volume がまた使用できるようになります。

続いて volume の内容をエクスポートします。以下のように busybox を使用して Windows のファイルシステムと Docker ボリュームをマウントした後、ボリュームの内容を tar.gz ファイル on Windows ファイルシステムとしてエクスポートします。

(ボリュームを確認)
C:\>docker volume ls
 DRIVER    VOLUME NAME
 local     myvolume
(ボリュームをエクスポート)
C:\>docker run -v myvolume:/src -v C:\Temp:/dest -i -t busybox
 / # tar -zcvf /dest/volume-backup.tgz src
 / # exit

C:\>

あとは WSL 2 backend に切り替え新規ボリュームを作成してから .tgz を戻せばよいです。私の場合は WSL の Linux ファイルシステム上に適当に展開して ‘-v /home/blogger323/dockervol:/mnt’ のように bind mount するようにしました。

ちなみにですが、WSL 2 backend に切り替えた後のボリュームは以下のパスで Windows からアクセス可能です。

\\wsl$\docker-desktop-data\version-pack-data\community\docker\volumes

参考情報:

Migrating existing containers from Hyper-V to WSL2 technology

追記: リソース消費量を抑えるには (2021.5.2)

WSL 2 を Docker エンジンとして選択すると、Docker 用の WSL distribution として以下のように docker-desktop、docker-desktop-data の 2つが新たに作成されます。

C:\>wsl.exe --list -v
  NAME                   STATE           VERSION
* Ubuntu                 Running         2
  docker-desktop         Running         2
  docker-desktop-data    Running         2 

上のリストで Ubuntu は元から使っていた distribution ですが、WSL integration の設定でこの Ubuntu と Docker を統合すると Docker を使っているときはこれら 3つの WSL 環境が起動することになります。本文では WSL integration の設定を行う想定で bind mount するとしましたが、WSL 用メモリの使用量は多くなりがち (参考)なので統合せず Docker volume を作ることで起動する WSL 環境を docker 関連の 2つのみにするという選択肢もありだと思います。

その場合も、本文最後に書いたパスを用いて Windows から直接 volume 内のファイルにアクセスすることが可能です。

更に追記 (2021.5.15):
よく考えると Docker Desktop を使わずに WSL で Linux 版 Docker を動かせばよいような気がしてきました。

WSL 2 で Windows から Linux マシンをどう参照するか問題

WSL 2 に mysql や apache を入れて Windows のプログラムから接続したくなることがあります。さてどうしましょうか。

build 18950 以上であれば localhost 指定で OK

Windows 10 build 18950 以上であれば Windows のループバックアドレス (localhost) のポートから自動的に Linux マシンにポートフォワードしてくれます。ですので Linux で Apache を動かして Windows のブラウザから http://localhost/ と指定してアクセスすることができます。

参考記事: https://www.atmarkit.co.jp/ait/articles/1909/09/news020.html

生のアドレスでアクセスしたい時もある

ただ、Web アプリ関連でリダイレクト等が絡むと localhost を使った URL では動かないケースがあるようです。私もそのあたりが原因でハマりかけたので開発時は Linux マシンのアドレスを使ってアクセスしています。ここで困るのが Linux マシンの IP アドレスを固定できないことです。調べた限りでは方法がなさそうです。

そこで策として考えられるのが、Windows の hosts ファイルを Linux マシン起動後に更新してホスト名を固定する方法です。世の中にはこれを Windows サービス化された方がいらっしゃいます。

https://github.com/shayne/go-wsl2-host

私の場合はこれだけのためにサービスを増やすのも大ごとのように感じてしまい、以下のような簡単なスクリプトを作って WSL 起動後に Windows 管理者権限で実行しています。元々 hosts ファイルはいじっておらず空のままだったので完全に置き換える動作にしていますが、ちょっと変えれば静的レコード+動的レコードみたいな使い方もできると思います。ご参考まで。

Linux 側 IP アドレス表示スクリプト

$ cat /usr/local/bin/genhosts
#!/bin/sh
IP_ADDR=hostname -I
while read -r line; do
  echo $IP_ADDR $line
done < /usr/local/etc/wslhosts

Windows 側から参照したいホスト名を記述するファイル

hetarena.com 開発環境の例ですw

$ cat /usr/local/etc/wslhosts
hetarena-dev.com
en.hetarena-dev.com
ura.hetarena-dev.com

Windows 側スクリプトファイル

以下の内容の .cmd ファイルを作って WSL 起動後に管理者権限で実行します。

wsl.exe genhosts > C:\Windows\System32\drivers\etc\hosts 

すると以下のような hosts ファイルが生成されます。

172.27.43.123 hetarena-dev.com
172.27.43.123 en.hetarena-dev.com
172.27.43.123 ura.hetarena-dev.com

これで Windows ブラウザから http://hetarena-dev.com を参照したりすることができます。

その他

  • WSL 2 のファイルシステムを参照したいときは ‘\\WSL$\’ を Explorer から開けばよいですね。
  • 逆に Linux から Windows を参照する場合は “ホスト名.mshome.net” というホスト名が使用できます。

関連情報: WSL2 Set static ip? 
https://github.com/microsoft/WSL/issues/4210

Happy New Year!

昨年は春頃から仕事が忙しくなり、ブログもその他もろもろも放置してしまいました。 ブログに関して、今年はきちんと月1本は記事を書いていきたいです。

今考えているネタとしては以下の様な感じ。

  • 40歳を超えてからの転職
  • アジアの仕事と英語

けっしてブログを引退したわけではないです。 今年も細々と続けていきます。

バトスピ: 指定アタック後にブロックされない効果

いきなりバトルスピリッツです。 すみません、もともと方向性も何もない備忘録ブログだったりするので。

と言っても子供がハマっているのに付き合っている状態です。 ブースターパックを開けると、強いカードは全部子供のものになるので、余った黄色で頑張っています。 黄色はうまく使えればそこまで弱くないのでしょうが、萌え系カードということもあって余るのです。 決して私の趣味ではありません、はい。

まあ、負けてばかりなのが嫌で、ちょっとシングル買いをしたりもしますが…w

というわけで、今回は子供がカードダスナビに電話で質問した内容です。 備忘録&誰かのためになればと。

質問

ホワイトフォックスに蛇星鞭サビクがブレイヴした状態で、サビクのブレイヴ時効果を使って相手の疲労状態スピリットに指定アタックしたとします。 この後、アルティメットトリガーがヒットし、相手のスピリットからブロックされない効果が発揮したとき、どうなるのでしょう?

ひょっとして、そのままライフ削れる? (by 子供)

答え

この場合、相手のスピリットからブロックされないので、指定アタックが無効となります。 なので、アルティメットが居ればアルティメットでブロックすることができます。

子供の反応

まあ、アルティメットが居たらクロスアルティメットトリガーで手札に戻すけどね (ニヤリ)。

父はいつまで経っても各カードの効果が頭に入らず、子供の記憶力のよさにバカ親ぶりを発揮している今日この頃です。

Minified JavaScript と Source Maps、とか

あっと言う間に 2015年が始まって 1ヶ月半が経過してしまいました。 本業がバタバタしているのもあってこれが今年初のエントリだったりします。

今回は PhpStorm を使った開発関連の小ネタを 2つほど。 PhpStorm といいつつ PHP には関係ない部分なので WebStorm でもオッケーなはずです。

Minified JavaScript とデバッグ

WordPress プラグインの開発に PhpStorm を使っているわけですが、いわゆる .min.js ファイルを作って圧縮しつつ、ソースファイルを使ってデバッグするための方法を探していました。 Source Maps ファイルを使えば良いということはわかっていたのですが、今一つうまく動かないのでちょっと気合を入れて調べてみました。

すると日本語で答えが見つかったので感動しました!

重要なのは Closure Compiler に以下のオプションを与える部分です。

--create_source_map $FileNameWithoutExtension$.min.js.map 
--output_wrapper "%output%//@ sourceMappingURL=$FileNameWithoutExtension$.min.js.map"

こちらの記事でも説明されていますが、圧縮して作った .min.js の最後に Source Maps ファイルの URL を埋め込まなければなりません。 以下のような内容を最後に入れるのです。

//@ sourceMappingURL=standard-widget-extensions.min.js.map

これを行うために –output_wrapper オプションを使って出力の最後にソースマップ用 URL を追加しています。

これで圧縮前の JavaScript ソースを使ってデバッグできるようになりました。 英語も含めて探したのですが、見つかったのは先の日本語記事のみ。 バシャログさん、有益な情報ありがとうございます。

ちなみに YUI Compressor では Source Maps 出力用オプションを見つけることができず、UglifyJS は調べてません。

Git merge と PhpStorm

WordPress プラグインは一人で開発しているので、なるべくマージが発生しないようにしてきましたし、やむを得ない場合はできるだけコンフリクトが生じないようにしてきました。 ところが最近、開発を進めてだいぶコードをいじった後に、リリースしているバージョンの微修正版を出さなければならない状況が生じました。 もう仕方ないのでブランチを切ってみました。 そして、恐る恐る PhpStorm 上で git merge をしてみました。

そしたら、思ったより簡単。 PhpStorm (WebStorm) はマージツールとしてとても扱いやすいです。 下の画面の様にマージ結果を中心に 3つ並べて「×」や「>>」、「< <」を使って操作することができます。 merge (phpstorm)

以下の記事に従えばコマンドライン git のマージツールとして PhpStorm (WebStrom) を登録することもできます。

結び

今年も相変わらずマイペースにブログを続けていこうと思いますので、よろしくお願いします。

WordPress Core ソースを読んでみよう (投稿更新編)

この記事は WordPress Advent Calendar 2014 の 10日めの記事として書いています。

WordPress に限らず Advent Calendar への参加は初めてです。 11月の終わりになってそう言えばそんな時期だなと思い、今年はどんなカレンダーがあるのだろうと Web サーフィン (死語) をしていたところ、WordPress Advent Calendar が埋まっていないのを見つけました。 そして、枠を埋めるつもりで気軽に応募し、この記事を書いています。

ちなみに私自身は WordPress プラグインの作者 (プラグインその1その2) ですが、本業は WordPress に関係ありません。 Advent Calendar 用として何を書くかについてはいろいろ悩みました。 利用者向けに書いてみたい気もしましたが、説教臭くなってしまう恐れがあるので(「何故、自前の WordPress なんでしょうか?ブログサービスじゃダメなんでしょうか?」とかw)、結局開発に興味ある方向けに、解説されることの少ない投稿の更新処理についてコードを追いかけてみることにしました。

というわけで今回は投稿編集画面で「公開」/「更新」ボタンを押したときの処理を見ていきます。 是非実際にコードを参照しながらお読みください。

なお、コードは WordPress 4.01 のものを参照しています。 紛らわしいのですが、HTTP メソッドの POST は大文字で、WordPress の投稿を指す際は小文字の post で表すことにします。

フォームの submit

まず、投稿編集画面ですが、wp-admin/post-new.php (新規) あるいは wp-admin/post.php (更新) によって表示されます。 編集画面を表示する実体としてはどちらも wp-admin/edit-form-advanced.php によって処理されるのですが、ここには深入りしません。

post-new.php の処理で注目したいのが、71行目で get_default_post_to_edit() を呼んでいる点です。 この時点でデータベース内に投稿データが作成され、新しい post ID が付与されることになります。 つまり、新規投稿であろうが更新であろうが、公開/更新ボタンを押したときの処理はデータベース内の post データを更新する処理になり、実のところ submit されたフォームデータの処理から先は共通になっています。

編集画面は post.php に POST するフォーム (form 要素は id=post) になっています。 hidden 項目の一つとして (name=’action’, value=’editpost’) という項目が POST されます。 これが wp-admin/post.php 17行目の wp_reset_vars() で $action = ‘editpost’ となり、104行目からの switch ($action) で edit_post() が実行されます。

以下関数名を使って追いかけていきます。

edit_post() in wp-admin/include/post.php

edit_post() では、引数が null で実行されることになるので POST されたデータが $post_data に設定されます。 $post_ID も POST された値が用いられます。

そしてもろもろの前処理 (詳細省略) を行った後 320行目で wp_update_post() を呼びます。

wp_update_post() in wp-includes/post.php

引数の $postarr には編集画面のフォームより POST された投稿データが含まれています。 3541行目の get_post() で $post にデータベース内の投稿データがセットされます。 attachment の更新の場合は wp_insert_attachment() をコールしたりとかありますが、今回は post なので wp_insert_post() が呼ばれます。 まあ、大した処理をしていないのでさっさと次に行きましょう。

次の wp_insert_post() の処理が最も重要です。

wp_insert_post () in wp-includes/post.php

引数 $postarr には編集画面のフォームより入力された投稿データが含まれています。 wp_update_post() に続きここでも get_post() でデータベースの投稿データを引っ張りだしています (3095行目)。 非効率な気もしますが、get_post() から呼ばれる get_instance() で読んだデータをキャッシュする仕組みがあるようなので大丈夫でしょう。

3090行目からの if 文の処理ですが、編集画面フォームから submit したときの処理は新規の場合でも $postarr[‘ID’] が存在して $update = true になります。 では $update = false となるのはどんな時かと言うと、編集画面を表示する際に get_default_post_to_edit() 経由で呼ばれたときですね。

そして、この wp_insert_post() はかなり長いのですが、やっていることの大半は post データの設定です。 どこでデータベースが更新されるかわかりづらいのですが、$update = true の場合は 3341行目の $wpdb->update() で更新されます。 この周辺にはアクションフック/フィルターフックも多数存在するので、この wp_update_post() だけでも一度真剣に読んでみると良いと思います。

さて、ここまでで投稿されたデータは無事データベースに反映されましたが、リビジョンの処理についてもう少し見てみます。

wp-includes/default-filters.php 内で既定のフック関数が登録されているのですが、この中に post_updated のフック関数として wp_save_post_revision() が登録されています (251行目)。 これが wp_insert_post() から呼び出されます (3478行目)。

wp_save_post_revision() in wp-includes/revision.php

それではリビジョン関連処理を見ていきます。 下の図は投稿更新時のリビジョン関連処理の概要を表したものです。 同じ色は同じコンテンツを表していますが、このように現在の post の内容と同一のものが最新リビジョンとして post_type=’revision’ でデータベース内に存在します。 詳しくはこちらの記事をどうぞ。

Update flow 3.6

では wp_save_post_revision() の処理を見ていきます。

まず wp_get_post_revisions() で更新された投稿についての全リビジョンを取得します (104行目)。 そして、最新のリビジョン、すなわち更新前の post と同じデータを持つリビジョンの ID を $last_revision にセットします (106行目からの foreach ループ)。 続く、127行目の if 文では内容に変更があったかを確認し、変更が無いようであればそのまま return してしまいます。

その後に _wp_put_post_revision() を呼んで新しい post と同じ内容を持つ post_type=’revision’ のデータをデータベース内に作成します (142行目)。 この処理において _wp_put_post_revision() から wp_insert_post() が呼ばれるわけです。 「$postarr のデータが post_type=’post’ でなく、post_type=’revision’ だったら処理はどう変わるのだろう?」という新たな視点から wp_insert_post() を読み直すことができるわけですねw

wp_put_post_revision() を呼んだ後 (144行目以降) は不要となったリビジョンの削除処理です。 保持するリビジョン数は wp_revisions_to_keep() が返す値を使っていますが、この関数の中をみると、WP_POST_REVISIONS を設定する他にも wp_revisions_to_keep フックをつくれば何世代リビジョンを保持するかを指定できることがわかります。

まとめ

ここまで説明してきた関数をコールツリーにまとめておきます。

call-tree

いかがでしょう? 思ったより簡単に読めたのではないでしょうか?

この記事がきっかけで Core のコードに興味を持ち、実際にプラグインを書いたりする人が増えるととても嬉しいです。 他にも WordPress 関連記事はありますので、気が向いたらどうぞ。 なお、投稿更新「編」としましたが、関連記事がこれっきりになる可能性がそれなりに高いことをあらかじめご承知おきくださいw

明日は岡本秀高さん濱田文さんです。 何と 2つの記事が読めちゃいます!