あわてんぼうのサンタクロースPodをクリスマス前にデプロイさせないようにする

# はじめに

あわてんぼうのサンタクロース
クリスマスまえにやってきた

吉岡治作詞、小林亜星作曲「あわてんぼうのサンタクロース」より

曲中のサンタクロースは「しかたがないから踊ったよ」とあるように、その場をコミュ力で乗り切りインシデントを回避しましたが、普通は障害扱いで大目玉をくらうような事態です。

リリースの手続きを踏んだとしてもチェックミスなどによる障害は往々にして起きるものなので、システム側であわてんぼうのサンタクロースがデプロイされないように対策をとってみましょう。

# How to implement

## どうやって実現する？
KubernetesのAPI Serverへのリクエストを検証する仕組みの一つである「Admission Controllers」を利用して上記の要件を実現できそうです。しかし、Admission Controllerはkube-apiserverに組み込む必要があり、apiserverの起動時にのみ指定が可能です。

それでは少々不便なため、Kubernetesでは「Admission webhook」という動的に追加する機能が提供されています。
今回はAdmission webhookを利用して実装してみます。

## 実際に作ってみる

### ValidatingWebhookConfiguration
Podが作成されるタイミングでチェックをしかけるValidatingWebhookConfigurationを書きます。
ValidatingWebhookConfigurationは、その名の通りリクエストを通していいかどうかのチェックのための設定です。
（MutatingWebhookConfigurationというのもありますが、今回は割愛）

clientConfigには、クリスマスかどうかを検証するサーバのURLを記載しておきます。

証明書関連の情報は適宜入れておいてください。

```ValidatingWebhookConfiguration.yml
piVersion: admissionregistration.k8s.io/v1beta1
kind: ValidatingWebhookConfiguration
metadata:
  name: check-hasty-santa-clause
webhooks:
  - name: check.hasty.santa.clause
    rules:
      - apiGroups:
          - ""
        apiVersions:
          - "v1"
        operations:
          - "CREATE"
        resources:
          - "pods"
    failurePolicy: Fail
    clientConfig:
      url: <クリスマスかどうかを検証するサーバ>
      caBundle: <証明書関連の情報>
```

### クリスマスかどうかを検証するサーバ
適当にGoで書きました。
今年の12月24日21時から12月25日3時までの間だけ受け付けるようにしています。

こちらも証明書関連の情報は適宜入れておいてください。

```main.go
package main

import (
    "fmt"
    "log"
    "net/http"
    "encoding/json"
    "time"
)

type ValidateResponse struct {
    Response struct {
	    Allowed bool `json:"allowed"`
		Status  struct {
			Status  string `json:"status"`
			Message string `json:"message"`
			Reason  string `json:"reason"`
			Code    int    `json:"code"`
		} `json:"status"`
	} `json:"response"`
}

func validate(w http.ResponseWriter, r *http.Request) {
    format := "2006-01-02 15:04:05"
	start, _ := time.Parse(format, "2018-12-24 21:00:00")
	end, _ := time.Parse(format, "2018-12-25 03:00:00")

	nowUTC := time.Now().UTC()
	jst := time.FixedZone("Asia/Tokyo", 9*60*60)
	nowJST := nowUTC.In(jst)

	w.Header().Set("Content-Type", "application/json")

	vr := ValidateResponse{}
	if nowJST.After(start) && nowJST.Before(end) {
		fmt.Println("Merry Xmas!")
		vr.Response.Allowed = true
	} else {
		fmt.Println("Not Xmas!")
		vr.Response.Allowed = false
		vr.Response.Status.Status = "Failure"
		vr.Response.Status.Message = "It is still before Christmas. Please wait a little more."
		vr.Response.Status.Reason = ""
		vr.Response.Status.Code = 402
	}

	res, _ := json.Marshal(vr)
	w.Write(res)
}

func main() {
    http.HandleFunc("/", validate)

    err := http.ListenAndServeTLS(
        ":8080",
        "<CERT>",
        "<KEY>", nil)
    if err != nil {
        log.Fatal("ListenAndServe: ", err)
    }
}
```

## 検証してみよう

### 環境
- Minikube v0.28.0
- Kubernetes v1.11.0

### クリスマスチェック機能のデプロイ
ValidatingWebhookConfigurationの適用

```bash
kubectl apply -f ValidatingWebhookConfiguration.yml
```

クリスマスかどうかを検証するサーバ

```bash
go run main.go
```

### サンタクロースPodのデプロイ
チェックがされるかデプロイしてみます。
（サンタさんの代わりにNginxさんで代用）

```bash
$ kubectl run --image=nginx --restart=Never santa
Error from server: admission webhook "check.hasty.santa.clause" denied the request: It is still before Christmas. Please wait a little more.
```

```bash
# 検証サーバ側の標準出力
Not Xmas!
```

12月16日に実行したため、チェックがはたらきデプロイはされませんでした。
検証サーバからのメッセージもきちんと表示されています。やったね。

# まとめと所感
今回の題材はおふざけでしたが、例えばIstioのSidecar InjectionもAdmission Webhook（MutatingWebhookConfiguration）を利用して実装されています。これを読んでいるあなたも気づかないところでお世話になっているかもしれません。

Kubernetesには上記で述べたAdmission WebhookやCRD+CustomControllerなどの多数の拡張機能が用意されています。
K8sをより深く理解するためにも拡張機能の作り方は知ってると良いかもしれません。

# TODO
- Githubにソースを置く
- 参考リンクをはっておく
- 12月24日にデプロイされるか確認（念のため）