GKEでScrewdriver.cdを動かしてみる

## Screwdriver.cdとは
![スクリーンショット 2017-09-20 17.03.54.png](https://qiita-image-store.s3.amazonaws.com/0/23755/725631e3-e582-aa4f-e531-a06cd7f31d7b.png "スクリーンショット 2017-09-20 17.03.54.png")
[^1]より

[Screwdriver.cd](http://screwdriver.cd/)はYahoo! (現在はOathの一部)が開発しているCI/CDツールです。
元々社内向けプロダクトだったものをOSS化に伴い、大規模な利用に耐えうる設計で作り直したものになります。[^2]
日本では[Yahoo! JAPAN Tech Conference 2018](https://techconference.yahoo.co.jp/2018/)で紹介されました。

コンセプトの概要や、特徴については下記のリンクを参考にしてみてください。
http://docs.screwdriver.cd/ja/about/

## このページの目標
できるだけ少ない設定で、GKE上のScrewdriver.cdで何らかのビルドが実行できることを目指します。

## 準備
GCPで以下のものを作成します。
- Screwdriver.cdが動作する[Kubernetes](https://kubernetes.io/)([GKE](https://cloud.google.com/kubernetes-engine/))
- Screwdriver.cdがArtifacts(ビルドのログなど)の保存に使用する[Cloud Storage](https://cloud.google.com/storage/)
- Screwdriver.cdがBuild情報などを保存に使用する[Cloud SQL](https://cloud.google.com/sql/)
 
今回はそれぞれが何ぞや、ということの詳しい紹介は割愛します。

### Kubernetes
Screwdriver.cdのコア部分を動作させつつ、ビルドの実行も行うKubernetesを構築します。

GKEの画面に行って、クラスタの作成からぽちぽちと作れば大丈夫です。
名前はscrewdriver、ゾーンはasia-northeast1-aを、クラスタのバージョンは1.8.6-gke.0(執筆時の最新)を選択しました。
![スクリーンショット 2018-01-25 23.30.33.png](https://qiita-image-store.s3.amazonaws.com/0/23755/7c93e109-705a-3525-0d01-bc20c476eb5a.png "スクリーンショット 2018-01-25 23.30.33.png")

また、Cloud StorageとCloud SQLに繋がるよう設定します。
｢その他｣を展開し、アクセス範囲の｢各APIにアクセス権を設定｣を選択します。
そうすると他のGCPのサービスの一覧が出てくるので、Cloud StorageとCloud SQLを｢有効｣にします。
![スクリーンショット 2018-01-25 22.17.07.png](https://qiita-image-store.s3.amazonaws.com/0/23755/2f714211-9f45-c621-6603-06bb0cba5ebe.png "スクリーンショット 2018-01-25 22.17.07.png")
![スクリーンショット 2018-01-25 22.17.14.png](https://qiita-image-store.s3.amazonaws.com/0/23755/31d81254-e916-736c-dac7-4fc80942d2b0.png "スクリーンショット 2018-01-25 22.17.14.png")
![スクリーンショット 2018-01-25 22.17.19.png](https://qiita-image-store.s3.amazonaws.com/0/23755/7bca92d2-74f6-155b-d5dd-58b051fb8c63.png "スクリーンショット 2018-01-25 22.17.19.png")
残りはデフォルトで作成しました。

Cloud Console上の｢接続｣からkubectlで構築したクラスタに接続できることを確認できれば準備完了です。

### Cloud Storage
Screwdriver.cdはArtifactsの保存にS3互換のStorageを利用できます。
Google Cloud Storageも設定をすることで、S3互換のAPIが利用できるようになります。
以下のような設定でCloud Storageを作成しました。
![スクリーンショット 2018-01-25 23.49.28.png](https://qiita-image-store.s3.amazonaws.com/0/23755/bcd786ea-e136-1603-1833-463e6c4a1104.png "スクリーンショット 2018-01-25 23.49.28.png")
｢設定｣から｢相互運用性｣タブを開き、｢新しいキーを作成｣を押し、キーとシークレットを払い出しました。
![Untitled.png](https://qiita-image-store.s3.amazonaws.com/0/23755/c4e6ac4f-a363-094e-4ae3-62321d5b6633.png "Untitled.png")

このキーとシークレットを後で利用します。

### Cloud SQL
Screwdriver.cdは各種情報の保存に[sequelize](http://docs.sequelizejs.com/)がサポートしているDBが利用できます。
今回はCloud SQLでMySQLを作成して利用します。

以下のような設定でMySQLのインスタンスを作成しました(パスワードは説明用に生成したもので、これはもちろん使用していません)
![スクリーンショット 2018-01-25 23.58.12.png](https://qiita-image-store.s3.amazonaws.com/0/23755/0c1b40fe-1eb0-1e1b-6e60-429b2d48dd78.png "スクリーンショット 2018-01-25 23.58.12.png")

Screwdriver.cdで使用するデータベースも作成しておきましょう。
![スクリーンショット 2018-02-05 23.26.41.png](https://qiita-image-store.s3.amazonaws.com/0/23755/b8f6c406-693c-067c-f4d6-04a61020ed46.png "スクリーンショット 2018-02-05 23.26.41.png")


次に、[Google Kubernetes Engineから接続する](https://cloud.google.com/sql/docs/mysql/connect-container-engine)を参考に、GKEからCloud SQLに繋がるようにします。
Screwdriver.cdのAPIのPodにsidecarとしてCloud SQL Proxyのコンテナを立ち上げ、APIのコンテナはそこ経由でCloud SQLと接続します。

ページの通りに進めていき、`INSTANCE_CONNECTION_NAME`さえ控えておけば今回は大丈夫なはずです。
参考に、自分が設定した際のログを貼っておきます(一部マスクしています)。

```bash
$ gcloud sql users create sd-proxyuser cloudsqlproxy~% --instance=screwdriver 
Creating Cloud SQL user...done.
Created user [sd-proxyuser].

$ gcloud sql instances describe screwdriver | grep connectionName
connectionName: <tk3fftk_connection_name>:asia-northeast1:screwdriver

$ kubectl create secret generic cloudsql-instance-credentials --from-file=credentials.json=<tk3fftk_credentials>.json
secret "cloudsql-instance-credentials" created

$ kubectl create secret generic cloudsql-db-credentials --from-literal=username=sd-proxyuser
secret "cloudsql-db-credentials" created
```

これらの設定やSecretは後ほど設定に利用します。

最後に、[Google Cloud SQL API](https://console.developers.google.com/apis/api/sqladmin.googleapis.com/overview)にアクセスしてAPIが有効になっているかを確認してください。

## Screwdriver.cdの構築
Screwdriver.cdは主に以下の3つのコンポーネントから構成されています。利用しているバージョンも併記しています。
- [API](https://github.com/screwdriver-cd/screwdriver) (v0.5.185)
- [UI](https://github.com/screwdriver-cd/ui) (v1.0.213)
- [Store](https://github.com/screwdriver-cd/ui) (v2.2.2, ログや成果物をS3互換ストレージに保存するAPIを持つ)
ビルドキューを使用する場合には、ここに[Queue(Redis)](https://github.com/screwdriver-cd-test/config-examples/blob/master/services/redis.yaml)と[Worker](https://github.com/screwdriver-cd/queue-worker)が追加されるのですが、今回は使用しないのでこの3つを構築します。
これらは全てDocker Imageとしてビルドされています([Docker Hub](https://hub.docker.com/u/screwdrivercd/))

以下のリポジトリにKubernetesにapplyするYAMLファイルを置いてあります。
https://github.com/tk3fftk/config-examples/tree/gke

このリポジトリは、[screwdriver-cd-test/config-examples](https://github.com/screwdriver-cd-test/config-examples)をforkしており、GKEで動かすために設定を変更･追加したものになります。

### 手順
今回は全て`namespace: default`で進めていきます。

#### ServiceAccountとClusterRole, ClusterRoleBinding作成
まず、ビルドに利用するServiceAccountを作成します。

```bash
$ kubectl apply -f serviceaccounts/sd-build.yaml
$ kubectl get sa
NAME       SECRETS   AGE
default    1         14d
sd-build   1         14d
```

次にビルド用Podを作成するためのClusterRoleを作成します。
が、GKEのk8sクラスタに対して、ClusterRoleを作成する権限をデフォルトでは持っていないので、まずは自分が操作するための権限を作成します。([参考](https://coreos.com/operators/prometheus/docs/latest/troubleshooting.html))

```bash
$ gcloud info | grep Account
Account: [your-mail@hoge.com]

$ kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=your-mail@hoge.com
clusterrolebinding "cluster-admin-binding" created
```

これで、ビルド用Podを作成するClusterRoleとClusterRoleBindingが作成できます。以下を実行して作成します。

```bash
$ kubectl apply -f roles.yaml
clusterrole "cluster-writer" created
clusterrole "cluster-reader" created

$ kubectl apply -f roleBinding.yaml
clusterrolebinding "cluster-write" created
clusterrolebinding "cluster-read" created
rolebinding "sd-build-write" created
```

#### Ingress作成
Ingressを利用してAPI,UI,Storeのアクセスを振り分けます。
Ingressについて詳しくは[GCPドキュメント](https://cloud.google.com/kubernetes-engine/docs/tutorials/http-balancer)か[Kubernetesドキュメント](https://kubernetes.io/docs/concepts/services-networking/ingress/)をご覧ください。
以下を実行しIngressを作成します。
ADDRESSが設定されるまで時間がかかるので、その間に次の作業を行います。

```bash
$ kubectl apply -f services/sd-ingress.yaml
$ kubectl get ing
NAME         HOSTS     ADDRESS        PORTS     AGE
sd-ingress   *         xx.xx.xx.xx   80        5d
```

#### Secret作成
Screwdriver.cd内で使う各種漏らすとまずいパスワードなどをKubernetesのSecretとして作成します。
`sd-secrets.yaml`内の値を埋めていきます。

```yaml
  jwt_private:
  jwt_public:
  scm_settings:
  cookie_pass:
  encryption_pass:
  s3_key_id:
  s3_key_secret:
```
##### jwt_private, jwt_public
JWTに署名するための秘密鍵と、署名を検証するための公開鍵です。
以下のコマンドを実行して生成します。

```bash
$ openssl genrsa -out jwt.pem 2048
$ openssl rsa -in jwt.pem -pubout -out jwt.pub
```

これらをbase64エンコードして`sd-secrets.yaml`に追加します。

```bash
$ cat jwt.pem | base64
$ cat jwt.pub | base64
```

##### scm_settings
`scm-settings.json`を埋めてbase64エンコードします。
`secret`はwebhooks署名用のパスワードを生成して記入してください。
`oauthClientSecret`と`oauthClientId`に関しては、[GitHub OAuth Apps](https://github.com/settings/developers)で作成したものを記入してください。

また、このときCallback URLは、Ingressに割り当てられたアドレスを利用して以下のように設定してください(まだアドレスがなければ割り当てられてから)。
![スクリーンショット 2018-02-09 16.20.33.png](https://qiita-image-store.s3.amazonaws.com/0/23755/3d14114a-4d19-e38b-7d9f-6577d096f0aa.png "スクリーンショット 2018-02-09 16.20.33.png")

`username`と`email`は今はこのままでも構いません。
先ほどと同様に`scm-settings.json`をbase64エンコードして、`sd-secrets.yaml`に追加します。

##### cookie_pass, encryption_pass
セッションデータを暗号化するためのパスワードと、Screwdriver.cdのSECRETを暗号化するためのパスワードです。
32文字以上の文字列を`mkpasswd`などで生成して、base64エンコードして`sd-secrets.yaml`に追加します。

##### s3_key_id, s3_key_secret
Cloud Storage 作成時に取得したキーとシークレットをbase64エンコードして`sd-secrets.yaml`に追加します。
このとき、改行コードが末尾に入らないように注意してください。

```bash
$ echo -n "YOUR-S3-KEYID" | base64
```

##### Secret作成
以下を実行してSecretを作成します。これらはPodに環境変数としてマウントされて利用されます。

```bash
$ kubectl apply -f sd-secrets.yaml
```

#### API, UI, StoreのYAML書き換えと作成
##### API
`services/api.yaml`の`# TODO: ~~~`とコメントが入っている部分を修正していきます。
- L61: Cloud SQL設定時に取得した`INSTANCE_CONNECTION_NAME`を`-instances`フラグに設定します。
- L117: 以下のコマンドで出力されるtoken名を設定します。これはServiceAccount作成時に生成されるもののはず。

```bash
$ kubectl get secrets | grep sd-build-token
sd-build-token-ffm4q            kubernetes.io/service-account-token   3         14d
```
- L126, 128, 130: IPアドレスをご自分のIngressのIPアドレスに書き換えます。

これらが済んだらapplyします。

```bash
$ kubectl apply -f services/api.yaml
```

##### UI
IPアドレスをご自分のIngressのIPアドレスに書き換え、のみ必要です。
これが済んだらapplyします。

```bash
$ kubectl apply -f services/ui.yaml
```

##### Store
- L70: Cloud Storageを作成したRegionに修正します。
- L72: 作成したBucket名に修正します。
- L79, 81: IPアドレスをご自分のIngressのIPアドレスに書き換えます。
これらが済んだらapplyします。

```bash
$ kubectl apply -f services/store.yaml
```

##### 確認
以下のように、全てRunningになれば完了です。

```bash
$ kubectl get po
NAME                       READY     STATUS    RESTARTS   AGE
sdapi-767cc944b6-m5cqs     2/2       Running   0          3d
sdstore-7ccf8d6fb7-tp6gt   1/1       Running   0          5d
sdui-855dd58447-pmmk4      1/1       Running   0          5d
```

## Buildを動かしてみる
こちらのNode.jsのリポジトリをBuildしてみます。
https://github.com/tk3fftk/screwdriver-cd-nodejs-test

Screwdriver.cdではGitHubの権限を利用して操作の制限をかけているため、こちらを使う場合はforkしてお試しください。

IngressのIPアドレスにブラウザでアクセスすると、以下のようなScrewdriver.cdのUIが表示されるはずです。
![Kobito.VzeAt7.png](https://qiita-image-store.s3.amazonaws.com/0/23755/f985f759-52cb-955e-8b78-e4570582a58a.png "Kobito.VzeAt7.png")

右上のえんぴつアイコンを押して新しいパイプラインを作成します。
作成前にGitHubのOAuth認証を求められるので許可してください。

GitHubからSSHのGit URLを取得してきてペーストします。
![Kobito.3WJbwz.png](https://qiita-image-store.s3.amazonaws.com/0/23755/3575835a-95d2-6c9a-eb61-eb5042a27fff.png "Kobito.3WJbwz.png")

以下のようなパイプラインのページにリダイレクトされます(ビルドはまだないと思います)
![Kobito.LbI2bz.png](https://qiita-image-store.s3.amazonaws.com/0/23755/6b2b23af-badf-9b2e-c375-b7fac410bdc4.png "Kobito.LbI2bz.png")

Startボタンを押してページの更新を行う[^3]と、以下のように青いアイコンが表示されるのでこれをクリックします。
![Kobito.4xviE8.png](https://qiita-image-store.s3.amazonaws.com/0/23755/5c14dd10-45ab-4259-e243-5b0811c04243.png "Kobito.4xviE8.png")

ビルドページが表示されるので、祈りながらビルドが通るのを待ちます。
![Kobito.kVOurZ.png](https://qiita-image-store.s3.amazonaws.com/0/23755/a7bb2939-73a6-7bcd-d51a-9e7957730bd3.png "Kobito.kVOurZ.png")

これでこのページの目的である、｢GKE上のScrewdriver.cdで何らかのビルドが実行できること｣は達成されました。

## まとめ
- GCPのプロダクトを使用して、GKE上でScrewdriver.cdを動作させ、ビルドを実行してみました。

## 注
- Screwdriver.cdは現在も開発が頻繁に行われているため、執筆当時のバージョンより少し古いバージョンのScrewdriver.cdを動かしています。そのため、最新のバージョンを動かすためには設定の追加などが必要な場合があります。
- GKEのIngressを使って同一IPアドレスで無理やり動かしている形になっています。[api,cd,store].screwdriver.cdのように[ドメインを取得してIngressで割り振る](https://cloud.google.com/kubernetes-engine/docs/tutorials/configuring-domain-name-static-ip)形のほうが設計者の意図した形だと思うのでおすすめです。
- ビルドのログが最初しか表示されない状態のようでした。こちらはちょっと原因を調査します。

### 開発をウォッチしたい方は…
- [Screwdriver.cd Slack Channel](http://slack.screwdriver.cd/)
- [Screwdriver.cd Blog](http://blog.screwdriver.cd/)
- [Screwdriver.cd GitHub Project](https://github.com/screwdriver-cd/screwdriver/projects/4)
- [Screwdriver.cd Issue](https://github.com/screwdriver-cd/screwdriver/issues)

[^1]: http://screwdriver.cd/
[^2]: https://www.infoq.com/news/2017/01/yahoo-screwdriver
[^3]: v1.0.223以降のバージョンのUIでは、Start後に自動更新がされるようになっています。