Mac mini増殖中!iOSアプリのビルドをマスター・スレーブ化して時間を短縮する
チームでiOSアプリの開発をしていると、ビルドのコストが肥大化しがちです。ピクシブでは、ビルドとテストをMac mini上に構築したJenkinsで行っているのですが、ビルドキューが詰まり、開発速度が上がりにくくなっていました。
これを改善するため、最近、iOSチームのビルドサーバーを、Mac mini 1台から3台に増やし、マスター・スレーブ環境でビルドできるようにしました。今回、そのための設定について、エンジニアの @anchan から紹介します!
ビルドサーバーの環境設定は、GitHubに公開しています!
モチベーション
Xcodeは1台のマシンでビルドを並列化できません。チームメンバーとアプリの数が増えるにつれ、それに比例してJenkinsのビルドキューが詰まるようになりました。
Mac mini 1台では厳しくなってきたので、Mac mini 3台でJenkinsのマスター・スレーブ環境を作る事にしました。既にあったMac miniをマスターのJenkinsサーバーとして使って、新しい2台をスレーブとして使うことにしました。
CIサービスも検証しましたが、ピクシブのiOSチームにはMac miniとJenkinsの方が合うと判断しました。CIサービスだと新しいXcodeのバージョンが出てから使えるまでに時間がかかります。Xcodeのバージョンによって対応しているSwift言語のバージョンが変わるのでCIサービスが新しいXcodeに対応するまでには新しいSwiftも使えません。パフォーマンスとしてもMac miniでビルドする方がCIサービスでビルドするより早いです。価格でもMac miniが負けません。CircleCIのGrowthプランやTravis CIのSmall Businessプランの1年分の価格でハイスペックのMac mini 2台を購入できます。
Mac miniで使用したOSについてですが、後述するようにmacOS Sierraでいくつか問題があったので今回の構成にはOS X El Capitanをつかっています。
構成管理システム
マスター・スレーブ環境は1台構成とくらべて問題を起こしがちです。例えば、1台構成だとXcodeをインストールするにはMac miniにリモートデスクトップで繋いで、Xcodeをインストールだけとシンプルです。これが3台だと、1台づつに繋いでXcodeをインストールすることになり、違うディレクトリにインストールしてしまうなどの間違いが起こりやすくなります。そこで、構成管理システムの出番です。
構成管理システムとしてSaltStackを選びました。Salt SSHを使えば、SaltのサーバーもクライアントもMac miniにインストールする必要がなくて便利です。Ansibleでもいいのですが、Saltでの構築経験があったのでSaltをつかうことにしました。
Saltの基本的な使い方については割愛します。ドキュメンテーションを参考にしてください。
ブートストラップスクリプト
SaltStackをつかう前に、基本なツールをインストールします。Homebrewが必要なのですが、インストールにはXcodeかXcode Command Line Toolsが必要です。Xcodeのインストールは自動化しにくいので、Xcode Command Line Toolsをsoftwareupdate
でインストールします。
#! /bin/bash # SSHログインを有効 sudo systemsetup -setremotelogin on # Xcode Command Line Toolsをインストール # 参考: https://github.com/Homebrew/install/blob/e28329a14d328b603e00756e99576c75f08e8dd9/install#L230-L237 if [ ! -d /Library/Developer/CommandLineTools ]; then COMMAND_LINE_TOOLS_PLACEHOLDER=/tmp/.com.apple.dt.CommandLineTools.installondemand.in-progress sudo touch $COMMAND_LINE_TOOLS_PLACEHOLDER COMMAND_LINE_TOOLS_NAME=$(softwareupdate -l | grep -B 1 -E "Command Line (Developer|Tools)" | awk -F"*" '/^ +\\*/ {print $2}' | sed 's/^ *//' | head -n1) sudo softwareupdate -i "$COMMAND_LINE_TOOLS_NAME" sudo rm $COMMAND_LINE_TOOLS_PLACEHOLDER fi # Homebrewをインストール。TRAVISを設定すると確認のためのエンターを押さなくても良くなる。 # 参考: http://brew.sh/ TRAVIS=1 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
これを実行すると、SSHログインが有効になり、Xcode Command Line ToolsとHomebrewがインストールされます。次に、SaltStackの設定を行います。
ユーザーとSSH鍵認証
すべてのMac miniに、同一のOS XのユーザーとSSH鍵と鍵認証を設定します。SSH鍵が同じだと、新しいスレーブを追加したり、スレーブからマスターにビルドアーティファクトをコピーしたり、マスターがスレーブにSSH接続するのが楽になります。ユーザーとSSH鍵の情報をSalt Pillarに入れて、SSH鍵と鍵認証を設定するSaltステートを作ります。
# pillar/user/init.sls user: account: pixiv password: himitsu
# pillar/ssh/init.sls ssh: public_key: ここに公開鍵を書いてね private_key: | ここに秘密鍵を書いてね authorized_keys: - authorized_keysに入れたい公開鍵を書いてね
# salt/ssh/init.sls {% set user = salt['pillar.get']('user:account') %} {% set ssh_directory = '/Users/' + user + '/.ssh' %} {{ ssh_directory }}: file.directory: - user: {{ user }} - group: staff - mode: 700 # ホスト鍵確認を無視する {{ ssh_directory }}/config: file.managed: - user: {{ user }} - group: staff - mode: 600 - contents: | Host * StrictHostKeyChecking no {{ ssh_directory }}/authorized_keys: file.managed: - user: {{ user }} - group: staff - mode: 600 - contents: {% for key in salt['pillar.get']('ssh:authorized_keys') %} - {{ key }} {% endfor %} # 公開鍵 {{ ssh_directory }}/id_rsa.pub: file.managed: - user: {{ user }} - group: staff - mode: 600 - contents_pillar: ssh:public_key # 秘密鍵 {{ ssh_directory }}/id_rsa: file.managed: - user: {{ user }} - group: staff - mode: 600 - contents_pillar: ssh:private_key
fastlaneの設定
ピクシブのiOSプロジェクトは、各種作業の自動化のためにfastlaneを使っています。fastlaneは、Apple IDの管理やプロビジョニングプロファイルの同期のような、全プロジェクトに影響がある作業の自動化に向いているので、システム全体にインストールするのがおすすめです。Xcodeのインストール自動化にも役立ちます。
fastlaneのインストール
OS X El CapitanのシステムRubyだとSSL関係の問題があるので、Homebrewで新しいバージョンのRubyをインストールします。
# salt/ruby/init.sls ruby: pkg.installed
その後、pixivユーザーとしてfastlaneをインストールします。
# salt/fastlane/fastlane/init.sls fastlane: gem.installed: - gem_bin: /usr/local/bin/gem - runas: {{ salt['pillar.get']('user:account') }}
fastlaneのApple ID管理
fastlaneにApple IDを設定すると、審査提出やプロビジョニングプロファイル管理などができるようになります。ピクシブの場合は、App Storeのアカウントとエンタープライズのアカウント両方があるので、複数アカウントを登録します。
まずはpillarにアカウントを登録してみましょう。
# pillar/apple/init.sls apple: accounts: store: username: app-store@example.com password: himitsu enterprise: username: enterprise@example.com password: naisho # Xcodeのダウンロードなどに使うアカウント default_account: store
fastlaneにアカウントを追加するにはfastlane-credentials add
が使えます。しかし、fastlaneでは既にアカウントが存在しているのかを、知る方法がありません。fastlane-credentials
のコードを見ると、キーチェーンにdeliver.$ACCOUNT
としてアカウントのパスワードを保存しているので、security find-internet-password
を使えば既に追加されたアカウントかがわかります。
# salt/fastlane/credentials/init.sls {% set user = salt['pillar.get']('user:account') %} {% set password = salt['pillar.get']('user:password') %} {% for name, account in salt['pillar.get']('apple:accounts').items() %} fastlane_credentials_{{ name }}: cmd.run: - name: | security unlock-keychain -p "{{ password }}" /Users/{{ user }}/Library/Keychains/login.keychain fastlane-credentials add --username {{ account['username'] }} --password {{ account['password'] }} - runas: {{ user }} - unless: security find-internet-password -s deliver.{{ account['username'] }} - require: - gem: fastlane {% endfor %}
プロビジョニングプロファイルの同期
fastlaneのsighで、プロビジョニングプロファイルの管理ができます。各Apple IDの最新プロビジョニングプロファイルを同期します。
# salt/fastlane/sigh/init.sls {% set user = salt['pillar.get']('user:account') %} {% set password = salt['pillar.get']('user:password') %} {% for name, account in salt['pillar.get']('apple:accounts').items() %} fastlane_sigh_{{ name }}: cmd.run: - name: | security unlock-keychain -p "{{ password }}" /Users/{{ user }}/Library/Keychains/login.keychain sigh download_all - runas: {{ user }} - cwd: /tmp - env: - FASTLANE_USER: {{ account['username'] }} - require: - gem: fastlane {% endfor %}
Xcodeのバージョン管理
ピクシブはプロジェクトによって、使っているXcodeのバージョンに差があります。複数Xcodeのバージョンをインストールして簡単に切り替えれるようにします。
Xcodeのインストールは複雑で自動化しにくいです。Xcodeをダウンロードするにはログインが必要な上、インストール後も初回起動時には利用規約に同意する操作が求められます。またXcodeのバージョンによって、.dmg
ファイルもあれば.xip
ファイルのものもあります。
そこでxcode-installを使います。xcode-installはfastlaneの開発者KrauseFxが作った、Xcodeのインストールツールです。fastlaneはxcode-installに依存しているので、既にインストールされています。これを使うと複数のXcodeのバージョンを簡単にインストールできます。
Pillarにインストールしたいバージョンを記述します。
# pillar/xcode/init.sls xcode: versions: - 7.3.1 - 8 - 8.1 # xcode-selectで設定するバージョン default_version: 8.0
ステートは以下のとおり。
# salt/xcode/init.sls {% set user = salt['pillar.get']('user:account') %} {% set password = salt['pillar.get']('user:password') %} {% set fastlane_user = salt['pillar.get']('apple:accounts')[salt['pillar.get']('apple:default_account')]['username'] %} # 各バージョンをインストール {% for version in salt['pillar.get']('xcode:versions') %} xcversion_install_{{ version }}: cmd.run: - name: | security unlock-keychain -p "{{ password }}" /Users/{{ user }}/Library/Keychains/login.keychain xcversion update xcversion install {{ version }} xcodebuild -license accept - unless: xcversion installed | grep {{ version }} - env: - FASTLANE_USER: {{ fastlane_user }} - require: - gem: fastlane {% endfor %} # デフォルトXcodeを設定 {% set default_version = salt['pillar.get']('xcode:default_version') %} xcversion_select: cmd.run: - name: | xcversion select {{ default_version }} xcodebuild -license accept - unless: xcversion selected | grep {{ default_version }} - require: - gem: fastlane
これで、Xcodeのバージョンを簡単にDEVELOPER_DIR
の環境変数で切り替えできます。
Xcodeのファイルサイズが大きいので(4~5 GB)、Mac mini 3台が同時に3つのバージョンのXcodeをダウンロードすると、全体で45 GB程度のダウンロードサイズになり、ネットワークの帯域を圧迫します。しかし、~/Library/Caches/XcodeInstall
に、各バージョンごとのXcodeのdmg
やxip
を置くとダウンロードが発生しません。事前に各Mac miniにXcodeのファイルを置くとよいでしょう。
コードサイニングの秘密鍵・証明書管理
開発中のiOSアプリを社内配布するには、コードサイニングの秘密鍵と証明書が必要になります。Ad hocとエンタープライズのビルド両方やっているので、複数のコードサイニングの秘密鍵と証明書をKeychainにインポートしなくてはいけません。
キーチェーンアクセスから、証明書と秘密鍵を書き出してpillarにbase64(base64 -b64 file.p12
)のデータとして設定します。
# pillar/keychain/init.sls keychain: keys: store_adhoc: passphrase: himitsu contents_base64: | 44GT44GT44Gr56eY5a+G6Y2144Go6Ki85piO5pu444KS5YWl44KM44Gm44Gt44CC 44Kt44O844OB44Kn44Oz44Ki44Kv44K744K544GL44KJcDEy44Go44GX44Gm5pu4 44GN5Ye644GX44Gm44Gt44CC5a6f6Zqb44Gr44GT44KM44KI44KK5YWo54S26ZW3 44GP44Gq44KL44Gv44Ga44CC store_developer: passphrase: himitsu contents_base64: | 44GT44GT44Gr56eY5a+G6Y2144Go6Ki85piO5pu444KS5YWl44KM44Gm44Gt44CC 44Kt44O844OB44Kn44Oz44Ki44Kv44K744K544GL44KJcDEy44Go44GX44Gm5pu4 44GN5Ye644GX44Gm44Gt44CC5a6f6Zqb44Gr44GT44KM44KI44KK5YWo54S26ZW3 44GP44Gq44KL44Gv44Ga44CC
ステートの方でbase64にデコードしてキーチェーンにインポートします。security import
に-T /usr/bin/codesign
のパラメーターを渡すとcodesignに鍵の使用を許可することができます。許可をしないと、ビルド時に許可のポップアップがでてしまいます。
# salt/keychain/init.sls {% set user = salt['pillar.get']('user:account') %} {% set password = salt['pillar.get']('user:password') %} {% set keychain = '/Users/' + user + '/Library/Keychains/login.keychain' %} {% set key_dir = '/Users/' + user + '/.keys' %} {{ key_dir }}: file.directory: - user: {{ user }} - group: staff - mode: 700 # base64デコード {% for name, key in salt['pillar.get']('keychain:keys').items() %} keychain_key_file_{{ name }}: cmd.run: - name: echo {{ key['contents_base64'] | replace('\n', '') }} | base64 -D > {{ key_dir }}/{{ name }}.p12 - runas: {{ user }} - creates: {{ key_dir }}/{{ name }}.p12 - require: - file: {{ key_dir }} # キーチェーンにインポート keychain_key_import_{{ name }}: cmd.run: - name: | security unlock-keychain -p "{{ password }}" {{ keychain }} security import {{ key_dir }}/{{ name }}.p12 -P "{{ key['passphrase'] }}" -k {{ keychain }} -T /usr/bin/codesign touch {{ key_dir }}/{{ name }}.p12.imported - runas: {{ user }} - creates: {{ key_dir }}/{{ name }}.p12.imported - require: - cmd: keychain_key_file_{{ name }} {% endfor %}
Java依存問題の解決
JenkinsはJavaに依存しています。スレーブもJavaが無いと動きません。簡単に自動でインストールするにはHomebrew Caskを使います。
まずはHomebrew Caskをインストールするステートを作ります。事前にCaskroom
のディレクトリを作っておかないと、初めてbrew cask
を実行する時にHomebrew Caskの設定を行ってエンターキーを押さないと進めなくなってしまいます。ディレクトリを事前に作れば、brew cask
の設定が終了したと認識されるため、自動化できるようになります。
# salt/tools/brew-cask/init.sls {% set user = salt['pillar.get']('user:account') %} brew_cask_install: cmd.run: - name: brew tap caskroom/cask - runas: {{ user }} - unless: brew tap | grep caskroom/cask /usr/local/Caskroom: file.directory: - user: {{ user }} - group: staff - mode: 775
Homebrew Caskをインストールすると、Javaを簡単にインストールできます。
# salt/tools/java/init.sls java_install: cmd.run: - name: brew cask install java - unless: brew cask list | grep -x java - require: - cmd: brew_cask_install
OS Xのセッションとキーチェーンについて
マスターにJenkinsをインストールする前に、OS Xのセッションとキーチェーンの仕組みを理解する必要があります。ここでも簡単に紹介しておきましょう。
セッションとは
セッションはOS Xのプロセスの分け方です。あるセッション内のプロセスは、他のセッション内のプロセスの存在を知ることができません。詳しくはAppleのドキュメンテーションをご覧ください。
OS Xには、ルートセッションとユーザーセッションの2種類のセッションがあります。ルートセッションは、mDNSResponder
やsshd
など、ユーザーと関係無いプロセスが動いています。ユーザーセッションは、ユーザーがUIにログインしてから作られ、ログインしたユーザーのためにプロセスが動くセッションです。
キーチェーンとは
OS Xはパスワード・鍵・証明書などをキーチェーンに保存しています。デフォルトでシステムのキーチェーンとユーザーのログインキーチェーンがあります。
システムキーチェーン(/Library/Keychains/System.keychain
)はどのセッションからも、Read Onlyとしてアクセスできます。
ログインキーチェーン($HOME/Library/Keychains/login.keychain
)はユーザー毎のキーチェーンになります。ユーザーセッションから自身のキーチェーンにのみアクセスできます。
iOS開発との関係
ルートセッションからiOSのシミュレータを起動できなかったり、ログインキーチェーンにアクセスできなかったりする事があります。対策はありますが、OS Xのバージョンや、Xcodeのバージョンによって動かなかったりと、問題は複雑です。可能な限りユーザーセッションで動くと楽になります。
マスターにJenkinsをインストール
Jenkinsのジョブからシミュレーターを起動したり、ユーザーのキーチェーンをアクセスするためにユーザーセッションの中でJenkinsを起動します。そのためにJenkinsをpixivユーザーのLaunchAgentsに入れます。
# salt/jenkins/init.sls {% set user = salt['pillar.get']('user:account') %} {% set jenkins_startup_file = '/Users/' + user + '/Library/LaunchAgents/net.pixiv.jenkins.plist' %} jenkins: pkg.installed jenkins_startup: # LaunchAgentを作る file.managed: - name: {{ jenkins_startup_file }} - user: {{ user }} - group: staff - makedirs: True - contents: | <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>net.pixiv.jenkins</string> <key>ProgramArguments</key> <array> <string>/usr/bin/java</string> <string>-Dmail.smtp.starttls.enable=true</string> <string>-jar</string> <string>/usr/local/opt/jenkins/libexec/jenkins.war</string> <string>--httpListenAddress=0.0.0.0</string> <string>--httpPort=8080</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> # launchdに登録 cmd.run: - name: launchctl load -w {{ jenkins_startup_file }} - runas: {{ user }} - onchanges: - file: jenkins_startup
これでpixivユーザーがログインしたら、Jenkinsはポート8080で起動されます。
Jenkinsの環境変数
Jenkinsの管理画面で全体的な環境変数を設定する必要があります。
まずはOS Xのデフォルトlocaleには問題があり、そのままではいろんなツールがうまく動きません。
Anchans-MacBook-Pro:~ anchan$ locale LANG= LC_COLLATE="C" LC_CTYPE="UTF-8" LC_MESSAGES="C" LC_MONETARY="C" LC_NUMERIC="C" LC_TIME="C" LC_ALL=
対策としてLC_CTYPE
をen_US.UTF-8
に設定します。
また、/usr/local/bin
をPATH
に入れないとHomebrewでインストールしたパッケージを実行できないのでPATHを設定します。
スレーブの設定
Jenkinsマスターは、スレーブにSSHで接続してJenkinsのSlave Agentを起動します。問題はsshd
がルートセッションで動いているので、スレーブでシミュレーターを起動したり、ログインキーチェーンをアクセスしたり、といったことができません。
Appleのドキュメンテーションによってユーザーセッション(このドキュメンテーションでは「Aqua session」と呼ばれています)が存在している、つまりユーザーはUIにログインしていれば、SSHで接続する時はユーザーセッションに接続します。ただEl Capitanだとルートセッションになるので、そのための対策が必要でした。
対策として、ユーザーセッションの中でユーザーとしてもう一つのsshd
を動かします。Jenkinsはこのユーザーセッションのsshd
に接続して、Slave Agentをユーザーセッションの中に起動するので、シミュレーターとログインキーチェーンを問題なく使うことができます。
ユーザー権限でsshd
を実行する方法について情報はあまりみかけませんが、2008年のCygwinのメールリストのポストを見つけました。これを参考にしてユーザーsshd
ステートを作ります。
{% set user = salt['pillar.get']('user:account') %} {% set sshd_directory = '/Users/' + user + '/.sshd' %} {% set key_types = ['rsa', 'dsa', 'ecdsa', 'ed25519'] %} {% set sshd_startup_file = '/Users/' + user + '/Library/LaunchAgents/net.pixiv.sshd.plist' %} # sshd_configを作る {{ sshd_directory }}/config: file.managed: - user: {{ user }} - group: staff - makedirs: True - contents: | Port 2222 {% for key_type in key_types %} HostKey {{ sshd_directory }}/ssh_host_{{ key_type }}_key {% endfor %} UsePrivilegeSeparation no PidFile {{ sshd_directory }}/sshd.pid SyslogFacility AUTHPRIV AuthorizedKeysFile .ssh/authorized_keys AcceptEnv LANG LC_* Subsystem sftp /usr/libexec/sftp-server # 各種類のホストキーを作る {% for key_type in key_types %} user_sshd_host_key_{{ key_type }}: cmd.run: - name: ssh-keygen -t {{ key_type }} -f {{ sshd_directory }}/ssh_host_{{ key_type }}_key -N "" - runas: {{ user }} - creates: {{ sshd_directory }}/ssh_host_{{ key_type }}_key {% endfor %} user_sshd_startup: # LaunchAgentを作る file.managed: - name: {{ sshd_startup_file }} - user: {{ user }} - group: staff - makedirs: True - contents: | <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>net.pixiv.sshd</string> <key>ProgramArguments</key> <array> <string>/usr/sbin/sshd</string> <string>-f</string> <string>{{ sshd_directory }}/config</string> <string>-D</string> </array> <key>RunAtLoad</key> <true/> </dict> </plist> # launchdに登録 cmd.run: - name: launchctl load -w {{ sshd_startup_file }} - runas: {{ user }} - onchanges: - file: user_sshd_startup # コンフィグファイルが変わったらsshdを再起動 user_sshd_restart: cmd.run: - name: | launchctl unload {{ sshd_startup_file }} launchctl load {{ sshd_startup_file }} - runas: {{ user }} - onchanges: - file: {{ sshd_directory }}/config
これでユーザーセッション内でsshd
がポート2222で起動されます。
Jenkinsの管理画面にスレーブを登録
Jenkinsの「Manage Nodes」の管理画面からこのスレーブを追加できるようになりました。追加する時は「Launch slave agents on Unix machines via SSH」を選んで「Advanced」から「Port」を「2222」に設定します。
これでマスターがスレーブに接続して設定が終わりました!
macOS Sierraを使わなかった理由
今後XcodeはSierraしか対応しなくなるので、最初はSierraを使おうとしていましたが、いくつかの越えられない壁が出てEl Capitanを使う事にしました。その理由を、いくつかご紹介します。
キーチェーンを切り替えられなかった
El Capitanだとssh
でログインするとログインとシステムのキーチェーン両方が見えます
$ security list-keychains "/Users/pixiv/Library/Keychains/login.keychain" "/Library/Keychains/System.keychain"
security unlock-keychain ~/Library/Keychains/login.keychain
をするとすぐログインキーチェーンを使う事ができます。
Sierraの場合は
$ security list-keychains "/Library/Keychains/System.keychain" "/Library/Keychains/System.keychain"
ログインキーチェーンをサーチリストに追加しようとしても
$ security list-keychains -s ~/Library/Keychains/login.keychain $ security list-keychains "/Library/Keychains/System.keychain" "/Library/Keychains/System.keychain"
変わりません…!追加ができないようです。
キーチェーンに許可済み状態として鍵をインポートできない
鍵のp12ファイルをキーチェーンに以下のようにインポートしようとすると
$ security import key.p12 -P passphrase -k ~/Library/Keychains/login.keychain -T /usr/bin/codesign
/usr/bin/codesign
にその鍵を読む許可をあげています。El Capitanではうまく動きますが、Sierraだとキーチェーンアクセスで見るとcodesign
に許可があるにもかかわらずビルド時に許可ダイアログが出てしまいます。これでは自動化できません。
まとめ
ピクシブで利用しているiOSアプリのビルドのJenkinsのマスター・スレーブ環境の設定を紹介しました。Mac mini 1台から3台にしてからというもの、ビルドキューが詰まることもなく、アプリチームメンバーは幸せです!
細かい話は飛ばしたので詳しくはGitHubのリポジトリ、SaltStackのドキュメンテーション、そしてJenkinsのマスター・スレーブドキュメンテーションを参考にしてください。