情報科学屋さんを目指す人のメモ

方法・手順・解説を書き残すブログ。私と同じことを繰り返さずに済むように。

Eclispe:プログラムをリモートサーバに送信・実行する手順メモ

Eclipse (60) Java (38) Linux (23) Maven (19) SSH (3) Windows (248)

手元のWindows上のEclipseで編集したり実行したりしているプログラムを、SSHで接続できるリモートの高性能マシン上で実行させる方法について、悪戦苦闘(長い)含め紹介します。結局採用した方法は後半に書いてあります。ちなみに、そんな簡単にできる設定はEclipseに存在しなさそうです。それと、リモート「デバッグ」では無い点に注意。

目標

目標は、Eclipseのデバッグ実行ボタンを押してローカルで実行されるのと同じように、Eclipseのボタンを押したらリモートのサーバに実行ファイルやら何やらが転送されて、リモートで実行され、デバッグもできる、という環境です。

ポイントは、ただ実行するためだけにコミットをしたりはしたくない、気軽に実行できたほうがいい、という点です。サーバなんかも立てたくありません。SSHで接続できるサーバを適当に指定して、そこにファイルを送りつけて、そこで実行したいのです。Eclipseが普段生成してくれている、実行用の長いコマンドとオプションを入力したくなんてないです。

リモートデバッグとは違う

Eclipse+リモートと言えばリモートデバッグを想像しがちですが、あれは「リモートで既に実行されているプログラム」が存在して、それにアタッチすることでデバッグするだけであって、実行は勝手にやれ、SSHログインして実行しろ、というものです。リモートデバッグなら、たくさん資料があります。

できるようになったこと

最終的に現状できるようになったのは次の通り。

Eclipse上の実行ボタンを押すと、.jarファイルが作られ、実行用のシェルスクリプトが自動生成され、ライブラリが一つのフォルダにまとめられて、.jar・実行用スクリプト・ライブラリ一式がリモートサーバに転送されて、実行用スクリプトに実行権限をつけてリモートサーバ上で実行されて、出力はEclipseのConsole上に表示されつつ、ファイルに出力され、最後にその実行結果がローカルにダウンロードされる(ここまで1ボタン)。

ローカルがWindowsで、リモートとの接続にはSSH+公開鍵認証+22番ポートじゃない、あたりが環境的なポイントです。また、もともとPuTTYを使っているのと、mavenを使っている、というのもポイントです(がっつりmavenプラグインを使って事項用スクリプトを生成しました)。

却下:実行ファイルを同期する

まず最初にやってみたのが、WinSCPを使って実行ファイルを常にリモートと同期しておく、というものです。

gitのworkingディレクトリを、SSH接続できるリモートサーバと同期するために、WinSCPを使うことにしました。

WinSCPで、ローカル側で「X:\...\git」を開き、リモート側で「/home/user-name/git」を開いた状態で、「Keep remote directory up to date」を開きます。

「Start」をクリックすると、

Do you want to perform full synchronisation of the remote directory first?
Function 'Keep remote directory up to date' works correctly only, if the remote directory is synchronized with the local one before it starts.
[Yes][No][Cancel][Help]

と表示されたので、「Yes」をクリックし、初回に完全同期を行うようにしました。すると、「Copying」の表示がしばらく続くので待ちます。

ただ、よくよく考えてみると、まるごと同期するだけだと、実行が面倒です。ライブラリは.m2ディレクトリにあって、それも同期するの?という感じですし、実行用のコマンドも手動で用意するの面倒だなぁ、という感じです。ついでに言うと、WinSCPの同期の動作が大げさというか、これまた面倒でなんか微妙でした。ちなみに、リモートでmvnを実行する方法も考えたのですが、リモート用のpom.xmlも作るってのはなんだかなぁ、となって、却下しました。

却下:mavenのdeploy機能を使う

pom.xmlに次を追加します。

<distributionManagement>
    <repository>
        <id>remote01</id>
        <url>scpexe://example.net:port/repository</url>
    </repository>
</distributionManagement>
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-deploy-plugin</artifactId>
    <version>2.7</version>
</plugin>

「X:\home\.m2\settings.xml」に、次を追加します。

<servers>
    <server>
      <id>remote01</id>
      <username>username</username>
      <configuration>
        <sshExecutable>plink</sshExecutable>
        <scpExecutable>pscp</scpExecutable>
	<knownHostsProvider implementation="org.apache.maven.wagon.providers.ssh.knownhost.NullKnownHostProvider">
          <hostKeyChecking>no</hostKeyChecking>
        </knownHostsProvider>
      </configuration>
    </server>
  </servers>

こんな方法で、リモートにdeployできるのですが、その前後でとりあえずたくさんのエラーに遭遇しました。

The build could not read 1 project

maven実行時に「[ERROR] The build could not read 1 project」が表示されたのは、pom.xmlにsettingsを書き込んでしまっていたのを修正したら直りました。

The packaging for this project did not assign a file to the build artifact

Eclipseの実行設定経由でmvnを実行していたのですが、「deploy:deploy」と設定していたところを「package deploy:deploy」にして解決。

パッケージorg.testngは存在しません

テストじゃないところでAssertを使っていたのでエラーが出てしまっていたので修正。本題と関係ないです。

No connector available to access repository

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-deploy-plugin:2.7:deploy (default-cli) on project project-name: Failed to deploy artifacts/metadata: No connector available to access repository remote01 (scpexe://example.net:port/repository) of type default using the available factories AsyncRepositoryConnectorFactory, WagonRepositoryConnectorFactory -> [Help 1]

settings.xmlでsettingsタグが重複していたので削除。wagon-ssh-externalが不要かと思っていたら必要っぽいので追加。これで解決。

Exit code: 1 - 'scp'

公式のサンプルに「1.0-beta-6」と書いてあったのに、実は「2.5」まで出ていたので、そちらを選択(Maven Repository: org.apache.maven.wagon » wagon-ssh-external)。

このエラーに続くエラーメッセージの文字化けをeclipseの設定では解決できず、次の方法に一度移ってしまいました。最終的にこのエラーが出ていた原因は、settings.xmlの「scpExecutable」を書くべきところを「sshExecutable」と書いてしまっていたからでした。

実は情報が古いままらしい

そんなこんなで調べていると、公式サイトの情報が古いよ、という書き込みを見つけました→java - Is there any way of having maven scp wagon work consistently on linux/mac/windows platform? - Stack Overflow。本当にmavenの公式サイトなどの古さというか、404の放置とかそういうところの頼りなさが気になります。

というわけで、JSchの公式サイトのeclipseについて書いてある部分を参考に。CVS-SSH2 Plug-in for Eclipse

ただ、ここの通りにも行かず、SSHの設定は「General>Network Connections>SSH2」にありました。ここで、SSH2 homeとPrivate keysを変更しました。

さらに、最初はFTPを使う人は書け、とか書いてあったextensionにwagon-sshも追加しました。

<extensions>
  		<extension>
  			<groupId>org.apache.maven.wagon</groupId>
  			<artifactId>wagon-ssh-external</artifactId>
  			<version>2.4</version>
  		</extension>
  		<extension>
  			<groupId>org.apache.maven.wagon</groupId>
  			<artifactId>wagon-ssh</artifactId>
  			<version>2.3</version>
  		</extension>
  	</extensions>

ここまで来て、ようやくまともなエラーに。エラーメッセージにもJschの文字が現れるようになりました。

「The host was not known and was not accepted by the configuration」というエラーメッセージがあったので、調べて見た結果、settings.xmlに次を追加しました。

	<knownHostsProvider implementation="org.apache.maven.wagon.providers.ssh.knownhost.NullKnownHostProvider">
          <hostKeyChecking>no</hostKeyChecking>
        </knownHostsProvider>

しかし、これでは結果が変わらず。

とりあえず、known_hostsに追加しようかと思ったのですが、EclipseにSSH接続できるプラグインをインストールして、そいつにknown_hostsにホストを追加してもらおうという作戦にしました。そこで、「Remote System Explorer」をインストールしました。

「File>New>Ohter...」から「Remote System Explorer>Connection」を選択し、「SSH Only」を選択。ただ、この時点ではポート番号を指定できないので、「Remote System View」を開き、Ssh Shellsのプロパティを開いて、「Subsystem」にある「Port」からポート番号を指定します。

右クリックから「Launch Shell」をすると、

Warning
The authenticity of host ... can't be estabilished. RSA key fingerprint is .... Are you sure you want to continue connecting?

と表示されるので、「Yes」をクリックします。

Pagentがちゃんと機能していると、突然「次の鍵への紹介を許可しますか」が表示されるので、もう確認しないように設定すればOKです。

mavenのdeploryだけでは実行が面倒

ただ、mavenのdeployでは、「artifact」を転送するようなのですが、よくよく見てみれば分かるとおり、それはつまり「.jar」だけです。つまり、それだけあっても実行用のコマンドは用意しないといけないし、pom.xmlも一緒に作られるものの、ライブラリも一緒に送ってくれれば良いのに、という感じで、時間がかかった割にかなり微妙なことになりました。deploy対象にいろいろ自由に追加する手順も見つけられませんでした。

採用:assembler+exec

結局採用した方法はここからです。

assembler-maven-pluginで実行用スクリプトを生成する。

まず重要なのは、「実行用のコマンド(シェルスクリプト)」を用意する、という点です。これについては、mavenの「assembler-maven-plugin」が優秀でした。

pom.xmlに次のように書きます。

  		<plugin>
  			<groupId>org.codehaus.mojo</groupId>
  			<artifactId>appassembler-maven-plugin</artifactId>
  			<version>1.6</version>
  			<configuration>
 				<extraJvmArguments>-Xms30000m -Xmx30000m</extraJvmArguments>
 				<platforms>
 					<platform>windows</platform>
 					<platform>unix</platform>
 				</platforms>
	  			<repositoryLayout>flat</repositoryLayout>
	  			<repositoryName>lib</repositoryName>
	  			<programs>
	  				<program>
	  					<mainClass>net.example.packagename.MainClassName</mainClass>
	  					<id>MainClassNameScript</id>
	  				</program>
	  			</programs>
  			</configuration>
  			<executions>
  				<execution>
	  				<phase>package</phase>
	  				<goals>
	  					<goal>assemble</goal>
	  				</goals>
  			  	</execution>
  			</executions>
  		</plugin>

ごちゃごちゃ書き直して行き着いたので、無駄なところもありそうですが、とりあえずこうしておくと、target(${project.build.directory})フォルダ以下のassembler/libにライブラリの.jarがコピーされ、assembler/bin以下に、実行用のスクリプトが生成されます(programの部分で指定したメインクラスを呼び出す)。また、extraJvmArgumentsのところで生成されたスクリプトの中のjavaコマンドにJVM用の引数を渡せるので、ここでリモートデバッグ用のオプションを設定すれば、リモートマシンで実行させてアタッチ、ということも簡単にできそうです(まだやってない)。

とりあえず、これでtarget/assemblerフォルダと、target/project-name-version-num.jarファイルさえ転送すれば、簡単に実行できる準備が整いました。

antrunのexecで力ずくだ

さて、そのファイルをどうやってSSHごしに転送するか、copy-maven-pluginなんかも検討したのですが、公開鍵認証がダメそうでした。

というか、いつもEclipse+SSHがらみだと、ポート番号が22から動かせなかったり、公開鍵認証できなかったり、だいぶましでもpassphraseをpom.xmlに直接書き込まないといけなかったりと、いまいちなことばっかりです。

というわけで今回は、次のような方式を採用しました。

まず、maven-antrun-pluginを使って、antの記法を使えるようにします。そして、antのexecを利用して、コマンドライン実行を行うようにします(コマンド実行にはexec-maven-pluginもあるが、antrunを選択)。そして、そのコマンドラインないでPuTTYに付属するコマンドラインツールplink(sshの替わり)とpscp(scpの替わり)を使ってファイルを転送したり、パーミッションを与えて実行したりすることにしました。

他に比べたらはるかに力ずくですが、もうそういう方法が一番っぽかったのでこうなりました。これを、assembler-maven-pluginより下に書いておきます。

<plugin>
	<artifactId>maven-antrun-plugin</artifactId>
	<version>1.7</version>
	<executions>
		<execution>
			<phase>package</phase>
			<configuration>
				<tasks>
					<exec command="X:\...\pscp.exe -r -P portnum ${project.build.directory}/appassembler username@example.net:."/>
					<exec command="X:\...\pscp.exe -P portnum ${project.build.directory}/${project.build.finalName}.jar username@example.net:."/>
					<exec command="X:\...\plink.exe -P portnum username@example.net chmod +x appassembler/bin/*"/>
					<exec command="X:\...\plink.exe -P portnum username@example.net appassembler/bin/MainClassNameScript | tee MainClassNameScript-result.txt"/>
					<exec command="X:\...\pscp.exe -P portnum username@example.net:MainClassNameScript-result.txt ."/>
				</tasks>
			</configuration>
			<goals>
				<goal>run</goal>
			</goals>
		</execution>
	</executions>
</plugin>

これで、mvn packageを実行すれば、コンパイルされて、テストされて、assembler-maven-pluginがライブラリをまとめて、実行用のスクリプトを生成してくれて、maven-antrun-pluginがそれらをリモートサーバに実行ファイルを転送してくれて、実行ファイルに実行権限を付与して、実行して、実行中の出力をEclipseのConsoleに表示しながら実行結果をファイルに出力して、そのファイルをローカルにダウンロードしてくる、までを自動実行してくれます。

今後

デバッガのアタッチ待ちで起動するとかそういう設定をassembler-maven-pluginでやって、リモートデバッグまで一気にできるように設定したいのと、毎回テストが動くのが厄介なので、そのあたりmvnについて調べてどうにかしようかな、と思ってます。

参考

コメント(0)

新しいコメントを投稿