Jenkins

2015年05月05日

このエントリーをはてなブックマークに追加

新規構築は問題が無い

自分の知る限り、1.611まではJava6で動いていた。そもそも、何故、Java6を使っていたか?それは、Jenkins環境を構築し始めた頃にまで遡る。その当時は、Java6では上手く動かなかったから。それ以降、Java7ではなく、Java6を使い続けて来た。

Java7必須になった変更点を追いかける。

Changelog | Jenkins CI より

What's new in 1.612 (2015/05/03)

  • As promised, shipping with Java7 class files. (issue 28120)

Java7必須となっている事が分かる。

Java6の場合、どうなるかは下記の通り。

検証環境

  • CentOS release 6.6
  • java-1.6.0-openjdk-1.6.0.0-11.1.13.4.el6.x86_64
Java7がインストールされてない場合
# service jenkins start
Starting Jenkins Jenkins requires Java7 or later, but you are running 1.6.0_32-b32 from /usr/lib/jvm/java-1.6.0-openjdk-1.6.0.0.x86_64/jre
java.lang.UnsupportedClassVersionError: 50.0
        at Main.main(Main.java:90)
                                                                   [  OK  ]

OK判定されているのに、実際は起動してない。

Java7をインストール
# yum install java-1.7.0-openjdk

Java6をアンインストールする必要は無い。

Jenkinsを起動
# service jenkins start
Starting Jenkins                                           [  OK  ]

無事に起動。

まとめ

1.612以降は、Java7を利用する。

あとがき

毎週定期的に入れ替えてるからこそ気づける細かな変化。




編集
@hansode at 12:00|PermalinkComments(0)TrackBack(0)

2015年03月02日

このエントリーをはてなブックマークに追加

CLI(jenkins-cli.jar)を扱った情報が少ない

今回はCLI(jenkins-cli.jar)を使ってジョブをbuildしてみる。軽く検索した限りでは欲しい日本語情報が見つからなかったので、探していた人にとっての有用情報になってくれたなら、幸いである。

CLI(jenkins-cli.jar)セットアップ手順は、以下のエントリ。

検証環境

  • CentOS release 6.6
  • jenkins-1.599-1.1.noarch
  • java-1.6.0-openjdk-1.6.0.0-11.1.13.4.el6.x86_64

CLI(jenkins-cli.jar)でbuildする方法

http://127.0.0.1:8080/cli/command/buildより:

java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ build JOB [-c] [-f] [-p] [-r N] [-s] [-v] [-w]
Starts a build, and optionally waits for a completion.
Aside from general scripting use, this command can be
used to invoke another job from within a build of one job.
With the -s option, this command changes the exit code based on
the outcome of the build (exit code 0 indicates a success)
and interrupting the command will interrupt the job.
With the -f option, this command changes the exit code based on
the outcome of the build (exit code 0 indicates a success)
however, unlike -s, interrupting the command will not interrupt
the job (exit code 125 indicates the command was interrupted)
With the -c option, a build will only run if there has been
an SCM change
 JOB : Name of the job to build
 -c  : Check for SCM changes before starting the build, and if there's no
       change, exit without doing a build
 -f  : Follow the build progress. Like -s only interrupts are not passed
       through to the build.
 -p  : Specify the build parameters in the key=value format.
 -s  : Wait until the completion/abortion of the command. Interrupts are passed
       through to the build.
 -v  : Prints out the console output of the build. Use with -s
 -w  : Wait until the start of the command

オプション指定方法を除くと、基本は

java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ build JOB

である事が分かる。

基本編

ここからは基本的なbuildを順に試して行く。

基本1: オプションなし/ジョブ登録(予約)

ジョブ登録(予約)である。何故、予約なのか?それは、CLIがジョブの結果を待たないからだ。これはWebUIからbuildするのと同じである。WebUIの操作に慣れている場合は、いつもの操作であるはずだ。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing

ジョブ登録が成功した場合は何も表示されず、凄くUNIXらしい振る舞いをする。終了コードもUNIXらしい振る舞いをする。成功時の$?0で、失敗時はnon 0だ。 ‘

基本2: -w/ジョブが開始するまで待つ

ジョブ予約するだけでなく、ジョブが起動するまでは待ちたい場合もある。そんな時は、-wオプションの出番だ。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -w
Started testing #39

Started <JOB> #<build-ID>が表示され、コマンドが正常終了する。

基本3: -s/ジョブの実行結果を待つ

ジョブが予約・起動するのを確認するだけでなく、ジョブの結果が出るまで待ちたい場合もある。そんな時は、-sオプションの出番だ。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -s
Started testing #40
Completed testing #40 : SUCCESS

先程の-wオプションによる実行結果に加え、Completed <JOB> #<build-ID>: <build>成否が表示され、コマンドが正常終了する。

基本4: -s -v/ジョブの実行結果を待ち、buildのconsoleログを表示

-sオプションで分かるのは、buildの成否までだ。しかし、buildのconsoleログが表示されない。デバッグ時やJOB登録直後の調整時にはconsoleログを表示したくなる。そんな時は、-vオプションの出番だ。-vは単体では無く-sと一緒に使う事が期待されている。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -s -v
Started testing #41
Started from command line by hansode
Building remotely on ct103.ci03.dh (centos-6.4 vz.vm vz.kemumaki fpmbox rbenv) in workspace /var/lib/jenkins/workspace/testing
 > git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > git config remote.origin.url https://github.com/axsh/wakame-vdc.git # timeout=10
Fetching upstream changes from https://github.com/axsh/wakame-vdc.git
 > git --version # timeout=10
 > git fetch --tags --progress https://github.com/axsh/wakame-vdc.git +refs/heads/*:refs/remotes/origin/*
 > git rev-parse origin/master^{commit} # timeout=10
Checking out Revision ed54f8de7e72f8902700fff011a26398790bf710 (origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f ed54f8de7e72f8902700fff011a26398790bf710
 > git rev-list ed54f8de7e72f8902700fff011a26398790bf710 # timeout=10
 > git tag -a -f -m Jenkins Build #41 jenkins-testing-41 # timeout=10
[testing] $ /bin/bash -xe /tmp/hudson9218072417606499332.sh
+ date
Sun Mar  1 22:53:02 JST 2015
Finished: SUCCESS
Completed testing #41 : SUCCESS

-sオプション単体指定時よりも出力結果が多い事は一目瞭然だ。

基本編のまとめ
  1. オプションなし: ジョブ登録のみ
  2. -w: ジョブ登録、そしてジョブ起動するまで待つ
  3. -s: ジョブ登録・ジョブ起動、そしてジョブ成否が出るまで待つ
  4. -s -v: ジョブ成否が出るまで待ち、更にconsoleを出力

応用編

次に応用編を試して行く。

応用1: -p KEY=VALUE/パラメタつきbuild

ジョブの中には、パラメタ化してあるものもあるはずだ。常にデフォルト値で良い訳が無い。そんな時は-p KEY=VALUEの出番だ。以下の例ではビルド結果表示を分かりやすくするために-wを指定している。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -w -p GIT_BRANCH=ci-fail
Started testing #80

もしも存在しないパラメタを指定すると、その旨を表示して教えてくれる。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -w -p GIT_BRANCH2=ci-fail
'GIT_BRANCH2' is not a valid parameter. Did you mean GIT_BRANCH?

似たパラメタが存在する場合は、なんと、似たパラメタ名を教えてくれている!

応用2: -c/ソースコードに変更がある場合のみbuild

build命令を受け取ると、Jenkinsはソースコードに変更があろうともなかろうともbuildする。時には変更がある場合のみbuildした場合もある。そんな時は、-cオプションの出番だ。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -c
Using strategy: Default
[poll] Last Built Revision: Revision e6cc9351e46bc8d062d5b7d27d18cba366e138bf (origin/ci-fail)
 > git --version # timeout=10
 > git ls-remote -h https://github.com/axsh/wakame-vdc.git # timeout=10
[poll] Latest remote head revision on origin/ci-fail is: e6cc9351e46bc8d062d5b7d27d18cba366e138bf - already built by 80

この結果では、already built by 80と表示され、buildされない事が分かる。もしも疑わしい場合は再度-cオプション指定で実行してみると良い。何度実行しても同じbuild番号が出力されるので、buildされてない事を確認出来る。

応用3: -f/ジョブの中断を無視?

-fオプション指定で実行した場合、コマンドを中断した場合でも、ジョブを中断しないように制御出来るらしい。それに対し、-sオプションの場合はコマンドを中断すると、それに応じてジョブを中断するようである。

ここで曖昧な表現になっているのは、-f-sの違いを明確に確認出来なかった為である。今後もしも明確な差を得られた場合は、この内容を加筆・修正する。

応用編のまとめ
  1. -p KEY=VALUE: パラメタ付きbuild
  2. -c: 変化がある時のみbuild
  3. -f: ジョブ中断を無視?

参考文献

補足:終了コードの扱い

コマンド成否は、コマンドの終了コードを使って判断可能である。その為、パイプと組み合せたコマンドライン操作やシェルスクリプトとの相性は良い。

ジョブが正しく失敗する場合

意図的にジョブが失敗するように設定を変更してみる。確認したいのは、出力結果と終了コードだ。実行し、結果を確認してみる。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa build testing -s -v
Started testing #94
Started from command line by hansode
Building remotely on ct103.ci03.dh (centos-6.4 vz.vm vz.kemumaki fpmbox rbenv) in workspace /var/lib/jenkins/workspace/testing
 > git rev-parse --is-inside-work-tree # timeout=10
Fetching changes from the remote Git repository
 > git config remote.origin.url https://github.com/axsh/wakame-vdc.git # timeout=10
Fetching upstream changes from https://github.com/axsh/wakame-vdc.git
 > git --version # timeout=10
 > git fetch --tags --progress https://github.com/axsh/wakame-vdc.git +refs/heads/*:refs/remotes/origin/*
 > git rev-parse origin/master^{commit} # timeout=10
Checking out Revision ed54f8de7e72f8902700fff011a26398790bf710 (origin/master)
 > git config core.sparsecheckout # timeout=10
 > git checkout -f ed54f8de7e72f8902700fff011a26398790bf710
 > git rev-list ed54f8de7e72f8902700fff011a26398790bf710 # timeout=10
 > git tag -a -f -m Jenkins Build #94 jenkins-testing-94 # timeout=10
[testing] $ /bin/bash /tmp/hudson2042532681956823107.sh
+ hoge
/tmp/hudson2042532681956823107.sh: line 5: hoge: command not found
Build step 'Execute shell' marked build as failure
Finished: FAILURE
Completed testing #94 : FAILURE

ジョブは完了し、結果はFAILURE

$ echo $?
2

そして終了コードは2non 0だ。

終了コードのまとめ
  1. 成功時: 0
  2. 失敗時: non 0

失敗時の終了コードは、失敗内容によって変化する。

あとがき

CLI(jenkins-cli.jar)を使ってみて分かったのは、curlコマンドなどによるRemote API経由操作よりも、細かな操作が可能である事だ。認証プラグインとの組み合わせ課題をきっかけに検証を始めた訳だが、今の所は使ってみて良かったと思える。もしかしたら、近いうちに他のコマンドも試してみるかも知れない。




編集
@hansode at 10:30|PermalinkComments(0)TrackBack(0)

2015年03月01日

このエントリーをはてなブックマークに追加

「変態Jenkins」の裏側を少しずつ公開

ChatOps導入検討にて、WebUIを介さずにJenkinsジョブ操作をする必要性が高まった。手元のJenkinsさんが世に出回ってる様な設定・構成であれば問題はないのだが、世の中のそれとは違う為に遭遇したであろう事を、ここにまとめておく。

背景・経緯

Jenkins設定にて:

  1. グローバルセキュリティの設定を有効化
  2. アクセス制御にはGithub Authentication pluginを使用
    1. 深く掘り下げてないが、結果としてGitHub Authentication pluginを使用していると、API Tokenを利用できない。
  3. WebUI経由でしかジョブ実行する手段がないのか…と調べた結果、Jenkins CLIとCredentialの組み合せに辿り着いた。
おまけ:GitHub Authentication plugin有効時のTokenによるAPI呼び出し失敗結果

Jenkins CLI導入前は、Remote APIをAPI Token付きで使用する事を検討・検証していた。しかし、この検証ではGitHub Authentication pluginとの組み合わせ問題に遭遇した。参考までにGitHub Authentication pluginを有効にしたJenkinsにHTTPリクエストしてみると、こうなる。

$ curl curl -fsSkL -X POST -u hansode:******** http://127.0.0.1:8080/job/testing/build
curl: (22) The requested URL returned error: 400 Nothing is submitted

調査・検討した結果、自分はJenkins CLIを使う事にした。

Jenkins CLIとCredentialを使って動かす

Jenkins CLI - 日本語 - Jenkins Wikiを参考に設定して行く。

1: CLIの取得

CLIの取得 Jenkins CLIは jenkins.war の中に、jarファイルとして含まれています。http://yourserver.com/cli からダウンロードすることができます。CLI jarはJenkinsのバージョンに依存していますが、実際には異なるバージョン間でも互換性を保つことができるようになっています。

このように書かれている。手元のJenkinsUI(http://yourserver.com/cli)にアクセスしてみると、以下の様なページが表示される。ページ内にはjenkins-cli.jarダウンロードURIが存在するので、ダウンロードURIをクリックし、手元にダウンロードする。

jenkins-cli

2: Credentials(証明書) を使って動かす (1.419 以降)

Credentials(証明書) を使って動かす (1.419 以降) Jenkinsの利用に認証が求められる場合は、公開鍵認証を設定する必要があります。Web UIからログインし、 http://yourserver.com/me/configure にアクセスして下さい。そして、公開鍵を所定のテキストエリアに入力します。

http://yourserver.com/me/configureにアクセスすると、以下の様な個人設定画面が表示されるので、SSH Public Keysに公開鍵を貼り付けて、保存する。

jenkins me config

3: 秘密鍵指定で認証確認

先程ダウンロードしたjenkins-cli.jarを使い、who-am-iコマンドを実行してみる。認証が成功すると、以下の様にユーザー名が表示される。

$ java -jar jenkins-cli.jar -s http://127.0.0.1:8080/ -i /path/to/id_rsa who-am-i
Authenticated as: hansode
Authorities:
  authenticated

hansodeとして認証された事が分かる。これ以降は好みのコマンドでJenkinsを操作可能だ。

あとがき

やや不本意な状況で辿り着いたCredentialを使ったCLIではあるが、意外と使い勝手が良さそう。




編集
@hansode at 16:00|PermalinkComments(0)TrackBack(0)

2015年01月24日

このエントリーをはてなブックマークに追加

新規構築は問題が無い

気にすべきは、マイグレーションが実行されるので、ビルドログが多い環境ほど注意が必要となる事。いつものアップグレード時間よりも長く待たされる事を、承知した上でメンテナンスを行えたなら 、言う事は無い。なお、手元のJenkinsクラスタでは、1.596から1.597へのアップグレードは問題なくマイグレーションが完了した。

ハイライトを追いかける。

Changelog | Jenkins CI より

What's new in 1.597 (2015/01/19)

  • JENKINS_HOME layout change: builds are now keyed by build numbers and not timestamps. See Wiki for details and downgrade. (issue 24380)

JENKINS_HOMEのレイアウトを変更したとの事。

レイアウト変更により、buildIDとbuild時刻の関係が逆転

[#JENKINS-24380] Use build numbers as IDs - Jenkins JIRA より

Before:

% ls -la
total 16
drwxrwxr-x 4 kohsuke kohsuke 4096 Sep 15 17:51 .
drwxrwxr-x 4 kohsuke kohsuke 4096 Sep 15 17:51 ..
lrwxrwxrwx 1 kohsuke kohsuke   19 Apr 18 10:42 1 -> 2014-04-18_10-42-35
lrwxrwxrwx 1 kohsuke kohsuke   19 Sep 15 17:51 2 -> 2014-09-15_17-51-46
drwxrwxr-x 2 kohsuke kohsuke 4096 Apr 18 10:42 2014-04-18_10-42-35
drwxrwxr-x 2 kohsuke kohsuke 4096 Sep 15 17:51 2014-09-15_17-51-46
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastFailedBuild -> -1
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:51 lastStableBuild -> 2
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:51 lastSuccessfulBuild -> 2
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastUnstableBuild -> -1
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastUnsuccessfulBuild -> -1

After:

% ls -la
total 16
drwxrwxr-x 4 kohsuke kohsuke 4096 Sep 15 17:52 .
drwxrwxr-x 4 kohsuke kohsuke 4096 Sep 15 17:51 ..
drwxrwxr-x 2 kohsuke kohsuke 4096 Apr 18 10:42 1
drwxrwxr-x 2 kohsuke kohsuke 4096 Sep 15 17:51 2
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:52 2014-04-18_10-42-35 -> 1
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:52 2014-09-15_17-51-46 -> 2
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastFailedBuild -> -1
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:51 lastStableBuild -> 2
lrwxrwxrwx 1 kohsuke kohsuke    1 Sep 15 17:51 lastSuccessfulBuild -> 2
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastUnstableBuild -> -1
lrwxrwxrwx 1 kohsuke kohsuke    2 Apr 18 10:39 lastUnsuccessfulBuild -> -1
  • 以前は、日付が実態で、build番号がsymlink
  • 今後は、日付がsymlinkで、build番号が実態

この仕様変更によるマイグレーション処理が、システムアップグレード時(プロセス起動時)に発生する。今までのbuildログが多く残されていれば多く残されている程、マイグレーションに時間がかかる。

念の為、アップグレード前にJENKINS_HOMEをバックアップしておいた方が良いかも知れない。それと、事前にbuildログをある程度掃除しておいた方が良いかも知れない。

migration進捗は/var/log/jenkins/jenkins.logで確認可能

migration進捗が気になる場合は/var/log/jenkins/jenkins.logtail -fしておくと良い。過去のbuild番号の数だけdoMigrateが記録されて行く。

Jan 24, 2015 4:08:11 PM jenkins.model.RunIdMigrator doMigrate
WARNING: found no build.xml in 2014-03-06_16-55-39
Jan 24, 2015 4:08:11 PM jenkins.model.RunIdMigrator doMigrate
WARNING: found no build.xml in 2014-01-10_16-56-22
Jan 24, 2015 4:08:11 PM jenkins.model.RunIdMigrator doMigrate
WARNING: found no build.xml in 2013-10-23_16-56-23
Jan 24, 2015 4:08:11 PM jenkins.model.RunIdMigrator doMigrate
WARNING: found no build.xml in 2014-06-16_16-56-47
Jan 24, 2015 4:08:11 PM jenkins.model.RunIdMigrator doMigrate
WARNING: found no build.xml in 2013-12-22_16-56-22

おまけ:buildログ定期削除スクリプト

参考までに、自分が使用しているbuildログ定期削除スクリプト。これを日次実行させている。Jenkinsの定期実行ジョブとして。

#!/bin/bash
#
# requires:
#  bash
#
set -e
set -o pipefail

LANG=C
LC_ALL=C

threshold=+$((30 * 2))

function disk_usage() {
  df -h
  df -k
}

disk_usage

for target_dir in /var/lib/jenkins/jobs/*/builds; do
  [[ -d "${target_dir}" ]] || continue
  echo ... ${target_dir}

  while read line; do
    [[ -f "${line}" ]] || continue
    rm ${line}
  done < <(find ${target_dir} -type f -mtime ${threshold})
done

disk_usage
  • threshold=+$((30 * 2))

thresholdで直近60日分を残すように指定している。この文字列は、findコマンドの引数として使用されるので、+に意味があるので、+を決して忘れぬように。

あとがき

Jenkinsコアパッケージアップグレードは、細目に実施した方が良い。




編集
@hansode at 18:00|PermalinkComments(0)TrackBack(0)

2014年12月13日

このエントリーをはてなブックマークに追加

Wakame-vdc / OpenVNet Advent Calendar 2014 12/13担当 (3回目)

enter image description here

引き続き、開発の話は、開発担当者にお任せし、自分はCI環境周りのお話。

Wakame-vdc/OpenVNetのCI/CDを支えるJenkinsクラスタ

気付いたら結構な大きさに成長していた手元のJenkinsクラスタ。今回はJenkinsクラスタの一部を紹介。

jenkins ci

JenkinsCIクラスターSlaveノード一覧: 全36ノード

2014/12/13時点のSlaveノードをキャプチャしたもの。

ノード  Jenkins

JenkinsCIクラスターの構成図

Slaveノード一覧キャプチャを見る限りでは、フラットな構成に見えている。クラスタ構成を図に書き出してみると、下記の様になる。

wakame-ci-jenkins-blog

※クリックで拡大(少々見づらいので改善予定)

ノードの色の意味

  • 緑: Jenkins-Master
  • 白: Jenkins-Slave
  • 灰: 多段SSH用踏み台

ノードの分類

  • 物理
    • ci01.dh (nested kvm host)
    • ci02.dh (nested kvm host)
    • ci03.dh (nested kvm host)
    • phys023 (nested kvm on lxc host)
    • phys024 (nested kvm on lxc host)
    • phys025 (nested kvm on lxc host)
    • phys026 (nested kvm on lxc host)
    • phys027 (nested kvm on lxc host)
    • opty (踏み台)
  • KVM
    • master
    • kemumaki12 x2
    • kemumaki13 x2
    • vzkemumaki20 x3
    • lxckemumaki21
    • kemu50 (踏み台)
    • kemu51
    • dsv-fgw01 (踏み台)
    • stg-muscle01-01
    • stg-jenkins01-01
  • LXC Container for KVM Host
    • phys024a (nested kvm host)
    • phys024b (nested kvm host)
    • phys025a (nested kvm host)
    • phys026a (nested kvm host)
    • phys026b (nested kvm host)
    • phys026c (nested kvm host)
    • phys027a (nested kvm host)
  • OpenVZ Container
    • ct101 x3
    • ct102 x3
    • ct103 x3
  • LXC Container
    • lxc101
    • lxc102
    • lxc103

使ってるサーバ仮想化技術

これらはWakame-vdcで使っているもので、個人的には使い慣れた技術の一部。時にはJenkinsクラスタで試験運用し、Wakame-vdcへ反映する事もある。

大半が使い捨て環境

仮想化してあるノードに関しては週1程度の周期で入れ替えを実施。
入れ替えタイミングは、

  • JenkinsCIがリリースされた時
  • OpenVZのvkernelがリリースされた時
  • セキュリティパッチ

1人でメンテナンス、作業時間は5分程度。

各種工程をスクリプト化・自動化してるので、一人で面倒見切れる状況。入れ替え作業は、手動実施で、作業時間は5分程度。作業と言っても、入れ替えスクリプトを実行するだけである。簡単な作業なので自動入れ替えすれば良いのだけども、今の所は検討段階。

あとがき

『こんなJenkinsの使い方をしてる人は居ない。』そんな事を何度か言われた事がある。しかも、変態を見るようなに。そんな経緯があり、愛着を込めて『変態Jenkins』と呼んでいる。




編集
@hansode at 10:30|PermalinkComments(0)TrackBack(0)