Apache SparkをLivy経由でZeppelinから使ってみる

SparkにREST API経由でアクセスできるようにするApache Livyは以前から知っていたのですが、アドホックなジョブを投げるにはやっぱり画面がないと辛いなぁと思っていたところ、ZeppelinにもLivy用のインタプリタがあるっぽいのでこれを使えばZeppelinのノートブックからLivy経由で簡単にSparkにジョブを投げつけることができるのではと思い試してみました。

github.com

github.com

Livy側の設定

LivyはデフォルトではSparkをローカルモードで実行します。Sparkクラスタに接続するには conf/livy.conf にSparkのmasterのアドレスを設定しておきます。Livyによってセッションが動的に生成されるためYARNモードでの実行が推奨されていますが、スタンドアロンクラスタでも動作はします。

# What spark master Livy sessions should use.
livy.spark.master = spark://spark-master:7077

SPARK_HOMEHADOOP_CONF_DIRを指定して起動。

$ export SPARK_HOME=/usr/lib/spark
$ export HADOOP_CONF_DIR=/etc/hadoop/conf
$ ./bin/livy-server start

LivyのURLはデフォルトでは http://localhost:8998 になります。ブラウザでアクセスするとWeb UIが表示されます。

Zeppelin側の設定

特に何も考えずに起動すればOKです。ZeppelinはSparkも内蔵しているので別途インストールする必要がないのは楽です。

$ ./bin/zeppelin-daemon.sh start

ブラウザで http://localhost:8080 を開き、Livyインタプリタの設定でLivyサーバのアドレスを指定しておきます。Livyをローカルで立ち上げている場合はデフォルトの設定のままで大丈夫なはずです。リソースのアロケーションなど必要な設定も行っておきます。

f:id:takezoe:20210222124622p:plain

使ってみる

Zeppelin上でlivyインタプリタを指定してノートブックを作成します。

f:id:takezoe:20210222125102p:plain

適当なScalaコードを実行。

f:id:takezoe:20210222125118p:plain

ZeppelinからアクセスするとLivy側ではこんな感じでセッションが自動的に作られます。同一のZeppelin上で複数のノートブックを作成した場合、セッションは共有されるようでした。

f:id:takezoe:20210222125132p:plain

Sparkクラスタに直接アクセスできない環境でも、ありものを組み合わせるだけでUIからHTTPプロキシ経由でジョブを実行できるようにできるのは便利なのですが、ZeppelinはせっかくScalaで書いていても入力補完が微妙だったりするのが残念な感じなので、IntelliJから直接Livyに投げつけられたりするプラグインがあったりすると便利かもしれないと思いました。ただ、Livyは最近あまり開発が活発ではない感じなのが若干気になるところです。Spark 3対応もmasterブランチにマージはされているもののまだリリースされていないという状態のようです。

Scala Love in the CityでGitBucketの発表をさせていただきました

主催者のOliさんからのDMで急遽 Scala Love in the City で話させていただくことになり、あまり時間もなかったので準備なしでも話せるGitBucketの紹介をさせていただきました。スライドは以下になります。

www.slideshare.net

他のトークScalaでの関数型プログラミングやScala3の話題が多かったと思いますが、具体的なアプリケーションレイヤから見たScalaという観点での話ができればと思い、前半はGitBucketの紹介、後半は8年間開発を続けてきて難しかったこととしてScala 2.12へのアップグレードについて話させていただきました。

なぜScala 2.12へのアップグレードが辛かったのかというと、

結局こういうことで、Scalatraはレガシーフレームワークとして開発者とユーザーが離れ開発が停滞したことでScala 2.12への対応が遅れ、Slickは逆にDBIOによる破壊的変更が導入されたことで移行コストが非常に大きかった、ということが一度に起きたのがScala 2.12のタイミングだったということです。GitBucketは本体だけでなくプラグインもあるのでフレームワークを変更するコストが非常に大きく(Slickに関しては当時変更も検討しましたが)、趣味プロジェクトで開発リソースが限られていたこともあり、どうにかして最小限のコストでこの問題を解決する必要があったのでした。

しかしなんというか、半分Scalaディスみたいな感じになってしまった感もあるのですが、個人的にはScalaはむしろソースコードレベルではよく後方互換性に配慮されており、型安全や関数型プログラミングのメリットを活かしつつスクリプト言語のような記述性で既存のJava資産を活かすことができる、アプリケーションを書くのに適した言語だと思っているので、もう少しそういったポジティブな面も強調すればよかったかなと反省しています。

ともあれ、これまで海外のカンファレンスで何度かショートセッションをさせていただく機会はあったのですが、小田好先生やScala界隈の錚々たるスピーカーの揃ったメジャーな国際Scalaカンファレンスで通常のセッション枠で話させていただくというのは大変貴重な経験になりました。次の機会があれば、今度は昔話ではなく未来の話ができるようネタを仕込んでおきたいと思います。ちなみに発表後のQAセッションでは「会社を作ることを考えたことはないのか?」という質問があったのですが、どうでしょうねw

jgitverでMavenのバージョン番号をGitの情報から自動設定する

sbtではsbt-dynverプラグインを使ってGitのタグ、コミット情報から自動でバージョン番号を付与できるのですが、同じようなことをMavenでやるにはどうするのがいいのかなぁと以前から疑問に思っていたので、どのような方法があるのか少し調べてみました。結論としては以下のjgitverを使うのがよさそうかなという感じです。

github.com

Mavenのextensionとして導入すると、デフォルトでは以下のようなルールでバージョンが使用されます(pom.xmlに記述されているバージョン番号は無視されます)。

  • タグ(x.x.x) -> x.x.x
  • masterブランチ -> x.x.y-SNAPSHOT
  • その他のブランチ -> x.x.y-<ブランチ名>-SNAPSHOT

CIでMavenリポジトリにデプロイするようにする場合でも、タグ以外からデプロイする場合は自動的にSNAPSHOTになりますし、ブランチの場合は自動的にブランチ名がバージョン名に含まれるので別ブランチのSNAPSHOTで上書きしてしまうということもなくなります。

導入方法は簡単で、プロジェクトのルートディレクトリに.mvnディレクトリを作成し、そのディレクトリ内にextensions.xmlを以下の内容で作成します。

<extensions xmlns="http://maven.apache.org/EXTENSIONS/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd">
  <extension>
    <groupId>fr.brouillard.oss</groupId>
    <artifactId>jgitver-maven-plugin</artifactId>
    <version>1.5.1</version>
  </extension>
</extensions>

これだけでpackage、deployなどのゴールを実行する際にデフォルトのルールで自動的にバージョンが付与されます。mvn validateを実行するとどのようなバージョン番号になるかを確認できます。

$ mvn validate
...
[INFO] jgitver-maven-plugin is about to change project(s) version(s)
[INFO]     io.github.gitbucket::markedj::1.0.16 -> 1.0.16-SNAPSHOT

.mvn/jgitver.config.xmlで細かい挙動の設定が可能です。たとえば以下のようにすると未コミットの変更がある場合にバージョン番号にdirtyというフラグメントが含まれるようになります。

<configuration xmlns="http://jgitver.github.io/maven/configuration/1.1.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://jgitver.github.io/maven/configuration/1.1.0 https://jgitver.github.io/maven/configuration/jgitver-configuration-v1_1_0.xsd">
    <useDirty>true</useDirty>
</configuration>

また、以下の設定を追加するとmasterブランチの代わりに使用するメインブランチのブランチ名を指定することができます。

<nonQualifierBranches>main</nonQualifierBranches>

設定方法の詳細についてはjgitverのドキュメントを参照してください。

バージョン番号にコミットIDを含めるオプションもあるのですが、このオプションが有効な場合でもSNAPSHOTバージョンの場合はコミットIDを含まないという仕様のようです。このあたりもうちょっと柔軟性があるといいなと思いますが、通常のユースケースでは概ねjgitverの標準の挙動で問題ないのではないかと思います。

ところでこのjgitverの作者のMatthieuはGitBucketの初期の頃によく手伝ってくれていたコミッタだったりします。当時は仕事でGitBucketを使っていたようで、Scalaは書けないとのことでしたがw ユーザサポートを手伝ってくれたり、プラグインの紹介サイトを作ってくれたりしていました。数年ぶりに今度は自分が彼のOSSのお世話になっているというのはなかなか面白い巡り合わせだなと思います。OSSのエコシステムに改めて感謝ですね。

sbt 1.4.xで作ったjar/war内のファイルのタイムスタンプがおかしくなる問題を修正した

最近GitBucketでTomcatにデプロイするとエラーになるというレポートがあり、調べていたのですが、

github.com

どうやらwar内のファイルのタイムスタンプが負の値になっており、warの展開時にその値をファイルの更新日時として設定しようとしてエラーになっているようで、sbt 1.4.0での以下の修正が原因であるらしいことがわかりました。

github.com

以下のような感じで環境変数SOURCE_DATE_EPOCHで明示的にタイムスタンプを設定するようにすれば回避できます。

envVars := Map(
  "SOURCE_DATE_EPOCH" -> System.currentTimeMillis().toString
)

GitBucketではsbt-ioのAPIを直接使用して、warファイルをいったん展開して内容を変更してからwarファイルを作り直すということを行っているのですが、その際に引数でタイムスタンプを指定することもできます。

IO jar (
  sources = contentMappings.map { case (file, path) => (file, path.toString) }, 
  outputJar = outputFile, 
  manifest = manifestFile, 
  time = Some(System.currentTimeMillis())
)

とりあえずはこれで問題を回避できたのですが、なんでこんな動きをするのか不思議だったのでもう少し詳しく調べてみました。

sbt 1.4.0以降では前述の修正により、packageタスクでjarファイルやwarファイル*1を作成する際にデフォルトでが各エントリのタイムスタンプに0を設定するようになっているのですが、sbt-io側の以下のタイムゾーン調整*2により、タイムゾーンによってはセットする値が負の値になってしまっているようです。

さらにJDKZipEntryのコードを見ると、どうやらzipファイルの各エントリはxdostimeという値に加えてextraフィールドにmtimeというミリ秒単位のタイムスタンプを持っており、xdostimeの方には想定通りの値(= 0)がセットされるものの、mtimeの方には負の値がセットされてしまうようです。

github.com

というわけで最終的にsbtに以下のプルリクエストを送ってマージしていただきました。

github.com

修正は1行だけですし、実際にこれが原因で困るというケースはほとんどないような気もするのですが、原因を調べるのがなかなか大変だったので記事を書いてみました。*3

*1:GitBucketではxsbt-war-pluginを使っています。

*2:JDKのZipEntry.getTime()がタイムゾーンによって返すタイムスタンプを調整しているため、予めタイムゾーン調整済みのタイムスタンプを設定する必要がある。

*3:あとからさらに調べてわかったのですがunzipコマンドではちゃんとタイムスタンプのレンジチェックが入っているようなのでsbt-ioでの展開時の処理でも補正するべきなのかも…。

CSVをMarkdownのテーブルに変換するVSCode拡張を作ってみた

昔Atom用に作ったものを移植しただけですが、こんな感じでCSVテキストを範囲選択してコマンドパレットから「Convert CSV to Markdown table」を選択すると適当にフォーマットしてMarkdownテーブル記法に変換します。

https://raw.githubusercontent.com/takezoe/vscode-csv-markdown/master/csv_to_markdown.gif

GitHubリポジトリはこちら。

github.com

VSCodeのMarketplaceからもインストールできるようにしてあります。

marketplace.visualstudio.com

VSCode拡張の作成方法については公式のドキュメントにまとまっています。Yeomanでプロジェクトの雛形を生成し、vsceコマンドでパッケージの作成や公開を行うという感じです。

code.visualstudio.com

パッケージの公開のところが少し面倒で、マイクロソフトアカウントでAzure DevOpsにサインインしてアクセストークンを作成する必要があります。また、ドキュメントではvsce create-publisherコマンドでpublisherを作成するとなっているのですが、このサブコマンドはすでに非推奨になっているようで、Marketplaceの管理画面から作成する必要がありました。ここまで準備しておけばvsce publishでMarketplaceに公開できます。

また、vsce packageでvsixパッケージを作成し、拡張マネージャのプルダウンメニューから「Install from VSIX」を選択することでこのパッケージをオフラインでインストールすることもできます。

f:id:takezoe:20210109212401p:plain

Atomでは標準の拡張開発言語がCoffeeScriptでしたが、VSCodeではTypeScriptが使われているので開発もやりやすいのではないかと思いますし、ちょっとした拡張がサクッと作れるようになるとなにかと便利なので色々と研究していきたいと思います。

My Life in Red and White

昨年ベンゲルの自伝が出版されたのでこれは読まなくてはとKindle版を購入していたのですが、年末年始の休暇中にようやく読むことができました。

内容については幼少期〜選手時代からカンヌ、ナンシー、モナコでの監督時代、名古屋、アーセナル初期〜Invincibles時代、エミレーツスタジアム建設期〜辞任、そして現在のFIFAでの仕事とベンゲルの歩みが自身の言葉で綴られています。

読んでまず思ったのは、晩年のイメージと違ってかなりスパルタンな経歴と思想の持ち主なんだなということです。とにかくハードワークと精神論という感じですw 若手を中心に一度信じたプレーヤーを辛抱強く使うのは積み重ねられた成功体験によるものであることがわかる一方、アーセナルでの監督時代はそれが足枷になっていた面があることも否めないのですが、まあそういう部分も含めてベンゲルらしいなという感じがします。

当時は知りませんでしたが、名古屋での18ヶ月はフランスリーグでのスキャンダルから逃れてサッカーに集中できる充電期間にもなっていたようですし、異文化への適応という面でその後アーセナルでの仕事にも役立ったようです。選手が練習熱心すぎて練習しすぎないようボールを隠さなければならなかったとか、料亭でのプレスカンファレンスで足が痺れて死にそうだったなどの面白エピソードが多いのも名古屋時代の特徴ですねw 短い期間ながらここで日本との繋がりができたことは自分を含め、現在日本に多数のアーセナルファンが存在するきっかけにもなっていると思うので、当時のフランスリーグでのスキャンダルについてはむしろ感謝すべきなのかもしれません。

全編通して物議をかもしそうな負の側面についてはあまり触れられていないのもベンゲルらしいところで、アーセナルの監督を辞任したのは自分の意志ではなかったという点くらいでしょうか。エミレーツ建設時代の心労や、プレミアリーグの発展とともに肥大化するクラブ組織の中での苦労は痛いほど伝わってきましたし、それだけに辞任も無念であったろうと思います。ただ、個人的にはベンゲル自身もエミレーツの建設のための負債の返却が終わって、ある意味緊張の糸が切れてしまったことがその後の成績にも影響したのかもしれないと感じました。

巻末にはベンゲル監督の記録や写真なども掲載されており、アーセナルファンであればもはや記念品として持っておくべき一冊と思います。自分はKindleで読んだのですが(スマートフォンの翻訳機能が便利w)、あまりにも名著すぎたのでハードカバーもオーダーしてしまいました。日本にはアーセナルファンが多いので日本語訳も出るといいなーと思いますが、割とストレートな英語で書かれていますし、分量もさほどでもないので英語での読書の題材としてもちょうどよかったです。

なんにせよ、ベンゲル自身はまだサッカーへの情熱を失っていないようですし、今後はFIFAというより影響力の強い立場からサッカーの発展に尽力してくれるはずです。また、ベンゲル退任後に暗黒時代に突入してしまったアーセナルもまだまだ予断を許さないもののアルテタの下で一時期の絶不調を脱しつつあります。ベンゲル先生の今後の活躍を願いつつアーセナルを応援し続ける所存です。ハードカバーが届いたらもう一回読み直してみようw

(追記)ハードカバー版について

ハードカバー版が届いたのですが、表紙が厚いだけで紙質や印刷自体はペーパーバッグクオリティでした。巻末のスタッツも紙媒体ではきちんとレイアウトされているのかなと思っていたのですがそんなことはなく、ページまたぎまくりでした。これならペーパーバッグの方が読みやすくていいかも…と思いました。

f:id:takezoe:20210403232226j:plain

実践Terraform AWSにおけるシステム設計とベストプラクティス

仕事でTerraformを使っているのですが、Terraform弱者すぎて毎回「なんもわからん…」となっているので学習のために日本語で読める本として購入してみました。

Terraformの基本と、様々なAWSリソースのTerraformでの作成例がハウツー形式で紹介されているという感じです。一通り読んでみての感想はTerraformというよりAWSの知識が重要という点を再確認したという感じでした。とはいえ雰囲気でTerraformを使っていたので基本的な部分や実践的なノウハウをサクッと押さえられたのはよかったです。

チーム開発についての章がありCIとの連携例が紹介されていますが、Terraform EnterpriseやTerraform Cloudについても言及があってもよかったのではないかと思いました(tfstateの保存先としてTerraform Cloudが紹介されているのみ)。

インプレスさんのこのシリーズはキャッチーなトピックがタイムリーかつ読みやすい分量で書籍化されていますね。元々は技術書典で販売されていた書籍を商業出版するというコンセプトでスタートしたシリーズだそうですが、従来の出版形態では難しそうな書籍も出ていますし、技術分野が多様化し、移り変わりも早い現在の状況にあった出版形態だと思うので密かに応援しています。達人出版会さんでDRMフリーな電子書籍が購入できるのも良いですね。

tatsu-zine.com