GKEのコンテナ内のディレクトリにGCSバケットをマウントする

# 概要

[Cloud Storage FUSE](https://cloud.google.com/storage/docs/gcs-fuse?hl=ja)を使用して、Google Kubernetes Engine(以下、GKE)で動いてるコンテナ内のディレクトリにGoogle Cloud Storage(以下、GCS)のバケットをマウントすることを目指します。

WordPressのuploadsディレクトリなど、コンテナ間で共有したいリソースがある時に便利です。

# GCP側の準備

## プロジェクトを作成

コンソール画面などからプロジェクトを作成します。
今回は説明のためexampleというプロジェクト名であると仮定しています。

## バケットの作成

Cloud SDKかGCPコンソール画面でバケットを作成します。

バケット名やプロジェクト名は他ユーザも含めて全プロジェクトでユニークなので、
名前が被った場合は違うバケット名を試してください。

# クラスタ作成

コマンドで作成を行います。
重要なのは`--scopes`オプションで、必ず`storage-rw`か`storage-full`を指定してください。
他のオプションはお好みで大丈夫です。

```
$ gcloud container clusters create example-cluster \
--machine-type f1-micro \
--scopes storage-full
```

# Cloud Storage FUSEのインストール

ベースイメージを元に、FUSEがインストールされたイメージを作っていきます。
基本的には[公式ドキュメントの手順](https://github.com/GoogleCloudPlatform/gcsfuse/blob/master/docs/installing.md)に従って準備をしていきます。

## ベースイメージ

今回は例として`php:5.6-apache`を使用します。
他のイメージを使用する場合は、コマンドが異なる場合がございますのでご注意ください。

## ベースイメージOSのバージョン名を確認

公式ドキュメントの通り、`lsb_release`コマンドを使用してOSバージョン名を確認します。

```
# docker run -it php:5.6-apache /bin/bash
# apt-get install -y lsb-release
# lsb_release -c -s
jessie
```

## Apacheのユーザ名を確認

Apacheのユーザ名(=FUSEを使用するユーザ名)を確認します。

```/etc/apache2/apache2.conf
 .
 .
 .
# These need to be set in /etc/apache2/envvars
User ${APACHE_RUN_USER}
Group ${APACHE_RUN_GROUP}
 .
 .
 .
```

`/etc/apache2/envvars`で定義されている環境変数が指定されているようですので、コメントに書いてあるファイルを確認します。

```/etc/apache2/envvars
 .
 .
 .
: ${APACHE_RUN_USER:=www-data}
export APACHE_RUN_USER
: ${APACHE_RUN_GROUP:=www-data}
export APACHE_RUN_GROUP
 .
 .
 .
```

Apacheのユーザは`www-data`であることがわかりました。

## Dockerfileを用意する

gcsfuseのパッケージURLを指定する際に、先程確認したOSバージョン名を使用します。
後ほどApacheユーザーでFUSEを使用するため、`sudo`もインストールしておきます。
私は`vim`をApacheの設定を確認するためにインストールしていますが、必須ではございません。

また、fuseグループを作成し、apacheのユーザをfuseグループに追加します。

末尾ではENTRYPOINTを指定しています。
ここで実行するスクリプトの詳細は後述します。

```Dockerfile
FROM php:5.6-apache
WORKDIR /path/to/DocumentRoot

# gcsfuse-[バージョン名]を指定する
RUN echo "deb http://packages.cloud.google.com/apt gcsfuse-jessie main" | tee /etc/apt/sources.list.d/gcsfuse.list
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
RUN groupadd fuse
RUN usermod -aG fuse www-data

RUN apt-get update -y && apt-get install -y \
    gcsfuse \
    sudo \
    vim

COPY entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod a+x /usr/local/bin/entrypoint.sh

EXPOSE 80 443
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
```

### ENTRYPOINTを指定

この例では、ドキュメントルートをGCSにマウントしていますが、他のディレクトリでももちろん構いません。
Apacheユーザとして、gcsfuseコマンドを実行しないと、ページにアクセスした際に、403エラーが出てしまう可能性があります。

```entrypoint.sh
#!/bin/bash
source /etc/apache2/envvars
chown -R www-data:www-data *
sudo -u www-data gcsfuse example-bucket ./
exec apache2 -D FOREGROUND
```

## イメージファイルのビルド・プッシュ

イメージをビルドし、GCPのContainer Registryにプッシュします。

```
docker build -f ./Dockerfile -t example_image:0.0.0 .
docker tag example_image:0.0.0 gcr.io/example/example_image:0.0.0
gcloud docker -- push gcr.io/example/example_image:0.0.0
```

# k8sの設定ファイルを用意

```example.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: example
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: example
        tier: frontend
        environment : production
    spec:
      containers:
        - image: gcr.io/example/example_image:0.0.0
          imagePullPolicy: Always
          name: example
          ports:
            - containerPort: 80
              name: example
          securityContext:
            privileged: true
---
apiVersion: v1
kind: Service
metadata:
  name: example
  labels:
    app: example
spec:
  selector:
    app: example
    tier: frontend
    environment : production
  type: LoadBalancer
  ports:
    - port: 80
      protocol: TCP
      targetPort: 80
```

## GKEにデプロイする

```
$ gcloud container clusters get-credentials example-cluster --project example
$ kubectl create -f example.yaml
```

## GKEクラスタに接続して外部IPを確認

```
$ kubectl get svc example
```

上のコマンドを実行することで表示される`EXTERNAL IP`を確認します。

# 確認

GCSに`index.html`をアップし、ブラウザから`http://[確認したEXTERNAL IP]/`にアクセスします。
アップした内容が表示されたら成功です。
なお、GKEにデプロイするのに少し時間がかかる(イメージのプル時間とエントリポイントの実行時間)ため、表示されない場合は少し時間を置いてからアクセスしましょう。