Drone AutoscalerをAWSに構築する

[Drone](https://drone.io) はモダンなCIサービスを実現するOSSです。CircleCIやTravisCI風のCIを自前で構築することができます。

単一のマシンにインストール、複数マシンをエージェント（実際にジョブを走らせるノード）として使う、あるいはkubernetesを使う選択肢がありますが ( https://docs.drone.io/installation/github/ )、別の選択肢として [Drone Autoscale](https://autoscale.drone.io/) という仕組みが存在し、これはジョブの待ち状況によってエージェント（AWSの場合はEC2）の数を自動で増減させるものです。droneはkubernetesでも動かせるので似たようなことはできるかもしれませんが、autoscalerの方が素直な感じがするというかkubernetesの知識が不要で楽です。

## ライセンス
https://drone.io/enterprise/starter/

droneは5000 builds per yearまでならStarterプランとして無料で使えるようです。

https://autoscale.drone.io/faq/licensing/

あれ、AutoscaleのFAQにはそんなことが書かれていませんね。どっちが正しいんでしょうか？

https://drone.io/enterprise/features/

Open Source Editionでは使えないようです。Open Source EditionはSingle Nodeなのでそれはそう。Open Source Editionの説明は今の https://drone.io からのリンクが見当たらないような気がしますね。

## 構成

楽するためにdrone serverとdrone autoscalerを一つのEC2インスタンスで立てる構成を考えました。レジストリはECRを使います。

![Drone autoscaler.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/7546/7d4183c5-6229-6b71-0126-92edea8225e1.png)

以下のdocker-compose.ymlでなんとなく動きます。

```docker-compose.yml
version: '3.7'

services:
  server:
    image: drone/drone:1
    volumes:
    - serverdata:/data
    ports: [ "80:80" ]
    restart: always
    environment:
    - DRONE_GITHUB_SERVER=https://github.com
    - DRONE_SERVER_HOST=your.drone.server
    - DRONE_GITHUB_CLIENT_ID=${GITHUB_CLIENT_ID}
    - DRONE_GITHUB_CLIENT_SECRET=${GITHUB_CLIENT_SECRET}
    - DRONE_RPC_SECRET=${DRONE_SERVER_SECRET}
    - DRONE_USER_CREATE=username:${DRONE_ADMIN_USER},machine:false,admin:true,token:${DRONE_ADMIN_TOKEN}
    - DRONE_AGENTS_ENABLED=true
    - DRONE_SERVER_PROTO=http
    - DRONE_LOGS_TRACE=true
    - DRONE_GIT_ALWAYS_AUTH=true  # github enterpriseの場合は必要
  autoscaler:
    image: drone/autoscaler
    links:
    - server:your.drone.server
    volumes:
    - autoscalerdata:/data
    ports: [ "8080:8080" ]
    restart: always
    environment:
    - DRONE_SERVER_PROTO=http
    - DRONE_SERVER_HOST=your.drone.server
    - DRONE_SERVER_TOKEN=${DRONE_ADMIN_TOKEN}
    - DRONE_AGENT_TOKEN=${DRONE_SERVER_SECRET}
    - DRONE_LOGS_TRACE=true
    - DRONE_INTERVAL=30s
    - DRONE_POOL_MIN_AGE=2h
    - DRONE_POOL_MIN=0
    - DRONE_POOL_MAX=4
    - DRONE_AMAZON_SUBNET_ID=${DRONE_AMAZON_SUBNET_ID}
    - DRONE_AMAZON_SECURITY_GROUP=${DRONE_AMAZON_SECURITY_GROUP}
    - DRONE_AMAZON_IMAGE=${DRONE_AMAZON_IMAGE}
    - DRONE_AMAZON_IAM_PROFILE_ARN=${DRONE_AMAZON_IAM_PROFILE_ARN}
    - DRONE_AMAZON_SSHKEY=${DRONE_AMAZON_SSHKEY}
    - DRONE_AMAZON_REGION=ap-northeast-1
    - DRONE_AMAZON_INSTANCE=t2.small
    - DRONE_AMAZON_DEVICE_NAME=/dev/xvda
    - AWS_IAM=true
  
volumes:
  serverdata:
  autoscalerdata:
```

必要な環境変数は.envなどに書きます。

* server/autoscalerの`DRONE_LOGS_TRACE`

よくわからんところで止まるので、ログはたくさん出すようにしましょう。

* autoscalerの`DRONE_SERVER_HOST`

autoscalerの`DRONE_SERVER_HOST`はautoscalerがserverとやり取りするためのものですが、agentにもそのまま渡されるので、agentからも到達可能なドメインを指定する必要があります。ここでは`DRONE_SERVER_HOST`としてはpublicなドメインを指定して、linksでautoscalerからも名前解決できるようにしています。

* volume

droneはデフォルトではsqliteを使います。そのファイルを置く場所です。

* `DRONE_SERVER_SECRET`

agentがserverとやり取りするときに使われる値です。server側は`DRONE_RPC_SECRET`、autoscaler側は`DRONE_AGENT_TOKEN`という環境変数に与えます。autoscalerがagentを起動するときにそのまま渡すものだと思います。

* `DRONE_ADMIN_USER`, `DRONE_ADMIN_TOKEN`

`DRONE_USER_CREATE`という環境変数があれば、これに従ってserver起動時にuserが作られます。autoscaler側でもサーバに対してAPI呼び出しをするためにadmin userのtokenが必要なので、一緒に指定します( autoscaler側では`DRONE_SERVER_TOKEN` )。

* min ageの挙動

agentが起動してから`DRONE_POOL_MIN_AGE`以内はそのインスタンスは削除されません。越えると削除されます。今までどれくらいビルドしてたかは関係ないみたいです。初めはなんとなく、最後のビルドが終わってから`DRONE_POOL_MIN_AGE`が経過すれば削除されるのかなと思っていましたが、そんなことはありませんでした。

* IAM Roleとagentの起動とセキュリティグループ

アクセスキーとアクセスシークレットを指定します。あるいは、AWS_IAM=trueにするとEC2 IAM Roleが使われます。必要なactionは以下でした。

```
ec2:CreateTags
ec2:DescribeInstances
ec2:RunInstances
ec2:TerminateInstances
```

EC2インスタンス作成後、autoscalerはagentノードに対してagentのdockerイメージをpull & runします。このため、autoscalerからagentに対して2376ポートでアクセスできるようにします。ここは分かりにくいところで、FAQにも記載があります。

https://autoscale.drone.io/faq/errors/

* `DRONE_AMAZON_IMAGE`

`DRONE_AMAZON_IMAGE`に何も指定しなければそのリージョンのデフォルトのAMIが使われます。気になる人は自分でAMIを作りましょう。dockerがインストールしてあって起動後自動でdockerが立ち上がるようになっていればOKです。このときAMIによっては`DRONE_AMAZON_DEVICE_NAME`に何か値を指定する必要があるかもしれません。違ったdevice nameだとインスタンスが起動後すぐ停止します。

* `DRONE_AMAZON_IAM_PROFILE_ARN`

今回はECRを使いたいので、ECRが使えるIAM Roleを作って、そのARNをこの環境変数に指定します。agentノード立ち上げ時にそのIAM Roleが割り当てられます。

ECRへのアクセスはこちらを参考にしました。

https://qiita.com/kkkkk/items/61293125c98606a20d9b

ホストの`/var/run/docker.sock`をマウントし、事前のstepでイメージをpullします。ホストのディレクトリをマウントするのでリポジトリがtrustedである必要があります。serviceでprivate imageが使いたい場合はこれでpullしてから普通のstepで`detach: true`にすればできます ( https://docs.drone.io/user-guide/pipeline/services/#detached-steps )。

```yaml
steps:
- name: pull
  image: docker:18
  commands:
  - apk --update add python3 && pip3 install awscli
  - $(aws ecr get-login --region ap-northeast-1 --no-include-email)
  - docker pul account.dkr.ecr.ap-northeast-1.amazonaws.com/image
  volumes:
  - name: docker
    path: /var/run/docker.sock
```

* CLI

本体とautoscalerのドキュメントで情報が違います。autoscalerを使う場合はautoscalerの方が参考になります。

https://docs.drone.io/cli/
https://autoscale.drone.io/cli/

```ini
DRONE_TOKEN=http://localhost
DRONE_AUTOSCALER=http://localhost:8080
DRONE_TOKEN=${DRONE_ADMIN_TOKEN}
```

上記の設定で、EC2内からdrone cliが使えます。

## 感想

AWSのアクセスキーは発行したくないとかセキュリティグループはなるべく厳しくしたいとか考えており、そもそもの情報不足と色々知識がないせいもあって難しかったです。droneの気持ちになるというか全体的な動作の仕組みが頭に入っていると構築がやりやすいと思います。

- フォーラム
  - https://discourse.drone.io