この記事はLINE Advent Calendar 2017の5日目の記事です。
はじめまして。LINEのIT支援室という部署で社内システムの開発・運用を担当しております、suzuki-shunsukeです。
DroneというCIツールについて書きたいと思います。初心者向けに公式ドキュメントの「Installation」と「Usage」をベースに自身の経験や考えを肉付けして、Droneとは何か?、Droneの構築方法、.drone.ymlの書き方について説明します。Droneの日本語情報はまだまだ少ないので参考になればと思います。
JenkinsよりもDroneを贔屓した部分が見られますが、あらかじめご了承ください。
Droneとは
空飛ぶドローンではありません。GitリポジトリのイベントをフックしDockerコンテナ内でジョブを実行することに特化したCIツールです。競合としてJenkinsやCircle CIなどがあります。
Enterprise版もありますが、今回はOSS版を前提に説明します。Go製でGitHubで公開されており、2017年11月11日現在、12,000近くスターが付けられていて活発に開発されています。
DroneはいくつかのGitサーバに対応していますが、弊社ではオンプレミスでGitHub Enterpriseを運用しているので、この記事ではGitHubとの連携を前提として説明します。
こんな人・チームにオススメ
- 大人の事情でCircle CIなどが使えない方
- Dockerコンテナでジョブを実行したい方
- Jenkinsを使っているけれどJenkinsに精通した人がいないチーム
- JenkinsのWeb UIでの操作、設定管理に嫌気が差している方
- Jenkinsのグローバルな設定管理やプラグイン管理に嫌気が差している方
- CIの設定やコードを、リポジトリ中のコードとして管理したい方
- Gitリポジトリとの連携を簡単にしたい方
- Dockerイメージをビルドしたい方
リンク集
- 公式サイト
- 公式ドキュメント
- 公式ドキュメント(旧バージョン)
- 古いので鵜呑みにできませんが、新しいドキュメントにない情報があったりします。
- Droneのソースコード
- pipelineのソースコード
- Plugin Marketplace
- 各プラグインのパラメータなどはここに書いてあったりします。
- プラグインの多くはGitHubで公開されているので、良くわからない場合はコードを読みましょう。
- http://addons.drone.io/は多分古いので見ない方が良いです。
- 公式のフォーラム
- Droneのコアメンテナが精力的に回答しており、トラブルシューティングに重宝します。
- Stack Overflow
Droneの実行モデル
GitHub上のPRやtagのプッシュなどのイベントをフックし、リポジトリのルートディレクトリにある.drone.ymlに従ってpipelineを実行します。.drone.ymlはtravis ciでいう.travis.ymlのようなもので、pipelineとはいわゆるジョブのことです。pipelineは順に実行されるステップから構成されます。ステップ毎にDockerコンテナが作成され、コンテナのエントリポイントとして処理が実行されます(ステップ毎に異なるDockerイメージを選べます)。各ステップのDockerコンテナはワークスペースとして同じDockerボリュームを共有します。ボリュームを共有することによって、前のステップでの生成物を以降のステップで使うことができます。最初にcloneというステップが自動で挿入され、そのステップでGitリポジトリがボリュームにクローンされます。
Dockerコンテナを使い捨てることで、独立した環境でCIを実行することができます。しかも好きなDockerイメージが使えます。
Droneの管理
ここでは、Droneをオンプレミスで構築する方法を説明します。単なるユーザーの方は「Droneの利用」まで読み飛ばして大丈夫です。
環境構築
Dockerでdrone-serverとdrone-agentをのサーバを立てます。手抜きでDockerを使っているのではなく、この方法しか公式にサポートされていません。
Jenkinsを管理したことのある方であれば、Droneのserverとagentの関係はJenkinsのマスターとスレーブの関係だと思えば分かりやすいかと思います。
同じサーバ上でserverとagentを動かすこともできますが、スケールアウトすることを考え、別のサーバで動かします。agentのサーバを増やせば簡単にスケールアウトできます。
まずはGitHubの以下のページでOAuth Applicationを作成してください。そのClient IDとClient Secretが必要です。
https://github.com/settings/applications/new
- Homepage URL:構築するDroneのURL
- Authorization callback URL:
<Homepage URL>/authorize
- OAuth Applicationを作成すると、Droneのロゴをアップロードできる画面が開きます。
次にdrone-serverとdrone-agentのコンテナを起動してください。docker-compose.ymlのサンプルを示します。
server
version: '3' services: # http://docs.drone.io/release-0.8.0 drone-server: image: drone/drone:0.8.2 ports: # 外部に公開するポート - 8000:8000 # agentと通信するポート - 9000:9000 volumes: - /var/lib/drone:/var/lib/drone/ restart: always environment: # http://docs.drone.io/user-registration/ # Gitリポジトリのuserとorganizationを使って簡単にユーザー管理できます。 # falseに設定した場合は、管理者が明示的にユーザー登録する必要があります。 - DRONE_OPEN=true # Droneが使えるユーザーが属しているGitHub Organization名 - DRONE_ORGS= # Droneの管理者のGitHubユーザー名 - DRONE_ADMIN= # 構築するDroneのURL - DRONE_HOST= - DRONE_GITHUB=true # GitHubのURL - DRONE_GITHUB_URL= # GitHubのOAuth ApplicationとしてDroneを登録し、 # CLIENT IDとSecretを発行します。 - DRONE_GITHUB_CLIENT= - DRONE_GITHUB_SECRET= # agentと同じ適当な文字列。agentとの連携で使われます。 - DRONE_SECRET= # DBの設定 # http://docs.drone.io/database-settings/ # デフォルトでSQLiteですが、本運用ではMySQLあたりが無難です。 # SQLiteからMySQLへのマイグレーションは少々面倒なので # 最初からMySQLにしたほうが楽だと思います。 - DRONE_DATABASE_DRIVER=mysql - DRONE_DATABASE_DATASOURCE= # https://discourse.drone.io/t/github-claims-that-merge-refs-are-undocumented-feature/1100 - DRONE_GITHUB_MERGE_REF=false # publicなリポジトリのチェックアウトにも認証が必要な場合にtrueにします。 - DRONE_GITHUB_PRIVATE_MODE=true
agent
version: '3' services: # http://docs.drone.io/release-0.8.0 drone-agent: image: drone/agent:0.8.2 restart: always volumes: - /var/run/docker.sock:/var/run/docker.sock environment: # serverと通信するためのserverのホスト名とポート番号 - DRONE_SERVER= # serverと同じ適当な文字列。serverとの連携で使われます。 - DRONE_SECRET= # 同時に実行できる最大ビルド数。環境に合わせて変えます。 # 例えば、DRONE_MAX_PROCSを4にしたagentが2台ある場合、8個まで同時に実行可能です。 - DRONE_MAX_PROCS=4
NginxとApacheの設定
Droneの前にApacheやNginxのサーバを立てる場合の設定です。
Droneの利用
Droneの利用ステップ
- OAuth認証を受ける(初回のみ)。
- Repositoriesからリポジトリを有効化する。
- リポジトリのSettingsからフックするイベントを選択する。
- リポジトリのルートに.drone.ymlを作成する。
- (必要なら)リポジトリのDrone SecretsからDrone secretを登録する。
- GitHub上でイベントを起こし、ジョブを実行する。
DroneのWeb UI
JenkinsがWeb UIからいろいろ設定するのに対し、DroneではWeb UIからの設定は非常に少ないです。右上のメニューから各ページに遷移します。
- Repositories:リポジトリの一覧でリポジトリの連携の有効・無効化します。
- Settings:どのイベントをフックするかを指定します。デフォルトではプッシュとPRのみでtagのプッシュはフックしないので注意してください。
- Drone Secrets:Drone secretを追加・削除します。
- Restart Builds:失敗したジョブをリトライします。
「Hello World」を出力する
次のような.drone.ymlをリポジトリのルートディレクトリに作成してください。
pipeline: # ステップは上から順に実行されます(順序付きmap)。 hello: # ステップ毎にイメージを指定 image: alpine:3.6 # Dockerコンテナ起動時のエントリポイントとしてコマンドが実行されます。 # 各コマンドは同じシェルで実行されます。 commands: - echo "Hello World"
ローカルでpipelineを実行する
http://docs.drone.io/cli-exec/
Droneサーバで実行する前にローカルで動作確認ができます。
$ drone exec --local
ホストの環境変数が各ステップにそのまま渡るようです。そのため、Drone secretを使っている場合、次のように渡せます。
$ MY_SECRET=mybigsecret drone exec --local
プラグイン
http://docs.drone.io/plugin-overview/
DroneのプラグインはただのDockerイメージです。コマンドを指定するステップ(buildステップ)とは違い、イメージのエントリポイントを実行します。つまり、次のようなDockerfileから生成されたイメージをプラグインとして使うと、echo hello
が実行されます。
FROM alpine:3.6 ENTRYPOINT echo hello
プラグインにはパラメータを渡せます。
notify: # pipelineの結果をSlackに通知するプラグイン image: plugins/slack # 環境変数PLUGIN_CHANNELとして値が渡される。 channel: developers # 環境変数PLUGIN_USERNAMEとして値が渡される。 username: drone
便利なプラグインがPlugin Marketplaceに公開されているので見てみてください。
プラグインはただのDockerイメージなので簡単に自作できます。MarketplaceにあるプラグインはGoで作られているものが多い気がしますが、Goである必要は全くありません。
Jenkinsのプラグインと違い、システムにインストールして共用するものではないので、プラグインの管理に頭を悩ませることもありません。
条件設定
pipelineおよび各ステップを実行する条件を設定できます。
ブランチ名、tag名、イベントの種類などでpipelineやステップを実行する条件を設定することができます。配列による複数の設定、グロブ、excludeによるブラックリストの指定などもできます。
グロブを使う際は、グロブのMatchにはfilepath.Matchが使われているので、*4*
はrefs/tags/foo-4
にマッチしないことに注意してください。
pipeline: slack: image: plugins/slack channel: dev # masterブランチでのみ実行する。 when: branch: master # タグ名がfoo-*ならステップを実行する。 # ref: refs/tags/foo-* event: [tag, pull_request] branches: master # branches: [master, feature/*] # excludeでない場合はinclude扱いにする。 # branches: # include: [ master, feature/* ] # branches: # exclude: [ master, feature/* ]
pipeline実行時の環境変数
http://docs.drone.io/environment-reference/
tagをプッシュするイベントをフックする場合、tag名を参照したいと思われるでしょう。tag名のような実行時の情報は、環境変数として参照できます。
Drone secret
http://docs.drone.io/manage-secrets/
APIトークンのようなリポジトリにそのまま含めにくい情報は、Drone secretとしてDroneで管理できます。例えば、fooという名前のDrone secretを登録すると、FOOというsecret名を大文字にした環境変数として参照できます。Drone secretはWeb UIから登録可能です。
SecretにはGlobalなsecretとリポジトリ毎のsecretがありますが、GlobalなsecretはEnterpriseでしか使えません。
注意が必要なのは、Web UIから登録したsecretはPRをフックしたpipelineでは参照できないということです。これは悪意のあるPRによってsecretが盗まれるのを防ぐためのセキュリティ上の仕様です。Drone CLIやHTTP APIを使い、PRをフックしたpipelineからも参照できるsecretを作ることも可能です。
置換
http://docs.drone.io/substitution/
変数を加工して一部分のみを参照したい場合があります。例えばtag foo-1.0.0
の1.0.0
の部分のみを参照したい場合です。Secretを加工するいくつかの記法があり、先程の例では ${DRONE_TAG##foo-}
でOKです。
ステップの並列実行
http://docs.drone.io/pipelines/#parallel-execution
独立したステップを並列に実行して高速化できます。groupが同じステップは並列に実行されます。次の例では、backendとfrontendが並列に実行され、両方が完了してからpublishが実行されます。
pipeline: backend: group: build image: golang commands: - go build - go test frontend: group: build image: node commands: - npm install - npm run test - npm run build publish: image: plugins/docker repo: octocat/hello-world
ただし、次のように同じgroupでも間に別のgroupのステップ(ここではpublish)があると並列に実行されないので、同じgroupのステップは並べて書きましょう。
pipeline: backend: group: build image: golang commands: - go build - go test publish: image: plugins/docker repo: octocat/hello-world frontend: group: build image: node commands: - npm install - npm run test - npm run build
別のサービスに依存したステップの実行
http://docs.drone.io/services/
DBを使ったテストなど、別のサービスに依存したステップを実行したい場合、サービスをservicesとして定義しましょう。
次の例ではGoのテストにMySQLとRedisを利用しています。
pipeline: build: image: golang commands: - go build - go test services: database: # ホスト名はdatabase image: mysql cache: image: redis
servicesで定義されたDockerコンテナが作られた上でpipelineが実行されます。servicesのコンテナにはDocker Composeのようにサービス名でアクセスできます。servicesではbuildステップのようにcommandsを指定することはできませんが、環境変数は設定できます。
services: database: image: mysql environment: - MYSQL_DATABASE=test - MYSQL_ALLOW_EMPTY_PASSWORD=yes
サービスの初期化に時間がかかる場合、buildステップのcommandsでsleepを挟む必要があります。次の例では、MySQLの初期化に時間がかかるため、15秒sleepしています。
pipeline: test: image: golang commands: - sleep 15 - go get - go test services: database: image: mysql
マトリックスビルド
http://docs.drone.io/matrix-builds/
1つのイベントに対し複数の設定でpipelineを実行できます。
次の例ではGoとMySQLのバージョンを変えてテストをしています。それぞれ2種類のバージョンを組み合わせて、pipelineが4回実行されます。
pipeline: build: image: golang:${GO_VERSION} commands: - go get - go build - go test services: database: image: mysql:${DATABASE} matrix: GO_VERSION: - 1.4 - 1.3 DATABASE: - 5.5 - 6.5
Drone CLIとDrone API
Drone CLIとHTTP APIを使って、secretを設定したりpipelineを実行したり、いろいろな処理ができます。Droneのことが分かってきたら使ってみましょう(自分は使えていないです)。2017年11月11日現在、APIのドキュメントがほとんどないのがとても残念です。
ステータスバッジ
リポジトリのREADMEなどにCIの結果を示すステータスバッジをつけられます。
おまけ1 docker-pluginのcustom_dns設定
おまけではありますが、GoogleのDNSではなく社内のDNSを使わないといけない人には重要な注意点です。自分はこれに気づかずかなりハマりました。docker-pluginを使うとDockerイメージをビルド・プッシュすることができます。
pipeline: # リポジトリのDockerfileを使ってイメージをビルドし、 # suzukishunsuke/hello-world にプッシュする。 build: image: plugins/docker repo: suzukishunsuke/hello-world
このプラグインはDocker in Dockerを利用しており、custom_dnsオプションを指定しないとDockerイメージをビルドする際にGoogleのDNSを見に行きます。
社内のDNSを使わないといけない人は、custom_dnsオプションに社内のDNSのIPをカンマ区切りの文字列で渡してあげましょう。
おまけ2 SSHの秘密鍵の扱い
DroneにはJenkinsのSSH Agent Pluginのようなものはないので少々扱いが面倒です。1つのやり方は、Drone secretに秘密鍵を保存し、秘密鍵を生成する方法です。
commands: - echo "$${ID_RSA}" > ~/.ssh/id_rsa - chmod 600 ~/.ssh/id_rsa
パスフレーズ付き秘密鍵では、expectを使うなどしてパスフレーズを入力する必要があります。ちなみにMarketplaceにSSH Pluginがありますが、これはパスワード認証にしか対応していませんし、自分の好きなDockerイメージでコマンドが実行できるわけでもありません。
おまけ3 DroneでGatlingをぶっ放す
最後に少し変わった(?)Droneの事例を紹介します。DroneでGatling(Scala製のパフォーマンステストツール)を実行し、レポートをGitHub Releasesにアップロードするというものです。元々Gatling専用のサーバにsshログインし、Gatlingを手動で実行するという、割とアナログなことをしていたのですが、Droneを使ってサーバレスなパフォーマンステストを実現しました。今回はGitHub Releasesにアップロードしましたが、scpでWebサーバにアップロードしても良いと思います。
pipeline: test: image: denvazh/gatling:2.3.0 commands: - gatling.sh -s computerdatabase.BasicSimulation - cp -R /opt/gatling/results . - tar cvzf result.tar.gz -C results `ls results` when: event: tag release: image: plugins/github-release:1.0.0 files: - result.tar.gz secrets: - github_release_api_key base_url: upload_url: when: event: tag
最後に
オンプレミスでCIとなるとJenkins一択みたいなイメージですが、Droneもかなり有力なツールだと思っています。自分の場合、Droneを導入したことで今まで自動化できていなかった部分がかなり自動化でき、コードでCIを管理できるようになったということもあり非常に快適になりました。また、Dockerを使うことで好きな環境でジョブを実行でき、グローバルな設定やプラグインの管理に頭を悩ませなくても良くなりました。
Droneの情報は日本語で検索してもほとんど出てこないのですが、英語のフォーラムを見たりGoで書かれたコードを読んだりすれば大体解決するので、意外と困らないです。
皆さんがこの記事をきっかけにDroneを始めてくれたらとても嬉しいです。
明日はIT支援室の岩月さんの「Excel管理の座席表をLeafletでWeb化した話」です。お楽しみに。