Jenkinsのconfig.xmlや各ジョブのjob.xmlをバージョン管理したい人はSCM Sync configuration pluginを使いましょう

公式サイトに Keeping your configuration and data in Subversion | Jenkins CI というチュートリアルがありますし、設定ファイルをSCMにぶちこむジョブをmasterノード限定で動かすのが(俺の中で)当たり前みたいな空気でしたが、今の人はこれ使いましょう。

SCM Sync configuration plugin - Jenkins - Jenkins Wiki

Jenkinsの設定を変更した瞬間にコミットされます。

昔試した時はなんか動きが怪しいので使用を断念してしまいましたが、今動かしている限りではきちんと動いているようです。(Subversion環境で確認。SCMの内容でJenkinsの設定をリロードする機能は未検証。)

現状だとディスク使用量のプラグイン関連のガシガシ変更されるXMLまでコミットされるのがウザいですが、まあお手軽だし、多少はね?

2014/05/14追記

なんかジョブのIDをリネームすると動きがエラーログを吐き始めて不安定になるっぽい。

DockerでJenkinsサーバ(master/slave)を構築してみる

概要

タイトルどおり、DockerでJenkinsをマスター・スレーブ構成で作成します。

f:id:knjname:20140503205510p:plain

読者はDockerとJenkinsをちょっとずつやったことがある人を想定しています。

このエントリで使っているソースは全てGithubリポジトリにて入手可能です

どうやってやるか

DockerでJenkinsのマスターのイメージとスレーブのイメージをそれぞれ作って、それぞれ単純に立ち上げる。

マスターのイメージについては、おそらく1インスタンスしか立ち上げないが、スレーブについてはNインスタンス立ち上げるイメージ。

Dockerでやるメリットとデメリット

メリット

  • すぐに使い捨てスレーブ作れるよ。Dockerがインストールされていればそこにスレーブ作れるよ。
  • Jenkinsマスターも量産しようと思えばできるよ。
  • ビルドにどんなツールが必要なのかDockerfile見りゃわかるよ。

デメリット

素でやるのに比べたらまじで色々とめんどい。

Jenkins構成概要

マスターとスレーブ(1つ以上)にわけて構成します。どんなに小規模であってもそうします。

マスターノード

マスターノードは基本的に何もビルドさせません。また、ビルドに必要なツールを持つ必要すらありません。

初期はJavaとJenkinsだけインストールしてある状態ですね。

下記の役割を受け持ちます。

  • Webインタフェースの提供
  • プラグイン・設定・ビルド履歴の保持(永続情報の保持)
  • スレーブの管理

スレーブノード

スレーブノードが基本的にビルドを行います。

よって、ビルドに必要なツール(Antとかgccとか)やパッケージはスレーブで持ちます。

こうすることにより下記のメリットがあります。

  • マスターノードに依存してしまう、ジョブの作成を防ぐ
  • スレーブノードを好きなマシンに作成することができる

今回はDockerで構成するのでスレーブの複製・使い捨ても簡単です。

SSHでスレーブを構成します。

Dockerfile構成概要

上記の構成にあわせ、Dockerfileはマスターノード用とスレーブノード用にわけます。

マスター用Dockerfile

マスター用Dockerfileは下記要件を満たしている必要があります。

  • JDKインストール済みであること
  • Jenkinsインストール済み+立ち上がっていること
  • 細かい設定が環境変数で指定可能であること (→ここらへんは後述のrun.shにて実現しています)
    • 例)メモリetc JVMオプションが指定可能であること。
      • 言うまでもなく必須条件ですね。
    • 例)JNLPポート
      • → Docker上のJenkinsにWindowsスレーブ追加する場合はJNLPポートが固定かつ外部から接続可能である必要になる場合が多いはずです。そしてこのポート番号は基本的には内外一致する必要があります。(トンネル接続オプションを使えばたぶん回避可能。)そしてそのポート番号はconfig.xmlに書いてあります。これはできれば環境変数で指定できるとクールです。
    • 例)Jenkinsのログの出力場所
    • 例)JenkinsのURLのprefix
      • http://your_host/prefix にJenkinsを起動させたい場合も多いかと思います。(フロントにnginxをかませる場合など)これも環境変数で指定できるとクールです。

ログフォルダやJENKINS_HOME(ビルド履歴、プラグインなどが入るディレクトリ)は全て外部ボリュームとしてdocker run時に指定し、永続化させます。

(外部ボリュームを指定したほうがIOパフォーマンスは出るようです。 参考:LXCベースのDockerゲストマシンとホストマシンのディスクI/O性能を比較検証(Bonnie++編) - Y-Ken Studio))

スレーブ用Dockerfile

スレーブ用Dockerfileは下記要件を満たしている必要があります。

  • JDKインストール済みであること
  • ビルドに必要なツールをインストール済みであること
  • Open SSHサーバインストール済み+立ち上がっていること。
    • マスターが使用する公開鍵のログインを許容すること

今のところLinux以外のスレーブは想定してませんが、Linuxで多彩なスレーブが必要であればスレーブ用Dockerfileのバリエーションを作っておくことにより、色々なビルド要件に合わせることが可能です。

スレーブについて、起動時、ワークスペースを外部ボリュームにするかは任意ですが、ワークスペースを再構成するためのコスト(チェックアウトしなおしetc)のパフォーマンスが気になる人は外部ボリュームにしておきましょう。

Dockerfile実装例

マスター用Dockerfile

ここではknjname/jenkins-masterとしてDockerイメージをビルドします

基本的には、JDKSSHクライアント(多分要らない)入れ、SSH鍵をローカルに持っておいて(Jenkinsの設定次第では不要)、Jenkinsをインストールして、run.shという起動用のスクリプトファイルをコピーしているだけです。

起動用のrun.shというスクリプトについて

Dockerコンテナ起動時の環境変数で色々とJenkinsの挙動を制御できるようにしたいので、Dockerから叩くためのbashシェルスクリプトファイルとしてrun.shを作成し、追加しています。

(今回はもともとJenkinsのrpmに入っているサービススクリプト用ファイルをベースに自分の都合のいいように書き換えました。)

こういう便利な起動スクリプトが出来てしまうのは、Docker界隈ではよくあることです。

下記オプションが環境変数で指定可能です。-eで実行時に指定して下さい。(今回はJNLPポートしか指定しません)

: ${JENKINS_WAR:="/jenkins/bin/jenkins.war"}
: ${JENKINS_HOME:="/jenkins/home"}
: ${JENKINS_LOG_DIR:="/jenkins/logs"}
: ${JENKINS_URL_PREFIX:="/"}
: ${JENKINS_JNLP_PORT:=""}
: ${JENKINS_JAVA_CMD:="java"}
: ${JENKINS_USER:="root"}
: ${JENKINS_JAVA_OPTIONS:="-Djava.awt.headless=true"}
: ${JENKINS_PORT:="8080"}
: ${JENKINS_LISTEN_ADDRESS:=""}
: ${JENKINS_HTTPS_PORT:=""}
: ${JENKINS_HTTPS_LISTEN_ADDRESS:=""}
: ${JENKINS_AJP_PORT:="-1"}
: ${JENKINS_AJP_LISTEN_ADDRESS:=""}
: ${JENKINS_DEBUG_LEVEL:="5"}
: ${JENKINS_ENABLE_ACCESS_LOG:="no"}
: ${JENKINS_HANDLER_MAX:="100"}
: ${JENKINS_HANDLER_IDLE:="20"}
: ${JENKINS_ARGS:=""}

スレーブ用Dockerfile

ここではknjname/jenkins-slaveとしてDockerイメージをビルドします

基本的には、JDKとOpenSSHサーバ、他ビルドに必要なツールをインストールして、マスターノードをそのままSSHログインさせるためのauthorized_keysとその他(多分いらない)を持ち、最後にSSHDサーバを起動しているだけですね。

動かしてみよう

マスターノード

下記のコマンドで動かします。

-p ~~:~~ でDockerコンテナ内外のポートを結びつけています。8080でJenkinsのウェブインタフェース、10080でJNLPポートにバインドする構成です。

-e JENKINIS_JNLP_PORT=やらの指定でJenkinsの細かいオプションを指定しています。今回はJenkinsのJNLPポートを指定してみました。

-v ~~:~~ のところで、永続化のためにログディレクトリやJENKINS_HOMEのための外部ディレクトリ(ボリューム)を指定しています。

立ち上がるとブラウザでアクセス可能なはずです。

f:id:knjname:20140503171338p:plain

これにスレーブの設定を追加したいところですね。

スレーブノード

下記のコマンドで動かします。

今回は特に外部ボリュームでスレーブのワークスペースは外部指定しませんでしたが、パフォーマンスを考えると-v どっか適当なディレクトリ:/jenkins/ws したほうがいいです。

マスターとスレーブを連動させる

今までの手順を踏むと docker ps は下記に近い状態になっているはずです。

root@ubuntuserver:~/jenkins-docker-example/master# docker ps
CONTAINER ID        IMAGE                           COMMAND                CREATED             STATUS              PORTS                                              NAMES
03cf6daec38f        knjname/jenkins-master:latest   /bin/sh -c 'bash /je   18 minutes ago      Up 18 minutes       0.0.0.0:8080->8080/tcp, 0.0.0.0:10080->10080/tcp   jenkins-master
c77efec916c7        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   About an hour ago   Up About an hour    0.0.0.0:10022->22/tcp                              jenkins-slave

またここでは、 192.168.11.6サーバに上記コンテナが立ち上がっており、http://192.168.11.6:8080 で上記Jenkinsサービスにアクセスできるものとします。

さてさて!ここからjenkins-masterからjenkins-slaveをSSH経由でノード追加してみましょう。

Jenkinsの管理 → ノードの管理 → 新規ノード作成 で下記のようなノードを追加します。(ポートの指定忘れないように!!)

f:id:knjname:20140503174210p:plain

今回は認証情報にてシステムローカルの~/.ssh(Dockerfileにて追加済み)からSSH認証を読むようにしています。本当はSSH秘密鍵はどっかに別だしのほうがいいですね。それかユーザ名、パスワード認証でもいいでしょう。

そしてマスターのビルド機能は不要になったので、特定ジョブ専用にします。

f:id:knjname:20140503174430p:plain

上記例だと同時実行数も0にしていますが、config.xmlをジョブ経由でバックアップしたい場合など、マスターにしかできないジョブが発生すると考える場合は、わざわざ同時実行数を0にしなくていいと思います。

とりあえずできあがりました。

f:id:knjname:20140503174701p:plain

なんか実行してみましょう。

f:id:knjname:20140503174947p:plain

ちゃんとスレーブ上で実行できましたね!

スレーブ量産してみた

せっかくのDockerなんで、スレーブをいっぱい作ってみました。

root@ubuntuserver:~/jenkins-docker-example/slave# docker ps
CONTAINER ID        IMAGE                           COMMAND                CREATED             STATUS              PORTS                                              NAMES
c985b3b13e37        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   2 seconds ago       Up 2 seconds        0.0.0.0:20004->22/tcp                              jenkins-slave-20004
3b051e7e66ae        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   5 seconds ago       Up 5 seconds        0.0.0.0:20003->22/tcp                              jenkins-slave-20003
7dd880653474        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   7 seconds ago       Up 7 seconds        0.0.0.0:20002->22/tcp                              jenkins-slave-20002
7bf196ba719f        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   11 seconds ago      Up 10 seconds       0.0.0.0:20001->22/tcp                              jenkins-slave-20001
05ebb98e8ecc        knjname/jenkins-slave:latest    /bin/sh -c '/usr/sbi   19 minutes ago      Up 19 minutes       0.0.0.0:10022->22/tcp                              jenkins-slave
03cf6daec38f        knjname/jenkins-master:latest   /bin/sh -c 'bash /je   49 minutes ago      Up 49 minutes       0.0.0.0:8080->8080/tcp, 0.0.0.0:10080->10080/tcp   jenkins-master

f:id:knjname:20140503175726p:plain

ただ、同じホストでたくさん同じスレーブを動しても全く旨味がないので、実際Dockerでスレーブ量産する時は、自分でスレーブのイメージをプライベートリポジトリとかに登録しておき、他マシンのDockerからそれをロードしてスレーブを起動させましょう。

Windowsと繋いでみた

JNLPポートの確認と設定をして…

f:id:knjname:20140503171721p:plain

Windowsスレーブもたててみましょう!

f:id:knjname:20140503190301p:plain

普通にいけますね。

やったほうがいいかもしれないこと

  • 今回はDockerでJenkinsを立ち上げるまでの話なので、この後、当然素のJenkinsでも必要になってくる、JENKINS_HOMEのバックアップとか、設定XMLのバックアップとか、追加プラグインのインストールとかが必要になってきます。
  • スレーブはDockerのオプションでCPU優先度やメモリの制限かけちゃったほうが運用が安定すると思います。
  • JNLPポートについて、トンネル接続オプション使えば別に内外のポートを合わせる必要もないので、内側のポートを特定番号に固定してもいいかもしれません。(今回やってて途中まで気付かなかった。。。)
  • SSHの鍵はもうちょっと上手に管理しましょう。
  • ってか、もっと楽なスレーブの作り方あるんじゃね。

まとめ

  • Jenkinsはマスターとスレーブ分けよう。マスターは実務させない。スレーブに実務させる。
  • Dockerは開発用サーバセットアップでも役に立つ。

光学メディア使えない場合のLinuxインストール

OSのインストールといえば、光学メディアに焼いてドーン!が定番ですね。

ただ、社内のコンプライアンス()とかで光学メディアを使えない場合は、ネットワークブート(PXEブート)を使ってインストールした方が楽なことがあります。

例えば適当なWindowsノートPCと、Ubuntuを新規で入れたいマシンがあったら…

  1. そのWindows上にVirtualBoxを入れ、
  2. その上にUbuntuやらを入れ、Ubuntu上にPXEブート用のDHCP・tftpサーバを構築して、
  3. tftpブートフォルダの下(/var/lib/tfpdとか)にUbuntu用のPXEブート用tarをダウンロード( http://cdimage.ubuntu.com/netboot/ )して解凍し

(これでPXEブート用のサーバは準備完了)

  1. Windowsマシンとインストール対象マシンをLANケーブルで直繋ぎし、(この時VirtualBox上のゲストはブリッジモードでつなぐように変更する)
  2. インストール対象のマシンでブートオプションなどでネットワーク経由にするように設定し、
  3. 実際にPXEブートを開始させる。うまくいけばUbuntuインストーラ画面が開始する。

ってことをすればいいです。細かい手順は自分で調べてね。

こんなんでOS入れられるか心配な人は、VirtualBox上のPXEブートサーバと同じネットワークに閉じ込めたマシンで、ネットワークブートの試験が可能なのでやればいいです。(起動デバイスの指定にネットワークのみを指定すればPXEブートしてくれます。)

Ubuntu

ちなみに、UbuntuでのPXEブートの場合、tftpのブートイメージのほかに、OSの構成ソフトウェアをインストールするために別途ネットワーク経由でミラーからパッケージetcを取得しなければなりません。

ローカルでOSのisoイメージをファイルシステムとしてマウントしたものをApache HTTP Server上で公開し、それをPXEブート中のミラーサイト指定時にURL指定してあげれば、インターネットに繋がずともインストールできようなのですが、私はうまくいきませんでした。ディスク分割の後に落ちるんですよね。(HTTPサーバのインデックス表示とかのオプションが足りなかったのかなあ?)

結局、うまくいかなかったので、PXEブートの後はネットにつながる回線につないでネットからパッケージなどをインストールさせました。

まとめ

VirtualBoxが入ったマシンを他のマシンに直繋ぎしてVirtualBox内部のマシンでごにょごにょさせるメソッドは割りと使える場面が多い気がする。

tmuxはじめました

tmuxはじめました

100周ぐらい乗り遅れた感があるのですが、tmux(てぃーまっくす)というGNU Screenに似たシンプルな仮想端末ソフトを使いはじめました。sshでログインした後に使ったりします。

http://tmux.sourceforge.net/

下記のようなありがちな不満を解消してくれます。

  • 今動いているシェルターミナルの他に、シェルターミナルがもう一本欲しい。
  • ログアウトしていてもプログラムが動作していてほしい(従来はnohupかC-z -> bg -> disownでやっていたこと)
  • もっかいログインしたら続きからやりなおしたい
  • 画面上を流れる出力をじっくり見たり、コピペしたりしたい。
  • シェルの画面を分割したい。

リモート上で動くTeratermみたいなものですね。他人とシェル画面を共有したりもできたりします。(画面シェアしている場合はつないでいる人の最小画面サイズに調整される)

最近のUbuntu serverにはデフォルトで入っていると思います。

root@ubuntuserver:~# tmux -V
tmux 1.8

OS XでもHomebrewで入れられるはずなので、ぜひ使いましょう。

tmuxメモ

Ubuntu server 13.10にデフォルトで入っているtmux 1.8で検証しています。

tmuxの構造

tmuxはクライアント、サーバ対話型で、基本的にtmux内で起動したシェルやプログラムは全てサーバプロセスの下にぶら下がって動作します。

(1サーバプロセスがあったとしたら、nクライアントが接続する形です。)

サーバプロセスの方でシェルやプログラムを立ち上げる仕組みのおかげで、デタッチしてサーバからログアウトしても、ずっと実行したシェルやプログラムが残るわけですね。

プロセスツリーはこんな感じ:

root@ubuntuserver:~# pstree  -Ap
init(1)-+
        |
        |
        |-sshd(897)-+-sshd(1352)---sshd(1399)---bash(1400)---sudo(1486)---su(1487)---bash(1488)---tmux(2090) ← tmuxクライアント1号
        |           `-sshd(2407)---bash(2494)---tmux(2593) ← tmuxクライアント2号
        |
        |
        |-tmux(2092)-+-bash(2093)---pstree(2594) ← クライアント1号2号の繋ぎ先tmuxサーバとその一味
        |            |-bash(2254)
        |            `-bash(2303)
        |

サーバ⇔クライアント間の対話はUnixソケットで行われ、実ファイルは/tmp/tmux-0/defaultとかで見つかると思います。

C-b(Ctrl+B) がデフォルトのprefixキーなのはどう考えてもおかしい

http://sourceforge.net/p/tmux/tmux-code/ci/master/tree/FAQ

  • Why is C-b the prefix key? How do I change it?

The default key is C-b because the prototype of tmux was originally developed inside screen and C-b was chosen not to clash with the screen meta key.

初期のtmuxの開発環境との兼ね合いで適当にふられているだけっぽいですね。これといった合理的な根拠はないみたいです。

もちろんEmacsでもviでもbashでもC-bが封印されていたら私はイラつくので、C-tにします。

(~/.tmux.confファイル)

unbind C-b
set -g prefix C-t
bind C-t send-prefix

でも、これじゃあEmacsbashのtranspose characters(カーソル前後の文字入れ替え)が封じられて残念なことになるじゃないか、と思う人もいると思いますが、C-tを2連打すれば元通りC-tが送ることができるのでギリギリ妥協できませんかね。

tmux上で256色にならない

たぶんデフォルトのtmux上で下記のコマンドを実行した場合、色が死んだスライムが表示されるかと思います。 (Linux - motd に Dragon Quest のキャラクターのドット絵を表示させる - Qiita

(tmux上) #> curl https://gist.githubusercontent.com/makocchi-git/9775443/raw/746887fbc6e1a7c6b120af0abcfe58701e8b4550/slime-allstar.txt

256色カラーがtmuxで無効になっているのが原因で、ネットだと~/.tmux.confに何かオプション足せば直る的な記述が多いですが、私の環境だとそれを追加しても直らなかったです。

そういう場合は、下記のようにして$TERM変数直して起動するとちゃんと256色出ると思います。 (tmuxがTERM変数見て256色有効にするか判定してます)

#> TERM=xterm-256color tmux
(tmux上) #> curl https://gist.githubusercontent.com/makocchi-git/9775443/raw/746887fbc6e1a7c6b120af0abcfe58701e8b4550/slime-allstar.txt
→ ちゃんとカラフルなスライムが表示される

$TERM変数の書き換えについては~/.bashrcで書き換えるなり、tmuxにTERM=xterm-256color tmuxエイリアス貼るなりで対処すればOKだと思います。

tmuxで合わせ鏡やったらどうなるの?

答. 画面が0サイズまで縮む。

リモートデスクトップとかで合わせ鏡(自分を映す画面を映す自分を映す画面を映す…)やったことがある人は多いと思いますが、tmuxでも下記でできます。

#> tmux
(tmux上) #> TMUX= tmux attach

  以後, ...に画面を埋め尽くされる

自分が自分につないでいる構造ですね。

TMUX=はTMUXという変数に値があるとtmuxがネスト起動を許してくれないので、変数の中身を消去してからネスト起動をしています。

変数消去をやらないでネストさせたい場合は、tmux内で自分にssh接続してtmux attachしてもいいでしょう。

ネストされた側はステータスバー分表示領域が減るので、その減った表示領域にあわせてネスト親側も表示領域が減り、その減量に合わせてまたネストされた側のステータスバー表示領域も減り、というトランポリンで画面領域がなくなるのだと思われます。

まとめ

tmuxは便利。tmuxを使うとTeratermのウィンドウが劇的に減る!

tmuxを使わないのは損だと思います。

私みたいに乗り遅れちゃった人も使おう。

最初はキーとか覚えるのが大変なので、なんかチートシート探して印刷しておいておいてもいいかもしれませんね。

コピーモードとかもちょっとややこしいので、最初はふれなくていいと思います。

最初は以下覚えておけば、どうにかなるかな。

#> tmux          新規tmuxセッション開始
#> tmux attach   既存tmuxセッションに接続(Attach)

prefix 0-9   N番目のウィンドウに切り替え
prefix c     ウィンドウ作成
prefix x     ペイン削除
prefix d     セッションの切断(Detach)

コピペモードや時計モードとかの特殊モードに入ったらqで出られる

どんどんメタスペース(パーマネント領域)食べちゃうメタボなクラス

クラスいっぱいロードさせるようなの、ってこういうやり方しかないですかねえ…(←未検索のクズ)

※メモリがどうなるかなど未検証

→ 2014/03/25 追記 ちゃんとクラスロードしまくってメモリ食いつぶしてました。

Excelで意味のない番号を表に振っている人はこの数式使って下さい ~ INDIRECT関数

とくに意味はなくとも、番号を表に振っている人、よくいますね?

f:id:knjname:20140323131207p:plain

そういう人はおそらくコピペで番号がずれたりしてイライラしていると思うので、それを解消するために、この数式を使うといいと思います。

=MAX(INDIRECT(ADDRESS(1,COLUMN())&":"&ADDRESS(ROW()-1,COLUMN())))+1

数式の解説

ちょっと詰まっていて見づらいですね。

この数式を見やすく整形すると、こうなります。

=MAX(
  INDIRECT(
    ADDRESS(1,COLUMN())
    & ":"
    & ADDRESS(ROW()-1,COLUMN())
  )
 ) + 1

下記ポイントをおさえるとわかりやすいでしょう。

  • ROW関数は現在行の番号、COLUMN関数は現在列の番号を返す関数
  • ADDRESS関数は行と列番号をもらって、アドレス($A$1みたいなの)文字列を生成する関数
  • INDIRECT関数はアドレスを指す文字列をもらうとセルへの参照を作成する関数
  • MAX関数で今のセルと同じ列について、行番号1~今のセルの一個上までのMAXを取り、それに+1する。

たとえば、$C$10にこの数式をおいた場合は =MAX( $C$1:$C$9 ) + 1 と同じになります。

要するに今のセルより上の方の最大の番号+1を今のセルに与えるってこったな!

番号づけに数式を使う場合の注意点

ただし、この方法で番号を振ると、いわゆる個体識別的な意味で番号を振っているわけではないので、間に行を入れたりすると容赦なく番号が振り直されます。そういうのが困る人は、数式ではなく固定の番号をセルに入れましょう。

上から内容を追記するだけのシートは、このエントリの方法が一番適しています。

共有フォルダに共有モードで置かれた課題管理表とかいう呪われた代物の管理にはぴったりでしょう。

この数式のメリット

この数式の一番嬉しい点は 数式にアドレスが出現しないことです。

この数式に限らず、INDIRECT関数などを用いて数式からアドレスを排除することにより、コピペしても忠実に数式のセル位置から結果を算出してくれます。

たとえば、VBAでこういった番号を入れる場合、現在の行番号をなんらかの形で持たなきゃいけなかったりして、とっても面倒ですが、この数式を入れるだけなら、わりと簡単です。

' こうするだけでいい
currentRange = "=MAX(INDIRECT(ADDRESS(1,COLUMN())&"":""&ADDRESS(ROW()-1,COLUMN())))+1"

INDIRECT関数の応用方法

条件付き書式など、自分のセルの値に基いて書式を決定させる場合、下記のような数式を使えば忠実に自分からの相対セルを見るようになります。(コピペなどでズレたりしません)

="×"=INDIRECT(ADDRESS(ROW(),COLUMN()))

※自セルが×かどうか判定。

ただ、上記例であれば、"×"が入っているセルに連動して行全体が灰色になったりしてほしいケースがほとんどでしょう。

私の場合、そういったケースの時は、まず、"×"が入っている列のヘッダに名前を与えちゃいます。

f:id:knjname:20140323134723p:plain

その上で下記のような条件付き書式(数式を使用して、書式設定するセルを決定)をヘッダから下の行全体に適用させます。

="×"=INDIRECT(ADDRESS(ROW(),COLUMN(ステータス)))

f:id:knjname:20140323133539p:plain

ちゃんとぜんぶ灰色になりました。やったね!

f:id:knjname:20140323133642p:plain

Q&A

ROW()関数で番号振ればよくね?

歯抜けになったとき、途中で番号があがった時、このエントリの方法のほうが有利です。

それに加え、ヘッダ行より上の行を増減したとかで全部の数式書き換えたりしたくないですね。 (ただし、この場合は、=ROW()-ROW(ヘッダのセル) で対処しても可)

この呪文覚えるのめんどい

IMEに変換候補として登録してください。

まとめ

  • INDIRECT関数は便利だ。もっと使え!

みなさんもメンテナンス性のいいシートを作成してください。方眼紙作る奴は死刑だ!

2014-05-26追記) ちなみにINDIRECTで数字振るのは大量の行がある場合は、かなり重くなる場合も考えられるので、そういう場合は直接数字入れていったほうがいいです。大量の件数データ吐くマクロとか。

JenkinsでExcel VBAを動かす方法

あまり幸福でない開発をしていると、プロジェクトのユビキタスフォーマット()がExcelになっていて、そのうちJenkins上でExcelVBAを動かしたくなります。

サーバ上でExcelを動かすには諸問題があり、できればサーバサイドでExcelを動かさないに越したことはありません。

そこで、たとえばJavaであればPOIなどExcelブックを扱えるライブラリを検討するところですが、POIは下記のような問題もあり、そこまで魅力的ではありません。

  • 数式の計算がむずい(僕はやったことないです)
  • 新規行のセル書式の引き継ぎなどExcelVBAでの動きと多少異なる
  • セルの型の扱いが面倒くさい
  • 実装が面倒くさい
  • VBAが動かせない

(反面、POIは処理が高速であるというメリットがあります)

こういうこともあり、ExcelVBAマクロがJenkinsで動いてくれたらいいなあと思う人も大勢いると思い、このエントリーを書きました。

別にExcelVBAだけの手法ではないので、幅広くOfficeのオートメーション化に使えると思います。

動作環境の前提

  • Jenkins あるいは Jenkinsスレーブ が動作するWindowsマシン (今回はWindows7使いました。Linuxがマスターとかの場合はスレーブをWindows上で動くようにしてください。)
  • PowerShell がインストールされていること
  • Excel がインストールされていること

今回はJenkinsからPowerShellスクリプトを起動、PowerShellスクリプトからExcelVBAを起動して制御したいと思います。

実践

任意の新規PowerShellスクリプトを実行できるようにする

管理者権限でPowerShellスクリプトコンソールを開き、下記のコマンドを実行して任意の新規PowerShellスクリプトを実行できるようにしてください。

Set-ExecutionPolicy Unrestricted LocalMachine

JenkinsにてPowerShell Pluginを入れる

別にバッチの実行からpowershellコマンド叩いてもいいですが、専用のプラグインを入れてPowerShellを実行したいと思います。

マクロブックとPowerShellスクリプトを用意する

とりあえず例として、下記のようなマクロがモジュールとして入っているExcelのマクロブック(mymacro.xlsm)を適当なフォルダに用意します。

Sub outputFile(ByVal destPath$)

    Dim fso As Object
    Set fso = CreateObject("Scripting.FileSystemObject")

    With fso.CreateTextFile(destPath)
        
        .WriteLine Application.Version
        
        .Close
        
    End With

End Sub

指定されたパスにExcelのバージョンを書き込むだけの簡単なマクロです。

このマクロを呼び出すため、同じフォルダに下記のようなPowerShellスクリプト(invoke-mymacro.ps1)を用意します。

# 各種変数の定義
$macroBook = ".\mymacro.xlsm"
$procedureName = "outputFile"
$outputFilePath = ".\output.txt"

$macroBookFullPath = (ls $macroBook).FullName
$macroProcName = (ls $macroBook).Name + "!" + $procedureName
$outputFileFullPath = Join-Path (pwd).Path $outputFilePath

# Excelの起動
$excelApp = New-Object -com "Excel.Application"

try {
    # マクロの起動
    $excelApp.DisplayAlerts = $false
    $excelApp.Workbooks.Open($macroBookFullPath)
    $excelApp.Run($macroProcName, $outputFileFullPath)
} finally {
    # Excelの終了
    $excelApp.DisplayAlerts = $false
    $excelApp.Quit()
}

やってることとしては、

  • Excelを立ち上げて(New-Object -com "Excel.Applicaiton")
  • Excelマクロブックを開いて($excelApp.Open(...))
  • プロシージャを引数とともに実行するように指示している($excelApp.Run(...))

だけですね。ExcelVBAができる人ならごく自然にわかると思います。

ためしに動かしてみる

上記スクリプトとマクロを保存したフォルダをPowerShellで開いて下記のようなコマンドを実行してみましょう。

.\invoke-mymacro.ps1

正しくセットアップできていれば、問題なくoutput.txtが同フォルダにできているはずです。

あとでわけわからなくなるので、output.txtは消しましょう。

原理的に、これを膨らませれば皆さんの使っているマクロがコマンドラインから動くことが理解できると思います。

JenkinsからExcelVBAを動かすPowerShellスクリプトを実行してみる

さて、問題はここからです。

まずは、Jenkinsで上記のフォルダをワークスペースとするジョブを作ってみましょう。

Jenkinsは特に "どこかのディレクトリ\ジョブのディレクトリ" をワークスペースにする必要はなく、必要に応じてディレクトリのパスを直接指定することができます。

下記のような感じになると思います。(今回はC:\jenkins-workspace にいろいろファイルを置きます)

f:id:knjname:20140323023944p:plain

ビルドのところに、Windows PowerShellを追加して、下記のようなコマンドを入れましょう。

$ErrorActionPreference = "Stop"
.\invoke-mymacro.ps1

1行目の$ErrorActionPreference = "Stop"はPowerShellスクリプトでエラーがあったら即スクリプトをエラーで終了させるという意味で、これを入れないとエラーでもどんどこ正常終了してしまうので、ぜひいれましょう。

入れ終わったら、実行してみましょう。

※なお、この時点でテンポラリのPowerShell自体の動作に問題がある場合は、Windowsバッチコマンドの実行で下記を実行するとうまくいくかもしれません。

powershell "set-executionpolicy unrestricted -force"

動かないよー!

たぶん下記のようなメッセージが出て動かないと思います。

ユーザーanonymousが実行
ビルドします。 ワークスペース: C:\jenkins-workspace
[jenkins-workspace] $ powershell.exe "& 'C:\Windows\TEMP\hudson5340555559273927371.ps1'"
"1" 個の引数を指定して "Open" を呼び出し中に例外が発生しました: "ファイル 'C:\j
enkins-workspace\mymacro.xlsm' にアクセスできません。次のいずれかの理由が考えら
れます。
? ファイル名またはパスが存在しません。
? ファイルが他のプログラムによって使用されています。
? 保存しようとしているブックと同じ名前のブックが現在開かれています。"
発生場所 C:\jenkins-workspace\invoke-mymacro.ps1:13 文字:26
+     $excelApp.Workbooks.Open <<<< ($macroBookFullPath)
    + CategoryInfo          : NotSpecified: (:) []、ParentContainsErrorRecordEx 
   ception
    + FullyQualifiedErrorId : ComMethodTargetInvocation
 
Build step 'Windows PowerShell' marked build as failure
Finished: FAILURE

それらしいエラーメッセージとともに、Excelの起動後、マクロのブックを開くところで失敗しています。

実はこのエラーメッセージは正しくありません。 ~~ 単純にLocal Serviceアカウントで動いているJenkinsプロセスがインタラクティブに動作しないため、関係のないエラーメッセージが出ているだけです。 ~~

~~ では、プロセスをインタラクティブにしましょう。 ~~

~~ サービスの一覧より、Jenkinsのサービスのプロパティを開いて、ログオンのところで、"デスクトップとの対話をサービスに許可"にチェックします。 ~~

f:id:knjname:20140323024043p:plain

~~ しかし、これをやった後にもう一度実行しても、同じエラーが出続けると思われます。 ~~

→ 2014/03/23追記 再度インタラクティブ解除してやってみましたが、特にインタラクティブにする必要はないみたいです。

ここで、もう一手必要です。下記を実行してください。

mkdir C:\Windows\system32\config\systemprofile\Desktop

再度実行してみましょう。

下記のように、成功すると思います。

ユーザーanonymousが実行
ビルドします。 ワークスペース: C:\jenkins-workspace
[jenkins-workspace] $ powershell.exe "& 'C:\Windows\TEMP\hudson1185974244837218878.ps1'"


Application                      : System.__ComObject
Creator                          : 1480803660
Parent                           : System.__ComObject
AcceptLabelsInFormulas           : False
ActiveChart                      : 
ActiveSheet                      : System.__ComObject
Author                           : owner
AutoUpdateFrequency              : 0
AutoUpdateSaveChanges            : 
ChangeHistoryDuration            : 0
BuiltinDocumentProperties        : System.__ComObject
Charts                           : System.__ComObject
CodeName                         : ThisWorkbook
_CodeName                        : ThisWorkbook
CommandBars                      : 
Comments                         : 
ConflictResolution               : 1
Container                        : 
CreateBackup                     : False
CustomDocumentProperties         : System.__ComObject
Date1904                         : False
DialogSheets                     : System.__ComObject
DisplayDrawingObjects            : -4104
FileFormat                       : 52
FullName                         : C:\jenkins-workspace\mymacro.xlsm
(略)
PivotTables                      : System.__ComObject
Model                            : System.__ComObject
ChartDataPointTrack              : True
DefaultTimelineStyle             : System.__ComObject



Finished: SUCCESS

実際、ワークスペースを見れば、output.txtができているのが見えると思います。

これでExcel VBAがJenkins上で動きました。やったね!

(普通に使うときは、マクロ一式はリポジトリに入れて、ワークスペースは固定しないでつかってくださいね。)

まとめ

下記を満たせばだいたいExcelVBAは動くと思います。

  • ~~ Jenkinsを普通のユーザアカウントかLocal Serviceをインタラクティブ状態で動かす。 ~~
  • C:\Windows\system32\config\systemprofile\Desktop フォルダを作る。

ついでにマクロは下記を意識して作りましょう。

  • マクロの入出力ファイルパスをすべて指定できるようにする。
  • それらパスをすべてフルパスで外部から指定できるようにする。
  • 動作についてのパラメータもすべて指定できるようにする。手で動かすためのプロシージャとJenkins用のフルオートメーション用プロシージャをわけて定義するといいでしょう。
  • マクロからインタラクティブな要素(ポップアップなど)を一切排除できるようにする。当然ですが、Jenkins上で動いているマクロはユーザの入力をもらえません。

残る課題

  • Excel VBAが途中でエラーになる、ダイアログを出すなどすると、永久にJenkinsビルドが終了しない。
  • 上記が原因かわからないけど、なんかよくわからないままブック開きっぱなしで終わってたりする。
  • 上記の結果、Excelプロセスのごみが残る。手動でプロセス殺したりする必要があるのはイヤだ。

これを解消するために、ジョブが一定時間たったら殺すプラグインいれる、Excelのプロセスを定期的に掃除する、ちゃんとジョブ中でExcelプロセスを殺しきってあげるなど工夫が必要です。

また、Excelマクロがどこまで進んだか、外部にでないのでわかりづらいという問題もあります。(Debug.PrintがJenkinsに出ればいいのに。)

これについては、解消方法があるんで、後日紹介できたらと思います。

あと、今回はLocal ServiceでJenkinsを動かしましたが、個人的にはLocal Serviceじゃなくて、特定の実在アカウントで動かしたほうがいろいろ楽だと思います。普通にLocal Serviceだとユーザプロファイルに依存する設定がしづらいと思います。

さいごに

こんなことしなくていい世界にな~れ!

追記 2014/05/13

64bit環境だと求められるsystemprofile\Desktopフォルダの位置が異なるようです。

PowerShell とタスク スケジュールを使ってExcelファイルの自動処理・印刷を行う方法 - 元「なんでもエンジニ屋」のダメ日記 Excel 2007 automation on top of a Windows Server 2008 x64

C:\Windows\SysWOW64\config\systemprofile\Desktop にフォルダを作成しましょう。