OSS版 Drone.io を社内で使い始めてる話

標記の通りで、Docker tips直球じゃなかったりしますが、コアにDockerがいるツールではあるよと言うことで。

どんな感じで運用しているかの話をしてお茶を濁しつつDocker話題を交えていきます。

### インストール

Puppetのマニフェストをきっちり書いて、後述するようにそのマニフェストをdrone自身にCIさせている。

DockerのPuppetモジュールとして [garethr-docker](https://github.com/garethr/garethr-docker) と言うのを使っている。Droneのマニフェストはインストール手順を見て自分で書いた。

インスフトール手順をレシピに落とすのはそこまで難しくなかった。Puppetが苦手なら、ChefなりAnsibleなりで一度一通り書いておくと安心感があると思う。

### 監視

munin で雑にやっている。せっかくDockerが入るので普通にmuninのイメージを引っ張って監視が設定できる。便利。あ、 `udzura/munin-master` はぼくの作ったイメージです。。

```bash
docker pull udzura/munin-master
docker run -d -p 8080:80 -e "NODES=dokku001.local:192.168.46.110" udzura/munin-master
```

これだとデフォルトのプラグインしかないため、後述するようにfd周りが怪しいからそこを監視するプラグインを書く（もしくは同僚にもらう）必要がある...。

ちなみに上で紹介したPuppetのDockerモジュールがめっちゃ便利で、こんな感じのマニフェストを書けば上と同等のコンテナを立ち上げてくれる。

```puppet
package { 'munin-node': }

docker::image { 'udzura/munin-master': }

docker::run { 'drone001_munin001':
  image => 'udzura/munin-master',
  ports => ['8080:80'],
  volumes => ['/etc/localtime:/etc/localtime:ro', '/var/munin-data:/var/lib/munin'],
  # docker0 ネットワークの ip address が facter で取れる
  # これはコンテナから見たDockerホストのIPとなる
  env => ["NODES=drone001.local:${ipaddress_docker0}"],
}

```

### カスタムイメージについて

Droneの醍醐味はテスト用のDockerイメージを自作できることだと思う。

- https://github.com/udzura/drone-images

ひとまずこっちでフォークしてカスタムイメージを作る運用をしている。

Droneのイメージ作りにはいくつかコツがある：

- `bradrydzewski/base` もしくはそれをベースにした `bradrydzewski/*` からつくった方が何かと便利
- `bradrydzewski/base` には、[ここ](https://github.com/drone/images#base)に書いてある通りgccやらmake、PythonにRubyと言った基本的なツールや言語が既に入っている。言語をまたいで何かする場合も便利
- Droneのルールとして、イメージ名が `drone/*` または `bradrydzewski/*` で始まっていると`USER ubuntu`でコマンドが実行される。Dockerfileでもsudoなど適宜する必要がある。

Ruby 2.1.5 のイメージとかは公式にまだ無いみたいで、そういうのは自分でビルドしてある。

将来は、Dockerfileリポジトリを社内GH:Eとかに置いて、プルリクがマージされたらDroneのホストでビルドして（それもDroneでやる！）すぐ使えるようになる、みたいなのを構築しようと思っている。

### Docker in Docker なテストを作る

```docker:Dockerfile
# see https://registry.hub.docker.com/u/igneoussystems/docker-client/dockerfile/
FROM bradrydzewski/go:1.3
MAINTAINER udzura@udzura.jp

RUN sudo apt-get install -y aufs-tools ca-certificates curl git xz-utils

ENV DOCKER_VERSION 1.3.1
RUN sudo curl -SL https://get.docker.io/builds/Linux/x86_64/docker-$DOCKER_VERSION -o /usr/bin/docker \
    && sudo chmod +x /usr/bin/docker

# Total hack since this is typically a client looking at the host
ENV DOCKER_HOST tcp://172.17.42.1:2375
```

こう言うDockerfileで、`drone/docker-in-docker:1.3.1` という名前のイメージを作る。なお、後述するようにGoのサービスを動かしてたりするマニフェストのテストなので、`bradrydzewski/go:1.3`をベースにして一緒にGoのテストも動かしたりする。

* `docker` クライアントコマンドはバイナリをダウンロードすれば終わるので便利
* `ENV DOCKER_HOST tcp://172.17.42.1:2375` はかなり雑なハックだが大丈夫なのだ...。

このイメージから、DroneをホストしているDockerのコンテナを操作できる。

```yaml:.drone.yml
image: drone/docker-in-docker:1.3.1

env:
  - CONTAINER_NAME=drone-puppet_${DRONE_COMMIT:0:8}
  - IMAGE_NAME=drone-puppet

script:
  - docker version
  - docker build -t $IMAGE_NAME .
  - docker run --name=$CONTAINER_NAME --cap-add=SYS_ADMIN -d $IMAGE_NAME
  - sleep 3
  - docker exec $CONTAINER_NAME /bin/bash -c 'cd /var/puppet && librarian-puppet install --path=vendor/modules'
  - docker exec $CONTAINER_NAME /bin/bash -c 'cd /var/puppet && puppet apply --modulepath=modules:roles:vendor/modules manifests/drone.pp; echo $? > /tmp/puppet-result'
  - test $(docker exec $CONTAINER_NAME /bin/cat /tmp/puppet-result) -eq "0"
  - docker stop $CONTAINER_NAME
  - docker rm $CONTAINER_NAME || true
  - docker rmi $IMAGE_NAME || true
```

* プロジェクトに `ADD . /var/puppet` する感じのDockerfileを置いて、ビルド。
* 作ったイメージを立ち上げ。
* `docker exec` で `puppet apply` し、
* Serverspec も走らせる。

そうそう、`--cap-add=SYS_ADMIN` するとDockerコンテナ内でDockerをインストールできる。インストールはね（aufsかcgroups周りのパッケージインストールで必要だったのだ。詳細は忘れちゃった...）。

なお、 `docker exec` は 1.3.1 の段階では失敗しても0で終わるのでハックしている... [以前Qiitaに書いた](http://qiita.com/udzura/items/cf0fb8322bb616a733a2) 。

あと、まれに `docker rm` が失敗したりする（なんでだろう感はある...）が、それはテストの失敗ではないので `|| true` してビルド結果に反映しないようにしている。

ともかく言いたいのは、こんなDocker in Dockerなテストでも大丈夫なので、たいてい何でもできるであろうと言うことであった。

### Ikachanと連携する

弊社、IRCのハードユーザで、Ikachanと連携できると便利だったためなんとかした。

具体的には、Webhookの内容をIkachanにプロクシする感じのWebサーバをGoで書いて立ち上げておく。

Webhookからはこういう感じのパラメータが渡るので、Go側で構造体にマップし、メッセージを構築して、おもむろにIkachanのURLを叩くだけの人を作り、upstartで立ち上げてホストする。

```json
{
  "owner": {
    "remote": "enterprise.github.com",
    "login": "udzura",
    "name": "Uchio KONDO",
    "email": "udzura@udzura.jp",
    "gravatar": "ccb4e184486440f53806a21a97c2xxxx",
    "admin": true,
    "active": true,
    "syncing": false,
    "created_at": 1415183037,
    "updated_at": 1415239960,
    "synced_at": 1415183063
  },
  "repository": {
    "remote": "enterprise.github.com",
    "host": "ghe.tokyo.pb",
    "owner": "udzura",
    "name": "sample",
    "url": "http://ghe.******/udzura/sample",
    "clone_url": "git@ghe.******:udzura/sample.git",
    "git_url": "git://ghe.******/udzura/sample.git",
    "ssh_url": "git@ghe.******:udzura/sample.git",
    "active": true,
    "private": false,
    "privileged": false,
    "post_commits": true,
    "pull_requests": true,
    "timeout": 3600,
    "created_at": 1415183045,
    "updated_at": 1415183051
  },
  "commit": {
    "id": 2,
    "status": "Success",
    "started_at": 1415260461,
    "finished_at": 1415260502,
    "duration": 41,
    "sha": "e88332a0256d997787fcf73311380d863700e265",
    "branch": "sample-hook",
    "pull_request": "",
    "author": "udzura@udzura.jp",
    "gravatar": "1855706b918a88ed1401439986cbxxxx",
    "timestamp": "2014-11-06T16:53:12+09:00",
    "message": "Add webhook sample",
    "created_at": 1415260461,
    "updated_at": 1415260502
  }
}
```

Dockerの話ではないけど、Goでサーバ書いてupstartで雑にデーモンにしてPuppetの雑なレシピを書くのは凄く楽で最高の体験だった。


`.drone.yml` はこんな感じになる。以下の設定で、 `#sample` と言うチャンネルに失敗と成功寺に通知してくれる。

```
notify:
  webhook:
    on_success: true
    on_failure: true
    urls:
      - http://localhost:9292/channels/sample
```

IRCは普通に対応していると書かれているがSSL非対応のようだった。なんでこんな方法で。そのうちSSL対応のプルリク等送れると良いが...。もっとGo頑張ろう...。

あ、ちなみにDrone本体は、[SlackとかHipchatとかgitterとか普通に対応してる](https://github.com/drone/drone/tree/master/plugin/notify) んで、その辺のユーザさんは安心して良さそう。

### [FIXED] 困ったこと: fdをめっちゃ沢山掴んでulimitを超えることがあった

運用していて、意外と安定してるな〜と言う感想を持っているが、一つ明らかなバグがあった。

- https://github.com/drone/drone/issues/644

要するに、dronedプロセスが何かしらの原因でfdを掴みっぱなしになっているようで、それがどんどん増えていって最後は `too many open files` になってしまう。

Droneの起動スクリプトをこんな感じにして、なおかつ定期的に再起動して今はしのいでいる感じ。

```bash:drone.conf
start on (filesystem and net-device-up)

chdir /var/lib/drone
console log

script
        ulimit -n 65536 # 雑...
        /usr/local/bin/droned --config=/etc/drone/drone.toml
end script
```

Issueの通り認識済のバグなので直る日は来ると思う、 ~~けど11月から反応が無いぞい...~~ →FIXされました。素晴らしい。

----

——と言う感じで、Drone.ioのインハウス運用は予想以上に使えてるなーと言う感想を持っている。あと普通に運用してるだけでDockerの勉強になる...。

是非ユーザが増えて情報も増えると良い感じだなと思った。

明日は @hanhan1978 さんです！