猫好きモバイルアプリケーション開発者記録

Dockerを使ってWindowsとMacで共通設定ファイルを利用した開発環境を構築する

| Comments

Dockerはバージョン1.8よりDocker Toolboxと呼ばれるものが公式に配布されるようになり、 WindowsおよびMacの双方でDockerクライアントは然ることながら、docker-machineが利用できるようになりました。 従来の boot2docker が docker-machine に置き換わることになります。 WindowsでもDockerクライアントが利用できるようになったということで、以前紹介した「SSHを使ってWindowsからDockerコンテナ内のコマンドを直接実行する」のような小細工をする必要もなくなったということになります。

さて、突然ですがDockerの最大の売りとは何でしょうか。 ポータビリティに優れている点、プラットフォームに依存しないアプリケーションの実行、といった点が代表的な利点かと思いますが、 この利点を普段の開発環境構築において利用してみようというのが今回のテーマです。

私の職場では、Windowsマシンを利用している方とMacを利用している方の両方が存在します。 どちらか一方に寄せてくれれば大変ありがたいのですが、プロジェクトによってはWindows用のローカル開発環境しか用意されていないこともあるので、 どちらか一方に寄せるというのも少々難しい問題となっています。 大抵のケースではMacにParallelsを導入して回避しているのですが、できれば各OS上で動かせると開発はしやすいですよね。

そこでDockerの出番ということになります。 今回はWindowsとMacで共通の設定ファイルを利用した開発環境を構築する上で留意するべきポイントやテクニックを紹介します。

1. コンテナ化する部分とコンテナ化しない部分を決定する

Dockerを利用するのであればすべてコンテナ化したいところなのですが、 IDEでデバッグする利便性を考慮すると、Tomcatサーバのようなアプリケーションサーバ部分はそのままにしておきたいです。 そうなると、HTTPサーバ(Apache、Nginxなど)とデータベース(MySQL、PostgreSQLなど)のみコンテナ化する必要があります。

2. ディレクトリ構成は同じにしておく

WindowsとMacで共通の開発環境を構築するためには、ディレクトリ構成を同じにしておく必要があります。 例えば私の場合は、以下のように各ユーザディレクトリ配下に共通のディレクトリ構成を作成しています。

1
2
3
(User Directory) - Development - Projects - (Project Name) - tomcat - log
                                                           - httpd - log
                                                           - sources - (ソースファイルを格納するディレクトリ)

とはいえ、ユーザディレクトリ名はユーザごとに異なるため、 絶対パスを記述しなくてはいけない場合に $HOME の環境変数を直接取得することができないと少々困ります。 そういう場合は、WindowsとMacでそれぞれ全ユーザの共有ディレクトリが存在するため、そこへシンボリックリンクを作成し、 その共有ディレクトリ経由のパスを記述します。以下に例を記述します。

1
2
3
4
5
6
7
<Windows>
Users - Public - Development - Projects - (Project Name : symbolic link)
※上記のProject NameがUser DirectoryにあるProject Nameディレクトリのシンボリックリンクとなる

<Mac>
Users - Shared - Development - Projects - (Project Name : symbolic link)
※上記のProject NameがUser DirectoryにあるProject Nameディレクトリのシンボリックリンクとなる

実はVista以降のWindowsとMacはドライブレターの有無の違いはあれど、 上記のとおり殆ど同じディレクトリ構成を取ることが可能なのです。 違いがあるのは、共有ディレクトリの名称くらいです。

3. Private Docker Repositoryを構築する

こちらは特に重要なポイントとなります。 Private Docker Repositoryがないと長時間のビルドを利用者に強要してしまうことになるのと、msysGitの絶対パス問題(後述で紹介します)が災いして、共通のDockerfileを利用できないケースが出てきてしまうため、可能な限りPrivate Docker Repositoryを用意してください。 Private Docker Repositoryの構築についてはクラスメソッドさんのブログで詳しく解説されています。

1つ注意としては、Dockerはプライベートネットワーク上に存在する他のサーバに対して直接アクセスすることができません。 (やろうと思えばできるかもしれませんが、docker-machine上のOSのIPアドレスを同一ネットワーク上に属させたり、ルーティングの設定をしたりという結構面倒な作業が必要になるかと思いますのでオススメはしません)
そのため、社内の開発機に構築したところで、その開発機がグローバルからアクセスできないようだと直接利用することができないため注意です。 グローバルからアクセス可能な箇所にPrivate Docker Repositoryサーバを構築し、サーバ側にIP制限やBASIC認証を掛けて利用しましょう。

4. シェルスクリプトを利用してWindows用とMac用の処理を記述する

共通のディレクトリ構成を作成したところで、どうしても回避できない環境差異は存在してしまいます。 そのため、シェルスクリプトを利用してWindows用とMac用の処理を関数内でラップしてしまいましょう。 (ちなみに、Windowsでシェルスクリプトを実行する場合はGit for Windowsに付属してくるGit Bashを利用します。Docker Toolboxをインストールするときに合わせてインストールされます)
具体的にWindowsとMacで処理を分けなければならないのは以下の点です。

4-1. ユーザ共有ディレクトリの取得

こちらは前述のとおり、ユーザ共有ディレクトリの名称が異なるため、 WindowsとMacで返却する名称を変更する必要があります。

4-2. シンボリックリンクの作成

Windowsで本物のシンボリックリンクを作成するためにはmklinkコマンドを利用する必要があります。 Git Bashのlnではシンボリックリンクの作成は行わず、擬似的にコピーを作成するだけになるためです。 また、シンボリックリンクと似たような機能にジャンクションポイントというものがありますが、 こちらはDockerコンテナ内から参照できないため利用不可です。

4-3. 絶対パスの記述がWindowsの場合は異なる

通常だと「/Users」のように絶対パスは記述しますが、Git BashにおいてはmsysGitの仕様上、「//Users」と記述をしなくてはいけません。 もし「/Users」とGit Bashで記述すると、Git for Windowsのインストールディレクトリからの相対パス(デフォルトだと/Program Files/Git/Users)になってしまいます。

4-4. ホストOS側のIPアドレスの取得

ここでいうホストOSというのは、WindowsローカルまたはMacローカルのマシンのIPアドレスのことです。(docker-machine ip で取得できるIPアドレスとは異なります) このIPアドレスは、Dockerコンテナ内からホストOS側で起動しているTomcat等と連携する場合に必要になります。 WindowsとMacではネットワークコマンドが根本的に異なるため、こちらもシェルスクリプトで処理を分ける必要があります。

4-5. Docker環境変数の設定方法

WindowsのGit Bashで eval $(docker-machine env マシン名) を実行すると、
「On Windows, please specify either ‘cmd’ or ‘powershell’ with the —shell flag.」
というように怒られてしまいます。こちらについても対処を行う必要があります。

4-6. Windowsでシェルスクリプトをプログラム内から呼び出す

Macでは特に問題ありませんが、WindowsだとGit Bashを経由しないとシェルスクリプトは実行できません。 関連付けの設定を敢えて用意するのでもいいですが、いちいちそれを利用者に行わせるのは面倒なので、 専用のバッチファイルを用意して対処します。


それでは、上記の問題に対処したラッパー関数を紹介します。


OS判別用共通関数 (この関数は全スクリプトで参照されていることにする)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#---------------------------------------
# OS種別名を取得する。
#
# [Arguments]
# なし
#
# [Return]
# OS名 (osx or win)
#---------------------------------------
function os_name {

    # WindowsのmsysGit経由で実行されているかどうかを調べる
    uname | grep -E '^MINGW' > /dev/null 2>&1

    # WindowsのmsysGit経由の場合
    if [[ $? -eq 0 ]]; then

        # Windowsとして出力する
        echo 'win'

    # OSXの場合
    elif [[ $(uname) = 'Darwin' ]]; then

        # OSXとして出力する
        echo 'osx'

    else

        # サポート外プラットフォームとして出力する
        echo 'unknown'

    fi

}

4-1. ユーザ共有ディレクトリの取得

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#---------------------------------------
# ユーザ共有ディレクトリを取得する。
#
# [Arguments]
# なし
#
# [Return]
# ユーザ共有ディレクトリパス
#---------------------------------------
function share_dir {

    # OS別処理
    case $(os_name) in

    # OSX
    osx)
        echo "$(cd ${HOME}/../ && pwd)/Shared"
        ;;

    # Windows
    win)
        echo "$(cd ${HOME}/../ && pwd)/Public"
        ;;

    # その他
    *)
        echo "Unknown platform"
        ;;

    esac

}

4-2. シンボリックリンクの作成・削除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#---------------------------------------
# シンボリックリンクを作成する。
#
# [Arguments]
# 1 : リンク名称
# 2 : リンクターゲット
#
# [Return]
# なし
#---------------------------------------
function mklink {

    # Windowsの場合
    if [[ $(os_name) = 'win' ]]; then

        # ドライブレターをWindows形式に変換する
        local work_link=$(echo "$1" | sed -E 's;^/([a-zA-Z]);\1:;g')
        local work_target=$(echo "$2" | sed -E 's;^/([a-zA-Z]);\1:;g')

        # ターゲットがディレクトリの場合
        if [[ -d "$2" ]]; then

            # ディレクトリのシンボリックリンクを作成する
            cmd <<< "mklink /D \"${work_link//\//\\}\" \"${work_target//\//\\}\"" > /dev/null

        else

            # ファイルのシンボリックリンクを作成する
            cmd <<< "mklink \"${work_link//\//\\}\" \"${work_target//\//\\}\"" > /dev/null

        fi

    else

        # lnコマンドでシンボリックリンクを作成する
        ln -s "$2" "$1"

    fi

}


#---------------------------------------
# シンボリックリンクを削除する。
#
# [Arguments]
# 1 : リンクパス
#
# [Return]
# なし
#---------------------------------------
function rmlink {

    # こちらは敢えて関数化する必要はないが、mklinkとの対比のために用意しておく
    rm -f "$1"

}

紹介している関数mklinkではlnコマンドと引数が逆なので、
分かりづらい場合は上記関数を修正してみてください。

また、1点Windowsの問題として、mklinkコマンドは「管理者として実行」を行わないと実行できません。 Git Bashで管理者として実行を行うためにはどうすればいいのでしょうか。 簡単な方法としては、以下のような「管理者として実行」を行うためのスクリプト(sudo.sh)を用意します。

シェルスクリプトを管理者として実行するためのスクリプト (sudo.sh)

1
2
3
4
5
6
7
8
9
10
11
12
# Windowsの場合
if [[ $(os_name) = 'win' ]]; then

    # 管理者権限で実行する
    powershell -Command "Start-Process \"C:\Program Files\Git\bin\bash.exe\" -argumentlist \"--login -i $1\" -Verb runas | Out-Null"

else

    # そのまま実行する
    . $1

fi

上記スクリプトを利用して、以下のように実行します。

1
./sudo.sh (管理者として実行したいシェルスクリプトファイル)

こうすることで、mklinkコマンドをシェルスクリプト内で実行することが可能になります。 (実行時に管理者として実行するかどうかの確認ダイアログが表示されます)

4-3. 絶対パスの記述を変換する関数

以下のような関数を使って、引数に含まれる絶対パスを修正します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#---------------------------------------
# Dockerで利用するファイルパスを取得する。
#
# Windows版Dockerで、かつmsysGit経由で実行している場合のみ有効となる。
# msysGit経由の場合、単一の開始スラッシュはmsysGitインストールディレクトリからの相対パスとなるため、
# 開始スラッシュを 2 つにする必要がある。
#
# [Arguments]
# 1 : 変換するファイルパス (絶対パス)
#
# [Return]
# Dockerで利用するファイルパス
#---------------------------------------
function docker_path {

    # Windowsの場合
    if [[ $(os_name) = 'win' ]]; then

        # 単一スラッシュで開始するパラメータをスラッシュ2つに変更する
        echo "$@" | sed -E 's;^/([a-zA-Z_\-\.]);//\1;g' | sed -E 's; /([a-zA-Z_\-\.]); //\1;g'

    else

        # そのまま出力する
        echo "$@"

    fi

}

絶対ファイルパスを利用する箇所で以下のように利用します。

1
docker run -it -v $(docker_path /c/Users/kkoudev/Temp):/Temp debian:wheezy bash

-vのように、途中にスペースが入らない場合は先頭のパスだけが対象になります。

また、docker execなどで外部から引数を渡してコマンドを実行したい場合、 どの部分が絶対パスかコマンドによるので判らないかと思いますが、以下のように指定することで引数に含まれた絶対パスを全て変換してくれます。

1
docker exec -it imlib2-image $(docker_path "$@")

4-4. ホストOS側のIPアドレスの取得

ホストOS側のIPが複数ある場合は、最初にヒットするIPアドレスを返却するようになっています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#---------------------------------------
# HostのIPを取得する
#
# [Arguments]
# なし
#
# [Return]
# HostのIP
#---------------------------------------
function host_ip {

    # OS別処理
    case $(os_name) in

    # OSX
    osx)
        ipconfig getifaddr $(netstat -rn -f inet | awk '/^default/{print $6; exit}')
        ;;

    # Windows
    win)
        ipconfig | grep -a IPv4 | head -n 1 | awk -F ': ' '{print $2}'
        ;;

    # その他
    *)
        echo "Unknown platform"
        ;;

    esac

}

4-5. Docker環境変数の設定方法

実はこちらは —shell オプションを明示すれば特別処理を分ける必要はありません。
以下のように指定します。

1
eval $(docker-machine env --shell=sh (マシン名))

4-6. Windowsでシェルスクリプトをプログラム内から呼び出す

以下のようなバッチファイル(bash.bat)を用意します。

1
2
3
@echo off

"C:\Program Files\Git\bin\bash.exe" --login %*

シェルスクリプトをプログラム内から実行する場合は、以下のコマンドを実行します。

1
bash.bat (実行するシェルスクリプトのファイルパス)

以上、紹介した関数があれば大抵のプラットフォーム差異は吸収することが可能です。

5. 設定ファイルはホスト側にあるソースファイルリポジトリから参照する

各コンテナ起動時に、ホスト側のボリュームをマウントし、ソースファイルリポジトリで管理されている Apacheの設定ファイル等を参照できるようにしておきます。 このとき、Apacheの設定ファイル内でログ出力先をマウントしたホスト側ボリュームとしておけば、 わざわざコンテナ内に入らなくてもログを閲覧できます。
以下はコンテナ起動時のコマンドとApacheの設定ファイルにおける記述例です。
(ここまでに紹介した関数を利用しています)

<起動コマンド>

1
2
3
4
docker run -p 80:80 -p 443:443 -d \
--add-host=docker.host:$(host_ip) \
-v $(docker_path $(share_dir)/Development/Projects/(Project Name)):/(Project Name) httpd-2.2 \
$(docker_path /opt/httpd-worker/bin/httpd) -f $(docker_path /(Project Name)/sources/server/conf/apache.local/httpd.conf) -DFOREGROUND

<Apacheの設定ファイルの一部分>

1
2
3
ServerRoot /(Project Name)/sources/server
PidFile /var/run/httpd.pid
ErrorLog /(Project Name)/httpd/log/error_log

※(Project Name)には任意の名称を入れてください

こちらは最初に紹介したディレクトリ構成が元となっています。 /(Project Name) でコンテナ内からプロジェクト配下のディレクトリを全てアクセスできるようにしておくことで ホスト側からソースファイルリポジトリで管理されている設定ファイルを読み込んでサーバを起動することができます。

また、—add-hostを利用することで、IPアドレスではなくホスト名で設定ファイル内からアクセスを行うことが可能になります。 Dockerコンテナ内のApacheからホスト側で起動しているTomcatへ連携する場合は、 ここで定義した名前をApache設定ファイル内に記述して連携します。

まとめ

上記で紹介したテクニックとポイントを抑えておけば
共通の設定ファイルを利用してWindowsとMacの開発環境を構築することが可能になります。
WindowsとMacが混在しているような現場では大いに役立つと思いますので、是非活用してみてください。

Comments