Chef Provisioningを使ってUbuntuのVSO Agentサーバをプロビジョニングしてみる

Chef Provisioningを使ってUbuntuのVSO Agentサーバをプロビジョニングしてみる
==================================================================

[Visual Studio Online](http://www.microsoft.com/ja-jp/dev/products/visual-studio-online.aspx)のビルド機能は結構イケてる。想像もしていなかったが、実はWindows系だけではなく、UbuntuとかMacなどのビルドもできたりする。

![SnapCrab_Microsoft Visual Studio Online ‎- Microsoft Edge_2015-9-10_23-21-10_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/3470/2d85a9ab-aa26-cebb-d2cc-faafc276f189.png)

ただし、Ubuntuでビルドしたい場合、Visual Studio Online(以下VSO)のエージェントを入れたビルドサーバーを作る必要がある。本番でAgentを入れたビルドサーバーをいちいちつくるのは面倒だ。
そこで、今回、AzureとChef-Provisioningを使って実際にあるプロジェクトのビルドサーバーを自動でプロビジョニングするコードを書いてみた。

![SnapCrab_Microsoft Visual Studio Online ‎- Microsoft Edge_2015-9-10_23-20-51_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/3470/264d3d18-dae3-800e-e3a4-f58285bd6a23.png)


今回題材につかっているのは[HOL - Parts Unlimited MRP App Continuous Integration with Visual Studio Online Build](https://github.com/Microsoft/PartsUnlimitedMRP/blob/master/docs/HOL_Continuous-Integration-with-Visual-Studio-Online-Build/HOL_Continuous-Integration-with-Visual-Studio-Online-Build.md)
にあるこのプロジェクトをVSOをつかってビルドする手順をUbuntuのバーチャルマシンを払い出すところから自動化してみることにした。

![SnapCrab_Parts Unlimited MRP! ‎- Microsoft Edge_2015-9-11_1-6-10_No-00.png](https://qiita-image-store.s3.amazonaws.com/0/3470/60b586c1-504d-17d8-3f99-af6beda4bb06.png)




1. 事前準備
----------

さて、事前準備としてはAzure CLIがインストールされて、設定されている必要がある。この詳細は次のページに書いておいたので参考にされたい。

[Azure CLIとChefを始めるためのSubscription関係まとめ](http://qiita.com/TsuyoshiUshio@github/items/27bc5e9d7e93214c01f0)

ほかにはGitが必要だ。この手順はWindows10で作成したが、コマンドラインのコマンドはMacも同じなので、読みかえていただければMacでも動作すると思われる。

[Git for Windows](https://git-for-windows.github.io/)

2. ChefDKのインストール
--------------------

これもWindows10の手順だが、ChefDKをインストールしたい。

まずその前にWindowsでもコマンドラインでごそごそといろいろ入れられるツールChocolateyをぶち込んでおいて、それでChefDKも入れてみよう。

### 2.1. Chocolatelyをダウンロードする

[Chocolatey](https://chocolatey.org/)のページにいくつかの方法が載っている。私はcmd.exeを管理者モードで動かして、次のコマンドを打つ方法を採用した。

```
 @powershell -NoProfile -ExecutionPolicy Bypass -Command "iex ((new-object net.webclient).DownloadString('https://chocolatey.org/install.ps1'))" && SET PATH=%PATH%;%ALLUSERSPROFILE%\chocolatey\bin
```

### 2.2. ChefDKのインストール

このツールを入れておくと、次のようにコマンドラインでいろいろなアプリケーションをインストールできる。たまにバージョンが古いものしかない場合もあるので注意が必要だ。[Chocolateyのページ](https://chocolatey.org/)でバージョンがご希望のものか検索してみよう。
SETXコマンドでパスを永続的に設定できるようだ。入れただけだとパスは通らない様子。

```
> choco install chefdk -y
> SETX /M PATH "%PATH%;c:\opscode\chefdk\bin"
> SETX /M PATH "%PATH%;c:\opscode\chefdk\embedded\bin"
> SET PATH=%PATH%;c:\opscode\chefdk\bin;c:\opscode\chefdk\embedded\bin"
```

### 2.3. Chef-Provisioning-Azureのインストール

次にChef-Provisioning-Azureのドライバをぶち込む。

```
> chef gem install chef-provisioning-azure
```

これで準備完了。ちなみに、本日2015/9/10にアップデートがかかって、Ubuntuのユーザ名の指定が失敗するバグが解決されているので、ぜひアップデートされたい。
以前にインストールしている人は次のコマンドでアップデート可能だ。

```
> chef gem update chef-provisioning-azure
```

3. Chefのレシピとプロビジョニング用のコードを書く
----------------------------------------

### 3.1. サンプルコードとその実行

さて、今回のコードはすべてGitHubにあげておいたので、皆様もクローンして、ちょっと変えたらいつでも動作するだろう。

[Visual Studio Online Build Agent Machine for Ubuntu](https://github.com/TsuyoshiUshio/vsoagentserver)

Gitがインストールされているなら、次のコマンドで取得できる

```
> git clone https://github.com/TsuyoshiUshio/vsoagentserver.git

```

ぜひ楽しんでください。そのままだと、私のサーバーとかぶってしまうので、[provisioning.rb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/provisioning.rb)と、[attributes/defailt.rb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/cookbooks/vsoagent/attributes/default.rb)を適当に変更して動かしてみてください。
`provisioning.rb`のvm_name, cloud_service_nameあたりを変えるだけで動くだろう。

ちなみにプロビジョニングさせるコマンドは次の通り。

```
> cd vsoagentserver
> chef-client --local provision.rb

[chef-client](https://docs.chef.io/chef_client.html)

```

ここでは割愛するが[HOL - Parts Unlimited MRP App Continuous Integration with Visual Studio Online Build](https://github.com/Microsoft/PartsUnlimitedMRP/blob/master/docs/HOL_Continuous-Integration-with-Visual-Studio-Online-Build/HOL_Continuous-Integration-with-Visual-Studio-Online-Build.md)にあるとおり、
VSO側の手順が存在するので、それを実施したのちサーバーにログインし次のコマンドを打つ。ちなみにサーバーのパスワードは、provision.rbに書いてあるのでこれも変えておくとよい。

```
> ssh azureuser@pendricachefdemo01.cloudapp.net
> cd myagent
> node agent/vsoagent
```

ちなみにこのサンプルコードを作成するにあたっては、ChefDKをインストールしたのち

```
> chef generate repo partsmrp
> cd partsmrp
> git init
> git add .

```

といった手順でテンプレートを生成したところから、始まる。これから、書き換えた部分のみを簡単に解説していく。

### 3.2. コードの解説

#### 3.2.1. Provisionファイル

Chef Provisionは、既に存在するマシンにプロビジョニングをするだけではなく、マシンの生成事態も、コードで実施できるイケメンだ。
様々なドライバが存在するが、Azureの場合は[chef-provisioning-azure](https://github.com/chef/chef-provisioning-azure)だ。
これは先ほどの手順ですでにインストール済みである。
　次のコードはリポジトリのトップにある[provisioning.rb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/provisioning.rb)だ。これが今回の起点となるコードだ。中身をみてみよう。

machine_optionsに様々なVMを上げるときのオプションが書いている。これらを指定できる。大体みていただければなんとなくお分かりかと思うが、
ポイントのみ解説したい。:passwordと:vm_userは各セクションが違うので注意。:vm_userは指定しないとデフォルトは`ubuntu`になる。これはVMへsshログインするときのユーザに
なっている。vm_sizeで指定できる値は[New D-Series Virtual Machine Sizes](https://azure.microsoft.com/ja-jp/blog/new-d-series-virtual-machine-sizes/)で確認可能。
:locationで指定可能な値は`azure vm location list`コマンドで確認できる。そして、machineリソースでmachine_optionsで指定した内容でマシンを起動する。
次のポイントはrecipeでVM起動後に実行されているレシピを指定しているところにある。ちなみに今はclassicVMにしか対応していない。

またvm_sizeの小さいものは`Small`が指定できてエコノミーだが、プロビジョニングするときに遅すぎてタイムアウトしたりするケースがあったので、多少強力なマシンにしている。
プロビジョニング後に小さなものに変えると安くていいだろう。

```
require 'chef/provisioning/azure_driver'
with_driver 'azure'

machine_options = {
  :bootstrap_options => {
    :cloud_service_name => 'pendricachefdemo01',
    :storage_account_name => 'pendricachefdemo01',
    :vm_size => "Standard_D11",
    :vm_user => "azureuser",
    :location => 'East Asia'
  },
  :image_id => 'b39f27a8b8c64d52b05eac6a62ebad85__Ubuntu-14_04_2-LTS-amd64-server-20150706-en-us-30GB',
  :vm_name => "penchefdemo01",
  :password => "P2ssw0rd"
  }
  machine 'penchefdemo01' do
    machine_options machine_options
    recipe 'vsoagent'
    converge true
  end
```

### 3.2.2. VSO agent cookbookのレシピ

レシピは、起動したVMに対してツールをインストールするなどの指定をする。今回のコードは次の通り。

[cookbooks/vsoagent/recipes/default.rb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/cookbooks/vsoagent/recipes/default.rb)

これもコードを切り取って少しづつ解説したい。基本的にChefのいいところは冪等性を保証してくれているところだ。
つまり、何回プロビジョニングしても同じ状態になってくれる。例えば前回途中までしかいかなかったとすると、途中までの分はうまくスキップ
してくれたりして、常に同じ状態になってくれる。これは地味に便利だ。ところで、本当はpackageのインストールの周りはまとめることができる。なぜまとめてないかというと、まとめるとしょぼいマシンをつかったときにタイムアウトが発生したからだ orz

```
execute "add-apt-repository ppa:openjdk-r/ppa" do
  user "root"
end

execute "apt-get update" do
  user "root"
end

package "gradle" do
  action :install
end

package "openjdk-8-jdk" do
  action :install
end

package "openjdk-8-jre" do
  action :install
end

package "mongodb" do
  action :install
end

execute "curl --silent --location https://deb.nodesource.com/setup_0.12 | sudo bash -" do
  user "root"
end

package "nodejs" do
  action :install
end

execute "/usr/bin/npm install vsoagent-installer -g" do
  user "root"
end

directory "/home/#{node["vsoagent"]["vm_user"]}/myagent" do
  owner node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  mode '0755'
  action :create
end

execute "/usr/bin/vsoagent-installer" do
  cwd "/home/#{node["vsoagent"]["vm_user"]}/myagent"
  user node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  not_if {File.exist?("/home/#{node["vsoagent"]["vm_user"]}/myagent/agent")}
end

template "/home/#{node["vsoagent"]["vm_user"]}/.bash_profile" do
  mode '0664'
  source '.bash_profile.erb'
  user node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  not_if { File.exist?("/home/#{node["vsoagent"]["vm_user"]}/.bash_profile")}
end

template "/home/#{node["vsoagent"]["vm_user"]}/myagent/.agent" do
  mode '0664'
  source '.agent.erb'
  user node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  not_if { File.exists?("/home/#{node["vsoagent"]["vm_user"]}/myagent/.agent")}
end
```

#### 3.2.2.1. executeリソース

これは、普通に対象サーバーでコマンドを実行してくれるものだ。後述するが、冪等性をカバーするために、工夫が必要
何回も実行されても影響のないコマンドなら気にしなくてよい。

[execute](https://docs.chef.io/resources.html#execute)


```
execute "add-apt-repository ppa:openjdk-r/ppa" do
  user "root"
end
```

#### 3.2.2.2. packageリソース

これは、パッケージをインストールするためのもの。Ubuntuの場合apt-getが実行される。Yes-Noの確認をすることをスキップする`-y`は指定される。

[package](https://docs.chef.io/resource_package.html)

```
package "mongodb" do
  action :install
end
```

#### 3.2.2.3. attributes

レシピを書くときに変数を使いたいケースがある。そのような場合次のように書ける。

```
directory "/home/#{node["vsoagent"]["vm_user"]}/myagent" do
  owner node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  mode '0755'
  action :create
end
```

これらの変数はもとはcookbook内にある[cookbooks/vsoagent/attributes/default.rb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/cookbooks/vsoagent/attributes/default.rb)で定義されている。これはデフォルト値のようなものなので設定によっては上書き可能だ。

```
default["vsoagent"]["pool_name"] = "demo01pool"
default["vsoagent"]["server_url"] = "https://devopsjapan.visualstudio.com"
default["vsoagent"]["agent_name"] = "demo01agent"
default["vsoagent"]["vm_user"] = "azureuser"
default["vsoagent"]["vm_group"] = "azureuser"

```

#### 3.2.2.4. templateリソース

最後に[Resource Template](https://docs.chef.io/resource_template.html)。これは、何らかのファイルを書きたいときによく使う。

```
template "/home/#{node["vsoagent"]["vm_user"]}/.bash_profile" do
  mode '0664'
  source '.bash_profile.erb'
  user node["vsoagent"]["vm_user"]
  group node["vsoagent"]["vm_group"]
  not_if { File.exist?("/home/#{node["vsoagent"]["vm_user"]}/.bash_profile")}
end
```

これは、sourceのところで指定されたテンプレートの中身でファイルを作る。この例では[cookbooks/vsoagent/templates/default/.bash_profile.erb](https://github.com/TsuyoshiUshio/vsoagentserver/blob/master/cookbooks/vsoagent/templates/default/.bash_profile.erb)のテンプレートが適用されて、bash_profileが作られる内容はこうだ

```
# Set environment variables for Java
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=$PATH:/usr/lib/jvm/java-8-openjdk-amd64/bin
```

もちろん、テンプレートにも変数は使える。ほかの.agentなどのコードを見てほしい。

4. 今回苦労したところ
------------------

自動化野郎としては、エージェントのサービス起動まで自動化したかったところだ。VSOエージェントは今のままだと、どうしても手作業が入ってしまう。VSOエージェントのページをみると、実はサービスで動かすモードがある
らしい。これは熱い！！！[Microsoft Cross Platform Build Agent](https://github.com/Microsoft/vso-agent/blob/master/docs/service.md)しかし、残念ながらこのページを見ると、MacではあるがUbuntuでのサポートはまだのようだ。無念。

次の作戦はDockerHubだ！DockerHubは自動化野郎の心のふるさと。こまったらここだ。しっかりと[jgarverick/vsoagent](https://hub.docker.com/r/jgarverick/vsoagent/)なんてのがある。
これ使ったらDockerだといちころよね。しかも、Dockerfileみると設定がわかる！というわけで見てみよう。[Dockerfile](https://hub.docker.com/r/jgarverick/vsoagent/~/dockerfile/)

```
  :
RUN mkdir /opt/vsoagent
RUN mkdir /opt/vsoagent/_work
WORKDIR /opt/vsoagent
RUN vsoagent-installer
WORKDIR /opt/vsoagent/agent
COPY agent.config /opt/vsoagent/agent/.agent
RUN chown -R vsoservice /opt/vsoagent
  :
```

世の中あまくなかった。.agentファイルに、visual studio onlineを指定する場所などを記載するところがあり、`node agent/vsoagent`を初回起動したときにいろいろごねごね入れる手順のみが
省けるようで、今のままだと自分のエイリアスユーザコードとパスワードを入力するのは避けられない。[対話式のコマンドをスクリプト化する方法](http://qiita.com/dogyear/items/e58ddab9a49bf82ed43f)みたいな方法でなんとかなるのかな。
でも本質的にはサービス化待ちのほうが本番はいいですね。

5. おわりに
---------

というわけで、最後の一歩だけまだですが、Azure + Chef ProvisiningでがっつりVM生成、プロビジョニングを決めてVSOエージェントのビルドサーバーを作れたのでよしとしましょう。
ワンコマンドでエージェント動作します。こういうインフラストラクチャのコード化をしていると、いつでも安心して環境を再構築できるので、ほんま気が楽ですね！

ではよいコーディングを！