Chonaso's Commentary

InternetやIT技術などについて知ったこと、試したこと、考えたことを書いていきます。

RundeckのSlack通知テンプレート更新

Rundeckはシンプルなジョブコントローラーですがプラグインによる機能拡張が可能です。

Rundeckのジョブ実行に関する通知をSlackへ飛ばすには公式GitHubにあるslack-incoming-webhook-pluginというプラグインを使用します。

インストール自体はJARファイルを設置するだけなので簡単ですが、通知内容のカスタマイズができません。

通知の内容自体は汎用性が高いのでそのままでも使えるのですが、メンションを付けたいなどの要求にはテンプレートをカスタマイズする必要があります。

このプラグインでの通知内容はFreeMarkerのテンプレートで定義されており、かつては特定の場所にテンプレートファイルを置くことで通知内容をカスタマイズできるようになっていたのですが、あるバージョンから外部テンプレート参照の機能がオミットされています。


というわけで、少々強引ではありますがJARパッケージ内にあるテンプレートファイルを上書きすることで通知内容をカスタマイズすることができました。

JARパッケージを直接変更するため、実施の際には各自の責任において実施してください。

  • 動作環境
    • CentOS7
    • rundeck-3.1.2
    • slack-incoming-webhook-plugin v1.2.5
# 作業用ディレクトリ作成
sudo -u rundeck mkdir /etc/rundeck/templates

# JARファイルからテンプレートファイルを抽出
cd /etc/rundeck/templates
sudo -u rundeck jar -xvf /var/lib/rundeck/libext/slack-incoming-webhook-plugin-X.X.X.jar templates/slack-incoming-message.ftl

# テンプレートを編集
sudo -u rundeck vi slack-incoming-message.ftl

# JARファイル内のテンプレートファイルを上書き
sudo -u rundeck jar -uf /var/lib/rundeck/libext/slack-incoming-webhook-plugin-X.X.X.jar -C /etc/rundeck templates/slack-incoming-message.ftl

Rundeckdの再起動は不要で即反映となります。

【Java8】実録 緊急JVMチューニング


発端

Amazon EC2Tomcat/Java8を運用していたとあるサービス。

サービス終了は告知済みでUUも減ってきたのでインスタンスサイズを小さくして運用費下げたいとの相談。

どのようなサイズダウンかと聞いたところ、メモリが現在31GBから9GBになります、とのこと。

この時点でEC2慣れしてる方ならピンと来るでしょう。

当時EC2のスペックなどよく知らなかった私は(ずいぶんリッチな環境で運用してたんだなぁ、最初から9GBでも十分じゃないの?てかなんで奇数?と思いつつ)とりあえず承諾。

そして当日「サイズダウンと動作確認終わりました」のお知らせを受けたのでこの件は終了、と思っていたら「一部サーバが応答しないようだ」との報告が上がってきました。

どう考えてもダウンサイズが原因でしょ、と思い色々調べてもらうと

「OutOfMemoryError出てますね😇」

まじかー、雑な運用しちまったなぁ、このサービス舐めてたわーと反省しつつ、まずはメモリ消費量の確認を依頼。

ところが調べてもらうとどうもソロバンが合いません。 メモリが枯渇するほどは使用されていませんでした。

ここで答え合わせ。 メモリサイズ(GB)として聞いていた値はECU1でした。 というわけで9GBと聞いていたメモリは本当は3.5GBでした。

さすがにこれはマズいです。

サイズ戻せば一応解決ではあるのですが、JVMチューニングで凌ぐ余地がありそうだったのでそちらで解決することにしました。


メモリ設定の確認

まず今のメモリ設定とメモリ状況をチェックします。

psコマンドでjavaコマンドラインパラメータを見れば現在のパラメータがわかります。

→ 特に指定なし

デフォルトですね(にがわら)。

$ ps auxww | grep tomcat | grep -v grep
tomcat    1348  9.0 36.9 3691336 1376316 ?     Sl   Xxx22 106:39 /usr/bin/java -Djava.util.logging.config.file=/usr/local/tomcat/tomcat8/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Djava.library.path=/usr/local/apr/lib -Dignore.endorsed.dirs= -classpath /usr/local/tomcat/tomcat8/bin/bootstrap.jar:/usr/local/tomcat/tomcat8/bin/tomcat-juli.jar -Dcatalina.base=/usr/local/tomcat/tomcat8 -Dcatalina.home=/usr/local/tomcat/tomcat8 -Djava.io.tmpdir=/usr/local/tomcat/tomcat8/temp org.apache.catalina.startup.Bootstrap start

メモリ使用状況の確認

次にメモリ使用状況。

私はjstatコマンドが好きなのでこちらを使いました。

$ sudo jstat -gc 1348
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
54272.0 54784.0  0.0    0.0   200704.0 196670.3  622592.0   622576.1  173312.0 141172.7 20736.0 16110.2     92    3.362 1767  2886.327 2889.688

主要な項目は以下の通りです。

  • OC Old領域の容量(キロバイト)
  • OC Old領域の使用量(キロバイト)
  • YGC Young領域GC(Scavenge GC)の回数
  • YGCT Young領域GCにかかった総時間(秒)
  • FGC フルGCの回数
  • FGCT フルGCにかかった総時間(秒)

ああこれはひどいですね...

YGC(YoungGC)が92回しかないのにFGC(FullGC)は4桁超えています。 明らかにメモリ不足です。

これでもある程度サービスが動いていたのだから大したものです。
(むしろ起動直後に即死してくれた方がよかった)

必要メモリを決める

ここから最善のメモリ設定を割り出していきます。

まずJavaアプリケーションが動作するために確保すべきメモリは以下の通りです。 (Linuxを想定しています)

  • OS
    • カーネルや最低限起動が必要なデーモンなど
    • Buffer/Cache
  • Java
    • heap領域サイズ
    • native領域サイズ(Metaspace, CompressedClassSpace, Cヒープなど)

OSについてはこの状況下ではBuffer/Cacheはほぼ見捨てていいかと思います。もしメモリが余っていれば優先度が低いなりにOSがうまく使ってくれるはず。 その他有象無象でどのくらいメモリが必要かはpsやtopコマンドなどから割り出してください。

Native領域

Javaについてはできる限りHeapにメモリを回したいのでNativeの見極めが重要です。

一番いい材料はJVMの各メモリ領域のサイズや使用量の変化履歴になりますが、そんなのがあるなら普通監視してますよね。

次点としては十分な期間起動していたJVMのメモリ状況があれば判断できると思います。

それもなければjmeter等で負荷かけまくった後のJVMメモリ状況が使えるのではないでしょうか。

今回は落とす前のjstatの結果がありますのでこれを使います。 Metaspaceは140MB程使われていました。余裕をもたせて256Mとします。

CompressedClassSpace(CCS)は16MBちょっとでした。アバウトで申し訳ないんですが64Mにしています。

CCSの必要サイズはヒープ内に存在するオブジェクトの数で決まります。

つまりアプリの作り方次第となってしまうため見極めが難しい部分ではあります。 可能であればオブジェクト統計情報を取得して設定値を検討したいところです。

またCompressedClassSpaceのサイズはデフォルトで1GBですが、このサイズはよほどオブジェクトが多くなければ消費できるサイズではないので設定することをお勧めします。 特に搭載メモリの少ない環境では必須2です。

他にもNative領域として確保される値はあるのですが、 これも適当で申し訳ないんですがCヒープはJNIを多用している・スタックは大量のスレッドを使用しているなどの状況でなければ設定自体は無視できるのではないかと思います。

Heap領域

jstat結果で確認したOld容量約608MBでは足りていないのは明らかです。

極力Old領域に振り分けることにしました。これでダメならメモリを増やします。

最終には以下の設定になりました。

-Xmx2560m -Xms2560m -XX:NewRatio=3 -XX:CompressedClassSpaceSize=64m -XX:MaxMetaspaceSize=256m

物理メモリサイズは3.5GB、スワップは極力させない、という前提です。

-Xmx2560m-Xms2560m ですが、Xmxはヒープの最大サイズ、Xmsはヒープの初期サイズです。 初期サイズを最大サイズにすることでJVM実行中の自動拡張のオーバーヘッドを無くします。

-XX:NewRatio=3 はYoung領域とOld領域の割合を1:3にする、という設定です。 つまりOld領域は1,920MBとなります。

ヒープが足りているかの判断

ベストな確認方法は負荷試験をすることです。 負荷試験は処理性能のネックだけではなくサーバリソースの限界を見極めるためにも重要な試験といえます。

短時間で見極めるにはヒープの消費トレンドを観察します。

Full GC頻度/時間

まずはFullGCの頻度を見ます。

「一定時間内に何回あったらダメ」といった絶対的な指標はありませんが回数が少ないに越したことはないです。

それに加えてFullGCに掛かった時間を見ます。1回あたりのFullGCが大きいとそれ自体がシステムへの負荷が大きく、アプリケーションの応答時間に影響してきます。

1回のFullGC時間が極端に大きければはメモリ不足にならない程度にOld領域を小さくする対応も視野に入れる必要があります。

Old使用量の増加トレンド

Full GCはOld領域が不足した時に発生します。 Old領域はFull GC以外では使用量は減少しません。

つまりYoung領域からOld領域へ移動するオブジェクトが多いかどうかでOld使用量の増加ペースが決まります。

オブジェクトがOld領域への移動するメカニズムはここでは割愛しますが、JVM設定で対応できるのはYoung領域のサイズとオブジェクトがOldに移動するGC回数(-XX:MaxTenuringThreshold)となります。

ちなみに筆者は -XX:MaxTenuringThreshold を設定したことはあまりないです。 そこまでシビアな運用をしたことがない、アップデートなどでコードが変わればトレンドが変わるなどが理由です。

メモリリークしていないか

手前味噌ですが、ここにまとまっています。 Javaアプリケーションサーバの監視 - Chonaso's Commentary

メモリリークしているかどうかはほぼコードの問題ですが、メモリサイズが大きかった場合はそれが顕在化していなかった可能性もあります。

Javaアプリはメモリ設定が変われば挙動が変わる(かもしれない)という覚悟をしながら運用していかないとならないですね。


その後

このサービスはチューニング以降何事もなくサービス終了を迎えたのでした。

めでたしめでたし(合掌)

追記

今回のケースの直接の原因はEC2サービスに対する誤解や検証不足ですが、メモリ(ヒープ)使用量やCPU使用率、レスポンスタイム、FullGC回数などの監視を適切に行なっていればサービス影響が発生する前に問題の検出ができた可能性が高く、この辺は猛省せねばならないところですね。

Apple Watchに複数のタイムゾーン時刻を表示させる(2019/11/1 追記)

先日海外出張で台湾へ行ってきました。 初めての海外ということもあり準備の勝手がわからない状況でしたが、現地でちょっと困ったのが時計でした。

現在時刻が分かる持ち込み機器はスマートフォン(数種)、iPadApple WatchMacbookと条件によって自動的に時刻が変わってしまうものばかり。そして日本と台湾の時差は1時間と体感上ほぼ違いがないというのが災いし「今表示されている時刻はどこの時刻なんだろう…」という状況に陥りました。

普段時計として主に使っているのはスマートフォンとその子機状態になるスマートウォッチ(Apple Watch)で、 基本的にはSIMが刺さっていればNITZによって現地時間に同期されるはず、なんですが設定をいじったりすると不意に日本時間に戻ってたりしてどうも不安です。

そこで思いついたのが「関係する複数のタイムゾーンを一覧できるようにすればよい」というものです。 iOSであれば時計アプリの世界時計で複数のタイムゾーンを同時表示できるので現在位置さえ分かっていれば現在時刻を確実に知ることができます。

f:id:chonaso:20190421235742j:plain
※台湾で取ったスクリーンショットなので端末は台湾時間です

でも毎回時計アプリを開くのは面倒です。 せっかくApple Watchを身に着けているのだからこれで時刻を確認したいものです。

フェイスの自作ができないApple Watch(2019/4/22時点)ですが、コンプリケーションを設定することで複数のタイムゾーンを表示することができましたのでその手順を残します。

海外に行かなくても普段UTCJSTの狭間で過ごしているITエンジニアにもおすすめです(笑)

手順

(1) iPhone上で表示させたいタイムゾーンを世界時計に登録する

iPhone側の時計アプリから「世界時計」を選択して表示させたいタイムゾーンを追加します。

f:id:chonaso:20190422000002j:plainf:id:chonaso:20190421235222p:plain

(2) Apple watchアプリのフェイス(文字盤)を追加する

iPhone側のWatchアプリを起動して「文字盤ギャラリー」を選択すると、追加したいフェイスが選べます。

f:id:chonaso:20190421235858p:plain

コンプリケーションが設定できるフェイスは限られており、今回は最低でも2つ以上コンプリケーションが設定できるフェイスが必要です。

この用途の場合は5コまでコンプリケーションが設定できるモジュラーがおすすめです。「追加」をポチるとマイウォッチに追加されます。

f:id:chonaso:20190422000614p:plain

(3) コンプリケーションの設定

「マイウォッチ」を選択すると追加されたフェイス(この場合はモジュラー)が並んでいます。

f:id:chonaso:20190422001112p:plain

追加されたモジュラーを選択すると、コンプリケーションが配置できます。

コンプリケーションの項目にある「世界時計:〇〇〇〇〇(←追加したタイムゾーン)」を選択すると、そのタイムゾーンの時刻がApple Watchのフェイス上に表示されるようになります。

f:id:chonaso:20190422001339p:plain f:id:chonaso:20190422001353p:plain

(4) UTCの表示

出来上がりはこんな感じです。もはやメインの時刻表示が不要な気もします。

f:id:chonaso:20190422001715j:plain

今回はUTCも追加してみましたが、UTCApple watchの言語設定が日本語だと「協定世界時」と表示されてしまいます。 特に小さい表示領域だと「協定…」と表示されるのでダサいです。

Apple Watch側の言語を英語にすると「UTC」になりました。(というか全部英語になります)

2019/11/1 追記

いつの間にか日本語でもwatch上で「UTC」表記になっていました。 (iPhoneアプリ上は「協定...」のまま)

f:id:chonaso:20191101131730p:plainf:id:chonaso:20191101131734j:plain

普段も業務上台湾とのやり取りがありますので当分Watchはこの状態で使おうと思います。

ちなみに海外渡航には普通の時計を持っていくのをオススメします。

参考 Can set my watch so I can see TWO time zones?

近況など(2018年末)

引き続き、昨年異動したグループ会社で働いています。 といいながら事実上別のグループ会社に派遣状態になっています。 資本関係がややこしく、出向先は自分の籍を置いている会社の孫会社、作業場所は自分の籍をおいている会社の子会社(最近になって子会社じゃなくなりましたが)という感じです。

昨年から取り組んでいるのはとあるスマホ向けゲームの開発です。 細かいプロジェクト体制の説明は割愛しますが共同開発という体で他社のプロジェクトへ参画しています。 プロジェクト推進のイニシアチブを持たないため事実上派遣社員のようなポジションです。

サーバサイドのプログラムを担当していましたが、新規ゲーム開発としてはかなり少人数で作ったな感がかなりあります。 どうにかサービスインに漕ぎ着けることができましたが、ローンチ直後は様々な問題が発生し、自分の力の及ばない外的要因が多々あったとはいえ最初の1週間は生きた心地がせず、久々にB2Cサービス運用で内臓をキリキリバクバクいわせる日々でした。 自分としてはなんとか早期に諸問題を収束させることはできたと思っていますが、この間プレイヤーの皆さんにもかなりご迷惑をお掛けしてしまった部分も多々あり反省しきりでした。

よく考えてみると自分のキャリアの中で他社のプロジェクトを他社内で開発するという経験は初めてでした。 新卒時はSIerでしたがすべて自社内での開発作業でしたし、その後は多くの仕様決定やほとんどの技術要件については強い影響力を持つ立場でやってきたこともあり、あまりの勝手の違いに戸惑い納得のいかない日々を過ごしました。 特にプロジェクトマネジメントに関してはフラストレーションが溜まるものではありましたが、アンチパターンの数々を身をもって知ることが出来たともいえます。 また、これまでそういう立場で一緒に仕事をしていた人たちが何を感じてきたか、ということも少し理解できた気がします。

逆にサービス運営については知らないことばかりで、今所属している会社の社長からは配属直後に「スマホゲームはリリースした時点でスタートライン、リリース以降どれだけ熱をもって盛り上げていくかが重要」的な教えをいただきましたが、いざローンチしてみるとその施策は私の知らないことばかりでした。 今回ローンチしたゲームは手前味噌ながら非常に面白いゲームだと思っていますが、ただ面白いだけでは広がらない未プレイの人に届かない、続けられるだけの要素がなければ続けてもらえない、そして基本プレイ無料でも誰かに課金してもらわないと運営が成り立たないわけで、これを決して大きくない組織で他のビッグタイトルと渡り合っていかないとならないというハードな運営が続いていきます。

前職でもそれまで知る機会もなかった営業の大変さを知ることができましたが、「こんな機能要らないだろ」と思いながら作っていたものがゲームに不可欠な要素あるいは非常に意義深いものであったことをローンチ後になって初めて気付かされることもありました。 これまでも事業会社に身を置いていた者としてある程度俯瞰できていると思っていた「事業」というものに対して自分の見識の狭さ・浅はかさを改めて痛感させられています。


この体制下での作業については1~2月には撤収、徐々に引き継ぎモードに入りつつあります。 その後はようやく自社での作業となり1年半ぶりのプロマネ業に戻る見込みです。

自分の本籍である出向元の会社はその親会社の植民地化が進み、今年は古くからの知り合い(特に管理職)の多くが会社を去っていきました。 私が戻れるところももはや無さそうです。2019年は背水の陣と思って取り組んでいきたいと思います。

JJUG CCC 2018 Fallに行ってきました #jjug_ccc

ここのところ、仕事の大半がPHPなのでJavaの話題に触れたい&刺激をもらいにJJUG CCC 2018 Fallに足を運びました。 所用があったので後半からでしたが非常に興味深い内容ばかりでした。


Deep dive into instanceof #ccc_a5

Deep dive into instanceof

広告配信システムを担当している登壇者が指摘された 「ハイパフォーマンスが求められる環境においてinstanceofは悪手、そもそもdowncastはoop的に設計が間違っているケースが多いのでは」 というレビューコメントを端緒にJavaVMのコードを追ってみた、という内容で、 単なるループで回している(辿る)わけではなく、実際にはinstanceofに耐えうるKlassのクラス設計(紛らわしい)や継承/実装の違いやツリーの深さなどでアルゴリズムを変えるなどのそれなりの考慮がなされているということでした。

Javaとは縁遠いプログラムカウンタやスタックポインタなどを知らないと即死するような内容でしたが、 デバッガを使っているとたまによく見るKlassが出てきたり、 instanceofのパフォーマンス向上についてはそれなりの紆余曲折があった*1のだな、ということを知りました。

思えば学生時代はC言語のライブラリのソースコードを読んでいたのにJVMのソースとか読む気が起きたことがなかったなぁ。 (PHPは必要にかられて読むことがある…)

ただ、あの内容だと場合によってはレビューコメント論破できる(instanceofでもいい)ケースもあるのでは?という気もしなくない(笑)


オイラ大地の18年拡張し続けているECサイトをSpring Bootとk8s on Azureでマイクロサービス化する事例 #ccc_a6

オイラ大地の18年拡張し続けているECサイトをSpring Bootとk8s on Azureでマイクロサービス化する事例

  • 品質の維持
  • ローンチスピード
  • スケーラビリティ

この3つを満たすシステムを開発・保守していくにはという問題に対し、マイクロサービスアーキテクチャでアプローチしていくことを決め、 kubernetes on Azureに辿り着いて絶賛マイグレーション中です、という内容。

自分も以前とある死にかけの会社にブチこまれて上記3課題を解決するよう命ぜられ、マルチクラウド移行やマイクロサービス(当時はこの呼び方はまだなかった)を絡めたシステム移行を手がけたので色々思うところがありました。 そしてECでJavaでモノリシックでOracleで…とこれまた構成も似てて「歴史のあるJavaシステムってなんだか知らないけどOracle多いよね!」と色々と思い出しました。

マルチクラウドあるあるとしては「サービスがクラウド越えするのでレスポンスが悪化する(下手すりゃ複数回往復する)」「これまで磨き上げたCD(継続的デリバリ)が使えない」など、マイクロサービス化あるあるとしては「トランザクションが越えられない」「今まではDB上でjoinしてた」などあり、これらの問題は、個々に少しづつ解決・移行していっているとのこと。

マイクロサービス化のフレームワークとしてのSpring bootは割愛だったのでもはやJavaとは直接関係ないセッションになっていましたが、Javaを使う会社は硬い感じがするので一度動いてるシステムに手をいれるということに難色を示す非エンジニアリング部署(ともすればシステム担当も)との云々も多いでしょうから皆感じ入るところが多かったのではないでしょうか。


GCを発生させないJVMとコーディングスタイル #ccc_a7

GCを発生させないJVMとコーディングスタイル

タイトルからすると「こういう処理はこういうコードを書けばGCは起こらない!」的な内容に見えますが、このセッションではJVM(GC)の種類やコンパイラオプションによってどのように変わるかという内容でした。

昔とあるJavaの勉強会で何度かJavaのメモリ設定やGCについての内容を発表したことがありますが、その辺とはレベルが全く異なり2つ前のセッション同様PCやSP、ニーモニックが普通に出てくる内容でした。 また、デモが多かったのですがJVMオプションを手打ちでバシバシ入れていく姿はさすがJVM開発者ですね...

恥ずかしながらTLAB(Thread-Local Allocation Buffer)やEscape Analysis(EA)は初めて知りました。 説明は非常にわかりやすかったです。 ただ、正直この動きを読みながらチューニングしたりできるかと言われると・・・今度Javaシステムのお世話をした時に妙なGC挙動があったとき思い出せればいいなと思います。

つまるところGC抑止の唯一無二の策というものは存在せずJVMの種類やインライン状況によってはGCが発生したりしなかったりするもので、コンパイラの動きを予測してトリッキーなコードを書くのではなく「まずは見やすいコードを」とのことでした。 おそらく見やすい素直なコードが日々進化していくJVM進化の恩恵を一番受けやすいということなのでしょう。


※発表スライドが公開されたら更新します