hatenob

プログラムって分からないことだらけ

Kongを試してみる

Kongとは、「スケーラブルなOSSAPIレイヤー(API GatewayAPI 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にログ転送するのを試していましたが、思わずはまったのでメモ。

環境

  • rsyslogのホスト:CentOS 6
  • rsyslogのバージョン:CentOS 6の標準バージョン(v5)

転送元のプログラム

ただログを飛ばしたいだけなので、こんなので十分。

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>

転送先のrsyslog設定

単にデフォルトの設定からコメントアウトされている以下設定を有効にするのみ。

$ModLoad imtcp
$InputTCPServerRun 514

はまったこと

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なんかを使える環境にいればよいのですが、そうでない場合には簡単な置換ツールを自作したりするわけです。
手元にJavaMavenな環境があるならこんな感じでもできそうかな、と調べたときのメモ。

前提

変数化する部分は階層を持たないこととします。
繰り返し要素が出てくるとかなら、素直に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でできそうな予感。

少し進んだ。