Kongを試してみる
Kongとは、「スケーラブルなOSSのAPIレイヤー(API GatewayやAPI Middlewareとも呼ばれる)」(直訳)です。
APIにおいて共通的な処理を集約してAPIのエンドポイントを提供し、バックエンドにプロキシをするプロダクトです。
ひょんなことから知る機会があったので触ってみました。
こうやって、あぁやって、、と書こうと思ったけども結局チュートリアルをやっただけでした。
インストール
Dockerイメージがすでにあるので簡単です。
もう本当に、以下のURLをなぞるだけです。
Install - Docker | Kong - Open-Source API Management and Microservice Management
何も考えずにただ打ち込むだけで無事にインストールし、起動することができました。
チュートリアル
こちらもちゃんと用意されているのでなぞるだけです。
5-minute Quickstart - v0.8.x | Kong - Open-Source API Management and Microservice Management
mockbin.comなんてサイトがあるのを知りました。
・・・以上です。
プラグインが色々あるのでその辺も試してみたいなぁと思いつつも、動いて満足した感があります。
マイクロサービスアプリケーションにする場合、1つ1つのサービスは軽量にし、共通処理を切り出した入口を用意してあげましょうということです。
ゴテゴテすると「なんかESBだね」となってしまいそうですが、それ自体が軽量だしスケールアウトもしやすいからよいのかなぁと思います。
標準ではGUIがついていなさそうなので、真面目に使うのならEnterpriseのほうがよさげ。いくらするんだろうか?
日本ではコミュニティもまだまだこれからのようですが、これからのAPI化時代に気軽に使えそうに思えました。
curlでftp
ちょっと調べたメモ。
ダウンロード
$ curl -o <file> -u <user>:<pass> --ftp-pasv ftp://<hostname>/<path>
アップロード
$ curl -T <file> -u <user>:<pass> --ftp-pasv ftp://<hostname>/<path>
パスワード秘匿
パスワードをシェルに直書きすると怒られる時のための気休め対応。
パスワードはファイルに落としておく。
$ openssl aes-256-cbc -e -base64 -pass pass:<pass> > <passfile> $ curl -u <user>:$(cat <passfile> | openssl aes-256-cbc -base64 -pass pass:<pass>) ...
あくまで、「パスワードを直書きしていない」と屁理屈をこねるための対応です。
log4j2でrsyslogにTCP転送
log4j2でrsyslogにログ転送するのを試していましたが、思わずはまったのでメモ。
転送元のプログラム
ただログを飛ばしたいだけなので、こんなので十分。
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class Main { private static final Logger log = LogManager.getLogger(Main.class); public static void main(String... args) { log.debug("debug message"); log.info("info message"); log.warn("warn message"); log.error("error message"); } }
log4j2.xmlの設定。
https://logging.apache.org/log4j/2.x/manual/appenders.html#SyslogAppenderを参考に。
<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <Syslog name="syslog" host="sysloghost" port="514" protocol="TCP" /> </Appenders> <Loggers> <Root level="trace"> <AppenderRef ref="syslog" /> </Root> </Loggers> </Configuration>
はまったこと
UDPで送れることは確認できていたので、単に「protocol=TCP」に変えるだけと油断していました。
なんでか、ファイルに出力されない。UDPだと出ているのに、TCPにした途端に出ない。。
調べてみるとSELinux周りで、514/tcpはrsh用なので、rsyslogでは601/tcpじゃないと、みたいなのを見つけたけれども、そもそもPermissiveなのでSELinux関係ない。
もちろん、iptablesは無効。
rsyslogを「rsyslog -d -n」でデバッグモード起動してみると、確かにTCPでも受信はしているけれど、その後のログ出力に至っていない。
TCP転送自体の問題か?と思って、2ノード間でのログ転送環境を用意して試したところ、ちゃんとTCP転送はされている。
なんでかなぁ・・と調べては眠くなって寝て、、を繰り返して3日目にして、ようやく解決。
結局のところ
TCP転送ができているということは、サーバ側の設定には問題なしと見て間違いない。となるとクライアント側ということになる。
とは言え、log4j2なんて枯れた部類のはずで、今さらTCP転送できないなんてことはないはず。
tcpdumpやデバッグログを見ていると、データの受信はできているようだが、ログ出力には至っていないということは、サーバ側が「ログ出力してよし」という判断に至っていないものと予想する。
つまり、終端文字的なものを受け取っていないので、サーバ側が「まだデータが来るはず」と思って待っている状態なのだろうという読みです。
確かに、ほかのAppenderでログフォーマット指定する時、わざわざPatternLayoutに「%n」で改行コード入れるよねぇ、、ということで、再度ドキュメントを確認すると「newLine」なる属性が。
そんなわけで、log4j2.xmlを以下のように変えて、無事ログ転送を確認できましたとさ。
<Appenders> <Syslog name="syslog" host="sysloghost" port="514" newLine="true" protocol="TCP" /> </Appenders>
UDPの時はなんかそれっぽく転送されていることが話をややこしくした感じです。。
Javaでtail -f
ログにエラーが記録されたら○○したい、みたいなことってよくあるので、Javaでtail -fする方法を模索中のメモ。
自分で作る
手抜きするとこんな感じ。
public static void main(String[] args) throws Exception { try (BufferedReader br = Files.newBufferedReader(Paths.get(args[0]))) { while (true) { String line = br.readLine(); if (line == null) { TimeUnit.SECONDS.sleep(1); } else { if (line.contains("ERROR")) { System.out.println(line); } } } } }
単に1行呼んで、指定の文字列にマッチしたら処理する、というもの。
ファイルの末端に達していたら、1秒待って読む、を繰り返す。
ファイルを頭から舐めてしまうので「いやそれはもういいよ」というのも拾ってしまうのがイマイチ。
あとはローテートされたらダメ。
元のファイルを読み続けてしまうみたい。
commons-ioのTailer
commons-ioにTailerという簡易なプログラムがあるのでこれを使う。
細かなオプションはさておいて、こんな感じ。
まずはリスナー。
public class ErrorListner extends TailerListenerAdapter { @Override public void handle(String line) { if (line.contains("ERROR")) { System.out.println(line); } } }
で、main処理。
public static void main(String[] args) { Tailer tailer = new Tailer(new File(args[0]), new ErrorListner()); ExecutorService es = Executors.newFixedThreadPool(1); es.submit(tailer); }
割と簡単。
ファイルを先頭から読むか、末端から開始するかは選べる。
内部的にはRandomAccessFileを使っている。
ローテートしたときの振る舞いはListnerで定義できるみたいだけれど、新しいファイルに向けなおすやり方はどう書けばよいか思いつかなんだ。
とりあえず要件としては、
- メモリ効率が良い。
- ファイルを書いている側のプロセスに影響しない。
- ローテートしても新しいファイルを見てくれる。
あたりかな。
今日はこのくらい。
Mavenで環境ごとの設定ファイルを作成する
一人Web開発シリーズも認証で行き詰ったので閑話休題的に。
はじめに
複数環境の設定ファイルを作るのに、1つのテンプレートをベースにして差分だけを変数化するというのはよくやることです。
ChefやAnsibleなんかを使える環境にいればよいのですが、そうでない場合には簡単な置換ツールを自作したりするわけです。
手元にJava+Mavenな環境があるならこんな感じでもできそうかな、と調べたときのメモ。
前提
変数化する部分は階層を持たないこととします。
繰り返し要素が出てくるとかなら、素直にMustacheとかのテンプレートエンジンを使うとか、完全に自作するとかしたほうが早いです。
用意するもの
テンプレートファイルと、変数値を記載したプロパティファイルと、pom.xmlだけです。
pom.xml src main template httpd.conf props prod.properties dev.properties
ここではhttpd.confを例にしています。
# template/httpd.conf #==================== ServerRoot /etc/httpd ServerName ${server.name} Listen ${server.port} MaxClients ${server.worker.max}
# props/prod.properties #====================== server.name=prod.example.com server.port=80 server.worker.max=250
# props/dev.properties #====================== server.name=dev.example.com server.port=8080 server.worker.max=100
pom.xml
pom.xmlはこんな感じ。
環境ごとのプロパティファイルを読み込んで、resourceで置換するだけです。
<build> <plugins> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>properties-maven-plugin</artifactId> <version>1.0.0</version> <executions> <execution> <phase>initialize</phase> <goals> <goal>read-project-properties</goal> </goals> <configuration> <files> <file>src/main/props/${target.env}.properties</file> </files> </configuration> </execution> </executions> </plugin> </plugins> <outputDirectory>${project.build.directory}/${target.env}</outputDirectory> <resources> <resource> <directory>src/main/template</directory> <filtering>true</filtering> </resource> </resources> </build>
環境の指定は実行時にプロパティとして与えることにしています。
もちろん、まじめにprofileを書いてもよいと思います。
作成
実行はこんな感じ。
mvn -Dtarget.env=prod process-resources mvn -Dtarget.env=dev process-resources
これで下のようなファイルが出来上がります。
target prod httpd.conf dev httpd.conf
中身はプロパティの値で書き換わっています。
# target/prod/httpd.conf #======================= ServerRoot /etc/httpd ServerName prod.example.com Listen 80 MaxClients 250
# target/dev/httpd.conf #======================= ServerRoot /etc/httpd ServerName dev.example.com Listen 8080 MaxClients 100
めでたし、めでたし。
コマンド一発で出来ないとか、出力ディレクトリやファイル名に融通が利かないのはまぁ手抜きなので致し方なし。
あとはこれをシェルやらでラップしてあげればまぁそれとなく使える感じになるんではなかろうか。
ま、やってることは単なる文字列置換なので、シェルが使えるならシェルでやったほうが早いんじゃないの?とかそういうのはナシで。
一人Web開発 Season.2~第5夜 認証・認可の検討
前回、HTTPサーバの検証をしたので、それに続いて認証・認可の仕組みを検討することにした。
が、正直全然分からん。。
普通のJavaEEだとJAASとかあって、web.xmlに色々書いて認証・認可ができるようになるのは前にも調べたことがある。
ユーザID/パスワードによるBasic認証であったりForm認証であったりが選択できる。
Web APIの場合はどうするんだろうかなぁと調べていたところ、OAuth 2.0が本命のように見えてきた。
OAuth自体は認可プロトコルで、この上に作られたOpen ID Connectというのが認証プロトコルとしてある模様。
IDプロバイダとサービスプロバイダ、クライアントの間で認証のやり取りをし、アクセストークンを払い出して利用可能にするらしい。
分かったのはざっくりこんなことくらい。。
この分野はとても弱いので頑張って勉強いたします。
なにか分かりやすいサンプルがあってくれると助かるなぁ。。
一人Web開発 Season.2~第4夜 HTTPサーバ探し~
HTTPで会話できるAPIコンテナを模索しておりまして、やはり馴染み深いJBossプロジェクトに落ち着きつつあり、とは言え今だAlphaのwildfly-swarmは使えないしあまりフルスタックなのは求めていない、という流れで、ひとまずJAX-RSをスタンドアロンで実行できる環境としてRESTEasyのEmbedded Container(組込コンテナ)を試してみた。
Chapter 34. Embedded Containers
仕様
URLをたたくと「hello world」と返す例のやつです。
pom.xml
今回はjunitテストで動かしているので、mainで実行するならjunitは不要。Undertowは、coreとservletが必要。(最初、servletを入れていなくてコンパイルエラーになって少し詰まった)
<dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>test</scope> </dependency> <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-core</artifactId> <version>1.3.9.Final</version> </dependency> <dependency> <groupId>io.undertow</groupId> <artifactId>undertow-servlet</artifactId> <version>1.3.9.Final</version> </dependency> <dependency> <groupId>org.jboss.resteasy</groupId> <artifactId>resteasy-undertow</artifactId> <version>3.0.13.Final</version> </dependency> </dependencies>
リソース
Applicationクラスと、実際にエンドポイントとなるリソースを作る。
@Path("hello") public class Hello { @GET @Produces("text/plain") public String say() { return "hello world"; } } @ApplicationPath("rs") public class App extends Application { @Override public Set<Class<?>> getClasses() { HashSet<Class<?>> classes = new HashSet<>(); classes.add(Hello.class); return classes; } }
リソースは普通にJAX-RSで書いて、それをApplicationのgetClassesでSetに詰めて返してあげるよい模様。
複数のクラスがある場合はここで複数addすればいいんじゃないかな。
実行
今回はテストクラスとして実装したけれど、mainでやればプロセスが上がるはず。
public class HelloTest { private UndertowJaxrsServer server; @Before public void initClass() { server = new UndertowJaxrsServer().start(); server.deploy(App.class); } @After public void stop() { server.stop(); } @Test public void test_hello() throws Exception { Client client = ClientBuilder.newClient(); String res = client.target(TestPortProvider.generateURL("/rs/hello")) .request().get(String.class); assertThat(res, is("hello world")); client.close(); } }
UndertowJaxrsServerを起動して、Applicationをdeployすればよいみたい。
これで組込コンテナが起動してREST APIのエンドポイントが起動する感じ。
サーバのポート番号とか諸々の設定までは見ていないけれど、これで最低限のAPI公開はできそう。
ただしmainにした場合はdependenciesも含めてjarにしてあげないと色々とめんどい。
これはmaven-assembly-pluginでできそうな予感。
少し進んだ。