どこか遠くへ行きたい
第 4 弾は、ballerina の体験です。
ballerina
公式サイト : https://ballerina.io/
公式FAQ : https://ballerina.io/learn/faq/
API ドキュメント : https://ballerina.io/learn/api-docs/
リポジトリ : https://github.com/ballerina-platform/ballerina-lang
Examples 一覧 - https://ballerina.io/learn/by-example/
Guides 一覧 - https://ballerina.io/learn/by-guide/
Ballerina makes it easy to write cloud native applications while maintaining reliability, scalability, observability, and security.
Ballerina is a compiled, transactional, statically and strongly typed programming language with textual and graphical syntaxes. Ballerina incorporates fundamental concepts of distributed system integration into the language and offers a type safe, concurrent environment to implement microservices with distributed transactions, reliable messaging, stream processing, and workflows.
用途 | 汎用 |
開始 | 2014/04 |
最新バージョン | 0.990.0 ( 2018/10/13 ) |
ライセンス | Apache License Version 2.0 |
プラットフォーム | JVM |
型 | 静的型付け |
パッケージ管理 | 標準搭載 |
Githubスター | 1548 ( 2018/12/18 ) |
- Microservice 志向
- 各種ネットワークプロトコルへの標準対応
- gRPC
- HTTP 2
- WebSockets
- Messaging
- AMPQ
- Kafka
- RESTful
- Open API ( Swagger )
- デプロイメント
- Artifact 生成
- Dockerfile
- Docker image
- Kubernetes Resources
- Artifact 生成
- セキュリティ対策
- Authentication 対応
- Base Auth
- jwt
- OAuth (client only)
- Sanitizing 強制アノテーション
- Authentication 対応
- サービスメッシュ機能を標準搭載
- Circuit Breaker, Failover, Retry, Load Barancing...
- Istio との共存も可能
- Prometheus に標準対応
- OpenTrace に標準対応
- Zipkin, Jaeger
- Elastic Stack への対応 ( 微妙 )
- 各種ネットワークプロトコルへの標準対応
- Visual Programming
- 並列・非同期処理
2018/12/18 現在、Commit が 56,988、Closed Issue が 3,710、Open Issue が 584、Closed Pull Request が 8,497、 Open Pull Request が 40、 リリースページの熱量も半端ない。babel/babel
に比肩する盛況ぶり。
インストール
・Windows 10 Pro 1803
・Docker for Windows 18.09.0
・Kubernetes ( docker-for-desktop ) v1.10.3
・kubectl v1.10.3
・heml v2.12.0
インストーラが提供されているので、ダウンロードしてインストールを行う。
https://ballerina.io/downloads/
一つ気を付ける必要があるのが、JDK 1.8 にしか対応していない事。
インストーラには JRE が含まれているので問題ないが、Zip ファイルを展開した場合は別途インストールが必要。
$ ballerina -v
# Ballerina 0.990.0
エディタ支援
とても充実している
https://ballerina.io/learn/tools-ides/
VS Code を使うのであれば、以下拡張が使える。
https://marketplace.visualstudio.com/items?itemName=ballerina.ballerina
オートコンプリートや定義ジャンプもでき、デバッガも用意されている。
また、その大きな特徴として、Diagram View があること。
開発の流れ
以下より重要そうなトピックをピックアップしながら進める。
https://ballerina.io/learn/
初期化
$ ballerina init
# Ballerina project initialized
$ tree -a
# .
# ├── .ballerina
# │ └── .gitignore
# ├── .gitignore
# ├── Ballerina.toml
# └── hello_service.bal
Ballerina.toml
がプロジェクト管理ファイルのようだ。
[project]
org-name = "user"
version = "0.0.1"
詳細は以下
https://ballerina.io/learn/how-to-structure-ballerina-code/
実行・ビルド
init
でできたファイルは以下である ( コメントは削除した )。
import ballerina/http;
service hello on new http:Listener(9090) {
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload("Hello Ballerina!");
_ = caller -> respond(response);
}
}
実行のみであれば、以下コマンドでできる。
$ ballerina run .\hello_service.bal
# Initiating service(s) in 'hello_service.bal'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9090
$ curl http://localhost:9090/hello/sayHello
# Hello Ballerina!
また、ビルドをする場合は以下。
# build
$ ballerina build
$ tree
# ├── Ballerina.toml
# ├── hello_service.bal
# └── target
# ├── Ballerina.lock
# └── hello_service.balx
target/hello_service.balx
が生成されたバイナリとなっている。
$ ballerina run .\target\hello_service.balx
# Initiating service(s) in 'hello_service.bal'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9090
ダイアグラム
VS Code か IntelliJ を使えば、ダイアグラムを使った Visual Programming が可能。
こんな感じで色々とできる。
今の所あまり自由度が高いとは言えず、基本はコードの全体像を把握するためのツールと捉えたほうが良さそう。
詳細は以下
https://ballerina.io/learn/tools-ides/
デバッガ
VS Code と IntelliJ には、デバッガーも用意されている。
アノテーション
ballerina は、様々なアノテーションを付与することで、service や endpoint, resource のカスタマイズができる。
import ballerina/http;
+ @http:ServiceConfig {
+ basePath: "/api"
+ }
service hello on new http:Listener(9090) {
+ @http:ResourceConfig {
+ methods: ["POST"],
+ path: "/say"
+ }
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload("Hello Ballerina!");
_ = caller -> respond(response);
}
}
$ ballerina run .\hello_service.bal
# Initiating service(s) in 'hello_service.bal'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9090
$ curl http://localhost:9090/api/say -X POST
# Hello Ballerina!
設定ファイル
ballerina/config
を使えば、標準で設定ファイルが読み込める。
access_token = "qwertyuiop"
import ballerina/http;
+ import ballerina/config;
@http:ServiceConfig {
basePath: "/api"
}
service hello on new http:Listener(9090) {
@http:ResourceConfig {
methods: ["POST"],
path: "/say"
}
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload(config:getAsString("access_token"));
_ = caller -> respond(response);
}
}
$ ballerina run --config myconfig.toml hello_service.bal
...
$ curl http://localhost:9090/api/say -X POST
# qwertyuiop
モジュール
モジュールを追加する場合は、結構簡単にできる。
自作モジュールは、こんな感じに作れる。
$ mkdir math
$ cd math
$ touch add.bal
$ tree
#
public function addTwice(int a, int b) returns int {
return a + a + b + b;
}
...
+ import user/math;
...
service hello on cmdListener {
...
+ resource function add(http:Caller caller, http:Request request) {
+ http:Response response = new;
+ response.setTextPayload(<string>math:addTwice(10, 30));
+ _ = caller -> respond(response);
+ }
}
$ ballerina run hello_service.bal
...
$ curl http://localhost:9090/api/add
# 80
import user/math
の user
は、Ballerina.toml
の project.org-name
から取られる。
詳細は以下
https://ballerina.io/learn/how-to-structure-ballerina-code/
公開モジュール
ballerina では、 Ballerina Central というモジュールを共有する公開リポジトリが提供されている。
https://central.ballerina.io/
公開されているモジュールを利用するには、ballerina pull
で取得する必要がある。
取得されたモジュールは、~/.ballerina/cache
以下に展開される。
$ ballerina search github
# Ballerina Central
# =================
#
# |NAME | DESCRIPTION | DATE | VERSION |
# |----------------| ------------------------------| ---------------| --------|
# |wso2/github4 | Connects to the GitHub Grap...| 2018-12-08-土 | 0.9.19 |
# |shan1024/chalk | Bring colors to Ballerina p...| 2018-09-29-土 | 0.1.6 |
# |mirage/os | Run OS system commands. | 2018-08-10-金 | 0.1.0 |
# |chanakal/comm...| Generates WSO2 Committer Re...| 2018-12-09-日 | 0.9.12 |
# |maryamzi/github | A basic webhook accepting G...| 2018-08-06-月 | 0.0.3 |
# |pasanw/wordpr...| Connects to Wordpress from ...| 2018-09-28-金 | 0.0.6 |
# |anuruddha/kub...| Ballerina connector for Kub...| 2018-08-10-金 | 0.0.2 |
# |wso2/ethereum | Connects to Ethereum from B...| 2018-12-10-月 | 0.8.5 |
# |hemikak/strin...| String utility functions fo...| 2018-08-01-水 | 0.0.4 |
$ ballerina pull wso2/github4
# wso2/github4:0.9.19 [central.ballerina.io -> home repo]
あとは、import
すれば利用できる。
import ballerina/http;
import ballerina/config;
+ import wso2/github4;
...
自分が利用しているモジュール一覧は、ballerina list
で確認可能
$ ballerina list
# Compiling source
# hello_service.bal
# hello_service.bal
# ├── ballerina/http
# │ ├── ballerina/internal
# │ │ ├── ballerina/time
# │ │ └── ballerina/log
# │ ├── ballerina/system
# │ ├── ballerina/config
# │ │ └── ballerina/system
# │ ├── ballerina/math
# │ ├── ballerina/crypto
# │ ├── ballerina/mime
# │ │ ├── ballerina/io
# │ │ └── ballerina/file
# │ ├── ballerina/file
# │ ├── ballerina/time
# │ ├── ballerina/io
# │ ├── ballerina/runtime
# │ ├── ballerina/cache
# │ │ ├── ballerina/time
# │ │ ├── ballerina/task
# │ │ └── ballerina/system
# │ ├── ballerina/log
# │ ├── ballerina/reflect
# │ └── ballerina/auth
# │ ├── ballerina/system
# │ ├── ballerina/time
# │ ├── ballerina/log
# │ ├── ballerina/internal
# │ │ ├── ballerina/time
# │ │ └── ballerina/log
# │ ├── ballerina/cache
# │ │ ├── ballerina/time
# │ │ ├── ballerina/task
# │ │ └── ballerina/system
# │ ├── ballerina/runtime
# │ └── ballerina/config
# │ └── ballerina/system
# └── ballerina/config
# └── ballerina/system
また、自分の作ったモジュールをアップロードするのも簡単。
$ ballerina push math
詳細は以下
https://ballerina.io/learn/how-to-publish-modules/
テスト
テストも標準で提供されている。Testerina と呼ばれる。
先程の math
モジュールにテストを追加する。
```powershell
$ mkdir .\math\tests
$ touch .\math\tests\addTest.bal
import ballerina/io;
import ballerina/test;
@test:BeforeSuite
function beforeFunc() {
io:println("called once before all tests");
}
@test:BeforeEach
function beforeEachFunc() {
io:println("called before every test");
}
@test:Config
function testFunction() {
test:assertEquals(addTwice(10, 40), 100 , msg = "Failed");
}
@test:AfterSuite
function afterFunc() {
io:println("called once after all tests");
}
$ ballerina test math
# Compiling tests
# user/math:0.0.1
#
# Running tests
# user/math:0.0.1
# called once before all tests
# called before every test
# called once after all tests
# [pass] testFunction
#
# 1 passing
# 0 failing
# 0 skipped
ソースファイル内に直接記述もできるので、そっちのほうが便利かも。
import ballerina/test;
public function addTwice(int a, int b) returns int {
return a + a + b + b;
}
@test:Config
function testFunction() {
test:assertEquals(addTwice(10, 40), 100 , msg = "Failed");
}
詳細は以下
https://ballerina.io/learn/how-to-test-ballerina-code/
ドキュメント
コードのドキュメント記述には、Docerina という拡張 Markdown で記述する。
# Add given params twice
#
#
#
# + a - One number
# + b - Other number
# + return - Added number
#
public function addTwice(int a, int b) returns int {
return a + a + b + b;
}
また、HTML でドキュメントを出力も可能です。
$ ballerina doc
# docerina: API documentation generation for sources - [math]
# docerina: HTML file written: C:\sample_1219\target\api-docs\math.html
# docerina: HTML file written: C:\sample_1219\target\api-docs\index.html
# docerina: HTML file written: C:\sample_1219\target\api-docs\module-list.html
$ cd .\target\api-doc\
$ armor
モジュールフォルダに Module.md
を置いておくと、それも読み込んでくれる。
上記画像でいくと、マスモジュールです。
の所がそれだ。
詳細は以下
https://ballerina.io/learn/how-to-document-ballerina-code/
Docker
Docker に対するアノテーションを追加することで、ビルド時に Dockerfile を自動生成し、 Docker build してくれる。
import ballerina/http;
import ballerina/config;
import ballerinax/docker;
+ @docker:Config {
+ name:"helloworld",
+ tag:"v1.0"
+ }
+ @docker:CopyFiles {
+ files:[
+ {source:"./myconfig.toml", target:"/home/ballerina/conf/myconfig.toml", isBallerinaConf:true}
+ ]
+ }
+ @docker:Expose {}
+ listener http:Listener cmdListener = new(9090);
@http:ServiceConfig {
basePath: "/api"
}
- service hello on new http:Listener(9090)
+ service hello on cmdListener {
@http:ResourceConfig {
methods: ["POST"],
path: "/say"
}
resource function sayHello(http:Caller caller, http:Request request) {
http:Response response = new;
response.setTextPayload(config:getAsString("access_token"));
_ = caller -> respond(response);
}
}
$ ballerina build hello_service.bal
# Compiling source
# hello_service.bal
# Generating executable
# ./target/hello_service.balx
# @docker - complete 3/3
#
# Run the following command to start a Docker container:
# docker run -d -p 9090:9090 helloworld:v1.0
$ docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# helloworld v1.0 4bfa8d7b4f54 38 seconds ago 128MB
# ballerina/ballerina-runtime 0.990.0 5d1249acb988 8 days ago 128MB
# ...
$ docker run -d -p 9090:9090 helloworld:v1.0
# 02d446fc149b9cdf8e3d5327ac24be46affc4fa6a2f475f2f8122e72669ec39e
$ docker ps
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
# 02d446fc149b helloworld:v1.0 "/bin/sh -c 'balleri…" 6 seconds ago Up 3 seconds 0.0.0.0:9090->9090/tcp hardcor e_burnell
$ curl http://localhost:9090/api/say -X POST
# qwertyuiop
生成物を確認すると、
$ tree target
# target
# ├── Ballerina.lock
# ├── hello_service
# │ ├── Dockerfile
# │ └── myconfig.toml
# └── hello_service.balx
# Auto Generated Dockerfile
FROM ballerina/ballerina-runtime:0.990.0
LABEL maintainer="dev@ballerina.io"
COPY hello_service.balx /home/ballerina
COPY myconfig.toml /home/ballerina/conf/myconfig.toml
EXPOSE 9090
CMD ballerina run --config /home/ballerina/conf/myconfig.toml hello_service.balx
ベースイメージが ballerina/ballerina-runtime
になっている。
Docker for Windows を利用している場合は、以下の設定をしないと ballerina から繋げない。
こんなエラーが出たら、DNS Server を固定すると解決できる。
デザインプリンシプル
ここまでで、おおよその開発フローは理解できたので、ここからはもう少しコンセプトについて深めていく。
Microservice
ballerina は Microservice 志向であり、多数のサービス間でネットワーク通信をしながら協調して動作するように設計されている。
● Protocol
以下機能がデフォルトで提供されているネットワークプロトコルだ。
- HTTP, HTTPS
- HTTP 1.1
- HTTP 2.0
- 一部認証へも対応
- Basic 認証
- JWT
- OAuth (クライアントのみ)
- LDAP ?
- WebSocket
- WebSub
- Database
- gRPC
- AMPQ (JMS)
● Traffic Management
また、サービスメッシュを構築するための機能も提供されている。
- クライアント
- Circuit Breaker
- Load Balancing
- Timeout, Retry, Failover
- Transaction ( Experimental )
Istio が担う範囲までアプリケーションでやるつもりなんだろうか。
https://ballerina.io/learn/faq/#how-does-ballerina-compare-to-a-service-mesh-like-istio
How does Ballerina compare to a Service Mesh like Istio?
Service meshes exist to make it easier to write resilient distributed systems. They apply transaction resilience at the network request level and Ballerina applies it within the logic level. Ballerina works both with and without a service mesh! In situations where a service mesh is not present, Ballerina provides network bridging and transaction management for invocations in between services written with Ballerina or integrated via the Ballerina Bridge. In situations where a service mesh already exists, Ballerina services can be configured to delegate routing and transaction capabilities to the underlying mesh.
ということなので、Istio とは共存していくんだろうと。
Security
ネットワークには付き物のセキュリティについても配慮がなされている。
https://ballerina.io/learn/how-to-write-secure-ballerina-code/
● untrusted data
データを外部から受け取って処理をする以上、Injection や Path Manipulation の危険性からは逃れられない。
ballerina では、データの信頼性を表すアノテーションをデータに付与し、危険な操作をコンパイル時に弾くことができる。
Database に SQL を発行する select
関数は、引数の sqlQuery
に @sensitive
アノテーションが付与されている。
public extern function select(@sensitive string sqlQuery, typedesc? recordType,
boolean loadToMemory = false, Param... parameters)
returns @tainted table|error;
これにより、sqlQuery
には信頼できないデータは入れられない様になっている。
では、信頼できないデータとは何か。
信頼できないデータには、@tainted
アノテーションが付与される。
先程の select
メソッドの戻り値が @tainted
になっている。これは、SQL で取得したデータが安全ではないと考えれば当然。
この様に、外部から受け取ったデータは基本的に全て信頼できないデータとなる。
しかし、外部から受け取ったデータは全て @sensitive
に使えないと困るので、その場合はサニタイズを行い怪しさを洗浄する事でまた信頼を取り戻すことができる。
function sanitizeSortColumn (string columnName) returns @untainted string {
string sanitizedSortColumn = columnName;
// Sanitize logic to make sure return value is safe
return sanitizedSortColumn;
}
この関数が返した @untainted
な文字列は、@sensitive
な引数へも渡すことができるようになる。
しかし、サニタイズ処理はユーザに任されているのはどうなんだろう。標準パッケージで一通りのサニタイザが提供されてるんだろうか。
● Secrets
接続情報や Token, API Key のような Secrets を使う場面は多い。
その時は、暗号化して設定ファイルに持たせることができる。
$ ballerina encrypt
# Enter value:
#
# Enter secret:
#
# Re-enter secret to verify:
#
# Add the following to the runtime config:
# @encrypted:{ufeieFEIUFE309f83kJIFJjeifeKFE=}
#
# Or add to the runtime command line:
# -e<param>=@encrypted:{ufeieFEIUFE309f83kJIFJjeifeKFE=}#
これを設定ファイルの値にそのまま入れる。
access_token="@encrypted:{eDLMHga9NW15siGHplUzlPP+9kALRMDA1f1rPBEJXJc=}"
$ ballerina run -c .\myconfig.toml .\hello_service.bal
# ballerina: enter secret for config value decryption:
#
# ↑ ここで、`Enter secret:` で入力したパスワードを入力
# ↓ 成功すると、サーバが動き出す
#
# Initiating service(s) in 'hello_service.bal'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9090
$ curl http://localhost:9090/api/say -X POST
# encrypted value
Parallel, Asynchronous
● Parallel
ballerina は Worker と呼ばれる単位で並列化されている。ダイアグラムの Actor に相当するのがこの Worker だ。
https://ballerina.io/learn/by-example/workers.html
https://ballerina.io/learn/by-example/worker-interaction.html
import ballerina/io;
import ballerina/runtime;
public function main() {
worker w1 {
int i = 100;
float k = 2.34;
io:println("[w1 -> w2] i: ", i, " k: ", k);
(i, k) -> w2;
json j = {};
j = <- w2;
string jStr = j.toString();
io:println("[w1 <- w2] j: ", jStr);
io:println("[w1 ->> w2] i: ", i);
() send = i ->> w2;
io:println("[w1 ->> w2] successful!!");
io:println("[w1 -> w3] k: ", k);
k -> w3;
k -> w3;
k -> w3;
io:println("Waiting for worker w3 to fetch messages..");
error? flushResult = flush w3;
io:println("[w1 -> w3] Flushed!!");
}
worker w2 {
int iw;
float kw;
(int, float) vW1 = (0, 1.0);
vW1 = <- w1;
(iw, kw) = vW1;
io:println("[w2 <- w1] iw: " + iw + " kw: " + kw);
json jw = { "name": "Ballerina" };
io:println("[w2 -> w1] jw: ", jw);
jw -> w1;
int lw;
runtime:sleep(5);
lw = <- w1;
io:println("[w2 <- w1] lw: " + lw);
}
worker w3 {
float mw;
runtime:sleep(50);
mw = <- w1;
mw = <- w1;
mw = <- w1;
io:println("[w3 <- w1] mw: ", mw);
}
wait w1;
}
メッセージ送信の場合、<-
で待ち受けて、->
でメッセージを送信。
->>
は送信して受信を待つ。() send = i ->> w2;
では i を w2 に送って、()
が返るのを待つ = w2 が終わるのを待つ。
受信と送信では型検査がなされており、型安全となっている。
また、受信と送信の回数はチェックされていて、回数が合わないとコンパイルエラーとなる。
試しに foreach
の中で送信してみたが、invalid worker send statement position, must be a top level statement in a worker
と怒られた。
また、Fork-Join にネイティブに対応している。
fork {
worker w1 {
int i = 23;
string s = "Foo";
io:println("[w1] i: ", i, " s: ", s);
(i, s) -> fork;
}
worker w2 {
float f = 10.344;
io:println("[w2] f: ", f);
f -> fork;
}
} join (all) (map results) {
int iW1;
}
● Asynchronous
標準エンドポイントの多くは同期的に処理されるが、Future 化する事もできる。
future<http:Response | error> f1 = start nasdaqServiceEP -> get("/nasdaq/quote/GOOG");
io:println(" >> Invocation completed!" + " Proceed without blocking for a response.");
var response = await f1;
start
で Future 化し、await
で結果を待つ。
API
Microservice 間でのデータの通信や、外部サービスとの通信を安全に行うための様々な配慮がなされている。
● Typesafe payload
サービス間ペイロードのデータ構造としては JSON と XML をデフォルトで扱える。
ballerina では、Union 型を使ったパーシングを行いうことで、型安全を保っている。
type Payment {
string name,
string cardnumber,
int month,
int year,
int cvc;
};
json payload = check request.getJsonPayload();
Payment|error p = <Payment>payload;
match p {
Payment x => {
io:println(x);
res.statusCode = 200;
// return the JSON that has been created
res.setJsonPayload(check <json>x);
}
error e => {
res.statusCode = 400 ;
// return the error message if the JSON failed to parse
res.setStringPayload(e.message);
}
_ = caller -> respond (res);
}
<>
でキャストを行っているが、その際に成功したか失敗したかの Union 型が返ってくる。Result や Either の様なもの。
● gRPC
ballerina は、gRPC へもネイティブに対応している。
以下、proto\order.proto
を実装するものとする。
syntax = "proto3";
package grpc_service;
import "google/protobuf/wrappers.proto";
service order {
rpc findOrder(google.protobuf.StringValue) returns (google.protobuf.StringValue);
rpc addOrder(orderInfo) returns (google.protobuf.StringValue);
rpc updateOrder(orderInfo) returns (google.protobuf.StringValue);
rpc cancelOrder(google.protobuf.StringValue) returns (google.protobuf.StringValue);
}
message orderInfo {
string id = 1;
string name = 2;
string description = 3;
}
for Service
$ ballerina grpc --input proto\order.proto --output grpc_service --mode service
# Downloading proc executor.
# Download successfully completed.
# Successfully extracted library files.
# Successfully generated ballerina file.
$ tree grpc_service
# grpc_service
# ├── order_pb.bal
# └── order_sample_service.bal
サービス側を作成するときは --mode service
をつける。--output
が出力先。
order_pb.bal
が Stub で、order_sample_service.bal
が実装例。
import ballerina/grpc;
listener grpc:Listener ep = new (9000);
service order on ep {
resource function findOrder(grpc:Caller caller, string value) {
}
resource function addOrder(grpc:Caller caller, orderInfo value) {
}
resource function updateOrder(grpc:Caller caller, orderInfo value) {
}
resource function cancelOrder(grpc:Caller caller, string value) {
}
}
例えば、以下のように実装していくことになる。
...
service order on ep {
...
resource function findOrder(grpc:Caller caller, string value) {
orderInfo o = {id: value, name: string `name {{value}}`, description: "no description"};
var jsonValue = json.convert(o);
if (jsonValue is error) {
_ = caller->sendError(grpc:INTERNAL, <string>jsonValue.detail().message);
} else {
json orderDetails = jsonValue;
string payload = orderDetails.toString();
_ = caller->send(payload);
_ = caller->complete();
}
}
...
}
for Client
$ ballerina grpc --input proto\order.proto --output grpc_client --mode client
# Successfully extracted library files.
# Successfully generated ballerina file.
$ tree grpc_client
# grpc_client
# ├── order_pb.bal
# └── order_sample_client.bal
import ballerina/grpc;
import ballerina/io;
public function main (string... args) {
orderClient ep = new("http://localhost:9090");
orderBlockingClient blockingEp = new("http://localhost:9090");
}
service orderMessageListener = service {
resource function onMessage(string message) {
io:println("Response received from server: " + message);
}
resource function onError(error err) {
io:println("Error from Connector: " + err.reason() + " - " + <string>err.detail().message);
}
resource function onComplete() {
io:println("Server Complete Sending Responses.");
}
};
クライアント側は、main
にあるように Stub の orderClient
や orderBlockingClient
の Object を生成しながら実装していくことになるだろう。
● OpenAPI Specification
ballerina は、RESTful Endpoint からの OpenAPI Specification 生成、また OpenAPI Specification からのコード生成に対応している。
https://ballerina.io/learn/by-guide/open-api-based-service/
Ballerina → OpenAPI
ballerina のコードから OpenAPI Specification を生成する。
https://ballerina.io/learn/by-example/ballerina-to-swagger.html
import ballerina/http;
import ballerina/log;
listener http:Listener helloEp = new(9090);
service hello on helloEp {
@http:ResourceConfig {
methods: ["GET"],
path: "/say"
}
resource function hi(http:Caller caller, http:Request request) {
http:Response res = new;
res.setPayload("Hello World!");
var result = caller->respond(res);
if (result is error) {
log:printError("Error when responding", err = result);
}
}
}
$ ballerina swagger export .\hello_service.bal
# successfully generated swagger definition for input file - .\hello_service.bal
これで、ルートフォルダに hello_service.swagger.yaml
ができる。
Swagger UI で見てみると、
$ docker run -p 8080:8080 -v "${pwd}\hello_service.swagger.yaml:/swagger.yaml" -e SWAGGER_JSON=/swagger.yaml swaggerapi/swagger-ui
OpenAPI → Ballerina
今度は、OpenAPI Specification から Ballerina の実装 ( Service Mock / Client ) のコードを生成する。
先程生成した定義を使う。
1. Service Mock
-m
で出力するモジュールを指定する。-o
で出力先フォルダを指定することもできる。
$ ballerina swagger mock .\hello_service.swagger.yaml -m swagger_mock
# successfully generated ballerina mock service for input file - .\hello_service.swagger.yaml
$ tree swagger_mock
# swagger_mock
# ├── gen
# │ ├── hello.bal
# │ └── schema.bal
# └── hello_impl.bal
import ballerina/http;
import ballerina/log;
import ballerina/mime;
import ballerina/swagger;
listener http:Listener ep0 = new(9090);
@swagger:ServiceInfo {
title: "hello",
serviceVersion: "1.0.0"
}
@http:ServiceConfig {
basePath: "/"
}
service hello on ep0 {
@swagger:ResourceInfo {
summary: ""
}
@http:ResourceConfig {
methods:["GET"],
path:"/say"
}
resource function hi (http:Caller outboundEp, http:Request _hiReq) {
http:Response _hiRes = hi(_hiReq);
_ = outboundEp->respond(_hiRes);
}
}
public function hi (http:Request _hiReq) returns http:Response {
// stub code - fill as necessary
http:Response _hiRes = new;
string _hiPayload = "Sample hi Response";
_hiRes.setTextPayload(_hiPayload);
return _hiRes;
}
実装と API を分けるのは、gRPC と同じ考え方。
2. Client
$ ballerina swagger client .\hello_service.swagger.yaml -m swagger_client
successfully generated ballerina client for input file - .\hello_service.swagger.yaml
$ tree swagger_client
# swagger_mock
# └── gen
# ├── hello.bal
# └── schema.bal
import ballerina/io;
import ballerina/mime;
import ballerina/http;
//=====================================
//============Client Config============
//=====================================
public type helloClientConfig record {
string serviceUrl;
http:ClientEndpointConfig clientConfig;
};
//=======================================
//============Client Endpoint============
//=======================================
public type helloClientEp object {
public http:Client client;
public helloClientConfig config;
public function init(helloClientConfig config) {
http:Client httpEp = new(config.serviceUrl, config = {auth: config.clientConfig.auth, cache: config.clientConfig.cache});
self.client = httpEp;
self.config = config;
}
public function getCallerActions() returns (helloClient) {
return new helloClient(self);
}
};
//==============================
//============Client============
//==============================
public type helloClient object {
public helloClientEp clientEp;
new (clientEp) {}
public function hi() returns http:Response | error {
http:Client _hiEp = self.clientEp.client;
http:Request request = new;
// TODO: Update the request as needed
return check _hiEp->GET("/say", request = request);
}
};
使う時は、接続情報を渡した helloClientEp
を使って helloClient
を初期化みたいな感じだろう。
Deployment
既に Dockerfile 生成や については述べた が、その他に Kubernetes へのデプロイにも対応している。
https://ballerina.io/learn/how-to-deploy-and-run-ballerina-programs/
● Docker
改めてまとめると、
- Dockerfile generation
- copy local files
- Docker image generation
- Docker push support with Docker registry
- Docker-based Ballerina debug support
- Copy file support
等をコード内のアノテーションで指定ができ、ballerina build
時に実行してくれる。
● Kubernetes
Kubernetes も Docker と同様、コード内のアノテーションを解釈し、Build 時に実行してくれる。
- Kubernetes deployment
- Kubernetes service
- Kubernetes liveness probe
- Kubernetes ingress
- Kubernetes horizontal pod autoscaler
- Docker image generation
- Docker push with remote Docker registry
- Kubernetes secret
- Kubernetes config map
- Kubernetes persistent volume claim
試しに、以下で Build してみる。( 公式サンプルでは Ingress があったが、面倒なので削除した )
import ballerina/config;
import ballerina/http;
import ballerina/log;
import ballerina/mysql;
import ballerinax/kubernetes;
mysql:Client employeeDB = new ({
host:config:getAsString("database.host"),
port:3306,
name:config:getAsString("database.name"),
username:config:getAsString("database.username"),
password:config:getAsString("database.password")
});
@kubernetes:Service {
serviceType:"NodePort",
name:"ballerina-guides-employee-database-service"
}
listener http:Listener helloEp = new (9090, config = {
secureSocket:{
keyStore:{
path:"${ballerina.home}/bre/security/ballerinaKeystore.p12",
password:config:getAsString("store.key.password")
},
trustStore:{
path:"${ballerina.home}/bre/security/ballerinaTruststore.p12",
password:config:getAsString("store.trust.password")
}
}
});
@kubernetes:ConfigMap {
ballerinaConf:"./myconfig.toml"
}
@kubernetes:Deployment {
image:"employee_search_service_image:v1.0",
name:"employee_search_service",
copyFiles:[{target:"/ballerina/runtime/bre/lib",source:"./resources/mysql-connector-java-8.0.11.jar"}]
}
service records on helloEp {
@http:ResourceConfig {
methods: ["GET"],
path: "/one"
}
resource function getOne(http:Caller caller, http:Request request) {
http:Response res = new;
res.setPayload("One");
var result = caller->respond(res);
if (result is error) {
log:printError("Error when responding", err = result);
}
}
}
設定ファイルは以下。
[database]
host = "mysql-server"
name = "EMPLOYEE_RECORDS"
username = "root"
password = "root"
[store.key]
password = "abc123"
[store.trust]
password = "xyz123"
また、今回は依存するファイルとして mysql-connector-java-8.0.11.jar
も含めたいので、MySQL のサイトから取得し、.\resources
以下に配置している。
この状態で Build を行う。
$ ballerina build
# Compiling source
# hello_service.bal
# user/math:0.0.1
#
# Running tests
# user/math:0.0.1
# [pass] testFunction
#
# 1 passing
# 0 failing
# 0 skipped
#
# Generating executables
# ./target/hello_service.balx
# @kubernetes:Service - complete 1/1
# @kubernetes:Ingress - complete 1/1
# @kubernetes:Secret - complete 1/1
# @kubernetes:ConfigMap - complete 1/1
# @kubernetes:Deployment - complete 1/1
# @kubernetes:Docker - complete 3/3
# @kubernetes:Helm - complete 1/1
#
# Run the following command to deploy the Kubernetes artifacts:
# kubectl apply -f C:\sample_1219\target\kubernetes\hello_service
#
# Run the following command to install the application using Helm:
# helm install --name employee-search-service C:\sample_1219\target\kubernetes\hello_service\employee-search-service
$ tree target\kubernetes
# target/kubernetes
# └── hello_service
# ├── docker
# │ ├── Dockerfile
# │ └── mysql-connector-java-8.0.13.jar
# ├── employee-search-service
# │ ├── Chart.yaml
# │ └── templates
# │ └── hello_service.yaml
# └── hello_service.yaml
kubectl で直接扱うためのリソースが target\kubernetes\hello_service\hello_service.yaml
で、Helm の Local Charts が target\kubernetes\hello_service\employee-search-service
となる。
Docker Image
まずは Docker Images を確認すると、指定通りにイメージが作成されている。
$ docker images
# REPOSITORY TAG IMAGE ID CREATED SIZE
# employee_search_service_image v1.0 8f9a7acc559d 13 minutes ago 131MB
# ...
target\kubernetes\hello_service\docker\Dockerfile
の中身は Docker アノテーションの時とほぼ同じだ。
# Auto Generated Dockerfile
FROM ballerina/ballerina-runtime:0.990.0
LABEL maintainer="dev@ballerina.io"
COPY hello_service.balx /home/ballerina
COPY mysql-connector-java-8.0.13.jar /ballerina/runtime/bre/lib
EXPOSE 9090
CMD ballerina run --config ${CONFIG_FILE} hello_service.balx
Kubernetes resources
次に、YAML ファイルを見ていく。
大体指定したまま作成されている。
---
apiVersion: "v1"
kind: "Service"
metadata:
annotations: {}
finalizers: []
labels:
app: "hello_service"
name: "ballerina-guides-employee-database-service"
ownerReferences: []
spec:
externalIPs: []
loadBalancerSourceRanges: []
ports:
- port: 9090
protocol: "TCP"
targetPort: 9090
selector:
app: "hello_service"
type: "NodePort"
---
apiVersion: "extensions/v1beta1"
kind: "Ingress"
metadata:
annotations:
nginx.ingress.kubernetes.io/ssl-passthrough: "true"
kubernetes.io/ingress.class: "nginx"
finalizers: []
labels:
app: "hello_service"
name: "ballerina-guides-employee-database-service"
ownerReferences: []
spec:
rules:
- host: "ballerina.guides.io"
http:
paths:
- backend:
serviceName: "ballerina-guides-employee-database-service"
servicePort: 9090
path: "/"
tls:
- hosts:
- "ballerina.guides.io"
---
apiVersion: "v1"
kind: "Secret"
metadata:
annotations: {}
finalizers: []
labels: {}
name: "helloep-secure-socket"
ownerReferences: []
data: ballerinaTruststore.p12: "MIMBuTc ................<省略>"
stringData: {}
---
apiVersion: "v1"
kind: "ConfigMap"
metadata:
annotations: {}
finalizers: []
labels: {}
name: "records-ballerina-conf-config-map"
ownerReferences: []
data:
ballerina.conf: "[database]\nhost = \"mysql-server\"\nname = \"EMPLOYEE_RECORDS\"\
\nusername = \"root\"\npassword = \"root\"\n\n[store.key]\npassword = \"abc123\"\
\n\n[store.trust]\npassword = \"xyz123\"\n"
---
apiVersion: "extensions/v1beta1"
kind: "Deployment"
metadata:
annotations: {}
finalizers: []
labels:
app: "hello_service"
name: "employee-search-service"
ownerReferences: []
spec:
replicas: 1
template:
metadata:
annotations: {}
finalizers: []
labels:
app: "hello_service"
ownerReferences: []
spec:
containers:
- args: []
command: []
env:
- name: "CONFIG_FILE"
value: "/home/ballerina/conf/ballerina.conf"
envFrom: []
image: "employee_search_service_image:v1.0"
imagePullPolicy: "IfNotPresent"
name: "employee-search-service"
ports:
- containerPort: 9090
protocol: "TCP"
volumeMounts:
- mountPath: "\\ballerina\\runtime\\bre\\security"
name: "helloep-secure-socket-volume"
readOnly: true
- mountPath: "/home/ballerina/conf/"
name: "records-ballerina-conf-config-map-volume"
readOnly: false
hostAliases: []
imagePullSecrets: []
initContainers: []
nodeSelector: {}
tolerations: []
volumes:
- name: "helloep-secure-socket-volume"
secret:
items: []
secretName: "helloep-secure-socket"
- configMap:
items: []
name: "records-ballerina-conf-config-map"
name: "records-ballerina-conf-config-map-volume"
Deploy してみる。
$ helm install --name employee-search-service .\target\kubernetes\hello_service\employee-search-service
$ kubectl get pod
# NAME READY STATUS RESTARTS AGE
# employee-search-service-5f647b9cd6-pxmmx 0/1 Error 1 48s
エラーで動かない。原因究明中。
しかし、Kubernetes への対応がどの様なものかは大体理解できた。
Observable
ballerina は、メトリクス監視、分散トラッキング、ログ管理機能を標準で持っている。
https://ballerina.io/learn/how-to-observe-ballerina-code/
以降は、以下のサービスを監視することとする。
import ballerina/http;
import ballerina/log;
service hello on new http:Listener(9090) {
resource function sayHello (http:Caller caller, http:Request req) {
log:printInfo("This is a test Info log");
log:printError("This is a test Error log");
log:printWarn("This is a test Warn log");
http:Response res = new;
res.setPayload("Hello, World!");
_ = caller -> respond(res);
}
}
● メトリクス監視
メトリクス監視には、Prometheus を想定している。
$ ballerina run --observe .\hello_service.bal
# ballerina: started publishing tracers to Jaeger on localhost:5775
# Initiating service(s) in 'C:\PROGRA~1\BALLER~1\BALLER~1.0\bin\..\lib\balx\prometheus\reporter.balx'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9797
# ballerina: started Prometheus HTTP endpoint 0.0.0.0:9797
# Initiating service(s) in 'hello_service.bal'
# [ballerina/http] started HTTP/WS endpoint 0.0.0.0:9090
--observe
を付けて起動すると、メトリクス監視専用のエンドポイント ( デフォルトで Port 9797 ) を追加し、/metrics
でメトリクスが公開される。
また、設定ファイルで動作をカスタムすることもできる。
[b7a.observability.metrics]
enabled=true
reporter="prometheus"
[b7a.observability.metrics.prometheus]
port=9797
host="0.0.0.0"
今回のコードで公開されるメトリクスは以下である。
$ curl http://localhost:9797/metrics
# TYPE ballerina_scheduler_waiting_for_lock_worker_count_value gauge
ballerina_scheduler_waiting_for_lock_worker_count_value 0.0
# HELP ballerina_http:Caller_requests_total_value Total number of requests
# TYPE ballerina_http:Caller_requests_total_value counter
ballerina_http:Caller_requests_total_value{action="respond",http_status_code="200",} 1.0
# HELP http_inprogress_requests_value Inprogress Requests
# TYPE http_inprogress_requests_value gauge
http_inprogress_requests_value{resource="getMetrics",service="PrometheusReporter$$service$0",} 1.0
# HELP ballerina_http:Caller_response_time_seconds_value Response Time
# TYPE ballerina_http:Caller_response_time_seconds_value gauge
ballerina_http:Caller_response_time_seconds_value{action="respond",http_status_code="200",} 0.1376805
# HELP ballerina_http:Caller_response_time_seconds A Summary of ballerina_http:Caller_response_time_seconds for window of 60000
# TYPE ballerina_http:Caller_response_time_seconds summary
ballerina_http:Caller_response_time_seconds_mean{action="respond",http_status_code="200",timeWindow="60000",} 0.0
ballerina_http:Caller_response_time_seconds_max{action="respond",http_status_code="200",timeWindow="60000",} 0.0
ballerina_http:Caller_response_time_seconds_min{action="respond",http_status_code="200",timeWindow="60000",} 0.0
ballerina_http:Caller_response_time_seconds_stdDev{action="respond",http_status_code="200",timeWindow="60000",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="60000",quantile="0.33",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="60000",quantile="0.5",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="60000",quantile="0.66",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="60000",quantile="0.99",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="60000",quantile="0.999",} 0.0
# HELP ballerina_http:Caller_response_time_seconds A Summary of ballerina_http:Caller_response_time_seconds for window of 300000
# TYPE ballerina_http:Caller_response_time_seconds summary
ballerina_http:Caller_response_time_seconds_mean{action="respond",http_status_code="200",timeWindow="300000",} 0.13671875
ballerina_http:Caller_response_time_seconds_max{action="respond",http_status_code="200",timeWindow="300000",} 0.13671875
ballerina_http:Caller_response_time_seconds_min{action="respond",http_status_code="200",timeWindow="300000",} 0.13671875
ballerina_http:Caller_response_time_seconds_stdDev{action="respond",http_status_code="200",timeWindow="300000",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="300000",quantile="0.33",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="300000",quantile="0.5",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="300000",quantile="0.66",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="300000",quantile="0.99",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="300000",quantile="0.999",} 0.13671875
# HELP ballerina_http:Caller_response_time_seconds A Summary of ballerina_http:Caller_response_time_seconds for window of 900000
# TYPE ballerina_http:Caller_response_time_seconds summary
ballerina_http:Caller_response_time_seconds_mean{action="respond",http_status_code="200",timeWindow="900000",} 0.13671875
ballerina_http:Caller_response_time_seconds_max{action="respond",http_status_code="200",timeWindow="900000",} 0.13671875
ballerina_http:Caller_response_time_seconds_min{action="respond",http_status_code="200",timeWindow="900000",} 0.13671875
ballerina_http:Caller_response_time_seconds_stdDev{action="respond",http_status_code="200",timeWindow="900000",} 0.0
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="900000",quantile="0.33",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="900000",quantile="0.5",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="900000",quantile="0.66",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="900000",quantile="0.99",} 0.13671875
ballerina_http:Caller_response_time_seconds{action="respond",http_status_code="200",timeWindow="900000",quantile="0.999",} 0.13671875
# HELP ballerina_http:Caller_3XX_requests_total_value Total number of requests that resulted in HTTP 1xx informational responses
# TYPE ballerina_http:Caller_3XX_requests_total_value counter
ballerina_http:Caller_3XX_requests_total_value{action="respond",} 0.0
# HELP ballerina_http:Caller_inprogress_requests_value Inprogress Requests
# TYPE ballerina_http:Caller_inprogress_requests_value gauge
ballerina_http:Caller_inprogress_requests_value{action="respond",} 0.0
# HELP ballerina_http:Caller_2XX_requests_total_value Total number of requests that resulted in HTTP 1xx informational responses
# TYPE ballerina_http:Caller_2XX_requests_total_value counter
ballerina_http:Caller_2XX_requests_total_value{action="respond",} 1.0
# HELP http_requests_total_value Total number of requests
# TYPE http_requests_total_value counter
http_requests_total_value{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",} 1.0
# TYPE ballerina_scheduler_running_worker_count_value gauge
ballerina_scheduler_running_worker_count_value 0.0
# HELP ballerina_http:Caller_5XX_requests_total_value Total number of requests that resulted in HTTP 1xx informational responses
# TYPE ballerina_http:Caller_5XX_requests_total_value counter
ballerina_http:Caller_5XX_requests_total_value{action="respond",} 0.0
# HELP ballerina_http:Caller_1XX_requests_total_value Total number of requests that resulted in HTTP 1xx informational responses
# TYPE ballerina_http:Caller_1XX_requests_total_value counter
ballerina_http:Caller_1XX_requests_total_value{action="respond",} 0.0
# TYPE ballerina_scheduler_excepted_worker_count_value gauge
ballerina_scheduler_excepted_worker_count_value 0.0
# TYPE ballerina_scheduler_ready_worker_count_value gauge
ballerina_scheduler_ready_worker_count_value 0.0
# TYPE ballerina_scheduler_waiting_for_response_worker_count_value gauge
ballerina_scheduler_waiting_for_response_worker_count_value 0.0
# HELP startup_time_milliseconds_value Startup time in milliseconds
# TYPE startup_time_milliseconds_value gauge
startup_time_milliseconds_value 19697.0
# HELP http_response_time_seconds_value Response Time
# TYPE http_response_time_seconds_value gauge
http_response_time_seconds_value{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",} 0.4045906
# HELP http_response_time_seconds A Summary of http_response_time_seconds for window of 60000
# TYPE http_response_time_seconds summary
http_response_time_seconds_mean{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",} 0.0
http_response_time_seconds_max{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",} 0.0
http_response_time_seconds_min{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",} 0.0
http_response_time_seconds_stdDev{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",quantile="0.33",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",quantile="0.5",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",quantile="0.66",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",quantile="0.99",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="60000",quantile="0.999",} 0.0
# HELP http_response_time_seconds A Summary of http_response_time_seconds for window of 300000
# TYPE http_response_time_seconds summary
http_response_time_seconds_mean{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",} 0.404296875
http_response_time_seconds_max{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",} 0.404296875
http_response_time_seconds_min{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",} 0.404296875
http_response_time_seconds_stdDev{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",quantile="0.33",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",quantile="0.5",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",quantile="0.66",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",quantile="0.99",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="300000",quantile="0.999",} 0.404296875
# HELP http_response_time_seconds A Summary of http_response_time_seconds for window of 900000
# TYPE http_response_time_seconds summary
http_response_time_seconds_mean{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",} 0.404296875
http_response_time_seconds_max{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",} 0.404296875
http_response_time_seconds_min{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",} 0.404296875
http_response_time_seconds_stdDev{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",} 0.0
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",quantile="0.33",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",quantile="0.5",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",quantile="0.66",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",quantile="0.99",} 0.404296875
http_response_time_seconds{protocol="http",http_url="/metrics",resource="getMetrics",http_method="GET",service="PrometheusReporter$$service$0",timeWindow="900000",quantile="0.999",} 0.404296875
# TYPE ballerina_scheduler_paused_worker_count_value gauge
ballerina_scheduler_paused_worker_count_value 0.0
# HELP ballerina_http:Caller_4XX_requests_total_value Total number of requests that resulted in HTTP 1xx informational responses
# TYPE ballerina_http:Caller_4XX_requests_total_value counter
ballerina_http:Caller_4XX_requests_total_value{action="respond",} 0.0
今回は HTTP エンドポイントのみだが、多分他のプロトコルのエンドポイントを増やせばまた違ったメトリクスが取れるのかも知れない。
後は Prometheus の収集対象に追加するだけで、メトリクス監視が可能となる。
独自メトリクスを追加するのも簡単だ。
https://ballerina.io/learn/by-example/counter-metrics.html
https://ballerina.io/learn/by-example/gauge-metrics.html
● 分散トレーシング
分散トレーシングは OpenTracing に対応しているので、OpenTracing 準拠の Jaeger や Zipkin などを利用できる。
まずは Jaeger を立ち上げる。
$ docker run -d -p5775:5775/udp -p6831:6831/udp -p6832:6832/udp -p5778:5778 -p16686:16686 -p14268:14268 jaegertracing/all-in-one:latest
有効にするには、メトリクスと同様 --observe
を付けて起動する。
接続女王法などは設定ファイルで設定する。デフォルトで対応しているのは、Jaeger
と Zipkin
だ。
[b7a.observability.tracing]
enabled=true
name="jaeger"
[b7a.observability.tracing.jaeger]
reporter.hostname="localhost"
reporter.port=5775
sampler.type="const"
sampler.param=1.0
reporter.flush.interval.ms=2000
reporter.max.buffer.spans=1000
この状態で立ち上げ、Jaeger の画面を確認すれば、トレースができていることが確認できる。
スパンを操作するモジュールは、以下を参考に。
https://ballerina.io/learn/by-example/tracing.html
● ログ集約
ballerina では、ログの集約 ~ 可視化 プラットフォームとして Elastic Stack ( ELK + Beats ) をサポートしている。
ただ、サポートとは、標準の Logger で stdout にログ吐き出せるよ というだけのもので、それをファイルにリダイレクトしつつ Filebeats で監視して Logstash へと送るというやり方。
正直、サポートと謳っているのだから、てっきり beats 機能を標準的に持っているか、もしくは Elasticsearch に直接送信するのでは、と期待していた。
ログ収集には標準仕様がないので、Docker や Kubernetes への対応も考えると『stdout に出しておけばいいや』になるんだろう。
そのおかげで Fluentd での代用も可能だ。
サンプル体験
サンプルが多い!!
https://ballerina.io/learn/by-example/
ガイドも多い!!!!
https://ballerina.io/learn/by-guide/
もはや力尽きているので、これらはまた次の機会に。
ガイドを見ると、面白そうな Topics がいっぱいあるので、後でまた読みたい。
感想
まず思いついた感想が、とんでもないものに手を出してしまった でした。
言語仕様もマニュアルも膨大で、そして今も肥大化し続けている。BLAME! の超構造体状態。
DevOps を極めるとこうなるのか、という感想。しかし、これを使いこなせる Fullstack なソフトウェアエンジニアはどれほど居るんだろう。
参考
あとがき
※ この記事は個人の見解であり、所属する組織を代表するものではありません。