いしぐめも

プログラミングとかしたことを書きます。

Streamlit component APIに入門する

「StreamlitってReactっぽいよな~~~」って思いつつ、書けば使えてしまうので何となくで使ってきたStreamlitですが、Streamlitの動作に対して理解を深めるべく Streamlit Componentに入門してみたいと思います。

Streamlitとは

Streamlitはpythonで簡単にフロントエンドを開発できるライブラリです。Pythonのコードを書くだけで、JavaScriptで構成されたフロントエンドとPythonで動くバックエンドを作れてしまう優れものです。

Webで動作するデモアプリを書きたいときにフロントとバックエンドの通信はどうするだとか、各々のサーバーは何で構成するだとか考えなくて済むのでとても重宝します。

Streamlit Component API

Streamlitは「コンポーネント」という単位で画面を構成し、「見出し」「マークダウン」「グラフ」のような感じで何を表示していくかを順に記述していきます。デフォルトでもグラフやDataFrameを表示できるので、十分なぐらいに実装されているのですが、オレオレなコンポーネントを実装したい人向けに「Streamlit Component API」というものが提供されています。

ドキュメントはこちら↓

Components API - Streamlit Docs

自作コンポーネントを作ってみよう

というわけで、自作コンポーネントを作っていきます。極力ステップバイステップで所感を交えつつ手順を紹介したいと思います。

公式から提供されているテンプレートを使う

いきなりテンプレートのコピペとなってしまって申し訳ないですが、公式からコンポーネントのプロジェクトを作るにあたって活用できるテンプレートが提供されているので、ありがたくそれを使っていきます。

github.com

Streamlit公式から提供されているテンプレート「component-template」

画像の通り、「template(React Componentのように記述できる)」「template-reactless(React Componentを使用しないタイプ)」の2つのテンプレートがあります。今回の例ではReact Componentで試したいことがあったので、、前者を利用します。

というわけで、上記 component-template をクローンして、templateフォルダで作業をしていきます。

git clone https://github.com/streamlit/component-template
cd component-template/template

フロントエンド用の資材を準備・サーバー起動

まずは、フロントエンドはTypeScriptで記述したものをトランスパイルして使用するので、依存しているライブラリの導入が必要になります。フロントエンドのフォルダに移動して npm install を実行します。

pushd my_component/frontend
npm install
popd

ライブラリが揃ったら、npm start でサーバーを起動します。Reactユーザーにはお馴染みの react-scripts でWebサーバーが立ち上がります。

pushd my_component/frontend
npm start

開発中は react-scripts で適宜トランスパイルされてWebサーバーで公開されているものを読み取りに行く動きをするようですね。

Streamlitで動作確認する

Streamlit で my_component/__init__.py を実行することで動作確認することができます。

streamlit run my_component/__init__.py

起動すると 8501 番ポートで立ち上がるので、そこにアクセスすると動作している様子を確認することができます。

vscode で devcontainer を使用している場合、vscodeのポートフォワーディングでlocalhostの8501にポートがバインドされるので、ブラウザで http://localhost:8051 を開けばOKです。

VSCodeのポートフォワーディングでコンテナ内のポートにアクセス

Streamlit Component動作確認!

リリースする方法

ソースコードを見てもらえるとわかりますが、_RELEASE 変数の値によって動作が変わるようで、「_RELEASEがFalseの時はローカルサーバーを参照」「_RELEASEがTrueの時はトランスパイル済みのjsファイルを参照」するように動作が変わるみたいです。

なぜわざわざローカルサーバーを参照?ってところですが、これはまぁコンポーネントのソースコートが更新された時にホットリロードされるように、ということなのかなと思います。

_RELEASE をTrueにするとトランスパイル済み資材が参照されるので、これは別途 npm run build でトランスパイルしてそれを含めてモジュール化するという流れになるみたいです。これは公式のワークフローを参照するといいと思います。(ソースコード内の_RELEASE変数書き換えからやってて勉強になります。)

component-template/.github/workflows/ci.yaml at master · streamlit/component-template · GitHub

devcontainer化しました

と、ここまでの内容(+これから以下で試す内容)をdevcontainer化しました。

とはいえ、求められるのはnodeとpythonだけなので、いうほど環境準備はハードル高くないかなと思いますが。ローカル環境汚したくないよって人はぜひ使ってみてください。

GitHub - yoh1496/learning-streamlit-component: Repository for learning streamlit component

気になったこと

個人的に試して気になったことを書きます。(順次書き足していく予定です)

気になったことその1: マウントアンマウントは検知できる?

Streamlit ComponentはReact Componentライクに書くことができるので、Reactのライフサイクル同様、componentDidMount などの関数が呼ばれるかを試してみました。コンポーネントのアンマウント時にクリーンアップなどの処理ができると便利ですよね。

class MyComponent extends StreamlitComponentBase<State> {
  public state = { numClicks: 0, isFocused: false }

  public componentDidMount(): void {
    super.componentDidMount()
    console.log('mount');
  }

  public componentWillUnmount(): void {
    if (super.componentWillUnmount) super.componentWillUnmount();
    console.log('unmount');
  }

  public componentDidUpdate(): void {
    console.log(this.props)
  }

/** 略 **/

というわけでこんな感じに実装して、以下のようにマウント・アンマウントを試すコードを書いてみました。

    if 'mounted' not in st.session_state:
        st.session_state.mounted = False

    def on_click_unmount():
        st.session_state.mounted = False
        del st.session_state.hoge
    
    def on_click_mount():
        st.session_state.mounted = True
    
    mounted = st.session_state.mounted

    st.button('Unmount Component', disabled=not mounted, on_click=on_click_unmount)
    st.button('Mount Component', disabled=mounted, on_click=on_click_mount)

    if mounted:
        my_component('Mount/Unmount Test', key="mount_unmount_test")

session_state を用いてmountedフラグを管理し、mountedフラグの値によって表示/非表示を切り替えるコードです。

結果

これを実行した結果、componentDidMount, componentDidUpdate は呼ばれるが componentWillUnmount は上記pythonスクリプトでは呼ばれないことがわかりました。

この結果では 「非表示にしただけではStreamlit Componentはアンマウントされない」のか「Streamlitではアンマウントに際してcomponentWillUnmount` は呼ばれないのか」は不明ですが、ちょろっとコードを書くだけではアンマウント時の動作を記述することは難しいということは言えそうです。

どなたかアンマウントにあわせて処理を記述する方法をご存じの方がいらっしゃいましたら教えていただければ幸いです。

終わりに

今回はStreamlitをよりカスタマイズするために、Streamlit Componentを試してみました。

冒頭にも書きましたが、Streamlitはちょっとオタメシのフロントエンドを書くのにすごく便利で、Componentと組み合わせることで標準のStreamlitにはない機能を持たせることができることがわかりました。

個人的にはこれからも便利にStreamlitを使っていきたいなと思うところですが、いろいろ複雑なことをやると死ぬ(これについてはまたいつか書きたいです)というところも同時にわかってきたので、あんまりゴチャゴチャComponentで書こうとしてReactで書いた方が結局早くね?とならないように気を付けたいところです。

【Azure Functions】ローカル開発でもカスタムコンテナーを使いたい!

Azure Functionsを利用するアプリ開発(≠Azure Functionsの開発)において、どのようにローカル開発を行うかというのは大きな課題です。

一番ベタなのは azure-functions-core-tools を使用しての開発かなと思いますが、Functions自体はコンテナ化しておきたいと考えると、ローカル開発とデプロイされるものが異なってしまうのが難点かなと個人的には思います。

というわけで、今回はローカル環境で本番と同じコンテナイメージを使って開発してしまおうという内容になります。

はじめに: カスタムコンテナーとは?

そもそも、Azure Functionsにはソースコードをコンテナに配置して、そのイメージをデプロイするというオプションがあります。

カスタム イメージを使用して Linux 上で Azure Functions を作成する | Microsoft Learn

これをドキュメントでは「カスタムコンテナー」として扱っているんですが、クラウドにデプロイして使う方法しかドキュメントには載っていません。ローカルでも使えたらきっと便利ですよね。

カスタムコンテナーで内部的に使用されているのは azure-functions-host

上記の手順に従ってカスタムコンテナーを作って動かしてみると、どうも内部的に使用されているのは azure-functions-host というものらしいです。詳細は以下のリポジトリを参照。

GitHub - Azure/azure-functions-host: The host/runtime that powers Azure Functions

これはどうやらAzure Functionsで使用されているランタイムのようで、これ公開してるのすごいなと思いつつ、仕様がオープンになっているのでありがたく使わせていただきましょう。

カスタムコンテナーをローカル開発で使用する手順

では、ここからはAzure Functionsで使用するカスタムコンテナーをローカル開発で使用するにあたって必要な手順を書いていきたいと思います。

カスタムコンテナーを作る

まずはドキュメントに従ってカスタムコンテナーを作ります。

mcr.microsoft.com/azure-functions/python などをベースに、requirementsをインストールしてソースコード/home/site/wwwroot に配置するという感じになるかなと思います。(pythonのばあい)

起動用のdocker-composeを書く

次に、起動用にdocker-composeを書いていきます。

version: '3.7'

services:
  functions:
    build: .
    environment:
      AzureWebJobsStorage: DefaultEndpointsProtocol=https;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;QueueEndpoint=http://azurite:10001/devstoreaccount1;
      AZURE_FUNCTIONS_ENVIRONMENT: Development
    ports:
      - 80:80

  azurite:
    image: mcr.microsoft.com/azure-storage/azurite
    hostname: azurite
    restart: always
    ports:
      - 10000
      - 10001
      - 10002

ローカルでキューストレージのトリガを使ったりするために Azurite というストレージアカウントのエミュレータを同時に動かすようにしています。(なくても動くかも。)

(Optional)ローカル開発時の認証を消す

azure-functions-core-toolsでは、実行時にauthLevelがanonymousになるという仕様があるんですが、このカスタムコンテナーだとそれがありません(本番環境にも同じものがデプロイされるので当然)。なので、本番はauthLevelでfunction で使うことを想定している場合にカスタムコンテナーではローカル開発では使用できないという問題が発生してしまいます。

というわけで、私は起動時の実行コマンドで以下のようにsedでauthLevelをanonymousに変更して起動するようにしてみました。

    command: >-
      bash -c '
      sed -i -e "s/\"authLevel\":.*/\"authLevel\": \"anonymous\",/" /home/site/wwwroot/hogefunc/function.json
      && /azure-functions-host/Microsoft.Azure.WebJobs.Script.WebHost
      '

実行する

作ったdocker-compose.ymlファイルを通常通り起動すればOKです。

docker-compose up

終わりに

以上、ローカル開発でAzure Functionsのカスタムコンテナーを使用するという話でした。

ユースケースとしては、

「Azure Functionsに直接乗っかるものを開発するのには azure-functions-core-tools を使用して、ある程度固まったらカスタムコンテナー化して今度はそのFunctionsを利用する側のアプリを書く」

そんな感じでうまく使い分けられると良いんじゃないかと思いました。

今回作ったdocker-compose.ymlをベースにdevcontainersで使用したりするのもGOODですね。

えっ?!全部の受信ポートを拒否しながらVSCodeでリモート開発を?!【ConoHa編】

「できらぁ!!!!」

というわけで、この記事は、ConoHa Advent Calendar 2022 20日目 の記事です。急遽20日目が空いたので、急いで書きました。

はじめに

以前、Visual Studio Code(以下、VSCode) がRemote Tunnelという新しいリモート開発方法を提供開始したという記事を書きました。

yoh1496.hatenablog.com

こちらで「「え?ベンダが提供するWeb画面ベースのコンソールでもいいの?」に対しては「はい」です」と書いたので、実際に試してみましょうという記事になります。

著者のConoHa歴

Advent Calendarからいらっしゃった方は初めまして。@yoh1496と申します。

ConoHa VPSは趣味で2年近く使ってます(主にマイクラサーバーを立てたり、リバースプロキシにしてみたり)。 最近ConoHa WINGも契約しましたが死蔵しています。PHP勉強して動くものを載せたい…

想定読者

今回の内容は 前回記事の内容と変わりありません。ですので、詳しい内容は前回記事を参照いただくとして、今回は

  • 具体的なイメージが掴みたい
  • ConoHa使った場合にどうやるのかステップバイステップで知りたい
  • ただ興味を持っただけ。ザックリ概要を知りたい

という方に読んでいただければと思います。

ConoHa での VSCode Tunnelの始め方

さて、ここからはConoHa VPSを使って、全ポート受信拒否設定をしつつVSCodeで乗り込んでみよう!という話になりますが、、、

ここからの内容は危険な可能性のある手順を含みます。
実施を推奨するものではありませんし、実施する内容を理解したうえで、実験的用途に限って試してください。

サーバーを作成する

では気を取り直して…まずはVPSを作っていきましょう。再三書かせていただきますが、万一があっても問題にならないように大事な本番環境とは分けて行ってください。

まずはいつも通りVPSを作ります

パスワードとネームタグを設定していきます。ネームタグは「vscode_dekiraa」としました。

パスワードとネームタグも設定

そして本題の「接続許可ポートを全て拒否」にします。IPv4でもIPv6でも、80番ポートはおろかSSH用の22番ポートも開きません。

接続許可ポート「全て拒否」

ここまでの基本的な設定が終わったら、code tunnelを導入すべく、VSCodeCLIのダウンロードを行うスタートアップスクリプトを用意します。ここでの内容はVPSを作成したのちに、コンソールから自身で実施いただいても大丈夫です。

今回は、お手製のスタートアップスクリプトをGistに作ってそれを読むようにしてみました。(VSCode CLIのダウンロードと、バックグラウンドでの実行を行います)

【余談】
今回初めてスタートアップスクリプトを作ってみたんですが「テキスト入力」だと「追加」ボタンを押してもVPSが作成できませんでした。これは仕様…?

https://gist.githubusercontent.com/yoh1496/47023b37b0936502992622b658182e96/raw/c04ac04ea38a836ed04d7d9a4f710cf0bbf85c7c/vscode_tunnel_start.sh

スタートアップスクリプトのURLを入力

ここまで完了したら「追加」ボタンを押してVPSを作成します。

作成されたサーバーのコンソールでcode tunnelを設定する

そして作られたVPSがコチラ。接続許可ポートに何もチェックが入っていないのがポイントです。(鉄壁の守り!)

作成されたVPS。接続許可ポートが何もない

では早速、「コンソール」に入ってみましょう。先ほどのスタートアップスクリプトを実行した後は「GNU screen」を使ってバックグラウンドでcode tunnelが動作しているので、まずは

  1. root でログインする
  2. screen -r コマンドでバックグラウンドのcode tunnelにアタッチする

をやってみましょう。

rootでログイン後、screen -r

すると、code tunnelを使用するにあたってのプライバシーポリシーやライセンスが表示されますので問題なければ「y」で同意します。

利用許諾が最初に表示される

同意すると、GitHubのアカウントとデバイス(ここではVPS)を紐づけるための認証画面へのURL(https://github.com/login/device)が出ます。

認証画面URLと8桁の認証コードが出た

それを今度はブラウザで開いてみましょう。未ログインの場合はログイン画面が出ますが、ログイン済みの場合はこの画面は表示されません。

未ログインの場合はログイン画面が出ます

表示された認証コード入力画面に先ほどのコードを入力します。

8桁の認証コードを入力する画面

すると、「aaa.bbb.ccc.dddというIPアドレスからの認可のリクエストを許可しますか」的な画面が出るので「Authorize Visual Studio Code」ボタンを押します。ここではちゃんとIPアドレスが自分が建てたConoHa VPSのものであるか確認してください。

vscodeアプリに権限を与える画面です

以下の画面が出れば(ほぼ)完了です。

接続完了!

接続用のURLを取得する

さて、ここまではGitHubの画面でデバイスとの接続を確立しましたが、いったんConoHaのコンソールに戻ります。

すると、新たに1行出力され、「このマシンになんという名前を付けますか?」と聞かれますので、いい感じの名前を付けます。(conoha_vscode_dekiraa としようとしましたが、長すぎて怒られたので vscode_dekiraa にしました)

このマシンとの接続に使う名前を決めます

そして、以下のように接続用URLが出力されればOK!これを使って接続することができます。

接続用URLが出力されます

ブラウザでVSCodeを開いてみる

ここまで準備できましたら、いざ、接続用URLを開いてみましょう。前回デバイス接続に使用したブラウザで開いてみると、、、このようにお馴染みのVSCodeが開けばOKです。

接続用URLを開くとVPS上のVSCodeにつながる!

拡張機能から開いてみる

手元のVSCode拡張機能からもやってみましょう。拡張機能で「tunnel」と検索して、出てきた「Remote Tunnels」をインストールします。

Remote Tunnels をインストール

すると、「Remote Explorer」タブで「Remote」を選んだ時に、一覧に先ほど名前を付けたマシンの名前があることを確認できると思います。

Remote Explorer に tunnels の接続対象が

名前を右クリックして「Open in Current Window」(もしくは名前右のボタン)してみると、、、

いつものVSCodeアプリで開くことができる

このようにいつものVSCodeアプリで開くこともできます。

ここからは普段通りに拡張機能VPS側にもインストールして開発するもよし、VSCodeの便利機能を活用していろいろとやってみるもよしです。

終わりに

今回は「あえて接続許可ポートを無しにしてVSCode Remoteで乗り込む」ということをやってみたわけですが、無暗にポートを開けたくないときに頑張ってWeb画面のコンソールからポチポチやっていたのを劇的に改善できる方法かなと思っています(実際どれぐらいWeb画面のコンソールが使われているかは知りませんが。。。)。ネイティブ版(アプリ版?)VSCodeを使うことで、ファイルアップロードなどもできますし、ターミナルにコピペしたりも容易ですしね。

しかし、ここで紹介したのは極端な例で、あくまでネタと考えてください。実際にはrootのままトンネル作るのは危険ですし、まじめな環境で運用をする場合はコレをしない方がよいと個人的には思います。少なくとも、ログインユーザーは分けたいところです。

というか、ここまでするなら素直にSSHできるようにした方がよい気がしますね。もちろんキッチリセキュリティ対策はするとして。やっぱりcode tunnelが真価を発揮するのはグローバルIPを持たせられない環境(例えば自宅のPC)で使う場合かなという気がしますね。

ここまで書いておいて、結局使いどころは微妙な感じかなと思ってしまいましたが、初 ConoHa Advent Calendar、お邪魔させていただきました。21日目のかた、よろしくお願いいたします~!

ではでは、みなさまもよきConoHa VPSライフを!!
(そういえば、3年間のVPSきっぷ買ったけどまだ作ったVPS活用してないや… どうしよ)

qiita.com

Visual Studio Code の Tunnel 機能が便利

Visual Studio Code に新しいリモート開発「Tunnels」が追加されました。 これが便利すぎるので、是非とも使っていきたいなと思うんですが、同時に懸念点もあるのでそこらへん書きたいなと思います。

Remote Tunnels とは

Visual Studio Code (以下、VSCode) には今までも「Remote - SSH」「Remote - WSL」「Remote - Containers 」などといったリモート開発機能が提供されてきましたが、 今回は新たな接続方法として「Tunnel」が追加されました。

code.visualstudio.com

Remote Tunnels 概要(https://code.visualstudio.com/docs/remote/tunnels から引用)

(・・・この画像見ても何がSSHの場合と変わるのかわからんw)

というわけで、公式ページで確認してみると、

  • リモート側でトンネルを作る
  • そのトンネルを経由してSSH接続が行われる
  • 認証はGitHubのアカウントを通して行われる

ということみたいです。

メリット

一番のメリットは、リモート側で「外からの通信を受ける設定を行う必要がない」ことだと思います。この「外からの通信を受ける設定」ですが、

などでしょうか。

デメリット

  • トンネルを作るにあたって、トンネルの接続先を全面的に信用する必要がある。

ことに尽きるのかなと思います。トンネル経由でSSH接続を許可するなんて、自分でバックドアを仕掛けているようなものですよね。

こういうメリット・デメリットを勘案したうえで、使用可否を判断したいところです。

使い方

ターミナルでログイン

まずはターミナルでリモート側にログインしましょう。

「え?SSHで繋げないのにターミナルでログインってどういうこと?」となると思いますが(自分もちょっと思いました)、これは 踏み台からの接続でもOK です。

「え??それってSSHで多段接続すればいい話では?」ってツッコミもあると思いますが、それはそうです。多段SSHで入れるなら多段でRemote - SSHしましょう…今回の踏み台からの接続は Azureで言うところのBastion接続みたいなの を考えてください。

「え?ベンダが提供するWeb画面ベースのコンソールでもいいの?」に対しては「はい」です。Azureのシリアルコンソールでもいけます

CLIダウンロード・展開

ログイン出来たら、「CLI」をダウンロードします。(・・・こんなの前からあった?)

https://code.visualstudio.com/Download

Download VSCode CLI

ここのダウンロードについては、ターミナル内で行ってほしいので、ブラウザで適当にダウンロード用のリンクを取得したのちに、wget なりでダウンロードしましょう。

ダウンロードできたら展開します。展開すると code というファイルが単体で出てくるので、いい感じの位置に置きましょう。(自分は ~/opt に置きました)

mkdir ~/opt
cd ~/opt
tar zxvf ../vscode_cli_alpine_x64_cli.tar.gz

トンネリング開始

展開出来たらトンネリングを開始します。

./code tunnel

すると、最初に利用許諾が表示され、GitHubのデバイス認証を行うように言われます。

GitHub Device Activation

表示されたURLを開いて、ターミナルに出てきた8桁の文字を打ち込むとcode tunnelで使用されるアカウントが紐づけられます(おそらく)

ブラウザで開く

そこまで完了するとターミナルには接続するためのURLが表示されます。それを開くと…

ブラウザ上でVSCodeが開く

こんな感じでブラウザから使えるVSCodeが開きます。GitHub Codespaces でお馴染みの画面ですね。

アプリから開く

拡張機能をインストールすることでローカルのVSCodeからも開くことができます。

marketplace.visualstudio.com

Select remote machine

できること・できないこと

もちろん、拡張機能を使用することはできます。

拡張機能のインストールと利用

これを利用することで、ちょっとしたlintを効かせながらスクリプト書いて実行したりするのには便利そうです。

が、さらに踏み込んだ使い方(ポート転送、Dev Containers)はできなさそうでした。今後に期待ですね。

使いどころ

軽く触ってみた感じ、Web開発でガッツリ使用するのは厳しいのかなという気がしました。というのも、やはりポート転送やDev Containersが使えないためです。

一方、拡張機能が使えるのはやはり強くて、軽いスクリプトを構文チェックしながら書いて実行してみるといった用途ではなかなか使えるんじゃないかなと思います。

前述した懸念点については、「開発環境での使用にとどめるべき」だと思いました。間違っても本番環境のDbをVSCode拡張機能で触るためにトンネリングで繋いじゃおうなどとは考えてはいけないですね(そんなことする人いないか)

【VSCode】コンテナを利用した開発環境の構築について【devcontainer】

Dockerコンテナを使った開発が結構便利で人にもオススメできるレベルになってきたのでご紹介します!正直コンテナって聞いただけで「ウッ」となってしまう方もいると思うので、極力メリットとかも書いてみたいと思います。

Dockerコンテナ×開発環境

そもそも、なんでDockerコンテナを開発環境で使おうという発想に至るのでしょうか?個人的には以下のような「Dockerコンテナが開発環境構築に向いているわけ」があると思います。

Dockerコンテナが開発環境構築に向いているわけ

その①:揮発性・冪等性

コンテナイメージは内部的にレイヤごとに分かれた構成になっていて、commitしない限りはそのコンテナイメージに変更が加わることはありません。つまり、変更をコミットせずにコンテナを落としてしまうと良くも悪くも変更した内容は消えてしまうわけです。本番でDockerコンテナを使用しようとするとこの特性は最初面倒に感じてしまうこともありますが、逆に言えば再現したい状態をコミットしておくことで、新しく作れば毎回同じ状態が再現される というのは試行錯誤するうえではとてもメリットがあるのです。 つまりこれは、ゴチャゴチャになった開発環境をリセットしたい!! という開発環境あるあるを解決できます。

その②:エコシステム

Dockerコンテナを立ち上げるときに使用するイメージはもちろん自分でDockerfileを利用して1から組み立てることもできますが、Dockerが優秀なのはその イメージをベンダーが提供していたり、またそれがレジストリから落とすことができる「エコシステム」 にあると思います。

たいていのミドルウェアはコンテナイメージも簡単に入手できるようになっており、開発環境構築はこのイメージを持ってくるだけで完了してしまうことが多いんじゃないでしょうか。

その③:軽量

上にあげたメリットはうまくやればVMでもディスクイメージを取っておいて使いまわせば達成できなくもないですが、Dockerコンテナの場合はカーネルはホストOSが担うので、軽量ですし起動も早いと言われています。VM立ち上がるの待たなきゃ~~~っていうのがなくなりますね。

また、VMを複数立ち上げると複数のカーネルが立ち上がってしまうのでそこでメモリなりCPUなりリソースを少なからず消費するということも頭に入れておきましょう。別個のカーネルで動いていなければならない要件がなければ、カーネルを複数の環境で使いまわせるコンテナを使った開発環境がリソース的には有利になります。(まぁそんなカツカツな環境で開発するなというのはありますが)

Dockerコンテナを開発環境に組み込む3パターン

ではそんなDockerコンテナの開発環境への活用ですが、以下のように3パターンぐらい考えることができます。

パターン1: 依存しているものを1つでもDockerコンテナにしてみる

例えば開発にelasticsearchを使っていたら、開発機にインストールしているelasticsearchをDockerコンテナに置き換えてみましょう!

docker run -d -p 9200:9200 elasticsearch 

といった感じで使っているポート番号と同じポートでバインドするようにして起動すれば今までと同様に開発をすることができます。

汚れてきて気に食わなくなったらコンテナを落としてみましょう。コマンド1つできれいさっぱり、メリットにあげた「揮発性」の素晴らしさを体感できることでしょう。

パターン2: docker-compose.ymlを書いてみる

パターン1では開発環境の一部に単体のDockerコンテナを使用しましたが、さらに踏み込んで docker-compose.yml を書いてみましょう。今までいちいちdockerコマンドを叩いていたのが、docker-compose up -d で済みますし、何より開発環境に必要なコンテナ一式を一挙に管理できる のが便利です。

パターン3: 開発環境そのものをdockerコンテナ化する(devcontainer)

パターン2を使っていると自身がコードを書いている開発環境自体もDockerコンテナにしたくなるはずです!

開発環境自体をDockerコンテナ化することによるメリットとしては、例えばコンパイラやツール類のバージョンごとにコンテナイメージを作成することで、使用するコンテナイメージを切り替えるだけでパッとコーディングする環境自体を変えられることが挙げられます。

実際にPersoniumというOSSの開発ではJava1.8とJava17で同時に開発が走った時期があり、また、同時にelasticsearchの6系と7系の切り替えを検討するといったそれなりに環境に対してもインパクトのある改修を行ってきたんですが、それぞれDockerコンテナを用意して

みたいな4パターンを駆使して開発することがありました。これを手動でやっていたとすると、PATHだったりポートだったりもうゴチャゴチャになってしまい効率はすごく落ちていたと思います。

さらにdocker-compose.ymlにコーディングする環境自体を入れてしまうことによって得られるメリットとして、dockerネットワークが開発環境に活用できることがあります。いままで、開発環境がホスト側にある前提で構築してきた「パターン1」「パターン2」では、ホストへのポートのマッピングが必須でした。例えば、elasticsearchを3パターン用意するとしたら、9201, 9202, 9203といった感じで開発環境のパターンごとにポートを決め打ちで準備 する必要がありました。これってすごく面倒で、開発環境の設定も変えなきゃいけなくなります。

普段意識することは少ないんですが、composeを使用するとdocker-compose.ymlファイル(プロジェクト)ごとに dockerネットワーク というものが作成されています。dockerコンテナ間で通信をするときはコンテナごとに設定されたホスト名で通信できると思うんですが、これはdockerネットワーク単位で管理されます。つまり、A/docker-compose.ymlB/docker-compose.yml で別々のdockerネットワークが作成されていると、Aのコンテナ内で名前解決できるコンテナはAに閉じ、B内のコンテナに向くことはありません。

よって、必要な開発環境のバリエーションに合わせて「開発環境入りのdocker-compse.yml」を分けてしまえば、開発環境からは「同じcomposeファイルのxコンテナ9200番ポート」という設定のまま開発を行うことができ、また他の開発環境から独立して開発を進めていくことができるのです。

例: 実際の開発環境

と、ここまで書いて実践例もないのもアレかなと思い、拙作で恐縮ですが、自分がPersoniumというOSSの開発に使用しているdevcontainerについて紹介したいと思います。

github.com

devcontainerは、Visual Studio Codeでコンテナを使用した開発環境を構築できる拡張機能です。

Personiumの開発環境構成

Personiumではバックエンドに以下のようなものを使用しています。

また、バックグラウンドでタスク実行を行うエンジンと呼ばれるものがあります(Tomcat上で動かします)。

  • personium-engine (タスク実行エンジン)

動かし方

それでは使い方を説明します。

まずは開発環境を配置したいディレクトリで以下のコマンドを実行して、必要なソースコードを揃えます。

git clone https://github.com/yoh1496/personium-core-dev.git
git clone https://github.com/personium/personium-core.git
git clone https://github.com/personium/personium-plugins.git
git clone https://github.com/personium/personium-plugin-base.git
git clone https://github.com/personium/personium-lib-es-adapter.git

実行することで、以下のようなディレクトリ構成になることを確認してください。

┗┳ /personium-core-dev
 ┣ /personium-core
 ┣ /personium-plugins
 ┣ /personium-plugin-base
 ┗ /personium-lib-es-adapter

(とりあえずpersonium-coreが依存するリポジトリを全部入れてますが、personium-core-devとpersonium-coreだけでも大丈夫です)

次に、クローンしたpersonium-core-dev フォルダをvscodeで開きます。開けたら、左下の緑色のボタンをクリックし、「Reopen in container」をクリックします。

すると、vscodeの画面が更新され、左下には「Dev Container」と表示されていると思います。これはpersonium-core-devフォルダ内の.devcontainer/devcontainer.json が読み込まれ、指定されたコンテナにアタッチされている状態を示します。

あとは煮るなり焼くなり開発することができるというわけです。

ちょっと解説

なぜこういう構成に至ったのかなどは過去記事を書いているのでそちらを参照してください。

yoh1496.hatenablog.com

yoh1496.hatenablog.com

当時に比べたらvscodeの.devcontainerは格段に取り回しやすく(以下の記事参照)なっており、ところどころ書いてあることが違ったりしますが根幹はあまり変わっておりません。

いつの間にかVSCode の Remote - Containersが超絶パワーアップしていた件【docker client 不要】 - いしぐめも

devcontainerのメリット: 開発環境構成のコード化

と、ここまでコンテナを開発環境に使用するメリットと実際に使用してみた例を紹介しましたが、もっとも重要なdevcontainerの利点の紹介が抜けていました。正直、ここまでの説明ではdocker-composeでコンテナあげて、アタッチしてVimで開発するのと何が違うの?という話にもなってしまうかなと思います。

コンテナを用いた開発環境を整える1つの方法として「devcontainer」を紹介しましたが、devcontainerの本当の利点はVSCodeを使用した開発環境構成一式をコード化できる」 点にあると思っています。

今回devcontainerを使うことで、vscodeとdocker環境を整えることができさえすればボタン一つで開発環境構築を行えることを紹介しました。言ってみれば自動化なわけですが、vscodeとdockerが動く環境であればどこでも開発できるのです。

また、それだけでなく開発環境構築を自動的に行えるようにすることで、今までにあったような躓きやすいステップバイステップの開発環境構築ドキュメントとは別の手段を提供できるようになります。Personiumプロジェクトでも、開発環境の構築方法をドキュメントとして公開していますが、これを初学者に順に追ってもらうのはとても大変で、おそらくこの時点で挫折してしまう人も多いのではないかと思います。

開発用環境の構築手順 · Personium

その点、devcontainerを使用した開発環境構築では、ボタン一つで開発環境が整うのです。つまり、ちょっと興味を持っただけの1OSSを開発するためにJavaの開発環境構築やらなにやらといったノウハウを身につける必要がなくなります。プログラミングの最初のハードルは開発環境構築だと言われている(著者調べ)中で、ここが簡単になるのは非常に助かるんじゃないでしょうか。

GitHub Codespaces

は?何言ってんだvscode導入もdocker環境構築も難しいじゃねえか!!

というご意見もあるかなと思います。ごもっともです。

そもそも開発環境用のリソースを準備できないんだが??

ごもっとも(?)です。

そういうケースでも、devcontainerを使った開発環境構築をできるようにしておくことで使える、便利なサービスがあります。

GitHub Codespaces です。

Codespaces | GitHub

このサービスはGitHub上で使用できるSaaSサービスで、リポジトリソースコードから開発環境がVM上に構築され、ブラウザから操作できるというものです。

新規Codespace作成画面

起動中…

devcontainerから構築された開発環境が!

拡張機能も使える!

ついに「vscodeとdocker環境を揃える」という手間も必要なくなってしまいました。後はお金を払うだけ!!! (個人向けのベータ版は現在無料で使えるみたいです。)

現在はベータ版が提供されています

こんな便利なサービスが使えてしまうなんて、我々は未来に来てしまったんだな~と感じずにはいられませんね。

そしてついについに、このCodespacesも全ユーザーに開放され、月に無料で60H使えるようになるらしいですね。素晴らしい!

www.publickey1.jp

終わりに

だらだらと書いていたらまとまりのつかない記事になってしまいましたが、Codespacesが無料開放というビッグニュースが出てしまったので、今まで下書き状態だったのを公開します。

devcontainer便利なので是非使ってみてください。他にもこんな便利な使い方あるよ~だとかこうするといいよ~といった内容があればコメントなどいただけると嬉しいです。Personium参加してみたいとかそういうのも大歓迎です。

ではでは、よいdevcontainerライフを

Windows 8.1 でも VSCode で Remote - Containers したい!

Visual Studio Code の Remote - Containersですが、Remote - SSH と組み合わせることで、リモートのDockerホストを用意すればクライアント側にdockerが入っていなくてもdevcontainerを使用することができるようになりました。

詳細は以下に記事にしたのでそちらを参照してください。

yoh1496.hatenablog.com

これってつまり、Windows 8(8.1)などのWSL2が導入できないようなOSでもバリバリdocker使って開発ができるというわけです。

スゴイ。

注意点

というわけで、Windows 8や8.1でやるにあたって注意点を書きます。

SSHにパスを通す

Windows 10 と Windows 8 の大きな違い。

それは sshコマンドが標準で入っていない 点です。今やSSHDサーバーもWindowsのオプション機能から追加できるようになっていますので、便利なものです。その点Windows 8は不便ですねえ。

というわけで、OpenSSHを適当なフォルダに展開してパスを通しましょう。

www.eaton-daitron.jp

上記サイトではWindows10で手順が書かれていますが、Windows8でもだいたい同じです(環境変数のダイアログが不便なぐらい?)。

終わりに

画期的ですね(注意点がもう少しあるかと思ったけど1個で終わってしまった)。Windows 8でdockerを使おうとするとVirtualBoxにdockerマシン作ってそこに乗り込む形でやらざるを得ませんでしたが、これならVirtualBoxの導入なんて不要ですね。

VSCode と OpenSSH さえあればよかったんやー。

Java 8 から Java 17 への移行メモ

私が携わっているOSS「Personium」のJava 8からの移行に係るトラブルに対しての対処を書いていきます。Personiumでは Java 8 → Java 11 → Java 17 というステップで移行を進めていく方針になっていて、今回は「Java 11 → Java 17」を行った際のメモです。よって「Java 8 → Java 11」で取り切れなかった不具合を含んでいる可能性も多分にありますのでご了承ください。

目次

Unsupported class file major version 61

こんなエラーが出まくりました。

Caused by: java.lang.IllegalArgumentException: Unsupported class file major version 61
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:184)
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:166)
        at org.jacoco.agent.rt.internal_1f1cc91.asm.ClassReader.<init>(ClassReader.java:152)
        at org.jacoco.agent.rt.internal_1f1cc91.core.internal.instr.InstrSupport.classReaderFor(InstrSupport.java:247)
        at org.jacoco.agent.rt.internal_1f1cc91.core.instr.Instrumenter.instrument(Instrumenter.java:86)
        at org.jacoco.agent.rt.internal_1f1cc91.core.instr.Instrumenter.instrument(Instrumenter.java:118)

上記は jacoco のバージョンが低い時に出たエラーなんですが、surefireのバージョンが低いことでも同様のエラーが発生しました。

というわけで、こちらの問題に対しては pom.xmlJava 17 対応の jacoco, surefire のバージョンを記載してあげることで解決しました。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
</plugin>
<plugin>
    <groupId>org.jacoco</groupId>
    <artifactId>jacoco-maven-plugin</artifactId>
    <version>0.8.7</version>
</plugin>

(バージョンは一例です。)

java.lang.reflect.InaccessibleObjectException

こういうやつです。

java.lang.reflect.InaccessibleObjectException: Unable to make field protected volatile java.io.InputStream java.io.FilterInputStream.in accessible: module java.base does not "opens java.io" to unnamed module @hogehoge

どうも調べるとJava 17ではより高位なAPIがあるので、今までのローレベルなAPIは叩くなよという例外だそうです。

とはいっても、テストではゴリゴリそのAPIを使ってしまっているみたいで仕方ないのでテスト時オプションで例外的に許可してあげます。以下のように surefire の実行時引数に --add-opens で必要なパッケージを列挙します。

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <version>2.22.2</version>
    <configuration>
        <argLine>
            --add-opens=java.base/java.io=ALL-UNNAMED
            --add-opens=java.base/java.lang=ALL-UNNAMED
        </argLine>
    </configuration>
</plugin>

文法的なものはドキュメント(Java Platform, Standard Edition Oracle JDK 9移行ガイド, リリース9)を参照していただくとして、例外の書式が以下だったとすると、

module <MODULE> does not "opens <PACKAGE>" to <TARGET_MODULE>

以下のように書けば素直に消えてくれるはずです。(TARGET_MODULEがunnamed moduleだった場合)

--add-opens=<MODULE>/<PACKAGE>=ALL-UNNAMED

ただこの、ワイルドカードが使えないみたいで、java.util java.util.regex java.util.zip など複数パッケージに対して行う必要がある場合は以下のように愚直に書いていくしか無さそうでした(やり方あったら教えてください)。

--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.regex=ALL-UNNAMED
--add-opens=java.base/java.util.zip=ALL-UNNAMED

コレ、1件解決したら次の1件、といった感じで芋づる式に出てくるので根気よく対処しましょう。ゆくゆくは根本対処を何かしたいところ・・・

It is forbidden to use algorithm ... 問題

SHA-1使ってたコードが軒並み動かなくなった問題です。

It is forbidden to use algorithm http://www.w3.org/2000/09/xmldsig#rsa-sha1 when secure validation is enabled

こちらは別記事にしたのでそちらをどうぞ。

yoh1496.hatenablog.com

Jerseyのバージョンが古すぎる

今まで Jersey 1.10とかいう化石のようなものを使っていたので、以下のようなエラーが出てしまっていました。

java.lang.IllegalArgumentException
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:170)
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:153)
    at jersey.repackaged.org.objectweb.asm.ClassReader.<init>(ClassReader.java:424)

というわけで一気に2系の2.36まであげました。

1から2への移行ということで、Jersey Test Frameworkの仕様も変わっていましたが、Grizzly + Servlet を使ったテストを実装するにあたって以下のサイトがとても参考になりました。

vividcode.hatenablog.com

終わりに

以上が、PersoniumをJDK17に移行させるにあたって詰まった主な内容でした。どなたかの参考になれば幸いです。