GitHub Appを使ってGitOpsする

[Kubernetes3 Advent Calendar 2018 - Qiita](https://qiita.com/advent-calendar/2018/kubernetes3) の16日目です。

## 概要
既存のKubernetes環境向けCDツールがチームの要件に満たなかったのでGitHub AppでGitOpsをしていくことにしました。ここではその内容を共有しています。

GitOpsについての詳細はこちら: [GitOps](https://www.weave.works/technologies/gitops/)

## 小さな仕組みでGitOpsをする

 * 一般的にあらゆるプロジェクトの持つ予算と残された時間はそれほど多くありません。kubernetesの持つメリットを享受しつつ素早くデプロイフローを作る必要があります。
 * 環境は大きく変化していくため、変化に強くありたいと願います。端的に言えば**今しばらくは**ワークフローをシンプルでつまらないものに保つべきと考えています。

## 既存ツールなんかもさわってみたんですよ
 
 * ローカルで `kubectl` で `apply` する 
   * 2-3人の規模であれば問題は起こらないと思えます。ただし今は開発人数が増えてきていて、破綻が見えているか破綻します。
   
 * Spinnaker, Jenkins X, argo CDも触った
 
 私が少し機能追加しようとすると面倒に思えました。
 経験上もうしばらく時間を置けばデファクトが決まり技術も枯れて安定するはずで、それまでは待ちたいと思います。
 正味、複数RepositoryとDockerRepositoryとGitHubと、Slack等の通知先とやり取りすれば良いので、さっとやってみることにしました。

## GitHub Appを使ったワークフロー
以下のようなデプロイフローを想定します。

![deploy_flow.png](https://qiita-image-store.s3.amazonaws.com/0/37778/a6799f05-0725-680d-5b1d-55ad3b781418.png)


 1. 開発者はPull RequestをApp repo のstagingブランチに出す
 2. 1.のPull Request がmergeされる
 3. Github Appは2のイベントを検知すると、App repoのstagngブランチを ` docker build `して ` docker push `する。
 4. Github Appはstagngブランチを Staging用のManifestを自動生成してManifest RepoへPull Requestを出す
 5. 4のPull Requestがmergeされる。
 6. Github Appは5のイベントを検知すると、mergeされた内容をstagingクラスタへ`kubectl apply`する
 



## 使用する技術について

### probotを使ったGitHub Appが仲介業をする
probotはNode.jsでeasyに作れるGitHub App FrameWorkです。
[Probot | GitHub Apps to automate and improve your workflow](https://probot.github.io/)

GitHub Webhookを受け取って特定の動作をさせることができ、独自でHTTPエントリポイントを作って任意の動作をさせることが可能です。
このporobotを利用したGitHub Appのサーバを立ててRepositoryへのdeploy keysやGKEの場合はGCPサービスアカウントへの認証を持たせると仲介者の役割を果たせます。

ex: stagingにmergeされたら処理する

``` JavaScript 
    app.on('push', async context => {
      const branch = context.payload.ref.split('/')[2] // format is "refs/heads/$BRANCH"
      const repo = context.payload.repository.name
      if (branch === 'staging' && repo === 'AppRepo') {
        const result = someOps(branch, repo)
        app.log(result)
      }
    })
``` 

ex: GitHubAppにkubectl applyさせるHTTPエンドポイント

``` JavaScript 
  // kubectl apply action
  const router = app.route('/github-app')
  router.post('/apply', (req, res) => {
    if (req.header('token') !== token) {
      res.send('Recieved invalid token.') // check valid token
      return
    }
    const result = someApplyExec()
    app.log(result)
    res.send(`k8s Applied.`)
  })
```


### KustomizeとシェルスクリプトでManifestを自動生成する

例えばDeploymentを以下のようなシェルスクリプトで作り、`kustomize build`すればテンプレート的に作れそうです。

``` zsh
cat << EOS > $FILE_NAME
apiVersion: "extensions/v1beta1"
kind: "Deployment"
metadata:
  name: $DEPLOYMENT
spec:
  replicas: $REPLICA
  selector:
    matchLabels:
      app: $APP
  template:
    metadata:
     labels:
       app: $APP
    spec:
      containers:
      - name: $NAME
        image: $IMAGE
        ports:
          - containerPort: 80
EOS

```


### その他: 本番環境や他サービス連携
productionの場合は図1.のPullRequestのheadをstagingに、baseをproductionにします。
Slackにイベントの通知を流したい場合はGitHubAppの処理に記述を入れます。

## 終わりに:
今回のフロー自体には手間を感じ得ますが、それぞれの仕組みはシンプルでくだらない、分かりやすいものになります。
シェルスクリプトかJavaScriptの知識とそれからGitHubやKubernetesのAPIを眺めればその日のうちに修正を入れることができます。
私はこの仕組に全くこだわりがありません。デファクトが出たら/dev/nullにアーカイブするでしょう。
引き続きプロダクトとその先にいる顧客に集中したいと思っています。