デプロイスクリプトが必要な理由
Grailsアプリケーションは、WARで非常に簡単に構築できます。 これは次のように行われます。
grails war
WARが行われているという事実に加えて、このWARをサーバーにインストールしたいのです。 私たちの場合、これはTomcatです。 手動インストールには大騒ぎが必要です。
- サーバーを停止します。 プロセスが停止しない場合、プロセスを強制終了します。
- 古いアプリケーションファイルを削除する(念のため)
- 新しいWARをサーバーにコピーします。 名前を変更する必要がある場合があります(ROOT.warなど)
シェルスクリプトも使用できます。 しかし、素晴らしいクロスプラットフォームのGroovy言語があるのに、なぜ不快なシェル言語で書くのでしょうか?
Groovyでスクリプトを書く
Grailsを使用すると、アプリケーションのスクリプトフォルダーに追加するコマンドラインスクリプトをGroovyで簡単に作成できます。 これらのスクリプトはすべてgrails console コマンドで実行できます。 Deploy.groovyというスクリプトを作成します。
スクリプト内で、Grailsアプリケーションの構成データを使用できるのは素晴らしいことです。 たとえば、サーバー名とユーザー名は環境固有です。 Grails 1.3.7の場合、次のような構成にアクセスできます。
depends(compile) depends(createConfig) /* Gant, Config.groovy */ def host = ConfigurationHolder.config?.deploy.host def username = ConfigurationHolder.config?.deploy.username
grails-app / conf / Config.groovyの内部には次のようなものがあると想定されています。
... environments { production { ... deploy { host = 'www1.shards.intra' username = 'deployer' } } }
ちょっとしたトリックは、構成をダウンロードするには、最初にConfig.groovyファイルをコンパイルする必要があるということです。 これを行うために、 depends(compile)を宣言しました。compileは、プロジェクトをコンパイルするための既知のGantコマンド(タスク)です。
SSHを使用します
SSHアクセスを備えたサーバー上であらゆる種類の操作を行う必要があります。 簡単にするために、私は無料のJSchライブラリを使用し、パスワードアクセスオプションに制限しました。 したがって、スクリプトは次のように始まります。
@GrabResolver(name='jcraft', root='http://jsch.sourceforge.net/maven2') @Grab(group='com.jcraft', module='jsch', version='0.1.44') import com.jcraft.jsch.*
次に、JSchを使用して魔法の操作を行います。 次の2つが必要です。
- サーバーでUnixコマンドを実行する
- ファイルをサーバーにコピーする
Session.metaClass.exec = { String cmd -> Channel channel = this.openChannel("exec") channel.command = cmd channel.inputStream = null channel.errStream = System.err InputStream inp = channel.inputStream channel.connect() int exitStatus = -1 StringBuilder output = new StringBuilder() try { while (true) { output << inp if (channel.closed) { exitStatus = channel.exitStatus break } try { sleep(1000) } catch (Exception ee) { } } } finally { channel.disconnect() } if (exitStatus != 0) { println output throw new RuntimeException("Command [${cmd}] returned exit-status ${exitStatus}") } output.toString() }
簡潔にするため、実行が成功してもメソッドは何も出力せず、エラーが発生すると実行されたコマンドの出力ストリーム全体を出力するようにしました。
ここで、ファイルをサーバーに書き込むこともできます。
Session.metaClass.scp = { sourceFile, dst -> ChannelSftp channel = (ChannelSftp) openChannel("sftp") channel.connect() println "${sourceFile.path} => ${dst}" try { channel.put(new FileInputStream(sourceFile), dst, new SftpProgressMonitor() { private int max = 1 private int points = 0 private int current = 0 void init(int op, String src, String dest, long max) { this.max = max this.current = 0 } boolean count(long count) { current += count int newPoints = (current * 20 / max) as int if (newPoints > points) { print '.' } points = newPoints true } void end() { println '' } }) } finally { channel.disconnect() } }
実際、このメソッドの主な内容はすべて進行状況のインジケータです。
そして最後に、本格的なDSLを作成するには、構造をアタッチするクロージャーが必要です。 これは、たとえば次のように実行されます( doRemoteメソッドにフックされます)。
Session.metaClass.doRemote = { Closure closure -> connect() try { closure.delegate = delegate closure.call() } finally { disconnect() } }
doRemoteメソッドは「ブラケット」を形成し、その中でexecおよびscpメソッドを使用できます。
最後に、デプロイ手順自体を記述します
実際、スクリプトの本文は次のようになります。
// . grails help. target(main: " WAR- .") { .. JSch JSch jsch = new JSch() Properties config = new Properties() config.put("StrictHostKeyChecking", "no") config.put("HashKnownHosts", "yes") jsch.config = config // , host username . ... String password = new String(System.console() .readPassword("Enter password for ${username}@${host}: ")) Session session = jsch.getSession(username, host, 22) session.setPassword(password) session.doRemote { exec "- " ... scp warFile, '/opt/tomcat/latest/webapps/ROOT.war' ... } }
実際には、WARファイルの場所と、展開手順の前に収集するのが良いことをシステムに伝える方法を理解する必要があります。
これは、 dependsと呼ばれる既知のGantチームによって行われます 。
depends(clean) depends(war)
最初にプロジェクトをクリーンアップしてから、WARを収集します。 WARファイルへのアクセスに関して、不可能なことは何もありません。 grailsSettings変数はすべてのスクリプトで使用でき、そこから変数の場所を確認することもできます。
File warFile = grailsSettings.projectWarFile
GrailsのドキュメントでgrailsSettingsについて詳しく読んでください。
実際には、すべてが準備ができて、最後のタッチが残ります。 スクリプト(main)で宣言されたタスクは1つだけで、デフォルトで実行するように割り当てます。
setDefaultTarget(main)
さらに、Grails組み込みスクリプト( compile 、 warなど)を使用します。 それらをスクリプトにインポートするには( dependsコマンドで参照できるように)、スクリプトの先頭に次を追加します。
includeTargets << grailsScript("Clean") includeTargets << grailsScript("Init") includeTargets << grailsScript("War")
スペースを節約するために、最終的なスクリプト全体を公開しません。 Tomcatの完成したスクリプトはこちらからご覧ください 。
おわりに
SSH経由でサーバーにアクセスする機能を備えたデプロイスクリプトを記述するための小さなミニフレームワークをまとめました。 次のように実行できます。
grails deploy
ランニング
grails help deploy
スクリプトを使用するための指示さえ取得できます:)
シェルスクリプトと比較して、これには次の利点があります。
- 非常に強力なスクリプト言語。
- このスクリプトはGrailsプロジェクトに統合されており、構成にアクセスできます。 これにより、現在のGrails環境に応じて異なる方法でデプロイメントを行うことができます。以下に例を示します。
grails prod deploy
- Gant(およびそれに応じてAnt)のあらゆる手段にアクセスできます。
- スクリプトからプロジェクトコードやGORMモデルにアクセスできます(これはブートストラップと呼ばれ、このテキストではカバーされていません)。