KUSANAGI on GKE

# はじめに
GKE で WordPress を実装するために情報を探していたところ、[**KUSANAGI Runs on Docker**](https://kusanagi.tokyo/cloud/kusanagi-runs-on-docker/) というのが公開（まだベータ版みたいですが）されていましたので、この Docker イメージ（kusanagi-nginx、kusanagi-php7）をそのまま使って KUSANAGI を GKE 上に実装し、更には KUSANAGI へのアクセスに **Let's Encrypt** を利用して HTTPS 化したいと思います。

尚、本投稿でのコマンドの実行は、全て **Google Cloud Shell** 内で行っています。

# 実装イメージ図
![KUSANAGI-image.png](https://qiita-image-store.s3.amazonaws.com/0/94476/8a0dd693-3f6e-65fc-bcf4-77ccc9f4ee62.png)



# クラスタ作成
GKE に **リージョンクラスタ** を作成します。
リージョンクラスタ はリージョン内の全ゾーン（`--node-locations` オプションでゾーンを指定した場合は、指定された全ゾーン）にノードが作成されるので、可用性を高めることができます。
また KUSANAGI のパフォーマンスを出来る限り落とさないよう、永続ディスクには GCE の[**リージョン永続ディスク**](https://cloud.google.com/compute/docs/disks/?hl=ja#repds)（**Regional Persistent Disk**）を利用します。

今回は東京リージョン（ap-northeast1）の内、２ゾーン（asia-northeast1-a、asia-northeast1-b）にまたがるクラスタを作成します。

## クラスタ作成スクリプトの準備

クラスタ作成コマンドは、指定するパラメータの数が結構多いので、Shell スクリプト化します。

```bash:create-cluster.sh
#!/bin/bash

CLUSTER_ID=test-cluster                     #クラスタ名
MACHINE_TYPE=n1-standard-1                  #ノードのマシンタイプ（デフォルト:n1-standard-1）
REGION=asia-northeast1                      #クラスタを作成するリージョン
NUM_NODES=1                                 #ゾーン毎に配置するノード数
NODE_LOCATIONS=${REGION}-a,${REGION}-b      #ノードを配置するゾーン ※リージョン永続ディスクを使う場合は２ノードまで
NETWORK=default                             #ノードに接続されるVPC
SUBNETWORK=default                          #ノードに接続されるサブネット
CLUSTER_VERSION=$(gcloud beta container get-server-config --region ${REGION} --format='value(validMasterVersions[0])')

gcloud beta container clusters create ${CLUSTER_ID} \
    --cluster-version=${CLUSTER_VERSION} \
    --machine-type=${MACHINE_TYPE} \
    --region=${REGION} \
    --num-nodes=${NUM_NODES} \
    --node-locations=${NODE_LOCATIONS} \
    --network=${NETWORK} \
    --subnetwork=${SUBNETWORK} \
    --enable-autorepair
```

## スクリプトの実行

クラスタ作成スクリプトを実行します。作成完了まで多少時間がかかります。

```bash
$ sh create-cluster.sh
Creating cluster test-cluster...done.
Created [https://container.googleapis.com/v1beta1/projects/gooday-development/zones/asia-northeast1/clusters/test-cluster].
To inspect the contents of your cluster, go to: https://console.cloud.google.com/kubernetes/workload_/gcloud/asia-northeast1/test-cluster?project=gooday-development
kubeconfig entry generated for test-cluster.
NAME          LOCATION         MASTER_VERSION  MASTER_IP      MACHINE_TYPE   NODE_VERSION  NUM_NODES  STATUS
test-cluster  asia-northeast1  1.10.7-gke.1    35.200.88.154  n1-standard-1  1.10.7-gke.1  2          RUNNING
```

# Helm
GKE（K8s）で **Let's Encrypt** の証明書を管理（取得、自動更新）するには、K8s パッケージ（以降、**Chart**）の **cert-manager** を クラスタに用意します。
**cert-manager** をクラスタにデプロイするには、**K8s Helm**（以降、**Helm**）と呼ばれるパッケージマネージャを利用します。（Linux でいう `yum` や `apt-get`、Python でいう `pip` と似た感じですかね）

## Helm クライアントのインストール
Cloud Shell では **Helm クライアント**が用意されていないのでインストールする必要があります。

```bash
$ curl https://raw.githubusercontent.com/kubernetes/helm/master/scripts/get | bash
```

## Tiller のインストール

**Helm** のサーバ部分は **Tiller** と呼ばれ、Helm クライアントからのリクエストの受付、公式 Chart リポジトリ（以降、Helm Repo）から Chart をクラスタにインストール（デプロイ）、インストール済 Chart のリリース監視等の役割を担います。
Tiller は自身のクラスタ内にデプロイする必要があり、Helm クライアントと Tiller 間は、クラスタ管理権限を持つ専用のサービスアカウント **tiller** を介して接続されます。

![0001.jpg](https://qiita-image-store.s3.amazonaws.com/0/94476/aa22fbf6-a99d-2306-140c-958f2535101c.jpeg)



### サービスアカウント 'tiller' の作成
サービスアカウント `tiller` を、名前空間 `kube-system` に作成します。

```bash
$ kubectl create serviceaccount -n kube-system tiller

# サービスアカウント確認
$ kubectl describe sa tiller -n kube-system
Name:                tiller
Namespace:           kube-system
Labels:              <none>
Annotations:         <none>
Image pull secrets:  <none>
Mountable secrets:   tiller-token-XXXXX
Tokens:              tiller-token-XXXXX
Events:              <none>
```

### cluster-admin ロールにバインド
サービスアカウント `tiller` に クラスタ管理者ロール **cluster-admin** を割り当てます。

```bash
$ kubectl create clusterrolebinding tiller-binding \
    --clusterrole=cluster-admin \
    --serviceaccount kube-system:tiller

# ロールバインド確認
$ kubectl describe clusterrolebinding tiller-binding
Name:         tiller-binding
Labels:       <none>
Annotations:  <none>
Role:
  Kind:  ClusterRole
  Name:  cluster-admin
Subjects:
  Kind            Name    Namespace
  ----            ----    ---------
  ServiceAccount  tiller  kube-system
```

### Tiller のデプロイ
K8s クラスタに Tiller をデプロイします。
Tiller のデプロイには、`kubectl` コマンドではなく、 helm クライアントを用います。 

```bash
$ helm init --service-account tiller

# デプロイの確認
$ kubectl get pod,deploy,svc  -n kube-system -l name=tiller
NAME                                READY     STATUS    RESTARTS   AGE
pod/tiller-deploy-895d57dd9-gmtjs   1/1       Running   0          1d

NAME                                  DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
deployment.extensions/tiller-deploy   1         1         1            1           1d

NAME                    TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)     AGE
service/tiller-deploy   ClusterIP   10.1.163.92   <none>        44134/TCP   1d
```

### helm リポジトリのアップデート
利用可能な Chart の情報を、Helm Repo から Helm クライアントにキャッシュします。

```bash
$ helm repo update
```


# 証明書取得（DNS 認証方式）

Let's Encrypt では証明書を取得するにあたり、「自分がドメインの所有者かどうか」を認証してもらう方式として、次の３つがサポートされています。

- **HTTP-01**
Let’s Encryptの認証局からワンタイムトークンを発行してもらい、Webサーバに認証用ファイルを設置する。
認証局からHTTP(80番ポート)でアクセスしてもらい、ワンタイムトークンと認証用ファイルとの妥当性を検証する。

- **TLS-SNI-01**
HTTP-01と同じ方法だが、HTTPS(443ポート)を使用する。

- **DNS-01**
Let’s Encryptの認証局から発行してもらったワンタイムトークンを対象ドメインのTXTレコードに登録することで検証する。

DNS による認証方式（DNS-01）だと、事前にWebサイトを準備する必要が無いので、今回は DNS プロバイダに **Amazon Route53** を使い証明書を取得したいと思います。
※ Route53 の代わりに **Cloud DNS** を利用することもできます。
## cert-manager インストール
Helm を用いて cert-manager を K8s クラスタにインストールします。

```bash
$ helm install \
    --name cert-manager \
    --namespace kube-system \
    stable/cert-manager

# Chart 確認
$ helm list
NAME            REVISION        UPDATED                         STATUS          CHART                   APP VERSION     NAMESPACE
cert-manager    1               Tue Sep 11 11:57:24 2018        DEPLOYED        cert-manager-v0.4.1     v0.4.1          kube-system

# デプロイ確認
$ kubectl get deploy -n kube-system
NAME                    DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
cert-manager            1         1         1            1           2m
```

## AWS IAM ユーザー登録
cert-manager が Route63 を操作できるよう、AWS 側にて次のポリシーをアタッチした IAM ユーザーを作成します。
作成後は、Access Key と Secret Access Key を控えておきます。
※ IAM ユーザーの作成手順は割愛します。  

```json:ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": "route53:GetChange",
            "Resource": "arn:aws:route53:::change/*"
        },
        {
            "Effect": "Allow",
            "Action": "route53:ChangeResourceRecordSets",
            "Resource": "arn:aws:route53:::hostedzone/*"
        },
        {
            "Effect": "Allow",
            "Action": "route53:ListHostedZonesByName",
            "Resource": "*"
        }
    ]
}
```

## AWS Secret Access Key 登録

控えておいた Secret Access Key を Base64 エンコード （`echo -n '<Secret Access Key>' | base64`）し、その値を K8s クラスタの Secret リソースに登録します。
尚、Secret Access Key の値を持った Secret リソースは、cert-manager と同一 namespace（`kube-system`）に作成する必要がある点に注意してください。 

### Manifest ファイル作成

```yaml:secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: prod-route53-credentials-secret
  namespace: kube-system
type: Opaque
data:
  secret-access-key: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

### Secret 作成

```bash
$ kubectl apply -f secret.yaml

# Secret 確認
$ kubectl describe secret prod-route53-credentials-secret -n kube-system
Name:         prod-route53-credentials-secret
Namespace:    kube-system
Labels:       <none>
Annotations:
Type:         Opaque

Data
====
secret-access-key:  40 bytes
```

## Issuer/ClusterIssuer 作成

認証局（Let's Encrypt）と DNS プロバイダー（Amazon Route53）の情報を **Issuer/ClusterIssuer** リソースとして作成します。
尚、namespace 毎に利用可能なリソースは **Issuer**、K8s クラスタ全体で利用可能なリソースは **ClusterIssuer** で作成します。
今回は **ClusterIssuer** リソースとして作成します。

### Manifest ファイル作成

```yaml:cluster-issuer.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod

spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: myname@example.com #TODO: Your email address
    privateKeySecretRef:
      name: letsencrypt-prod
    http01: {}
    dns01:
      providers:
      - name: prod-dns
        #couddns: #for Google Cloud DNS
        #  ･･･
        route53:
          accessKeyId: AKXXXXXXXXXXXXXXXXXX #TODO: Your aws access key
          region: ap-northeast-1 #TODO: Your region of amazon route53
          secretAccessKeySecretRef:
            name: prod-route53-credentials-secret
            key: secret-access-key
```

### ClusterIssuer リソース作成

```bash
$ kubectl apply -f cluster-issuer.yaml

# ClusterIssuer リソース確認
$ kubectl describe clusterissuer letsencrypt-prod
Name:         letsencrypt-prod
Namespace:
Labels:       <none>
Annotations:  kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"certmanager.k8s.io/v1alpha1","kind":"ClusterIssuer","metadata":{"annotations":{},"name":"letsencrypt-prod","namespace":""},"spec":{"acme...
API Version:  certmanager.k8s.io/v1alpha1
Kind:         ClusterIssuer
Metadata:
  Cluster Name:
  Creation Timestamp:  2018-09-12T02:08:04Z
  Generation:          1
  Resource Version:    7907617
  Self Link:           /apis/certmanager.k8s.io/v1alpha1/clusterissuers/letsencrypt-prod
  UID:                 af2856dd-b630-11e8-9a5d-42010a92004d
Spec:
  Acme:
    Dns 01:
      Providers:
        Name:  prod-dns
        Route 53:
          Access Key ID:   AKXXXXXXXXXXXXXXXXXX
          Hosted Zone ID:
          Region:          ap-northeast-1
          Secret Access Key Secret Ref:
            Key:   secret-access-key
            Name:  prod-route53-credentials-secret
    Email:         myname@example.com
    Http 01:
    Private Key Secret Ref:
      Key:
      Name:  letsencrypt-prod
    Server:  https://acme-v02.api.letsencrypt.org/directory
Status:
  Acme:
    Uri:  https://acme-v02.api.letsencrypt.org/acme/acct/99999999
  Conditions:
    Last Transition Time:  2018-09-12T02:08:05Z
    Message:               The ACME account was registered with the ACME server
    Reason:                ACMEAccountRegistered
    Status:                True
    Type:                  Ready
Events:                    <none>
```

## TLS 証明書の取得
**Certificate** リソースにより TSL 証明書を取得します。

### Manifest ファイル作成
Let's Encrypt はSAN（Subject Alternative Name、サブジェクト代替名）対応の証明書を発行することができます。例えば `www.example.com` と `example.com` と２つのホスト名に対応した証明書を作成することができます。
またワイルドカード（*）証明書にも対応しています。

```yaml:certificate.yaml
apiVersion: certmanager.k8s.io/v1alpha1
kind: Certificate
metadata:
  name: cert-kusanagi
spec:
  secretName: cert-kusanagi-tls
  issuerRef:
    name: letsencrypt-prod
    kind: ClusterIssuer
  commonName: 'kusanagi.example.com'
  dnsNames:
    - 'www.kusanagi.example.com'
    - 'kusanagi.example.com'
  acme:
    config:
      - dns01:
          provider: prod-dns
        domains:
          - 'kusanagi.example.com'
```

### Certificate リソース作成（証明書取得）
証明書を Manifest ファイルを元に取得します。`kubectl describe` コマンドで出力された `Event` に `cert-manager  Certificate issued successfully` が出力されたら作成完了です。
証明書に追加取得が必要な場合は、**Certificate** リソース を作成することで簡単に追加取得することができます。

```bash
$ kubectl apply -f certificate.yaml

# Certificate 確認
$ kubectl describe certificate cert-kusanagi

･･･

Events:
  Type    Reason          Age   From          Message
  ----    ------          ----  ----          -------
  Normal  CreateOrder     3m    cert-manager  Created new ACME order, attempting validation...
  Normal  DomainVerified  1m    cert-manager  Domain "kusanagi.example.com" verified with "dns-01" validation
  Normal  IssueCert       47s   cert-manager  Issuing certificate...
  Normal  CertObtained    45s   cert-manager  Obtained certificate from ACME server
  Normal  CertIssued      45s   cert-manager  Certificate issued successfully
```

尚、取得処理の最中に cert-manager が Route53 のゾーン情報に `_acme-challenge.kusanagi.example.com` というホスト名の TXT レコードを追加し、その値（Value）に Let's Encrypt 認証トークンがセットしていることが確認できます。

### TLS 証明書、秘密鍵の確認
取得した TLS 証明書（`tls.crt`）と秘密鍵（`tls.key`）が **Secret** リソースに保存されていることを確認します。

```bash
$ kubectl describe secret cert-kusanagi-tls
Name:         cert-kusanagi-tls
Namespace:    default
Labels:       certmanager.k8s.io/certificate-name=cert-kusanagi
Annotations:  certmanager.k8s.io/alt-names=kusanagi.example.com
              certmanager.k8s.io/common-name=kusanagi.example.com
              certmanager.k8s.io/issuer-kind=ClusterIssuer
              certmanager.k8s.io/issuer-name=letsencrypt-prod

Type:  kubernetes.io/tls

Data
====
tls.crt:  3846 bytes  #TLS証明書
tls.key:  1679 bytes  #秘密鍵
```

これで証明書の取得は完了です。
更に、TLS 証明書の有効期限の30日前になると、cert-manager が 認証局（Let's Encrypt）に対し自動更新してくれます。


# StorageClassリソースの作成
リージョン永続ディスクの StorageClass を作成します。
StorageClass を作成することで、後述の PersistentVolumeClaim からリージョン永続ディスクをアサインすることが出来るようになります。


## Manifest ファイルの準備

```yaml:storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: regpd
provisioner: kubernetes.io/gce-pd
parameters:
  type: pd-standard  #pd-standard(HDD)|pd-ssd(SSD)
  replication-type: regional-pd
  zones: asia-northeast1-a, asia-northeast1-b
```

## StorageClass 作成、確認

```bash
$ kubectl apply -p storage-class.yaml
storageclass.storage.k8s.io/regpd created

#StorageClass 確認
$ kubectl describe sc regpd
Name:            regpd
IsDefaultClass:  No
Annotations:     kubectl.kubernetes.io/last-applied-configuration={"apiVersion":"storage.k8s.io/v1","kind":"StorageClass","metadata":{"annotations":{},"name":"regpd","namespace":""},"parameters":{"replication-type":"regional-pd","type":"pd-standard","zones":"asia-northeast1-a, asia-northeast1-b"},"provisioner":"kubernetes.io/gce-pd"}

Provisioner:           kubernetes.io/gce-pd
Parameters:            replication-type=regional-pd,type=pd-standard,zones=asia-northeast1-a, asia-northeast1-b
AllowVolumeExpansion:  <unset>
MountOptions:          <none>
ReclaimPolicy:         Delete
VolumeBindingMode:     Immediate
Events:                <none>
```

# Cloud SQL アクセス用サービスアカウントキーの取得

Pod から Cloud SQL を利用するため、アクセス用の**サービスアカウントキー** を取得します。

## サービスアカウント 'cloudsql' の作成

```bash
$ gcloud iam service-accounts create cloudsql --display-name="cloudsql"
```

## サービスアカウントへ役割（Cloud SQL クライアント）付与

```bash
$ gcloud projects add-iam-policy-binding <Project ID> \
    --member="serviceAccount:cloudsql@<Project ID>.iam.gserviceaccount.com" \
    --role="roles/cloudsql.client"
```

## サービスアカウントキーの取得

カレントディレクトリ内に `credentials.json` というキーファイル名でサービスアカウントキーを保存します。 

```bash
$ gcloud iam service-accounts keys create ./credentials.json --iam-account cloudsql@<Project ID>.iam.gserviceaccount.com
```

## Secret に登録
サービスアカウントキーを Secret に登録します。
ここで登録した Secret `service-account-token` を Pod 内の **Cloud SQL Proxy** コンテナが読み取り、Cloud SQL へ接続することができます。
 
```bash
$ kubectl create secret generic service-account-token --from-file=credentials.json=./credentials.json

$ kubectl describe secret service-account-token
Name:         service-account-token
Namespace:    default
Labels:       <none>
Annotations:  <none>

Type:  Opaque

Data
====
credentials.json:  2337 bytes
```

# Cloud SQL

KUSANAGI のバックエンド DB を Cloud SQL に作成します。

もし既に MySQL インスタンスが存在するなら、そのインスタンスを利用することもできます。
（その場合、インスタンスの作成部分は飛ばし、データベースの作成から行ってください）

## インスタンス作成スクリプト

Cloud SQL インスタンスを作成する Shell スクリプトを用意します。

```bash:create-cloudsql.sh
#!/bin/bash

INSTANCE_ID=kusanagi            #インスタンスID
REGION=asia-northeast1          #インスタンスを作成するリージョン
DATABASE_VERSION=MYSQL_5_7      #データベースバージョン
NUMBER_CPUS=1                   #割当てvCPU数
MEMORY_SIZE=3840MiB             #割当てメモリ容量
STORAGE_TYPE=HDD                #ストレージの種類（HDD|SSD）
STORAGE_SIZE=10GB               #ストレージの容量（GB単位）

gcloud beta sql instances create ${INSTANCE_ID} \
    --region=${REGION} \
    --database-version=${DATABASE_VERSION} \
    --cpu=${NUMBER_CPUS} \
    --memory=${MEMORY_SIZE} \
    --storage-type=${STORAGE_TYPE} \
    --storage-size=${STORAGE_SIZE} \
    --storage-auto-increase
```

## インスタンス作成

インスタンス作成スクリプトを実行します。しばらく待つとインスタンスが作成されます。

```bash
$ sh create-cloudsql.sh
Creating Cloud SQL instance...done.
Created [https://www.googleapis.com/sql/v1beta4/projects/gooday-development/instances/kusanagi].
NAME      DATABASE_VERSION  LOCATION           TIER              ADDRESS        STATUS
kusanagi  MYSQL_5_7         asia-northeast1-c  db-custom-1-3840  35.XXX.XXX.XXX RUNNABLE
```

## データベース作成

インスタンス内にデータベース（`kusanagi`）を作成します。

```bash
$ gcloud sql databases create kusanagi --instance=kusanagi
```

## ユーザー作成

データベース接続ユーザー（`kusanagi`）を作成します。

```bash
$ gcloud sql users create kusanagi --host=% --instance=kusanagi --password=<PASSWORD>
```

# 外部IPアドレスの予約
KUSANAGI 公開用に、外部 IP アドレス（グローバル IP アドレス）を予約します。
今回は `kusanagi-ip` という名前で予約しました。

```bash
$ gcloud compute addresses create kusanagi-ip --global

$ gcloud compute addresses list --filter="name=('kusanagi-ip')"
NAME         REGION  ADDRESS        STATUS
kusanagi-ip          35.201.XXX.XXX RESERVED
```

# Route53 に A レコードを追加
Amazon Route53 に A レコード `kusanagi.example.com`（`example.com` の部分は実際のドメイン名に置換してください）を追加し、名前解決ができるようにします。
その際 A レコードの値（Value）には、前述で予約した 外部 IP アドレス（`35.201.XXX.XXX`）を入力してください。
※ Route53 への A レコード追加方法は割愛します。

# KUSANAGI デプロイ

ここまでの手順で前準備が整いましたので、いよいよ KUSANAGI を GKE にデプロイします。

## KUSANAGI イメージを GCR へアップロード

KUSANAGI の Docker イメージのうち、`kusanagi-nginx` と `kusanagi-php7` を Container Registry（GCR）にアップロードします。

```bash
# kusanagi-nginx イメージのダウンロード、タグ付け、GCR へアップロード
$ docker pull primestrategy/kusanagi-nginx:1.10.0-1
$ docker tag primestrategy/kusanagi-nginx:1.10.0-1 asia.gcr.io/gooday_internal_system/kusanagi-nginx:1.10.0-1
$ gcloud docker -- push asia.gcr.io/gooday_internal_system/kusanagi-nginx:1.10.0-1

# kusanagi-php7 イメージのダウンロード、タグ付け、GCR へアップロード
$ docker pull primestrategy/kusanagi-php7:7.0.6-1
$ docker tag primestrategy/kusanagi-php7:7.0.6-1 asia.gcr.io/gooday_internal_system/kusanagi-php7:7.0.6-1
$ gcloud docker -- push asia.gcr.io/gooday_internal_system/kusanagi-php7:7.0.6-1
```

## KUSANAGI デプロイ
KUSANAGI 環境をデプロイします。
今回は１つの Manifest ファイルで次の環境を実装しています。

- PersistentVolumeClaim（`/home/kusanagi`、`/etc/kusanagi.d`、`/etc/nginx/conf.d`） 
- Deployment（`kusanagi-nginx`、`kusanagi-php7`、`cloudsql-proxy`）
- Service（NortPort）
- Ingress

## Manifest ファイルの準備

```yaml:deploy-kusanagi.yaml
# /home/kusanagi, /etc/kusanagi.d, /etc/nginx/conf.d データ永続用PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-kusanagi-data
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
  storageClassName: regpd
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  generation: 1
  labels:
    run: kusanagi
  name: kusanagi
  namespace: default
spec:
  replicas: 1
  selector:
    matchLabels:
      run: kusanagi
  template:
    metadata:
      labels:
        run: kusanagi
    spec:
      containers:
      - image: asia.gcr.io/gooday_internal_system/kusanagi-nginx:1.10.0-1
        imagePullPolicy: Always
        name: kusanagi-nginx
        env:
        - name: PROFILE
          value: "kusanagi_html"           #KUSANAGI プロファイル名
        - name: FQDN
          value: "kusanagi.example.co.jp"  #KUSANAGI ホスト名（FQDN）
        - name: WPLANG
          value: "ja"                      #WordPress の言語
        - name: BCACHE
          value: "off"                     #生成ページのキャッシュ（on|off）
        - name: FCACHE
          value: "off"                     #FastCGIレスポンスのキャッシュ（on|off）
        ports:
        - containerPort: 80
          protocol: TCP
        - containerPort: 443
          protocol: TCP
        readinessProbe:  #ヘルスチェック設定（Ingressが行うヘルスチェックの設定に反映）
          httpGet:
            path: /readme.html
            port: 80
          initialDelaySeconds: 5
          periodSeconds: 10
        volumeMounts:
        - mountPath: "/home/kusanagi"
          name: kusanagi-data
          subPath: home
        - mountPath: "/etc/nginx/conf.d"
          name: kusanagi-data
          subPath: nginx-confd
        - mountPath: "/etc/kusanagi.d"
          name: kusanagi-data
          subPath: kusanagid
        resources:
          requests:
            cpu: 20m
      - image: asia.gcr.io/gooday_internal_system/kusanagi-php7:7.0.6-1
        imagePullPolicy: Always
        name: kusanagi-php7
        ports:
        - containerPort: 9000
          protocol: TCP
        volumeMounts:
        - mountPath: "/home/kusanagi"
          name: kusanagi-data
          subPath: home
        - mountPath: "/etc/nginx/conf.d"
          name: kusanagi-data
          subPath: nginx-confd
        - mountPath: "/etc/kusanagi.d"
          name: kusanagi-data
          subPath: kusanagid
        resources:
          requests:
            cpu: 20m
      - image: b.gcr.io/cloudsql-docker/gce-proxy:latest
        imagePullPolicy: Always
        name: cloudsql-proxy
        command:
        - /cloud_sql_proxy
        - -dir=/cloudsql
        - -instances=<Project ID>:asia-northeast1:kusanagi=tcp:3306  #Cloud SQL インスタンス接続名
        - -credential_file=/credentials/credentials.json
        volumeMounts:
        - mountPath: /cloudsql
          name: cloudsql
        - mountPath: /etc/ssl/certs
          name: ssl-certs
        - mountPath: /credentials    #サービスアカウントキーのマウント
          name: service-account-token
      hostAliases:        #ホストエイリアスの設定
      - hostnames:        #  Nginx がバックエンドのコンテナ kusanagi-php7 (FastCGI) に対し
        - php             #  'php' というホスト名で Proxy している為
        ip: 127.0.0.1     #  各コンテナの /etc/hosts に追加される
      volumes:
      - name: kusanagi-data
        persistentVolumeClaim:
          claimName: "pvc-kusanagi-data"
      - name: cloudsql
        emptyDir:
      - name: ssl-certs
        hostPath:
          path: /etc/ssl/certs
      - name: service-account-token  #サービスアカウントキーの Secret
        secret:
          secretName: service-account-token
---
apiVersion: v1
kind: Service
metadata:
  name: kusanagi-nodeport
  namespace: default
  labels:
    run: kusanagi-nodeport
spec:
  type: NodePort
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: kusanagi
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: kusanagi-ingress
  annotations:
    #kubernetes.io/ingress.allow-http: "false"  #HTTPSのみ許可する場合はコメントアウトを外す
    kubernetes.io/ingress.global-static-ip-name: "kusanagi-ip"  #外部 IP アドレス名
    kubernetes.io/ingress.class: "gce"
    kubernetes.io/tls-acme: "true"
spec:
  tls:
  - secretName: cert-kusanagi-tls #TLS 証明書、秘密鍵が保存されている Secret 名
  backend:
    serviceName: kusanagi-nodeport  #NodePort サービス名
    servicePort: 80  #NodePortの待受ポート
```

## デプロイ

```bash
$ kubectl apply -f deploy-kusanagi.yaml
```

## Ingress 確認
Ingress の準備が完了（ヘルスチェックの完了）するまで暫く待ちます。（結構長い時間がかかる場合があります）

`Annotations:` にある `ingress.kubernetes.io/backends:` のステータスが `Unknown` から `HEALTHY` に変われば準備完了です。


```bash
$ kubectl describe ingress kusanagi-ingress
Name:             kusanagi-ingress
Namespace:        default
Address:          130.211.XXX.XXX
Default backend:  kusanagi-nodeport:80 (10.36.0.14:80)
TLS:
  cert-kusanagi-tls terminates
Rules:
  Host  Path  Backends
  ----  ----  --------
  *     *     kusanagi-nodeport:80 (10.36.0.14:80)
Annotations:
  kubernetes.io/tls-acme:                            true
  ingress.kubernetes.io/backends:                    {"k8s-be-30098--49f84941322af08b":"HEALTHY"}  #'Unknown' から 'HEALTHY' になるまで待つ 
  ingress.kubernetes.io/https-forwarding-rule:       k8s-fws-default-kusanagi-ingress--49f84941322af08b
  ingress.kubernetes.io/https-target-proxy:          k8s-tps-default-kusanagi-ingress--49f84941322af08b
  ingress.kubernetes.io/ssl-cert:                    k8s-ssl-d11f06e1315aac38-e5a1656857d9445c--49f84941322af08b
  ingress.kubernetes.io/url-map:                     k8s-um-default-kusanagi-ingress--49f84941322af08b
  ingress.kubernetes.io/forwarding-rule:             k8s-fw-default-kusanagi-ingress--49f84941322af08b
  ingress.kubernetes.io/target-proxy:                k8s-tp-default-kusanagi-ingress--49f84941322af08b
  kubectl.kubernetes.io/last-applied-configuration:  {"apiVersion":"extensions/v1beta1","kind":"Ingress","metadata":{"annotations":{"kubernetes.io/ingress.class":"gce","kubernetes.io/ingress.global-static-ip-name":"kusanagi-ip","kubernetes.io/tls-acme":"true"},"name":"kusanagi-ingress","namespace":"default"},"spec":{"backend":{"serviceName":"kusanagi-nodeport","servicePort":80},"tls":[{"secretName":"cert-kusanagi-tls"}]}}

  kubernetes.io/ingress.class:                  gce
  kubernetes.io/ingress.global-static-ip-name:  kusanagi-ip
Events:                                         <none>
```

# KUSANAGI 初期設定
ブラウザから `http://kusanagi.example.com` にアクセスし、次の手順で初期設定を行います。

※初期設定の際、HTTPSでアクセスすると CSS が読み込まれない為、初期設定の間は HTTP でアクセスします。

**`さあ、始めましょう！`** をクリックします。
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-admin-setup-config-php-2018-09-21-13_34_54.png](https://qiita-image-store.s3.amazonaws.com/0/94476/53edaf29-0847-a7f9-2cf7-fe26bea2a647.png)

`データベース名`、`ユーザー名`、`パスワード`、`データベースのホスト名`に Cloud SQL の接続情報を入力して**`送信`**をクリックします。
**※注**
`データベースのホスト名`には `localhost` がデフォルトで表示されていますが、`127.0.0.1` を入力してください。
`kusanagi-php7` のコンテナ内からは、名前解決ができないようです。（調査中）
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-admin-setup-config-php-2018-09-21-13_36_09.png](https://qiita-image-store.s3.amazonaws.com/0/94476/6ebdc9d6-3285-1efe-2d96-d0dcb8a677bd.png)

**`インストール実行`**をクリックします。
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-admin-setup-config-php-2018-09-21-13_37_10.png](https://qiita-image-store.s3.amazonaws.com/0/94476/8566eaf4-a653-e40d-a398-2c28862bc424.png)

各項目に任意の情報を入力して**`WordPress をインストール`**をクリックします。
**※ パスワードは必ず控えておきましょう。
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-admin-install-php-2018-09-21-13_38_28.png](https://qiita-image-store.s3.amazonaws.com/0/94476/85e4901d-33ad-5458-4777-b05085bb4f6b.png)

**`ログイン`**をクリックします。
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-admin-install-php-2018-09-21-13_38_51.png](https://qiita-image-store.s3.amazonaws.com/0/94476/eb5f4b89-cb01-dd38-d2d1-80b23b7899ef.png)

ログインページが表示されたら、一旦ブラウザを閉じます。

# WordPress 設定ファイルの変更

このまま HTTPS でアクセスすると、CSS や JS が読み込まれない為、WordPress 設定ファイル（`wp-config.php`）に細工をします。

## コンテナにログイン

```bash
#Pod 名確認
$ kubectl get pod
NAME                        READY     STATUS    RESTARTS   AGE
kusanagi-XXXXXXXXXX-XXXXX   3/3       Running   0          29m

# Pod 内のコンテナ kusanagi-nginx へログイン
$ kubectl exec -it kusanagi-XXXXXXXXXX-XXXXX -c kusanagi-nginx /bin/bash
```

## WordPress 設定ファイル編集
`wp-config.php` をエディタで開きます。

```bash
# vi /home/kusanagi/kusanagi_html/DocumentRoot/wp-config.php
```

次の`追加（ここから）`～`追加（ここまで）`間の5行を、該当箇所に追加して保存します。
※実際にはマルチバイト（日本語）部分が文字化けしていますが、構わず追加しましょう。

```php:wp-config.php
･･･
#define('FORCE_SSL_ADMIN', true);
#define('WP_CACHE', true);

/* 追加（ここから） */
if($_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https') {
  $_SERVER['HTTPS'] = 'on';
  $_ENV['HTTPS'] = 'on';
}
define('FS_METHOD', 'direct');
/* 追加（ここまで） */

/* 編集が必要なのはここまでです ! WordPress でブログをお楽しみください。 */
･･･
```

# HTTPS 確認

ブラウザを開き、`https://kusanagi.wxample.com/wp-admin` へアクセスします。

`ユーザー名`と`パスワード`を入力して**`ログイン`**をクリックします。
![screencapture-kusanagi-kaho-enterprise-co-jp-wp-login-php-2018-09-21-13_39_09.png](https://qiita-image-store.s3.amazonaws.com/0/94476/26c9adc2-7021-91ef-6808-e6c35ee752fa.png)

ダッシュボードが表示され、KUSANAGI を利用できる状態になります。
![FireShot Capture 16 - ダッシュボード ‹ MySite — WordPress - http___kusanagi.kaho-enterprise.co.jp_wp-admin_.png](https://qiita-image-store.s3.amazonaws.com/0/94476/6121c220-16d4-2448-6254-71bfd35a8e48.png)

# Tips

## HSTS ※2018.09.27 追記
**Cloud CloadBalancer** は HTTP接続を HTTPS へリダイレクトする機能を持たないため、次のような方法でリダイレクトを実装する必要があります。

- Cloud LoadBalancer の代わりに Nginx Controller (Helm Chart) を利用する。（`ssl-redirect` パラメータ）
- KUSANAGI Nginx 側でリダイレクトする。

今回は比較的手軽な後者の方法を利用することにします。

### ヘルスチェック設定
Cloud LoadBalancer からのヘルスチェックに対し、HTTP ステータスコード 200 を返すよう設定します。
Pod 内の `kusanagi-nginx` コンテナへログインし、次のような `/etc/nginx/confd/default.conf` を作成します。

```nginx:/etc/nginx/conf.d/default.conf
server {
    listen 80 default_server;
    access_log off;
    error_log /var/log/nginx/error.log warn;

    location / {
        add_header Content-Type "text/plain";
        return 200 "ok";
    }
}
```

### リダイレクト設定
HTTP アクセスされた場合、HTTPS の URL へリダイレクトさせるよう、`/etc/nginx/conf.d/<KUSANAGI の PROFILE名>_http.conf` を編集します。

```nginx:/etc/nginx/conf.d/kusanagi_html_http.conf
...
        access_log ...;
        error_log ...;

        # TLS Redirect
        if ($http_x_forwarded_proto != https) {    #追加
            return 301 https://$host$request_uri;  #追加
        }                                          #追加
...
```

### Nginx リロード
`kill` コマンドで設定をリロードします。

```bash
# kill -HUP `cat /run/nginx.pid`
```