WerckerでオリジナルのStepを導入する

このエントリーはOracle Cloudアドベントカレンダーの14日目です。

WerckerはOracleが提供するCI/CDサービスです。元々はオランダ発のスタートアップだったWercker社が提供していたサービスでしたが、2017年にOracleが買収し、その後継続してサービスが提供されています。

Werckerは全てのCI/CDパイプラインをコンテナで実行する仕組みになっており、ユーザーが任意のコンテナをパイプラインに組み込むことが可能です。 これにより、パイプラインで実行する処理内容を自由にカスタマイズできるようになっています。

このエントリーでは、簡単なサンプルを題材に、Werckerでオリジナルの処理をパイプラインに組み込む方法を書きたいと思います。

題材

Docker公式のWhalesayというコンテナを使って、クジラにセリフを喋らせる(標準出力に表示する)処理をパイプラインで実行してみます。

手順

では手順を書いていきます。

Stepを作る

Werckerのパイプラインで実行される処理は、Stepという最小単位で構成されています。Stepはおおよそシェルのコマンドを1回実行するのに相当する処理に当たります。

ここでは、Whalesayコンテナ上で実行する処理を定義するStepを作成します。

まず、Stepの作成に必要なファイルをホストするリポジトリGitHub上に用意しておきます。たとえば、wercker-step-whalesayなどの名前でリポジトリを作成します。

必要なファイルは以下の3つです。これらを作成して上記リポジトリにPushしていきます。

ファイル名 役割
run.sh Stepで実行する処理のエントリーポイントとなるスクリプト
step.yml Stepの仕様(名前や指定可能な変数など)を定義するymlファイル
wercker.yml Stepをビルドして、Werckerサービスに登録ときに利用される、ビルド定義ファイル

それぞれ、以下のような内容です。

run.sh
Step処理が実行されるときこのスクリプトがキックされます。中身はクジラに喋らせるコマンドを実行しているだけです。
$WERCKER_WHALESAY_MESSAGEは、Stepを使うときに指定した変数を環境変数から拾ってきています。

#!/bin/sh
cowsay $WERCKER_WHALESAY_MESSAGE

step.yml
ステップの仕様を定義します。名前はwhalesay、massageという変数を文字列で指定する、などといった内容が書いてあります。
指定した変数の値は、WERCKER<Step名><変数名>という形式の環境変数から取得できます。run.shでは、そうやってメッセージを拾っています。

name: whalesay 
version: 1.0.0
summary: whalesay
properties:
- name: message
  type: string
  required: true

wercker.yml
Stepの作成自体もWerckerのパイプラインで行うのですが、これはそのパイプラインの定義です。run.shのチェックとWerckerサービスへの登録を行います。
このパイプラインが正常に完了すれば、以降任意のパイプラインで利用可能なStepとして登録されます。

box: docker/whalesay

build:
  steps:
  - shellcheck:
      files: run.sh

publish:
  steps:
  - internal/publish-step:
      owner: hhiroshell
      # private: true

以上のファイルをリポジトリにPushしたら、Werckerでこのリポジトリに紐付いたApplicationsを作成してください。Applicationの作成は右上の"Add Application"から。基本デフォルトの設定でウィザードを流すだけなので、迷わないと思います。

f:id:charlier_shoe:20181217011249p:plain

次に、Workfrowタブでワークフローを作成します。Workflowタブを選択すると、デフォルトのBuildパイプラインがあるだけのフローが表示されています。

f:id:charlier_shoe:20181217011337p:plain

この画面の下の方にある"Add new pipeline"ボタンをクリックし、以下の設定でPipelineを作成します。

  • Name: publish
  • YML Pipeline name: publish
  • Hook type: Default

次にbuildパイプラインの右の[+]マークをクリックし、publishpパイプラインを追加します。"Execute Pipeline"でpublishを選択し、他の項目は全てデフォルトでOKです。

f:id:charlier_shoe:20181217011446p:plain

Runsタブを選択し画面下方のリンクからWorkflowを起動します。正常に終了したら、画面右上のアバターをクリックし、更にYour profileを選択します。Stepsの中にwhalesayが表示されていればStepの作成、登録は完了です。

f:id:charlier_shoe:20181217011509p:plain

whalesayステップの内容を開くと、以下のような説明が表示されます。

f:id:charlier_shoe:20181217011533p:plain

Stepを使う

それでは、作成したwhalesayステップを実際に使ってみます。新たなGitHubリポジトリを作成し、以下のファイルをPushしてください。

wercker.yml
デフォルトのpipelineであるbuildの中で、shalesayステップを呼び出しています。
変数messageの値をクジラが喋ってくれるはず。

build:
  box: docker/whalesay
  steps:
  - hhiroshell/whalesay@1.0.0:
      message: "hello whalesay !!"

先ほどと同様に、このリポジトリに紐付いたApplicationを作成します。今回はbuildパイプラインしかありませんので、ワークフローの編集は不要です。

最後にワークフローを実行してみます。

正常に終了したら、実行結果のbuildパイプラインに当たる部分をクリックし、その詳細情報にアクセスします。

f:id:charlier_shoe:20181217011550p:plain

whalesayというStepの部分を展開すると、そのStepの処理での標準出力の内容が表示されます。クジラが喋っていれば成功です。

f:id:charlier_shoe:20181217011604p:plain

まとめ

以上で、whalesayというコンテナをパイプラインに組み込んで、クジラに喋らせるコマンドを実行するStepを走らせることができました。

これと同じ要領で、任意のコンテナをパイプライン内で使うことが可能です。もちろん自作のコンテナを組み込んでStepとして実行することができますので、やろうと思えばほとんどどんなことでもできることになります。

以下は、TerraformをStepで実行してAWS環境の操作を行っている例です。ご参考までに、引用させていただきます。

TerraformとWerckerとAWS Organizationsで始めるステージング・開発環境構築

本エントリーは以上です。

Kustomizeを数週間触ってみての所感

このエントリーは、Kubernetes2 Advent Calendar 2018の11日目の記事です。

Kustomizeは、HelmやKsonnetのようなmanifestのテンプレーティングツールの1つです。 「Kustomizeとは」とか基本的な使い方の話は、@ryodocxさんのQittaの記事に非常によくまとまっていますのでそちらを参照ください。このエントリーでは、とある経緯でKustomizeを数週間ほど触ってみたので、そこで感じた諸々を書いていきたいと思います。

良かったところ

良かったところを書いていきます。

とっつきやすい

基本のmanifestを作っておいて、変更したい部分はそこだけを抜粋したmanifest(Overlay)を書いて被せるという仕組みです。

f:id:charlier_shoe:20181212011252p:plain
Kustomizeの基本的な考え方

このやり方のおかげで、以下のようなメリットがあると思います。

  • 仕組みが直感的に理解しやい(少なくとも自分には)
  • 基本的なことをやる分にはカスタマイズのために新たな文法を覚える必要がなく、入門時の学習コストがとても低い

Tillerいらない

Kustomizeで生成したmanifestはそのままKubernetesに適用可能なものができ上がります。このため、HelmのTillerのようにクラスター側に何かを用意する必要がありません。

管理対象が少ないのは良いことです。

ConfigMap/Secret Generatorは便利

ConfigMap/Secret Generatorは、適当なランダム文字列つきのConfigMap/Secretを生成して、他のリソースからの参照も自動で書き換えてくれる機能です。

変更頻度の高い設定をConfigMap/Secretで渡すようにすると、同じ名前で設定値が違うリソースが量産されて管理が困難になります。この機能を使うことで「設定が異なる場合はリソース名を変える」というポリシーを自動で適用できることになるので、使わないよりも管理しやすくなるんじゃないでしょうか1

うーんなところ

あまり良くないんじゃないかと思ったところを書いていきます。

Baseのリソースが消せない

上述のように直感的にmanifestのカスタマイズができる一方で、自由度があまり高くないように感じました。特に、baseのリソースをOverlayで消すということが現状ではできないようで2、自分のユースケースではこれが結構なダメージでした。

例えば、開発環境ではコンテナのMySQL、ステージング/本番ではRDSなどクラスター外のマネージドサービスを利用したいとします。 ここで、Baseだけでアプリが動くmanifestのセットとして成立させたいと思って、BaseにMySQLのコンテナのmanifestを書いてしまうと、ステージング/本番用のOverlayでこれを消すことができません。

f:id:charlier_shoe:20181212011446p:plain
やりたいこと

仕方なくBaseにはDBを除いた要素だけを書き、開発環境用のOverlayでコンテナのMySQLを足すことになるわけですが、データストアのないアプリだけのmanifestがBaseに残ることになります。

f:id:charlier_shoe:20181212011414p:plain
実際はこうなる

Baseっていうものの位置づけとしてこれでいいのだろうか、良くない気がする。

イメージのタグを更新したいだけなんですが

GitOpsをやる場合、manifest中に書かれたコンテナイメージのタグを変更するということをよくやると思います。

Kustomizeは1)Overlayを書いて2)それを被せる、という形でコレを実現することになるため、Overlayのファイルの編集が必要になります。kustomize editコマンドやsedでこれをやるのですが、せっかくOverlayの仕組みがあるのにやはり編集が必要なの…、という残念感が否めません。

f:id:charlier_shoe:20181212011621p:plain
Overlayを編集する…。

Helmだと変数なタグの文字列を埋め込むだけなので、スマートというか普通な感じで実現できます。考え方の違いといえばそれまでなのかもしれませんが…。

まだバギーですかね…?

Kustomzeを触った数週間で、2つほどバグらしき挙動に遭遇しました。片方は結構ダメージが大きくて、本番に耐えるmanifestを作るには結構な制約になってしまうものでした。

まだ治ってなかったら、年末休みの時間を使ってPR出そうと思います。

以上でございます。まだまだ発展途上のツールですので、今後に期待したいと思います!


  1. 実はあまり確信が持ててないです。

  2. このIssueによると、将来的には実装される可能性もありそう

OKEクラスターでコンテナを動かしてみる

このエントリーはOracle Cloudアドベントカレンダー その2の2日目です。

2日目も引き続き、Oracle Contianer Engine for Kubernetes(以下「OKE」。Oracleが提供するKubernetesのマネージドサービス)の話題です。このエントリーでは、1日目に作成したOKEでクラスターにkubectlでアクセスし、サンプルのコンテナをデプロイしてみます。

0 . 前提条件

  • 1日目の手順を終えるなどして、OKEクラスターが利用できること
  • Unix/Linux互換のターミナル環境が利用できること

手順の流れ

大雑把に言って、以下のような手順になります。

  1. kubectl等のコマンドラインツールをインストールする
  2. OKEにアクセスするためのkubectlの設定ファイルを取得する
  3. コンテナをデプロイする

それではやってみましょう。

1 . kubectl等のコマンドラインツールをインストールする

OKEクラスターの利用のためには、以下のコマンドラインツールが必要です。

  • Python 2.7.5+ or 3.5+
  • OCI CLIOracle Cloud Infrastructure、以下OCIのCLI
  • kubectl

kubectlでOKEクラスターにアクセスするためには、接続情報などが書かれたkubectlの設定ファイルを入手する必要があります。これを行うために、OCI CLIが必要です。

また、OCI CLIの利用のためにPythonを必要とします。

1.1. Pythonのインストール

現在のPythonのバージョンを確認するには、ターミナルで以下のコマンドを実行します。

Python 2.x 系の場合

python --version

Python 3.x 系の場合

python3 --version

もし上述の条件を満たしていない場合は、ここでPythonのインストール/アップデートを行ってください。

1.2. OCI CLIをインストールをする

OCI CLIのインストールを行っていきます。

1.2.1. OCIDの確認

OCIネイティブのアカウントでコンソールにログインし、画面右上の人型のアイコンをクリックします。さらに、展開されたメニューの"User Settings"をクリックします。

ユーザーの詳細情報の画面で”User Information"タブ内にユーザーのOCIDが表示されている箇所があります。OCIDの値の右にある"Copy"をクリックすると、クリップボードにOCIDがコピーされるので、これを手元のテキストエディタなどにペーストしておきます。

次に、同じく画面右上の人型のアイコンをクリックし、展開されたメニューにあるTenant名をクリックします。

Tenantの詳細情報の画面で”Tenancy Information"タブ内にOCIDが表示されている箇所があります。OCIDの値の右にある"Copy"をクリックすると、クリップボードにOCIDがコピーされるので、これを手元のテキストエディタなどにペーストしておきます。

1.2.2. OCI CLIのインストール

OCI CLIをインストールするには、以下のコマンドを実行します。

bash -c "$(curl -L https://raw.githubusercontent.com/oracle/oci-cli/master/scripts/install/install.sh)"

CLIのバイナリの配置先などを設定するインタラクションがあります。自身の環境に合わせて、好みの設定を行ってください(全てデフォルトのままで進めても問題ありません)。

インストールが完了したら、OCI CLIの初期セットアップを行います。以下のコマンドを実行してください。

oci setup config

ここでも設定をおこなうためのインタラクションがあります。ここでは、下表のように入力してください。

質問 応答操作
Enter a location for your config [/home/<ユーザー名>/.oci/config] なにも入力せず[Return]
Enter a user OCID 先の手順で確認したユーザーのOCIDを入力
Enter a tenancy OCID 先の手順で確認したテナントのOCIDを入力
Enter a region (e.g. eu-frankfurt-1, us-ashburn-1, us-phoenix-1) アクセスしたいリージョンを例に従って入力
Do you want to generate a new RSA key pair? (If you decline you will be asked to supply the path to an existing key.) [Y/n] Y + [Return]
Enter a directory for your keys to be created [/home/<ユーザー名>/.oci] なにも入力せず[Return]
Enter a name for your key [oci_api_key] なにも入力せず[Return]
Enter a passphrase for your private key (empty for no passphrase) なにも入力せず[Return]

CLIからOCIに対してアクセスを行う際には、OCIのAPIの認証が行われます。このため予め認証をパスするのに必要なAPIキーを、ユーザー毎にOCIにアップロードしておく必要があります。

OCI CLIの初期セットアップの際に作成した鍵ペアのうち、公開鍵の方を、管理コンソールからアップロードします。

まず、以下のターミナルで以下のコマンドを実行し、公開鍵を表示しておきます。

cat ~/.oci/oci_api_key_public.pem

続いてOCIのコンソールに移り、画面右上の現在ログイン中のユーザー名が表示されている箇所をクリックします。さらに、展開されたメニューの"User Settings"をクリックします。

ユーザーの詳細画面の左側のメニューで、"API Keys"をクリックし、さらに"Add Public Key"ボタンをクリックします。

”Add Public Key"ダイアログの入力欄に、先ほとターミナルに表示した公開鍵をペーストし、"Add"ボタンをクリックします("-----BEGIN PUBLIC KEY-----"と"-----END PUBLIC KEY-----"の行も含めてペーストします)。

以上でOCI CLIのインストールは完了です。

1.3. kubectlのインストール

Kubernetesの公式ドキュメントの手順に従って、kubectlをインストールしておきます。

2 . OKEにアクセスするためのkubectlの設定ファイルを取得する

最後にkubectlの設定ファイルを取得し、kubectlで実際にクラスターにアクセスしてみます。 OCIのコンソールで、画面右上のタブから"Developer Services"をクリックします。

あらかじめ作成しておいたOKEクラスターの名前をクリックします。

クラスターの詳細画面で、"Access Kubeconfig"をクリックします。

”How to Access Kubeconfig"ダイアログに、kubetctlの設定ファイルを取得するためのコマンドが表示されますので、これらを順次実行していきます。

1つ目のコマンドは、設定ファイルを格納するためのディレクトリの作成です。

mkdir -p $HOME/.kube

2つ目のコマンドは、OCI CLIの設定ファイル取得用のコマンドを実行しています(実際のコマンドはダイアログからコピーしてください)。

oci ce cluster create-kubeconfig --cluster-id ocid1.cluster.oc1.iad.xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx --file $HOME/.kube/config

最後に、以下のコマンドを実行して、kubectlの動作確認をしてみます。

kubectl get nodes

以下のような実行結果になれば、正常にクラスターにアクセスできています。

NAME              STATUS    ROLES     AGE       VERSION
129.213.100.157   Ready     node      3d        v1.11.1
129.213.118.118   Ready     node      3d        v1.11.1
129.213.95.69     Ready     node      3d        v1.11.1

3 . コンテナをデプロイする

それではいよいよ、OKEのクラスターにコンテナをデプロイしていきます。

今回は、公式のドキュメントにあるサンプルを利用して、Nginxを動かしてみます。以下のコマンドを実行してください。

kubectl apply -f https://k8s.io/examples/application/deployment.yaml

コマンドで指定されているymalファイルには、クラスターでコンテナを動かすための構成情報が記述されています(URLにアクセスすると、内容を確認できます)。上記のコマンドでは、このファイルの構成に従って、OKEクラスター上にコンテナをデプロイするよう指示しています。

以下のコマンドを実行すると、コンテナ(KubernetesではPodという単位に内包されています)の状態を確認できます。

kubectl get pods -l app=nginx
NAME                                READY     STATUS    RESTARTS   AGE
nginx-deployment-1771418926-7o5ns   1/1       Running   0          16h
nginx-deployment-1771418926-r18az   1/1       Running   0          16h

2つのPodに冗長化してデプロイされてるようでうす。どちらも"Running"のステータスになっていればOKです。

このままでは、クラスターの外からコンテナにトラフィックを届けることができないので、以下のコマンドでクラスター外に公開します。

kubectl expose deployment nginx-deployment --type="LoadBalancer" --port 80

これによって、上の手順で作成したPodのセット(nginx-deployment)に対して、ロードバランサーを通してトラフィックを届けることができます。ここでのロードバランサーは、このコマンドが実行されたときにOCIのロードバランサーを自動プロビジョニングして利用しています。

数十秒程度時間をおいてから以下のコマンドを実行すると、コンテナが公開されているIPアドレスロードバランサー)のIPアドレスを確認できます。

kubectl get services
NAME               TYPE           CLUSTER-IP      EXTERNAL-IP       PORT(S)        AGE
kubernetes         ClusterIP      10.96.0.1       <none>            443/TCP        4d
nginx-deployment   LoadBalancer   10.96.219.208   129.213.187.123   80:32053/TCP   33s

nginx-deploymentというエントリーのEXTERNAL-IPに表示されているものがそれです。

ブラウザでこのIPアドレスにアクセスすると、おなじみにNginxの画面が表示されます。

http://129.213.187.123/

f:id:charlier_shoe:20181203024728p:plain

コンテナのデプロイは以上で完了です!

お掃除

デプロイしたものを削除するには、以下のコマンドを実行してください。

kubectl delete deployment nginx-deployment

kubectl delete service nginx-deployment

OKEでKubernetesクラスターをプロビジョニングする

このエントリーはOracle Cloudアドベントカレンダー その2の1日目です。

Oracle Contianer Engine for Kubernetes(以下、OKE)は、Oracleが提供するKubernetesのマネージドサービスです。このエントリーでは、OKEでクラスターを作る手順をご紹介したいと思います。

0 . 前提条件

  • Oracle Cloud Infrastructure(Oracleの第2世代IaaS。以下OCI)が利用可能であること
  • OCIネイティブのアカウントが作成済みで、必要な権限を割り当て済みであること。試すけだならAdministratorグループに入れておけばOK1

登録したばかりのOracle Cloud環境では、これらの前提を満たすような準備が必要です。ゼロからOKEを使い始めるまでのStep by Stepのガイドは、paasdocsのチュートリアルにまとめてありますので、そちらを参照ください。

1 . 手順

OCIのコンソールに、OCIネイティブのアカウントでログインします。ネイティブアカウントの場合はログイン画面の右側のフォームです。

OCIコンソールへのログイン

コンソール左上のメニューを展開し、"Developer Services"をクリックします。

ここです。

画面左のメニューで"Clusters"をクリックし、List Scpesのメニューでクラスターを作成したいCompartmentを選択します。続けて、"Create Cluster"ボタンをクリックします。

Create Clusterでクラスターの作成を開始

"Create Cluster"ダイアログで、全てデフォルトの設定のまま"Create"ボタンをクリックします。

クラスター作成ダイアログ

デフォルトではクラスターが必要とするネットワークリソース等も自動的に構成されます。ダイアログの前半にある"CUSTOM CREATE"ラジオボタンを選択すると、クラスターの下回りのネットワークを細かく設定することもできます。

途中経過がダイアログに表示されますので、"Cluster and associated network resources created."というメッセージが表示されるまで待った後、"Close"ボタンをクリックします。

概ね数分程度で作成完了します。クラスターの一覧画面に、cluster1が表示されます。StatusがActiveになったらOKです。

クラスターのステータス

以上です。簡単!


明日も引き続きOracle Cloudアドベントカレンダーを担当します。明日はkubectlでクラスターにつないで、何かコンテナを動かして見たいと思います。


  1. AWSのIAMアカウントみたいなもの、と説明することにしていますが、細かい話をすると結構違います。まあ、そういうのが必要なんです。

gRPCを使ってみた

gRPC for Javaをいじってみたので、使い方のまとめ

gRPCとは

Googleが開発した、RPC(Remote Procedure Call)を実装するためのフレームワークです。

Protocol Bufferを利用した高速通信、.protoによるインターフェース定義、JavaやNode.jsなどを含む、多数のプログラミング言語に対応していることなどが特徴。

仕組み

大雑把な流れは、以下の通りです。

  1. .proto形式でインターフェース定義を記述
  2. .protoから、利用したいプログラミング言語用の、サーバーのスケルトン実装とクライアントスタブを生成
    1. で生成されたものをベースにサーバー、クライアントを実装

クライアントでスタブのメソッドを呼び出すと、通信はフレームワークがよしなにやってくれます。

f:id:charlier_shoe:20160908135226p:plain

公式ドキュメント「What is gRPC?」http://www.grpc.io/docs/guides/ より抜粋

Javaで実際にやってみた

Javaで実際にやってみました。 (コードはこちら

.protoの作成

以下の様な感じで、.protoを作成します。

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.oracle.jdt2016.hackathon.hr";
option java_outer_classname = "HrProto";
option objc_class_prefix = "HR";

import "google/protobuf/empty.proto";

package hr;

service Hr {
    rpc Employees(google.protobuf.Empty) returns (EmployeesReply) {}
}

message EmployeesReply {
    repeated Employee employee = 1;
}

message Employee {
    float commissionPct = 1;
    int64 departmentId = 2;
    string email = 3;
    int64 employeeId = 4;
    string firstName = 5;
    int64 hireDate = 6;
    string jobId = 7;
    string lastName = 8;
    int64 managerId = 9;
    string phoneNumber = 10;
    float salary = 11;
}

今回は、サーバーにあるダミーの従業員データを取得するインターフェースを作りました。引数は無し、返り値は従業員データの配列です。

"service"で始まる部分がメソッドの定義です。引数なしを定義するときは、"google/protobuf/empty.proto"をインポートして”google.protobuf.Empty”を引数に指定します。
返り値の型は”message”で始まる部分で定義しています。要素に配列を含みたい場合、”repeated"と記述すると、後続する型の配列だという宣言になります。

今回は、クライアント、サーバーとも同期的に処理を行なうインターフェースをとして定義していますが、非同期のインターフェースも使えます。
詳細は公式のドキュメントを参照して下さい。

サーバースケルトン、クライアントスタブの生成

今回はGradleを使います(Mavenでもいけるようです)。

まず、IDEを使うなりして適当なGradleプロジェクトを作成しておきます。

次に、src/main/protoフォルダを作成し、.protoファイルを配置します。

続いて、build.gradleを編集します。 今回使ったbuild.gradle(関連する部分を抜粋)は、以下の通りです。

buildscript {
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.protobuf:protobuf-gradle-plugin:0.8.0'
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'com.google.protobuf'def grpcVersion = '1.0.0'
sourceCompatibility = 1.8
targetCompatibility = 1.8

dependencies {
    …
    compile "io.grpc:grpc-netty:${grpcVersion}"
    compile "io.grpc:grpc-protobuf:${grpcVersion}"
    compile "io.grpc:grpc-stub:${grpcVersion}"
}

protobuf {
    protoc {
        artifact = 'com.google.protobuf:protoc:3.0.0'
    }
    plugins {
        grpc {
            artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}"
        }
    }
    generateProtoTasks {
        all()*.plugins {
            grpc {
                // To generate deprecated interfaces and static bindService method,
                // turn the enable_deprecated option to true below:
                option 'enable_deprecated=false'
            }
        }
    }
}

以下のコマンドを実行すると、build/generated配下に目的のコードが生成されます。

 > gradle generateProto

生成されるコードのパッケージ名には、.protoの"option java_package"で宣言したものになります。

サーバーの実装

サーバーの実装は以下の通りです(ほとんど公式のHelloWorldサンプルから持ってきています)。

//importは省略

public class HrServer {
    /* The port on which the server should run */
    private int port = 50051;
    private Server server;

    private void start() throws IOException {
        server = ServerBuilder.forPort(port).addService(new HrImpl())
          .build().start();
        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                System.err.println("*** shutting down gRPC server since JVM is shutting down");
                HrServer.this.stop();
                System.err.println("*** server shut down");
            }
        });
    }

    private void stop() {
        if (server != null) {
          server.shutdown();
        }
    }

    /**
     * Await termination on the main thread since the grpc library uses daemon threads.
     */
    private void blockUntilShutdown() throws InterruptedException {
        if (server != null) {
            server.awaitTermination();
        }
    }

    /**
     * Main launches the server from the command line.
     */
    public static void main(String[] args)
            throws IOException, InterruptedException {
        final HrServer server = new HrServer();
        server.start();
        server.blockUntilShutdown();
    }

    private class HrImpl extends HrGrpc.HrImplBase {
        @Override
        public void employees(
                Empty request, StreamObserver<EmployeesReply> responseObserver) {
            // DBから従業員データを取得する
            EntityManager em = EntityManagerUtils.getEntityManager();
            @SuppressWarnings("unchecked")
            List<Employee> entities =
                    em.createNamedQuery("Employee.findAll").getResultList();
            em.close();
            // 返り値を作成
            EmployeesReply.Builder replyBuilder = EmployeesReply.newBuilder();
            for (Employee entity : entities) {
                com.oracle.jdt2016.hackathon.hr.Employee employee =
                        com.oracle.jdt2016.hackathon.hr.Employee.newBuilder()
                        .setCommissionPct(entity.getCommissionPct())
                        .setDepartmentId(entity.getDepartmentId())
                        .setEmail(entity.getEmail())
                        .setEmployeeId(entity.getEmployeeId())
                        .setFirstName(entity.getFirstName())
                        .setHireDate(entity.getHireDate().getTime())
                        .setJobId(entity.getJobId())
                        .setLastName(entity.getLastName())
                        .setManagerId(entity.getManagerId())
                        .setPhoneNumber(entity.getPhoneNumber())
                        .setSalary(entity.getSalary())
                        .build();
                replyBuilder.addEmployee(employee);
            }
            responseObserver.onNext(replyBuilder.build());
            responseObserver.onCompleted();
        }
    }
}

サーバーの起動はServerBuilderからServerを作成→startという流れ。

ロジックは、"HrGrpc.HrImplBase"をextendsして、Employeesメソッドをオーバーライドして記述します。

クライアントに返却するデータも.protoファイルを元に生成されたクラスを使います。今回はEmployeesReply、Employeeというクラスが作られています。 オーバーライドしたメソッドの中でこれらのオブジェクトを作って、responseObsererに渡して上げればよいようです。

クライアントの実装

クライアントの実装は以下の通りです(こちらも公式のHelloWorldサンプルをベースにしています)。

//importは省略

public class HrClient {
    private final ManagedChannel channel;
    private final HrGrpc.HrBlockingStub blockingStub;

    public HrClient(String host, int port) {
        channel = ManagedChannelBuilder.forAddress(host, port)
                .usePlaintext(true).build();
        blockingStub = HrGrpc.newBlockingStub(channel);
    }

    public void shutdown() throws InterruptedException {
        channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
    }

    public void getEmployees() {
        EmployeesReply response;
        long begin = System.currentTimeMillis();
        try {
            response = blockingStub.employees(Empty.newBuilder().build());
        } catch (StatusRuntimeException e) {
            System.out.println("RPC failed");
            return;
        }
        System.out.println("Employees: " + response.getEmployeeCount());
    }

    public static void main(String[] args) throws Exception {
        HrClient client = new HrClient("localhost", 50051);
        try {
            client.getEmployees();
        } finally {
            client.shutdown();
        }
    }
}

スタブオブジェクトから、ローカルのメソッドを呼び出す感覚でemployeeを実行できます。

gRPCは、Protocol Bufferを使って高速な通信を実現しているわけですが、アプリケーションを実装する分には、プロトコル依存の部分を意識する必要はほとんどありません。

Spring Boot で組み込みDerbyデータベースを使う

【2016/06/14 追記】
JPAの部分は、Spring Data JPAの機能を利用するとpersistence.xmlを書かなくても動かせるはずです。コードを直したら、本ブログも修正する予定です。 コメント頂いた@makingさん。ありがとうございます。


先日のJava Day Tokyo 2016では、ショートハッカソンのセッションがあったのですが、そちらの準備と一部講師を担当させていただいてました。
3時間という、一般的なハッカソンに比べるとかなり短い枠でしたので、さすがに一からモノを作ってもらうわけには行かず、予めサンプル・アプリケーションを作っておいてそれをカスタマイズしてもらうという形態をとりました。

サンプルアプリを作っていく中で、Spring bootのアプリ内で組み込みDerby(読み取り専用)を動かすという技を習得したので、やり方を書きたいと思います。

ポイント

今回のアプリでは、DBのバイナリ/データを含む全ての依存成果物を、1つのjarに収める必要がありました。
Application Container Cloud(ACC)というOracleのコンテナ型のPaaSを利用するハッカソンでしたので、マイクロサービスらしくアプリをひとつにまとめたかったためです(本当は外部にDBを設けることもできます)。

これを実現するため、以下の2点を抑える必要がありました。

  1. Derbyのバイナリ/データファイルをjarに組み込む
  2. サンプルアプリから同じJVM上のDerbyに接続する

これらを踏まえて、以下、手順にいきます。

手順

1. データファイルの作成

はじめに、通常の手順でDerbyのデータベースを作成し、データを投入します。

以下の例では、ijを使ってC:\temp\dbフォルダ配下にhrというデータベースを作成し、同時に接続しています。ijは、DerbyのCLIクライアント(Oracle Databaseのsqlplusのようなもの)です。

> cd ${JAVA_HOME}\db\bin
ijバージョン10.11
> ij.bat
ij> connect 'jdbc:derby:C:\temp\db\hr;create=true';

続いてデータを投入していきます。例えば、create_tables.sql、populate_tables.sqlというスクリプトを使う場合、以下のコマンドを実行します。

ij> run 'C:\temp\db\scripts\create_tables.sql';
…
(実行結果の出力)
…
ij> run 'C:\temp\db\scripts\populate_tables.sql';
…
(実行結果の出力)
…

データの投入が終わったら接続を解除します。

ij> exit;

データファイルは接続時に指定したパスに作成されています。ディレクトリ構成は以下のようになっています。

C:\temp\db
└── hr
    ├── log
    │   ├── log.ctrl
    │   ├── log1.dat
    │   ├── logmirror.ctrl
    │   └── README_DO_NOT_TOUCH_FILES.txt
    ├── README_DO_NOT_TOUCH_FILES.txt
    ├── seg0
    │   ├── c10.dat
    │   ├── cf0.dat
    │   ├── …(.datがたくさん)
    │   └── README_DO_NOT_TOUCH_FILES.txt
    └── service.properties

次に、上記のファイル群の中にあるservice.propertiesを編集します。これはDerbyデータベースの設定を記述するファイルです。

derby.storage.tempDirectoryという設定を追記して、一時ファイルの出力先を変更します。
デフォルトでは、データファイル群のトップのフォルダ(上記のhrフォルダ)に一時ファイルが出力されますが、今回はそのフォルダはjarの中に格納されてしまいます。そのため、書き込み可能な領域(jarファイルの外)を一時ファイルの出力先にしておきます。

以下の例は、プログラムの実行カレント以下、temp/db/hrフォルダに設定しています。

SysschemasIndex2Identifier=225
SyscolumnsIdentifier=144
…
derby.storage.tempDirectory=temp/db/hr (←追記)

最後に、データファイル群をMavenプロジェクトの配下にコピーします。 今回の場合、src/main/resources/の下にdataという階層を切ってファイルを配置しました。こうしておくと、ビルドしたときに、jarファイル内のトップにdata/hrというフォルダ構成が作られてデータファイルが格納されます。

Mavenプロジェクト内のデータファイルの階層

[プロジェクトトップ]
├─src
│  ├─main
│  │  ├─java
│  │  │  └─…
│  │  └─resources
│  │      ├─data
│  │      │  └─hr
│  │      │      ├─log
│  │      │      └─seg0
│  ├─…  ├─…

jarファイル内のデータファイルの階層

xxx.jar
├─data
│  └─hr
│      ├─log
│      └─seg0
├─lib
├─…

2. JPAの設定

データベースにアクセスするには、クラスパスを使ったDBアクセス*1を利用します。
これは、データファイルをパッケージしたjarをクラスパスに入れておいて、クラスパス中のDBにアクセスする方法です。ただし、今回はアプリケーション本体のjarにデータファイルが入っていますので、新たにクラスパスを追加する必要はありません*2

注意すべきは、JDBC URLが、この方法特有の書き方になることと、JDBCドライバーにEmbeddedDriverを指定することです。

今回の場合、ビルド成果物のjar直下にdata/hrというパスでデータファイルが格納されています。それに合わせて、persistence.xmlに以下のように記述します。

    <persistence-unit name="HR" transaction-type="RESOURCE_LOCAL"><properties>
            <property
                name="javax.persistence.jdbc.url"
                value="jdbc:derby:classpath:data/hr"/>
            <property
                name="javax.persistence.jdbc.driver"
                value="org.apache.derby.jdbc.EmbeddedDriver"/>
        </properties></persistence-unit>

ユーザー名/パスワードは、データベースを作成した時に設定していれば記述する必要があると思われますが、とくに何もしていなければ不要です。

3. ビルド

pom.xmlには、依存モジュールにderbyのjarを追加しておきます。

    <dependencies>
        …
        <dependency>
            <groupId>org.apache.derby</groupId>
            <artifactId>derby</artifactId>
            <scope>compile</scope>
        </dependency>
    </dependencies>

あとはビルドして、アプリを実行します。

> cd [pom.xmlのあるフォルダ]
> maven package
> java -jar target/[jarファイル名]

まとめ

実際にハッカソンで使ったアプリケーションは、GitHubにコードがあります。今回の手法の実装例として、ご参考にどうぞ。

Derbyのマニュアルのトップはこちら

*1:Accessing databases within a jar file using the classpath

*2:この機能は、データファイルだけのjarを作っておいて、それをクラスパスに入れるという使い方が想定されているようです。しかし、データファイルのjarを作っておいてSpring Bootアプリのjarに入れる構造にすると、クラスパスからデータファイルのjarが見えなくなってしまい、DBにアクセスできません。

ローカル環境のWebLogic Serverに、warを自動デプロイする

WebLogic Maven プラグインには、ビルドした成果物をテスト環境に自動デプロイする機能があります。 今回は、この機能を使って、ローカルPCで稼働するDockerコンテナ上のWebLogic Serverに、Mavenから自動デプロイしてみます。

この機能の本来の用途は、インテグレーションテスト等のテストフェーズを自動化することだと思いますが、これにとどまらず、実装フェーズでも活用できます。
例えば、UI画面の開発時など、コード修正→画面確認を繰り返して試行錯誤するような場面がありますが、こんなとき自動でデプロイができるととても作業が捗ります。

手順

0. WebLogic ServerがインストールされたDockerコンテナを用意する

本記事では、ローカル環境のVirtualBox上でLinux仮想マシンを動かし、その仮想マシンをDockerのホストとして利用します。
WebLogic ServerのDockerコンテナの作成手順は、過去の記事を参照ください。

また、DockerそのものとDocker on WebLogicの詳しい解説は、先日のWebLogic Server勉強会の資料が参考になりますので、こちらもぜひご参照ください(←宣伝)。

1. WebLogic Maven プラグインをインストールする

WebLogic Maven プラグインを、ローカル環境にインストールします。
こちらの手順も、過去の記事を参照ください。「1-2. WebLogic Mavenプラグインのインストール」までの作業をやれば良いはずです。

2. pom.xmlを編集する

以下の例のように、<build> > <plugins>タグの配下に、WebLogic Mavenプラグインの設定を記述します。

<build>
    <plugins>
        <plugin>
            <groupId>com.oracle.weblogic</groupId>
            <artifactId>weblogic-maven-plugin</artifactId>
            <version>12.2.1-0-0</version>
            <executions>
                <execution>
                    <id>wls-redeploy</id>
                    <phase>pre-integration-test</phase>
                    <goals>
                        <goal>redeploy</goal>
                    </goals>
                    <configuration>
                        <adminurl>t3://localhost:8001</adminurl>
                        <user>weblogic</user>
                        <password>welcome1</password>
                        <targets>AdminServer</targets>
                        <upload>true</upload>
                        <remote>false</remote>
                        <source>${project.build.directory}/${project.build.finalName}.${project.packaging}</source>
                        <name>${project.build.finalName}</name>
                        <verbose>true</verbose>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

goalに"redeploy"を指定しているので、既にデプロイされている物があった場合、自動的に更新されます。

configration配下の記述は、環境に合わせて変更する必要があります。
各設定の意味は、以下のとおりです。

  • adminurl
  • user
    • WebLogic Serverの管理者ユーザーのユーザー名
  • password
    • WebLogic Serverの管理者ユーザーのパスワード
  • targets
    • ビルドした成果物のデプロイ先
  • upload
    • 成果物をアップロードするかどうか(管理コンソールからデプロイするときの、アップロード操作に相当すると思われる)
  • remote
    • サーバーがリモートに設置されているかどうか
  • source
    • 成果物(warなど)のパス
  • name
    • 成果物の名前
  • verbose
    • デプロイ時に詳細メッセージを出力するかどうか

今回のように、VirtualBox上の仮想マシン上のDockerコンテナでWebLogic Serverを動かしている場合、uploadは"true"、remoteは"false"が正解のようです。

3. ビルドを実行する

実際にビルドを実行してみます。以下のコマンドを実行します。

> mvn clean pre-integration-test

warのビルドが終わると、デプロイが開始されます。
以下のように、最終的に"redeploy 完了"のメッセージが表示されれば、デプロイ完了です。

[INFO] --- weblogic-maven-plugin:12.2.1-0-0:redeploy (wls-redeploy) @ linguistic
s ---
[INFO] Command flags are: -noexit -redeploy -username weblogic -password *******
-name linguistics-1.0 -source C:\Users\hhayakaw\Documents\GitRepository\linguis
tics\app\target\linguistics-1.0.war -targets AdminServer -upload -verbose -admin
url t3://localhost:8001
…
ターゲットの状態: サーバー AdminServerでredeploy 完了

実際に所定のURLにアクセスすると、アプリケーションが稼働していることがわかると思います。

せっかくなので、デプロイが完了したらブラウザで開くところまで一気にやるようにすると、さらにいい感じです。

WebLogic Maven Pluginを使った自動デプロイの手順は、以上です。
WebLogic Maven Pluginは、使いこなせるとまだまだ色々できそうなので、探求を続けていきたいところです。