mike-neckのブログ

Java or Groovy or Swift or Golang

Groovy製のプロビジョニングツール infrastructor

f:id:mike_neck:20150917235151p:plain

infrastructor は Groovy でできたサーバープロビジョニングツールで、まあ、 Chef とか itamae とか Ansible の類のツールです。2018/06/07 現在のバージョンは 0.1.8 ということで、まだ開発が始まってから1年くらいの若いツールです。

github.com

この記事では infrastructor のチュートリアルをやってみます。


インストール

インストールは sdkman を使います。sdk は古いバージョンのものだと、新しい candidate の名前を判断できないので、最新版(2018/06/07 時点で 5.6.4+305)を使ってください。なお、 Java 9 以降にしているといろいろと警告がうるさいので(groovyさん…) Java 8 にしておきます。

$ sdk i infrastructor

Downloading: infrastructor 0.1.8

In progress...

######################################################################## 100.0%

Installing: infrastructor 0.1.8
Done installing!


Setting infrastructor 0.1.8 as default.

完了後、次のとおりにコマンドをうって、バージョンを確認できればインストール成功です。なお、 Java 9 以降を使うと groovy のリフレクションに関する警告がうるさいので、 Java 8 を使っています。

$ infrastructor version
version:    0.1.8
revision:   89b025d4bc710e24859d096bce0de7f2d48dcbc8
build date: 22-05-2018 13:44

チュートリアル

チュートリアルでは Docker を使います。 Docker のインストールは別途 Docker のサイトを参照してください。

infrastructor からチュートリアル用のイメージが配布されています。それをローカルに持ってきます。

$ docker pull infrastructor/ubuntu-sshd:latest

なお、このイメージの Dockerfile はこちらを参照。

github.com

で、このイメージを起動しておきます。

$ docker run -d -p 10022:22 --name infra-test infrastructor/ubuntu-sshd:latest

で、次のファイルを example.groovy という名前で作ります。

inlineInventory {
  node host: 'localhost', port: 10022, username: 'devops', password: 'devops'
}.provision {
  task name: 'install some packages', actions: {
    shell 'sudo apt update'
    ['tmux', 'mc', 'htop'].each { pkg ->
        shell "sudo apt install $pkg -y"
    }
  }
}

次のコマンドを実行します。

$ infrastructor run -f hello.groovy

実行に時間がかかりますが、次のように表示されます。

$ infrastructor run -f hello.groovy 
[INFO] running script: hello.groovy
[INFO] task: 'install some packages'
[INFO] task: 'install some packages', done on 1 node|s
[INFO] running script: hello.groovy - done

EXECUTION COMPLETE in 1 minutes, 2.437 seconds

テスト機能はないようなので、とりあえず、 Docker のインスタンスにログインして確認します。

$ docker exec -it infra-test bash
root@53aa2afc480b:/# which mc
/usr/bin/mc
root@53aa2afc480b:/# which tmux
/usr/bin/tmux
root@53aa2afc480b:/# which htop
/usr/bin/htop
root@53aa2afc480b:/# exit
exit

インストールできているようです


前から JVM で動くプロビジョニングツールがあったら本気出すと思っていたのですが、本気を出す時が来たようです。

Swift-NIO の ChannelInboundHandler/ChannelOutboundHandler のメモ

単なるメモ

f:id:mike_neck:20180529230134p:plain

Swift-NIOChannelInboundHandler/ChannelOutboundHandler を記述する際のポイント

ChannelInboundHandler

  • Netty の ChannelInboundHandler と同じく、基本的には channelRead(ctx:ChannelHandlerContext, data: NIOAny) を実装する
  • NIOAny 型は Netty での Object とに該当するが、それらの型は associatedtypeInboundIn に型変換をおこなう
    • 変換は unwrapInboundIn(_: NIOAny) -> InboundIn にておこなう
  • associatedtype で関連される型は次のものがある
    • InboundIn : channelRead で渡される型
    • InboundOut : 次の ChannelInboundHandler に渡す型
    • OutboundOut : ChannelHandlerContext に書き込む型(書き込む場合に必要)
  • ChannelHandlerContext に値を渡す場合は次のように行う
    • fireChannelRead に値を渡す場合は NIOAny を渡す. その際は wrapInboundOut(_: InboundOut) -> NIOAny にて変換する
    • write(_: NIOAny, promise: EventLoopPromise<Void>?) に値を渡す場合は NIOAny で渡す. その際 wrapOutboundOut(_: OutboundOut) -> NIOAny にて変換する.

ChannelOutboundHandler

  • Netty の ChannelOutboundHandler と同じく write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) を実装する
  • data: NIOAnyassociatedtype OutboundIn にて型を指定することで型変換をおこなう
    • unwrapOutboundIn(_: NIOAny) -> OutboundIn で変換する

class EchoInboundHandler: ChannelInboundHandler {
  typealias InboundIn = ByteBuffer
  typealias OutboundOut = String

  func channelRead(ctx: ChannelHandlerContext, data: NIOAny) {
    var buffer = unwrapInboundIn(data)
    let message = buffer.readString(length: buffer.readableBytes)
    print("message: \(message)")
    ctx.write(wrapOutboundOut(message), promise: nil)
  }
}

class EchoOutboundHandler: ChannelOutboundHandler {
  typealias OutboundIn = String
  typealias OutboundOut = ByteBuffer

  func write(ctx: ChannelHandlerContext, data: NIOAny, promise: EventLoopPromise<Void>?) {
    let message = unwrapOutboundIn(data)
    var buffer = ByteBufferAllocator().buffer(message.utf8.count)
    buffer.write(string: message)
    ctx.writeAndFlush(wrapOutboundOut(buffer), proise: nil).whenComplete({ ctx.close() })
  }
}

これを書き終えてから、公式のドキュメントがあることに気がついた…(´・ω・`)

NIO Reference

Swift Package Manager のメモ

とある理由により、 Swift Package Manager を使っているけど、どこに書いてある方法を真似しても(Swift のドキュメントですら)エラーが出てしまうので、自分用にメモを取ることにした


プロジェクトの作成

プロジェクトを作成するには swift package init コマンドを使う.

今回はアプリケーションを作りたいので、オプションに --type executable を指定する.

$ mkdir Hello-Nio
$ cd Hello-Nio/
$ swift package init --type executable
Creating executable package: Hello-Nio
Creating Package.swift
Creating README.md
Creating .gitignore
Creating Sources/
Creating Sources/Hello-Nio/main.swift
Creating Tests/
$ tree ./
./
├── Package.swift
├── README.md
├── Sources
│   └── Hello-Nio
│       └── main.swift
└── Tests

3 directories, 3 files

このときに出来上がる main.swift の中身はこんな感じ.

print("Hello, world!")

また、プロジェクトの依存関係を記述する Package.swift は次のようになっている.

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Hello-Nio",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Hello-Nio",
            dependencies: []),
    ]
)

ではビルドしてみる

$ swift build
Compile Swift Module 'Hello_Nio' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio

これでビルドできたので、試しに実行してみる.

$ ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
Hello, world!

参考URL

swift.org


AppCode に取り込み

コードを追加したいので、AppCode で取り込むために、 xcodeproj を作る

$ swift package generate-xcodeproj
generated: ./Hello-Nio.xcodeproj
$ tree ./
./
├── Hello-Nio.xcodeproj
│   ├── project.pbxproj
│   └── xcshareddata
│       └── xcschemes
│           ├── Hello-Nio-Package.xcscheme
│           └── xcschememanagement.plist
├── Package.swift
├── README.md
├── Sources
│   └── Hello-Nio
│       └── main.swift
└── Tests

6 directories, 6 files

依存ライブラリーを取り込む

依存ライブラリーを取り込んでいく.

Swift-NIO を使いたいので、 Package.swift を以下のように書き換える

// swift-tools-version:4.0
import PackageDescription

let package = Package(
    name: "Hello-Nio",
    products: [
        .executable(name: "Hello-Nio", targets: ["Hello-Nio"])
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-nio.git", from: "1.6.1")
    ],
    targets: [
        .target(name: "Hello-Nio", dependencies: ["NIO"]),
        .testTarget(name: "Hello-Nio-Tests", dependencies: ["Hello-Nio"]),
    ]
)

ポイントは次の4つ

  • // swift-tools-version:4.0 を先頭に書く. これを書かないと version が 3 と認識されてコンパイルできない
  • products: に成果物の記述をする.
    • .executable は実行可能なバイナリーを作る場合に指定する.
    • .library はライブラリーを作る場合に指定する.
    • .executable / .library の中にある targets: は後続する targets:name: から選択して記入する
  • dependencies: には依存するライブラリーのgit レポジトリーのURLとバージョンを記述する.
  • targets: にはビルドのモジュールの単位を記述する(っぽい)
    • プロダクション用には .target を指定し、中にある dependencies: には dependencies: で指定した依存パッケージの products:(.library) の名前を指定する
    • テスト用には .testTarget を使う
    • なお、上記の記述の場合、テスト用の "Hello-Nio-Tests" というモジュール用のディレクトリーがないので、 Tests ディレクトリーの下に作っておく必要がある

AppCode に外部ライブラリーを読み込ませるために、 xcodeproj を作り直す

$ swift package generate-xcodeproj

ここまでを取り込んだ状態の AppCode は次のような感じ

f:id:mike_neck:20180529082459p:plain


適当にプログラムを書く

適当に書いた

import NIO

var buffer: ByteBuffer = ByteBufferAllocator().buffer(capacity: 64)
buffer.write(string: "foo-bar")

let string: String = buffer.readString(length: buffer.readableBytes) ?? ""

print("\(string)")

ビルドする

ビルドは先程と同じ swift build で行う.

$ swift build
warning: error while trying to use pkgConfig flags for swift-nio-zlib-support: couldNotFindConfigFile
Compile CNIOLinux shim.c
Compile CNIOSHA1 c_nio_sha1.c
Compile CNIOAtomics src/c-atomics.c
Compile CNIODarwin shim.c
Compile Swift Module 'NIOPriorityQueue' (2 sources)
Compile Swift Module 'NIOConcurrencyHelpers' (2 sources)
Compile Swift Module 'NIO' (51 sources)
Compile Swift Module 'Hello_Nio' (1 sources)
Linking ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
warning: target 'Hello-Nio-Tests' in package 'Hello-Nio' contains no valid source files

そして実行してみる.

$ ./.build/x86_64-apple-macosx10.10/debug/Hello-Nio
foo-bar

というわけで、一通りビルドまでできました.


TODO

テストについて書く

JJUG CCC 2018 Spring にて発表しました #jjug_ccc

表題の通り 2018/05/26 開催された JJUG CCC にて発表してきました。

www.java-users.jp


セッションが1つとLTが1つです。

セッションの方は 「(俺が)はじめての Netty」 で、LT のタイトルは「Spring Boot アプリケーションの起動を速くする 108 の Tips」です。

資料はこちらです。

  • セッション資料

www.slideshare.net

  • LT 資料

www.slideshare.net


題材についてですが、なぜいまさら Netty を選んだか?ですが、僕はこれまで Netty と聞くと「速いサービスで使われている I/O のライブラリーね、なるほどね(理解していない)速いよね(理解していない)」という感じで、技術者としては思考停止に陥っていたので一発奮起して、かつ勉強がてら発表してみようと思って CFP を書きました。

CFP を書く頃はまだチュートリアルを一通りやって、少しだけ Channel とか ChannelHandlerByteBufjavadocを軽く読んだだけの状態で、このあたりを押さえればよいだろうと言う感じで CFP を書いていました。

ただ、実際にその程度の理解では何も発表資料が書けないことが見えてたので、 Netty In Action を購入して、 Comprehensive な知識を習得することにしました。そして、 2週間程度で Netty In Action は読み終わったのですが、その結果として僕はまだ java.nio.channels(NIO) 周りのAPI および内部実装をちゃんと理解できていないことに気づきました。さらには NIO が解決している課題をちゃんと説明できないのではないかという思いも生じていました(C10K 含めて知識としてはあっても、それを初級者がわかるように説明できないのではないか)。そこでもう少し低レイヤーのシステムコールも含めて勉強しました。そのうえで、OIO/NIO の知識を構築し直すと、Netty が何を解決しようとしているのかがわかるようになってきました。また、 NIO が抱える諸問題(といっても大したあれではない)を Netty はどのように解決するか描き出すのが良さそうなことがわかったので、これが最終的な資料の原型になったと思います。

また Netty の内容の説明を全面に押し出そうとおも思っていたのですが、レベル設定を初心者向けにしていたこともあるので、NIO の仕組みを確実に抑えた方が理解に役立つ(むしろ Netty の説明だけしてたらポカーンされる)と考えたので、半分ほどを NIO の話になっています。本当はもっと応用っぽいこともやってみたかったし、その準備もしてました(WebFlux 〜 reactor-netty のあたりも調べてた)。

以上がセッション資料を書くときに考えてたことです。

発表前に まーや さんが面白いことを言ってくれたので会場の雰囲気が軽くなりました。非常に有難かったです。

発表終了後にいくつか質問をいただきました。

  • サーバーでコネクション(ペイロード長)の設定できるのか?
    • NIO の場合、 ServerSocketChannel#<T>setOption(SocketOption<T>, T) で設定します。 SocketOption<Integer> として StandardSocketOptions#SO_RCVBUF を使います。
    • Netty の場合、 ServerBootstrap#<T>childOption(ChannelOption<T>, T) で設定します。 ChannelOption<Integer> として ChannelOption#SO_RCVBUF を使います。
  • Http で Multipart で大きなファイルを受け取る場合の実装方法
    • netty example の http あたりが参考になります -> github.com
  • Netty の http モジュールでサーブレット相当のことができるか?
    • 残念ながら、そこまではできないので、別途 reactor-netty あるいは Spring WebFlux などの上位のフレームワークを使うのがよいです

LT 発表ですが、発表待ちの最中に セバスチャン が突然僕の前でLTすることになって、若干プレッシャーがかかりました。しかし、彼のおかげで LT を聞こうとする人が増えたのでラッキーな発表順だと思いもしました。

LT の方は以前 jsug で発表した内容の焼き直しです。jsug は Spring の勉強ということもあるので、技術的にどうしてこれをやると速くなるのかという説明を入れていましたが、CCC の LT ではそのあたりは省きました。懇親会なのでお酒入っていて理解できないだろうし、ここ数回ほどJJUG CCCでLTをして得ている印象では、そのような説明よりもっとわかりやすい部分を長く強く説明したほうが笑いを取れるだろうと考えたからです。なお、結果的に笑いがとれたので、僕的には満足です。

ちなみに、このようなお言葉をいただきました。

@SpringBootApplication になった人」、いい表現ですね。 じゅくちょー ばりにそれで売っていこうかな…でも、 「俺自身が @SpringBootApplication になる」の Spring Boot アプリケーションを書くのが非常に面倒なのでどうしようかな…


その他

  • 1本しか持っていないコネクターを会社で固定で使っている状態なので ゆうすけ さんと 速水 さんにコネクターをお借りしました。大変助かりました。
  • 某お嬢さんにお会いしました。ここ1年とちょっとほどツイートしていなかったので、某有名企業やめて、Javaもやめて、何かすごいよい条件の会社で働いているのかな?ひょっとしたらドラム缶にコンクリートで埋められて東京湾に投げ込まれてないかなとか心配しましたが、特に変わらず活躍されているご様子で安心しました。
  • 職業が自身のお名前でもある 戸倉彩 さんにご挨拶できました。