kubernetes を本番運用していくために Sorry Page イメージを作っておくと幸せになれるかもしれない話

:christmas_tree: この記事は [WESEEK Advent Calendar 2020](https://qiita.com/advent-calendar/2020/weseek) 25日目の記事です。:christmas_tree: 

# はじめに

皆さん Kubernetes (以後 k8s) 使ってますよね！最近では本番環境上でも k8s を採用する事例を見かけるようになってきました。

今回は前向きな夢の話ではなく、ちょっと泥臭いお話です。

# Sorry Page って何？

下記の画像のような「サービスがメンテナンス中だよ」ということを訪問者にお伝えするページです。

サンプルは弊社で提供しているサービスの [GROWI.cloud](https://growi.cloud/) の Sorry Page です。

| ![244138daa7d01a7766e52de0a380e4c4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/00d458ad-53bf-06d0-c81b-2cb913c9c799.png) |
|:--|

**宣伝:** [GROWI.cloud](https://growi.cloud/) は、Markdown で書ける wiki [GROWI](https://growi.org) を簡単に使い始めることができるサービスです。GROWI や同時編集機能を使うための HackMD を構築する手間を大幅に削減できますので、[GROWI.cloud](https://growi.cloud/) を是非ご利用ください。
 
# なんでやろうと思ったの？ (モチベーション)

k8s を運用していくにあたって、「落ちないサービス」や「メンテナンス時間が発生しないサービス」を構築することが理想的だとは思います。(だって、そういう仕組みが用意されているんだもの)

ですが、どうしても**一回ユーザからのアクセスを止めてから作業を実施した方が安全**なこともあります。(なるべく止めないように試行錯誤するにしたとしても)

それに加え、**いざというときにアクセスを止める手段があるという保険的な安心感**も得られます。

# Sorry Page イメージの作り方

では Sorry Page のイメージを作るための準備を進めていきましょう。

こんな感じのディレクトリ構成でファイルを用意します。

```
.
├── README.md
├── assets
│   ├── css
│   │   └── Sorry Page に必要な CSS
│   ├── favicon.ico
│   ├── img
│   │   └── Sorry Page に必要な 画像
│   ├── js
│       └── Sorry Page に必要な JS
├── docker
│   ├── Dockerfile
│   └── nginx
│       └── front.conf.template
├── index.html
└── kubernetes
    └── deployment.yaml

```

各種ファイルの内容を紹介していきます。

`index.html`, `assets` は Sorry Page 自体の HTML, CSS, JS などを書いておきます。

`Dockerfile` は基本的に nginx イメージをベースに nginx 用のコンフィグファイル、index.html, assets が含まれるように COPY や ADD をしています。

```dockerfile:docker/Dockerfile
# HOW TO BUILD:
#   $ docker build -t growi-cloud-sorry: -f docker/Dockerfile .
FROM weseek/nginx-auth-ldap:xxxxx

LABEL maintainer="XXX XXXX <xxxxxxx>"
LABEL description="This image is used for running to serve application files"
LABEL app=growi-cloud
LABEL role=release
LABEL component=nginx

COPY docker/nginx/front.conf.template /etc/nginx/conf.d/default.conf.template
COPY . /usr/share/nginx/html/

# kubernetes のヘルスチェック用のファイル
RUN touch /usr/share/nginx/html/.health-check
```

`front.conf.template` は `/usr/share/nginx/html` 配下のファイルを配信できるシンプルな設定にしています。

```nginx:docker/nginx/front.conf.template
# BASED ON nginx:1.13.6-alpine /etc/nginx/conf.d/default.conf

server {
    listen       80;
    server_name  localhost;

    gzip off;

    # Cache-controlを付ける
    add_header Cache-Control "max-age=0, public";

    location / {
        root   /usr/share/nginx/html;

        # redirect to https if accessed via http
        if ($http_x_forwarded_proto = "http") { return 301 https://$host$request_uri; }

        add_header Cache-Control "max-age=0, public";
        index index.html index.htm;
    }

    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }
}
```

`kubernetes` ディレクトリ配下は、k8s クラスタに Sorry Page イメージを deploy するときに使う manifest です。

`deployment.yaml` の image の部分は `example.com` で置換しています。

```yaml:kubernetes/deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  labels:
    app: growi-cloud-sorry
  name: growi-cloud-sorry
  namespace: growi-cloud
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: growi-cloud-sorry
  strategy:
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: growi-cloud-sorry
    spec:
      containers:
      - image: example.com/growi/growi-cloud-sorry:master
        imagePullPolicy: Always
        name: growi-cloud-sorry-demo
        ports:
        - containerPort: 80
          protocol: TCP
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      restartPolicy: Always
```


# イメージのビルド

`Dockerfile` の中にコメントとして書きましたが、作成したプロジェクト用のディレクトリトップで `docker build` を実行します

```
$ docker build -t growi-cloud-sorry -f docker/Dockerfile .
```

# k8s クラスタにデプロイして、Sorry Page に誘導する

想定する構成は下記の通りです。色々と省略していますが、サービスに対するアクセスの導線は `ユーザ -> Ingress -> Service -> Pod` という流れでリクエストが受け渡される、という前提とします。

| ![Step1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/6202dff0-e91a-fe3e-de41-24a1dc2f85d3.png) |
|:--|

`deployment.yaml` を k8s クラスタに apply して、Sorry Page イメージを deploy します

```
kubectl apply -f kubernetes/deployment.yaml
```

反映されると、下記の図のように GROWI.cloud Sorry Page Pod に向け変えができるような状態になります。

| ![002.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/53a41eaa-5f88-40d6-d250-9748a8176e21.png) |
|:--|

この状態で GROWI.cloud Service の内容を `kubectl edit ...` で書き換えることで、向け先を変えます。

| ![003.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/19717/1c7539c5-88f3-b5c4-37cb-4c7582e56c3f.png) |
|:--|


Service は `selector` の部分を `growi-cloud-sorry` に書き換えます。

```yaml
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: growi-cloud # <- ここを growi-cloud-sorry に書き換える
(省略)...
```

# アクセス先の切替方法

ここは非常に議論を呼びそうですが、 GKE Ingress の書き換えを実施すると反映に多少時間がかかるので、切替方法は用途に応じて実施するのが良いと思います。

* 即座にメンテ画面に変えたい場合は Service を書き換えて切り替える
* 時間に余裕がある場合(計画メンテなど)には GKE Ingress 側を書き換えて切り替える

# 最後に

今回は下記の流れで Sorry Page イメージの紹介をしました。

1. Sorry Page イメージの作り方
2. 作った Sorry Page イメージを k8s クラスタにデプロイする方法
3. Sorry Page Pod へのアクセス先の切り替え方法

いざというときには助かるかもしれませんが、なるべく Sorry Page イメージに頼らないで、サービス運用ができるように目指して行きたいですね :christmas_tree: