Corredor

ウェブ、プログラミングの勉強メモ。

Express で構築した WebAPI サーバをユニットテストする (コードカバレッジも見る)

Express で構築した WebAPI サーバをユニットテストしたく、やり方を調べた。

今回の要件

今回の前提、および達成したいことは以下のとおり。

  • Node.js の Express を使って構築した RESTful な WebAPI サーバがある
    • WebAPI サーバなので、常に JSON をレスポンスする。ファイルや Web ページを返すような箇所はない (画面を持たない)
  • このプロジェクトでユニットテストを書きたい
    • Angular を触っていたので、Jasmine みたいに書きたい
  • ユニットテストによるコードカバレッジを集計したい
    • Angular を触っていたので、Istanbul レポーターみたいな HTML 形式で見たい

Angular を触っていたので、Angular CLI による雛形プロジェクトが内包する、「Jasmine + Karma + Istanbul」な構成で、WebAPI サーバプロジェクトをテストしたいな、と思っている。

テストランナーは Mocha を使う

Angular アプリの場合は、テストを書くライブラリとして Jasmine があり、それを実行するテストランナーとして Karma が存在した。だが、Karma はブラウザ上でテストを行うテストランナーで、今回のように画面を持たない WebAPI サーバのユニットテストにはあまり適さないのだ。ということで Jasmine + Karma な構成は断念。

他のツールを調べてみると、Mocha というフレームワークを使う例が多かったので、コレを採用することにした。karma.conf.js のような設定ファイルを作ることなく、ゼロコンフィグでユニットテスト環境が作れる。

# mocha をインストールする
$ npm install mocha --save-dev

# mocha はデフォルトでプロジェクト直下の test/ ディレクトリ内にある .js ファイルをテストコードとして認識するので
# まずは test/ ディレクトリを作る
$ mkdir test

# test/ ディレクトリ配下にテストコードを書くファイルを1つ試しに作る
$ touch test/my-test.js

test/my-test.js の中身はこんな風に、describe()it() でテストケースを別けて書ける。Jasmine っぽい。

describe('HOGE', () => {
  it('FUGA', (done) => {
    if('aaa' === 'aaa') {
      done();
    }
    else {
      done('失敗');
    }
  });
});

まずはテストファイルのみで、何の外部ファイルも読み込んでいないが、コレでテストが動くか確認してみる。

# ローカルインストールした mocha を実行するため npx を使用
$ npx mocha

コレでいきなりテストが実行され、コンソール上に結果が表示されるはずだ。終了する際は Ctrl + C を押さないといけない様子。

Express サーバの制御には supertest を使う

次に、Express サーバを構成するコードを読み込んでテストをしたいので、Express サーバを制御するのに使える supertest という npm パッケージを使ってみる。

$ npm install supertest --save-dev

アプリ側のコードは以下のようにしておく。

const express = require('express');

const app = express();

// ルーティング定義などなど……
app.use('/', require('./routes/router'));

// UT 時はココが実行されないようにしておく
if(!module.parent) {
  // サーバ起動
  const port = process.env.PORT || 8080;
  app.listen(port, () => {
    console.log(`Listen : ${port}`);
  });
}

// 変数 app をエクスポートしておく
module.exports = app;

で、テストコード側は以下のように書く。

// 前述の Express サーバのコード
const app = require('../src/index');

// ココで listen() する
const supertest = require('supertest').agent(app.listen());

describe(`GET : '/'`, () => {
  it('200 が返されるべきである', (done) => {
    supertest.get('/')
      .expect(200)
      .end((error, response) => {
        if(error) {
          return done(error);
        }
        done();
      });
  });
});

アプリ側のコードで if(!module.parent) と判定している部分が見慣れないだろうか。コレは、ユニットテストコード内で supertest が複数回 app.listen() してしまうのを避けるために仕込んでいる。

この関係で、アプリ側のコードの if(!module.parent) ブロック内はコードカバレッジが通らなくなることに留意。まぁココはサーバ起動のためのコードだからいいでしょ。w

とりあえずコレでテストが書けるようになった。

HTML 形式でレポートを出力するために nyc を使う

続いて、テスト結果を Istanbul の HTML 形式で出力したいので、nyc というパッケージを利用する。コレは Mocha の動作結果をパイプして、Istanbul レポータを出力したり仲介してくれるツール。

コレをローカルインストールし、mocha コマンドの手前に挟む。

$ npm install nyc --save-dev

package.json で以下のように npm-scripts を組む。

"scripts": {
  "test": "nyc --reporter=text --reporter=html mocha --watch"
}

このようにして、$ npm test を実行すると、基本は mocha --watch のとおり、ファイルの変更を監視する Watch モードで動作し、ひととおりテストが終わると、nyc が HTML 形式のレポートを ./coverage/ ディレクトリに出力してくれる。また、Ctrl + C で終了すると、nyc がコンソールにレポートを出力してくれる。

任意 : Jasmine 風にテストを書けるように expect をインストールする

ココまででやりたいことは達成できたが、テストを書く時の比較検証を Jasmine 風に書きたいので、expect というツールを入れることにする。

$ npm install expect --save-dev

そしてテストコードを以下のように修正する。

// Jasmine 風な expect() を書くため入れる
const expect = require('expect');

// Express サーバのコード
const app = require('../src/index');

// ココで listen() する
const supertest = require('supertest').agent(app.listen());

describe(`GET : '/'`, () => {
  it('200 が返されるべきである', (done) => {
    supertest.get('/')
      .expect(200)
      .end((error, response) => {
        if(error) {
          return done(error);
        }
        // Jasmine 風に expect() が書けるようになる
        expect(response.text).toBe('Hello World');
        done();
      });
  });
});

こんな感じで expect().toBe() とかが書けるようになった。楽チン。

以上

ということで、

  • Express で作った、画面を持たない WebAPI サーバをユニットテストするには
    • mocha + supertest でテスト環境を構築し、
  • mocha でテストした結果をカバレッジレポートに出力するには
    • nyc をはさみ、
  • ついでに Jasmine と同じように expect() を書くには
    • expect を入れる

という構成で解決できた。

今回作ったサンプルコードを含む、実際に動作するサンプルプロジェクトを以下に作ったので、コチラも参考にしていただければ幸い。

テスト駆動開発

テスト駆動開発

Git For Windows・Git SDK の起動を爆速にする

Git For Windows やその上位互換である Git SDK (以降「GitBash」で総称する) の起動時のトロさといったら。Mac のターミナルくらい爆速で起動して使い始めたいのに、git-bash.exe を起動して最初のプロンプトが表示されるまで2・3秒待たないといけない。

今回はそんな Windows GitBash の起動を爆速にするための取り組みをまとめる。

Windows GitBash が遅い理由

そもそも何で GitBash は起動や動作が遅いのだろう。

最近気付いたのだが、MacOS 上で、VirtualBox で Windows 環境を作り (Modern.IE とかでいい)、その上で Git For Windows をインストールすると、仮想環境とは思えない速度でターミナルが起動するのだ。ということは、「MacOS の SSD が速くて、Windows マシンの SSD は遅いのかしら?」と、ハードウェアの性能差すら疑ってしまった。

で、そんな疑問を調べていたら、Twitter で素晴らしいリプライをいただいた。

Windowsのファイルアクセスとプロセス起動がUNIX系に比べて相当遅い傾向があるせいじゃないでしょうか。
ファイルアクセスが遅い問題はSSD使うとそれなりには緩和されると思います。
あとVirtualBoxのストレージコントローラには「ホストのI/Oキャッシュを使う」設定があり、これでも速くなりそうです

薄々勘付いてはいたが、そもそも Windows OS 自体が、ファイルアクセスとプロセス起動が遅い傾向にあるようだ。

ファイルアクセスというと、test コマンドなどでファイルの存在チェックを行って、source コマンドで読み込んだり、といったことが起動時に行われている。原理的には、OS を問わず、ファイルアクセスの回数や量に比例して動作が遅くなっていくが、Windows OS ではそれを MacOS や Linux OS よりも顕著に感じる、ということだろう。

GitBash の中身はほぼ MSYS2 で、Bash コマンドを Windows 向けに移植して .exe 化して取り揃えている。Bash の言語仕様上の細かなところを知らないので推測にはなるが、平たく考えると、Bash コマンドを実行する度に、コマンドの実体である .exe ファイルを読みに行き (ファイルアクセス)、そのプロセスを起動して、処理しているワケだ。起動時に使用するコマンド ≒ 処理が多ければ多いほど、Windows ではより顕著に速度性能を体感してしまう、というワケだ。

あと、コレは体感的な話ではあるが、echo によるターミナルへの出力自体も、もっさりしているように感じる。Git SDK はターミナル起動時に「Welcome」とかなんちゃら echo してきやがるので、コンソール出力も止めよう。

そうそう、Mac の VirtualBox 上で動作する Windows だとそのような遅さを感じなかった理由についても判明した。ホスト OS の I/O キャッシュが利用されているので、MacOS のファイルアクセスの性能が活かされているということのようだ。

じゃあ、速くするにはどうしたらいいのか?

Windows は「ファイルアクセス」と「プロセス起動」に時間がかかることが、GitBash の起動の遅さの原因と考えられることが分かった。

ということは、この裏を返せば、起動を速くする方法に繋がるだろう。

  • ファイルアクセスを減らす
    • test コマンドなどによるファイルの存在チェックを省けるだけ省く
    • source コマンドで外部ファイルを読み込んでいる箇所を省く : 呼び出し元のスクリプトファイルにインラインでベタ書きしてやれば同じ結果が得られるので、インライン化してしまう
    • mkdirchmod などファイルを操作するコマンドを調整する
  • プロセス起動を減らす
    • exec や、存在チェックなどの条件分岐や関数呼び出し、変数展開や置換など、考えられる「無駄な処理」は削れるだけ削り、使うコマンドの数を減らす

とりあえず、起動時に読み込まれたり、参照・操作されるファイル数を減らし、余計な処理をガンガン削ったら、原理的には速くなりそうだ。

しかし、起動時に読み込まれているファイルって何なのだろう?

一つずつ調べていくことにした。

Git SDK の前提環境

まずは Git SDK の起動を速くしていこうと思う。前提となる環境は以下のとおり。

  • git-sdk-installer-1.0.7-64.7z.exe (Git SDK v1.0.7) を C:\git-sdk-64\ にインストール後、1回以上普通に起動したことがある (初期設定が完了している)
  • C:\git-sdk-64\ ディレクトリの git log は以下のとおり
    • 74eb921e 2018-12-18 Update 20181218-031256 [Git for Windows Build Agent] (grafted, HEAD -> master, origin/master)
  • C:\git-sdk-64\.git\ ディレクトリを消しておく。そうしないと、/ が Git 管理されているテイになっているので、Git 管理外のディレクトリでも __git_ps1 が表示されてしまう

このような状態で、調査を開始した。

GitBash 起動時に読み込まれているファイル・処理内容を知る

Git SDK (git-bash.exe) を起動すると、現状は3秒くらい時間がかかっている。

まずは起動時にどのようなファイルが読み込まれているか調べるため、/usr/bin/bash.exe の動作ログを出力してみる。やり方は以前別の記事で紹介した。

neos21.hatenablog.com

$ PS4='+$BASH_SOURCE> ' BASH_XTRACEFD=7 bash -xl 7>&2

bash.exe が立ち上がると、C:\git-sdk-64\etc\profile (= /etc/profile) が読み込まれ、このファイルから次のファイル群が source されていた。

  • /etc/msystem : MSYSTEM_PREFIX など環境変数を設定する
  • /etc/post-install/ 配下の全ファイル : インストール直後の初期設定がメイン。設定ができていたら結果的に何もしないのだが、Git SDK を起動するたびに source するので遅くなっているようだった
    • /etc/post-install/01-devices.post : /dev/ 配下のシンボリックリンクを作成する
    • /etc/post-install/03-mtab.post : /proc/mounts の再現
    • /etc/post-install/06-windows-files.post : C:\Windows\System32\drivers\etc\ との設定
    • /etc/post-install/07-pacman-key.post : pacman-key の設定
    • /etc/post-install/08-xml-catalog.post : xmlcatalog の用意
    • /etc/post-install/99-post-install-cleanup.post : 不要なファイルの削除
  • /etc/profile.d/ 配下のファイル : perlbin.csh 以外が呼ばれる
    • /etc/profile.d/aliases.sh : alias 設定
    • /etc/profile.d/bash_completion.sh : Bash の複雑なタブ補完用ファイル /usr/share/bash-completion/bash_completion を読み込む
    • /etc/profile.d/bash_profile.sh : ~/.bash_profile~/.bashrc などのファイルの存在チェック・なければ新規生成する
    • /etc/profile.d/env.sh : 環境変数 PATH の設定
    • /etc/profile.d/git-prompt.sh : 環境変数 PS1 (プロンプト) の設定
    • /etc/profile.d/git-sdk.sh : sdk 関数の用意。Welcome to the Git for Windows SDK! のメッセージはこのスクリプトが出力している
    • /etc/profile.d/lang.sh : 環境変数 LANG (言語) の設定
    • /etc/profile.d/perlbin.sh : Perl に関する環境変数 PATH の設定
    • /etc/profile.d/tzset.sh : 環境変数 TZ (タイムゾーン) の設定
  • /etc/bash.bashrc : プロンプトの設定など
  • あとは ~/.bash_profile および ~/.bashrc。普段ユーザ定義する部分だが、ココのカスタマイズは今回対象外とする

この /etc/profile と、そこから読み込まれる外部ファイル群から「無駄」を省いていけば速くなるかもしれない。

  • これらのファイル群は、MSYS2 系の様々な環境で汎用的に動作するように書かれているので、32 bit 版向けの条件分岐や処理が含まれていたりする
  • SSH 接続時の調整など、自分の用途ではまず使わない場合向けの設定が含まれている

今回、起動を速くするためなら自分が使う環境に強く依存するようなカスタマイズでもいとわないことにした。

まずは source しているファイルの中身を /etc/profile 内にコピペし、外部ファイルの読み込みを全てなくした。その状態で、条件分岐や変数の設定内容などを細かく見ていき、通っていない処理、通っていても実質的に何も変更していない無駄な処理をコメントアウトした。ココは地道に変数の定義前・定義直後で変数を echo したりして調べていった。

この細かな調査記録は以下にコメント込みで記述したファイル全量があるので、気になる人は参照されたし。

Git SDK の起動を爆速にする /etc/profile

こうして極限まで無駄を省いた結果、/etc/profile の内容は以下のように収めることができた。

# Git SDK 向け /etc/profile 超削減版

MSYS2_PATH=/usr/local/bin:/usr/bin:/bin
MANPATH=/usr/local/man:/usr/share/man:/usr/man:/share/man
INFOPATH=/usr/local/info:/usr/share/info:/usr/info:/share/info
ORIGINAL_PATH="$PATH"  # 元のソースでは変数の内容チェックなどがあったが省いた

# 後述するが Git For Windows とは設定内容が違うので注意
MSYSTEM=MSYS
MSYSTEM_PREFIX=/usr
MSYSTEM_CARCH=x86_64
MSYSTEM_CHOST=x86_64-pc-msys
CONFIG_SITE=/etc/config.site

PKG_CONFIG_PATH=/usr/lib/pkgconfig:/usr/share/pkgconfig:/lib/pkgconfig

# 元のソースは /tmp ディレクトリの特定に色々処理していたが、全てベタ書きにした
ORIGINAL_TMP=/tmp
ORIGINAL_TEMP=/tmp
TMPDIR=/tmp

# 本来は "$(exec /usr/bin/hostname)" で代入していたところなのだが、ベタ書きにしてしまった。export する必要性がイマイチ分かっていない変数
HOSTNAME=Neos-Windows
# プロンプトは最小構成にしておく。自分は ~/.bash_profile で設定しているのでココでは余計なことはせずにシンプルにしておく
PS1='\n$ '
# locale コマンドの結果だけ書いてしまう
LANG=ja_JP.UTF-8
# tzset コマンドの結果だけ書いてしまう
TZ=Asia/Tokyo
# 環境変数 PATH への代入は数箇所に登場していたので、1箇所でまとめてやるようにした
PATH="$HOME/bin:$MSYS2_PATH:/opt/bin:$ORIGINAL_PATH:/usr/bin/vendor_perl:/usr/bin/core_perl"

# export コマンドの実行は1回だけにする
export PATH MANPATH INFOPATH PKG_CONFIG_PATH LANG TZ TMP TEMP TMPDIR HOSTNAME PS1 SHELL ORIGINAL_TMP ORIGINAL_TEMP ORIGINAL_PATH MSYSTEM MSYSTEM_PREFIX MSYSTEM_CARCH MSYSTEM_CHOST CONFIG_SITE

コメント・空行を省いて19行になった。コレ以外は、条件分岐に合致せず呼び出されていないコードだったり、実行されても何も変化がないコードだったりしたので、全て削った。

変数展開のコストがどれだけかかるのか分からないが、余計な処理コストがかかるのを避けるため、原則的にベタ書きに変更してしまった。

一応処理として通ってはいたので残したものの、起動後の使用箇所が分からずまだ削れそうな変数もある。

  • $ORIGINAL_TMP$ORIGINAL_TEMP : 中身は /tmp になっていたので、結局 $TMP$TEMP と同じじゃん、と。$TMPDIR ももしかしたら要らなさそう
  • $HOSTNAME : プロンプトに使用する \h では参照していないので、もしかしたらコレも要らないかも
  • $PS1 : ~/.bash_profile で独自に設定しているし、別にココで用意してあげなくても良いかも

とはいえ、基本は変数への固定値代入で、コマンドらしいコマンドは export 1回だけに押さえられた。

C:\git-sdk-64\etc\profile = /etc/profile のファイルの内容を、上述の内容に差し替えれば高速化できる。

Git For Windows も高速化する

同じ要領で、通常の GitBash である Git For Windows も高速化してみよう。

  • C:\Program Files\Git\ に v2.20.1 をインストール後、1回以上普通に起動した状態から調査・改良を開始した
  • /etc/profile の内容は Git SDK のモノと全く同一だったので、$MSYSTEM の初期値が 'MSYS' ではなく 'MINGW64' であることによる、一部の条件分岐の違い程度と推測した
  • が、念のため全体の動きを調べ直し、設定する環境変数の違いなどをまとめた

Git For Windows の起動を爆速にする /etc/profile

ということで出来上がった /etc/profile は以下のとおり。

MSYS2_PATH=/usr/local/bin:/usr/bin:/bin
MANPATH=/usr/local/man:/usr/share/man:/usr/man:/share/man
INFOPATH=/usr/local/info:/usr/share/info:/usr/info:/share/info
ORIGINAL_PATH="$PATH"

# 以下の内容が Git SDK と異なる
MSYSTEM=MINGW64
MSYSTEM_PREFIX=/mingw64
MSYSTEM_CARCH=x86_64
MSYSTEM_CHOST=x86_64-w64-mingw32
MINGW_CHOST=x86_64-w64-mingw32
MINGW_PREFIX=/mingw64
MINGW_PACKAGE_PREFIX=mingw-w64-x86_64
CONFIG_SITE=/mingw64/etc/config.site

PKG_CONFIG_PATH=/mingw64/lib/pkgconfig:/mingw64/share/pkgconfig

ORIGINAL_TMP=/tmp
ORIGINAL_TEMP=/tmp
TMPDIR=/tmp

# やりすぎず "$(exec /usr/bin/hostname)" とした方が良いかも。もしくは不要?
HOSTNAME=Neos-Windows

# env.sh より以下を追加した
DISPLAY=needs-to-be-defined
SSH_ASKPASS=/mingw64/libexec/git-core/git-gui--askpass

PS1='\n$ '
LANG=ja_JP.UTF-8

# Git For Windows には tzset.sh がなかったので本来は以下の変数が設定されないが、Git SDK と同じ内容を設定しておく
TZ=Asia/Tokyo

PATH="$HOME/bin:$MSYS2_PATH:/opt/bin:$ORIGINAL_PATH:/usr/bin/vendor_perl:/usr/bin/core_perl"

# ACLOCAL_PATH にも値が最初から入っていたので export 対象に入れた
export PATH MANPATH INFOPATH PKG_CONFIG_PATH LANG TZ TMP TEMP TMPDIR HOSTNAME PS1 SHELL ORIGINAL_TMP ORIGINAL_TEMP ORIGINAL_PATH MSYSTEM MSYSTEM_PREFIX MSYSTEM_CARCH MSYSTEM_CHOST CONFIG_SITE ACLOCAL_PATH DISPLAY SSH_ASKPASS

コメント行、空行を除くと24行になった。

環境変数 $MSYSTEM のデフォルト値が Git SDK と Git For Windows とで異なっていたので、その違いによる環境変数の差分を取り込んだ。32bit 版の Git For Windows を使っている人はまたちょっと変わってくるはず。

どのくらい速くなったのか検証する

さて、当初の体感では2・3秒起動時に待たされていたのが、上述の改修後は、起動して2秒後にはプロンプトが表示されているくらいに高速化された。

数値としてどのくらい高速化したのか、Git SDK 側の変更前後で違いを見てみる。

/etc/profile の1行目と最終行、および ~/.bash_profile の最終行 (~/.bashrc の読込後) の3箇所に、以下のコマンドを仕掛けた。

date '+%F %T %N'
# 'YYYY-MM-DD HH:mm:ss ナノ秒' が出力される

この状態で git-bash.exe を起動し、変更前の /etc/profile と変更後の /etc/profile とで比較してみた。

元々の /etc/profile の実行結果

2018-12-20 22:04:54 425751900  # /etc/profile 開始
Welcome to the Git for Windows SDK!

The common tasks are automated via the `sdk` function;
See `sdk help` for details.
2018-12-20 22:04:56 071816800  # /etc/profile 終了
2018-12-20 22:04:56 648292200  # ~/.bash_profile 終了

sdk() 関数の welcome の内容が出力されているので少し分かりづらいが、/etc/profile の終了まで2秒近くかかっていることが分かる。

~/.bash_profile の終了にも0.6秒程度かかっているが、コレはこのファイルから ~/.bashrcgit-completion.bash などを source していたりするため。ユーザ定義の場所なので参考程度に見て欲しい。

変更後の /etc/profile の実行結果

2018-12-20 22:32:09 508269500  # /etc/profile 開始
2018-12-20 22:32:09 548539700  # /etc/profile 終了
2018-12-20 22:32:09 983198600  # ~/.bash_profile 終了

2秒ほどかかっていた /etc/profile の読み込みは、なんと0.04秒程度で完了するようになった。コメントアウトや空行の有無は実行速度に影響なかった。

こうなると ~/.bash_profile~/.bashrc の読み込み速度の方が気になってくるレベル。それでも全体では0.5秒程度で起動スクリプトの実行が終わっているのは驚異的。

Git For Windows の方も同様だった。Git For Windows の方は /etc/post-install/ ディレクトリ自体が元々存在しなかったので、変更前から Git SDK よりも少し速かったのだが、それよりも格段に速くなった。

以上

体感できているとおり、GitBash の起動を爆速にすることができた。

/etc/profile の変更内容を見てもらえば分かるとおり、自分の環境で git-bash.exe を起動した時に上手く動きさえすれば良い、というノリで滅茶苦茶にコードを削っている。/etc/profile の内容は別環境には共用できないし、まだ遭遇していないだけで思いもよらぬ不具合が怒るかもしれない。ご利用は計画的に。

今回の改修および調査結果は、全て以下のリポジトリに置いているので、GitBash の起動を爆速化してみたい人は、参考にしてほしい。

その他参考

Windowsで使えるUNIX環境 Cygwin徹底入門

Windowsで使えるUNIX環境 Cygwin徹底入門

Cygwin環境構築ガイド

Cygwin環境構築ガイド

初心者のためのCygwin入門―「インストール」「基本操作」から「サーバ構築」まで (I・O BOOKS)

初心者のためのCygwin入門―「インストール」「基本操作」から「サーバ構築」まで (I・O BOOKS)

Cygwinコンパクトリファレンス (Compact reference)

Cygwinコンパクトリファレンス (Compact reference)

Git SDK を ConEmu で使う設定

以前、GitBash (Git For Windows) の上位互換である「Git SDK」というモノを紹介した。

neos21.hatenablog.com

Git SDK にはパッケージマネージャの pacman が装備されていたりするので、コチラをデフォルトで使っていきたいと思っていたのだが、ConEmu で使う方法を調べておらず、ダラダラとその日の気分で両者を適当に使っていた。

今回、きちんと腰を据えて、ConEmu で Git SDK を利用する方法を調べた。


Git SDK はインストーラのデフォルト設定に従って、C:\git-sdk-64\ 配下にインストールされている前提とする。

ConEmu を開いたら、Settings → Startup → Tasks と進み、新たなタスクを作って、以下のように Commands を登録する。

set "PATH=C:\git-sdk-64\usr\bin;%PATH%" & "C:\git-sdk-64\git-cmd.exe" --no-cd --command=%ConEmuBaseDirShort%\conemu-msys2-64.exe /usr/bin/bash.exe -l -i -new_console:p%

デフォルトで入っていた「Git bash」タスクを基に、Git SDK が配置されている C:\git-sdk-64\ 配下を見るようパスを2箇所修正しただけ。

コレで Git SDK が ConEmu 上で起動するようになったので、おしまい。これからは Git SDK オンリーでやっていけそうだ。

Windowsで使えるUNIX環境 Cygwin徹底入門

Windowsで使えるUNIX環境 Cygwin徹底入門

Cygwin環境構築ガイド

Cygwin環境構築ガイド

初心者のためのCygwin入門―「インストール」「基本操作」から「サーバ構築」まで (I・O BOOKS)

初心者のためのCygwin入門―「インストール」「基本操作」から「サーバ構築」まで (I・O BOOKS)

Cygwinコンパクトリファレンス (Compact reference)

Cygwinコンパクトリファレンス (Compact reference)

CentOS の Apache で Perl CGI がファイル生成できないのは SELinux のせいだった

CentOS Linux 上の Apache に Perl CGI を置いたのだが、上手くファイル生成やファイル書き込みができなかった。CGI 自体は動いていて、ファイルの読み込みまではできるのに、書き込みだけがどうにもできない、という状態だった。

  • Apache デフォルトの cgi-bin/ 配下に .cgi ファイルが置いてある
  • httpd.confExecCGI などの設定は出来ている
  • .cgi ファイルのパーミッションは 777、テキストの書き込み先である .log ファイルは 666 で権限が付与できている
  • オーナーの設定も apache に変えてあって問題ないはず

パーミッション周りの設定は上手くいっていて、Mac 上の Apache では動作するするスクリプトだったので、何が悪いのかしばらく分からなかったのだが、どうやら SELinux という機能が邪魔していることが分かった。

SELinux とは

SELinux とは、ディストリビューションの名前ではなく、ファイルやディレクトリに対する細かなアクセス制御をかけられる機能のことで、CentOS などでは最初から導入されているみたい。

もっと平たくいうと、ls -l で見える権限の他に、ls -Z で見える独特の権限を付与する機能がある、という話だ。

SELinux の状況を見る

SELinux が動作していて、アクセス制御を実施しているかどうかを確認するには、$ sestatus というコマンドを叩く。この中の Current mode:enforcing だったら、制御機能がかかっている状態だ。

他にも、$ getenforce というコマンドでも状況を確認できる。コマンドが見つからないと云われた場合は $ sudo getenforce と叩くと良い。コチラも、Enforcing なら動作中だ。

SELinux の動作を一時的に無効にする

本当に SELinux が邪魔しているのか確認するために、まずはコイツの機能を切ってみる。$ setenforce 0 (必要に応じて $ sudo setenforce 0) と叩くと、モードを Enforcing から Permisive に変えられる。

Permissive というのは、アクセス制御がかかるべき時に警告ログを出力はするものの、実際にアクセス遮断はしない、というデバッグモード。完全に Stop もできるが、この setenforce はマシンの再起動によって元に戻ってしまう一時的な設定変更なので、Permissive にできればまずは良いだろう。

で、この状態で httpd サービスを再起動させてみて ($ sudo systemctl restart httpd など)、CGI が動作するか確認してみよう。chmod で設定できるようなパーミッション周りや、httpd.conf の設定内容に問題がなければ、上手くファイル生成やファイル追記ができるはずだ。

SELinux の権限を正しく与えてみる

前述のとおり、setenforce コマンドによる SELinux 機能の無効化は、あくまで一時的なモノであり、セキュリティリスクを考えると無効にしっぱなしは怖い。

そこで使用するのが、restorecon というコマンド。コレを cgi-bin/ 配下にあるファイルに対して適用すれば、適切に権限を付与してくれる。

試しに、hoge.cgihoge.log というファイルを touch コマンドで新規生成してみた。比較用に、既に設定を終えて正常に動作している test.cgitest.log というファイルも置いてみた。この時点での状況は以下のとおり。

$ ls -lZ
-rw-rw-r--. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 hoge.cgi
-rw-rw-r--. apache apache unconfined_u:object_r:httpd_sys_script_exec_t:s0 hoge.log
-rwxrwxrwx. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 test.cgi
-rw-rw-rw-. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 test.log

正常に設定できている test.cgitest.log は、system_u:object_r:httpd_sys_script_exec_t:s0 といった SELinux の権限が設定されているようだが、hoge.cgihoge.log の方は、unconfined_u:object_r:httpd_sys_script_exec_t:s0 と書かれている。中身はよく分からないが、unconfined あたりの文言から察するに、何かイマイチっぽい感じがする (爆

そこで、restorecon コマンドを使って、SELinux の権限を自動で再設定させてみる。

$ restorecon -RF hoge.cgi hoge.log

$ ls -lZ
-rw-rw-r--. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 hoge.cgi
-rw-rw-r--. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 hoge.log
-rwxrwxrwx. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 test.cgi
-rw-rw-rw-. apache apache system_u:object_r:httpd_sys_script_exec_t:s0 test.log

すると、このようにどうも設定が上手くいったようである。念のため httpd を再起動すると、上手く CGI が動作するようになった。

参考文献

SELinuxシステム管理 ―セキュアOSの基礎と運用

SELinuxシステム管理 ―セキュアOSの基礎と運用

SELinux徹底ガイド―セキュアOSによるシステム構築と運用 基本的な仕組みから高度な運用管理方法までを徹底解説

SELinux徹底ガイド―セキュアOSによるシステム構築と運用 基本的な仕組みから高度な運用管理方法までを徹底解説

  • 作者: 中村雄一,水上友宏,上野修一,日立ソフトウェアエンジニアリング,日立ソフトウエアエンジニアリング=,日立SK=
  • 出版社/メーカー: 日経BP社
  • 発売日: 2004/03/06
  • メディア: 単行本
  • 購入: 3人 クリック: 37回
  • この商品を含むブログ (18件) を見る