LINE Engineering
Blog

Jenkinsに代わるGo製OSS CIツールDrone

Suzuki Shunsuke 2017.12.05

この記事はLINE Advent Calendar 2017の5日目の記事です。

はじめまして。LINEのIT支援室という部署で社内システムの開発・運用を担当しております、suzuki-shunsukeです。

DroneというCIツールについて書きたいと思います。初心者向けに公式ドキュメントの「Installation」と「Usage」をベースに自身の経験や考えを肉付けして、Droneとは何か?、Droneの構築方法、.drone.ymlの書き方について説明します。Droneの日本語情報はまだまだ少ないので参考になればと思います。

JenkinsよりもDroneを贔屓した部分が見られますが、あらかじめご了承ください。

1. Droneとは

空飛ぶドローンではありません。GitリポジトリのイベントをフックしDockerコンテナ内でジョブを実行することに特化したCIツールです。競合としてJenkinsやCircle CIなどがあります。

Enterprise版もありますが、今回はOSS版を前提に説明します。Go製でGitHubで公開されており、2017年11月11日現在、12,000近くスターが付けられていて活発に開発されています。

DroneはいくつかのGitサーバに対応していますが、弊社ではオンプレミスでGitHub Enterpriseを運用しているので、この記事ではGitHubとの連携を前提として説明します。

2. こんな人・チームにオススメ

  • 大人の事情でCircle CIなどが使えない方
  • Dockerコンテナでジョブを実行したい方
  • Jenkinsを使っているけれどJenkinsに精通した人がいないチーム
  • JenkinsのWeb UIでの操作、設定管理に嫌気が差している方
  • Jenkinsのグローバルな設定管理やプラグイン管理に嫌気が差している方
  • CIの設定やコードを、リポジトリ中のコードとして管理したい方
  • Gitリポジトリとの連携を簡単にしたい方
  • Dockerイメージをビルドしたい方

3. リンク集

4. Droneの実行モデル

GitHub上のPRやtagのプッシュなどのイベントをフックし、リポジトリのルートディレクトリにある.drone.ymlに従ってpipelineを実行します。.drone.ymlはtravis ciでいう.travis.ymlのようなもので、pipelineとはいわゆるジョブのことです。pipelineは順に実行されるステップから構成されます。ステップ毎にDockerコンテナが作成され、コンテナのエントリポイントとして処理が実行されます(ステップ毎に異なるDockerイメージを選べます)。各ステップのDockerコンテナはワークスペースとして同じDockerボリュームを共有します。ボリュームを共有することによって、前のステップでの生成物を以降のステップで使うことができます。最初にcloneというステップが自動で挿入され、そのステップでGitリポジトリがボリュームにクローンされます。

Dockerコンテナを使い捨てることで、独立した環境でCIを実行することができます。しかも好きなDockerイメージが使えます。

5. Droneの管理

ここでは、Droneをオンプレミスで構築する方法を説明します。単なるユーザーの方は「6. Droneの利用」まで読み飛ばして大丈夫です。

http://docs.drone.io/installation/

環境構築

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.1
    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=

agent

version: '3'
services:
  # http://docs.drone.io/release-0.8.0
  drone-agent:
    image: drone/agent:0.8.1
    restart: always
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      # serverと通信するためのserverのホスト名とポート番号
      - DRONE_SERVER=
      # serverと同じ適当な文字列。serverとの連携で使われます。
      - DRONE_SECRET=

NginxとApacheの設定

Droneの前にApacheやNginxのサーバを立てる場合の設定です。

6. Droneの利用

Droneの利用ステップ

  1. OAuth認証を受ける(初回のみ)。
  2. Repositoriesからリポジトリを有効化する。
  3. リポジトリのSettingsからフックするイベントを選択する。
  4. リポジトリのルートに.drone.ymlを作成する。
  5. (必要なら)リポジトリのDrone SecretsからDrone secretを登録する。
  6. 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に公開されているので見てみてください。

http://plugins.drone.io/

プラグインはただのDockerイメージなので簡単に自作できます。MarketplaceにあるプラグインはGoで作られているものが多い気がしますが、Goである必要は全くありません。

Jenkinsのプラグインと違い、システムにインストールして共用するものではないので、プラグインの管理に頭を悩ませることもありません。

条件設定

pipelineおよび各ステップを実行する条件を設定できます。

ブランチ名、tag名、イベントの種類などでpipelineやステップを実行する条件を設定することができます。配列による複数の設定、グロブ、excludeによるブラックリストの指定などもできます。

グロブを使う際は、グロブのMatchにはfilepath.Matchが使われているので、*4*refs/tags/foo-4にマッチしないことに注意してください。

https://github.com/cncd/pipeline/blob/b7427ae1fb86bb04e2997e5f3a1eec75c4b056ef/pipeline/frontend/yaml/constraint.go#L69

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.01.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のドキュメントがほとんどないのがとても残念です。

おまけ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

7. 最後に

オンプレミスでCIとなるとJenkins一択みたいなイメージですが、Droneもかなり有力なツールだと思っています。自分の場合、Droneを導入したことで今まで自動化できていなかった部分がかなり自動化でき、コードでCIを管理できるようになったということもあり非常に快適になりました。また、Dockerを使うことで好きな環境でジョブを実行でき、グローバルな設定やプラグインの管理に頭を悩ませなくても良くなりました。

Droneの情報は日本語で検索してもほとんど出てこないのですが、英語のフォーラムを見たりGoで書かれたコードを読んだりすれば大体解決するので、意外と困らないです。

皆さんがこの記事をきっかけにDroneを始めてくれたらとても嬉しいです。

明日はIT支援室の岩月さんの「Excel管理の座席表をLeafletでWeb化した話」です。お楽しみに。

AdventCalendar

Suzuki Shunsuke 2017.12.05

Add this entry to Hatena bookmark

リストへ戻る