Google Cloud Storage 静的ウェブサイトのホスティング 時、 index.html へリダイレクトされる

静的ウェブサイトのホスティング  |  Cloud Storage  |  Google Cloud

gcs を利用し、 websiteホスティングした時に、
スラッシュ無しのパスへアクセスしたとき、/index.html が存在する場合、/index.html が付加されたURLへリダイレクトしてしまう。
しかも、CNAME (正確には Host ヘッダー)に指定した Host でリダイレクトする。

Nginx などで Proxy した場合は、特に問題となる。

そのため、リダイレクトが発生した場合にRedirect を書き換える必要がある。
~ は正規表現を利用する宣言。
example.com を CNAME とした場合の設定。

    set $proto $scheme;
    if ($http_x_forwarded_proto = "https")  { set $proto "https"; } # ELB, GCLB
    if ($http_cloudfront_forwarded_proto = "https")  { set $proto "https"; } # CloudFront

    # ...
 
    resolver 8.8.8.8 valid=60s;

    proxy_pass http://c.storage.googleapis.com;
    proxy_redirect ~example.com\/(.*)\/index.html $proto://$http_host/$1/$is_args$args; # google cloud storage

    proxy_set_header Host example.com;

CloudFront の Origin に API Gateway を入れた場合に リダイレクトが発生する

API GatewayAPI を作った場合など、そのURLをCloudFrontのOriginに指定したい時がある。

その時に、Origin Protocol Policy を、HTTP Only にしていると、リダイレクトが発生する。HTTPS Onlyにする必要がある。

Vue のファイル出力先を変更する

Vue の デフォルトの出力フォルダは、dist フォルダになっているが、他のフレームワークと共に使うような時、出力先を変えたい場合がある。
さらに出力先を公開ディレクトリ(/public)のサブディレクトリ(/public/webview)にしたい場合の vue.config.js の設定方法。

/
├── frontend
│   ├── vue.config.js
│   ├── babel.config.js
│   ├── node_modules
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   └── src
└── public
    └── webview

$ vue.config.js

module.exports = {
  outputDir:'../public/webview', // ファイルの出力先ルート
  publicPath: './webview', // index.html などの出力されるファイルに書き込まれる、ルートとなるディレクトリ
  pages: { // 特に今回書く必要はない
    index: {
      entry: 'src/main.js',
      template: 'public/index.html',
      filename: 'index.html', // webview フォルダからの相対パス
    }
  }
}

GKE Kubernetes で 、無料で https を利用する

GCPでは、https にて通信するための、マネージドな証明書を発行する仕組みがあるため、とても簡単に利用できます。
GKEで利用するためには、 Ingress に annotations を追加するだけです。

まずマネージドな 証明書を作成
$ gcloud beta compute ssl-certificates create www-my-domain-com --domains www.my-domain.com
$ gcloud beta compute ssl-certificates create my-domain-com --domains my-domain.com
annotations に追加
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: basic-ingress
  annotations:
    kubernetes.io/ingress.global-static-ip-name: my-ip
    ingress.gcp.kubernetes.io/pre-shared-cert: "my-domain-com, www-my-domain-com"
spec:
  rules:
    - host: my-domain.com
      http:
        paths:
          - backend:
              serviceName: fuga-nodeport
              servicePort: 80
    - host: www.my-domain.com
      http:
        paths:
          - backend:
              serviceName: hoge-nodeport
              servicePort: 8001
  backend:
    serviceName: web-nodeport
    servicePort: 80

これでしばらくすれば、認証まで行われ、https でアクセスできるようになります。

GKE Kubernetes で Redis を利用する

GCP で フルマネージドのインメモリ データストア を使いたい場合、Memorystore for Redis を利用することになる。

Redis インスタンスに接続するためには、同じリージョンに配置され、同じネットワークを使用するGoogle Kubernetes Engine クラスタである必要がある。グローバルIPは無いということです。

そこで、Kubernetes の Cluster は、IP エイリアス を有効にして作成する必要があります。作成済みの場合は、削除して作り直しが必要です。

gcloud container clusters create myappname  --machine-type=g1-small --num-nodes=1 --enable-ip-alias

cloud.google.com

あとは Redis の IP アドレス に接続するだけです。

GKE Kubernetes で Ingress が 503 を返す

GKE を利用時に、GKE Ingress を利用した時に、どう考えても設定が正しいのに 503 が帰る場合があった。
注意しなければいけないポイントは、

  • ヘルスチェックのURLは200を返さなければいけない (当然BASIC認証もかかっていてはいけない)
  • ヘルスチェックのURLを変更するには、Deployment の readinessProbe を設定する必要がある
  • Ingress を再作成しなければ、設定変更が反映されない

ヘルスチェックのURLは200を返さなければいけない

Ingress は Service が有効かを判定するために、アクセスログを見ればわかるが、デフォルトだと / にアクセスしてくる。このとき200を返していなければならない。

ヘルスチェックのURLを変更するには、Deployment の readwinessProbe を設定する必要がある

わかりにくいが、Ingress が確認する readinessProbe は、Ingress が参照する Service の中で利用している、Deployment 側の readinessProbe が継承される。これがとてもわかりにくい。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: gcr.io/PROJECT_NAME/web:0.1
          ports:
            - containerPort: 80
          readinessProbe:
            httpGet:
              path: /healthcheck
              port: 80
            initialDelaySeconds: 15
            periodSeconds: 5

---
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
    - port: 80
      targetPort: 80

---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: ingress-name
  annotations:
    kubernetes.io/ingress.global-static-ip-name: my-ip
spec:
  backend:
    serviceName: web-nodeport
    servicePort: 80

Ingress を再作成しなければ、設定変更が反映されない

途中で Deployment の設定を変更しても / にずっとアクセスが来ていたが、ingress を作り直すことで反映された。
これは公開後に変更した場合はかなり困ることになるはず。

$ kubectl delete ing ingress-name
$ kubectl apply -f sample.yml

GKE Kubernetes で CloudSQL を利用する

Cloud SQL を利用するためには、直接接続するのではなく、 Cloud SQL Proxy を利用します。
Cloud SQL Proxy は Docker image として公開されているため、こちらを使います。


Googleのドキュメント通りに設定すれば通信出来ます。
Pod 内に Proxy の Container を含める構成になっており、この構成をサイドカーパターンと呼びます。

cloud.google.com

サンプル通りのやり方

ポイントは1つだけ、CloudSQL Client の役割をつけたサービスアカウントを作成し、cloudsql-instance-credentials などの名前で json を保存します。

kubectl create secret generic cloudsql-instance-credentials --from-file credentials.json=./secret/projectname-1234567890.json

簡単にするため、ユーザー名パスワードは埋め込みました。
Pod内のContainer同士は、127.0.0.1 でアクセスできます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: app-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: app
  template:
    metadata:
      labels:
        app: app
    spec:
      containers:
        - name: app-backend
          image: gcr.io/project_desu/app:0.9
          ports:
            - containerPort: 3000
          env:
            - name: PORT
              value: '3000'
            - name: DATABASE_HOST
              value: 127.0.0.1
            - name: DATABASE_DATABASE
              value: hoge
            - name: DATABASE_USER_NAME
              value: name_desu
            - name: DATABASE_PASSWORD
              value: password_desu
        - image: gcr.io/cloudsql-docker/gce-proxy:1.12
          name: cloudsql-proxy
          ports:
            - containerPort: 3306
          command: ["/cloud_sql_proxy", "--dir=/cloudsql",
                    "-instances=PROJECT_NAME:asia-northeast1:INSTANCE_NAME=tcp::3306",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
            - name: ssl-certs
              mountPath: /etc/ssl/certs
            - name: cloudsql
              mountPath: /cloudsql
      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
        - name: cloudsql
          emptyDir:

Service を使い Pod を分ける方法

アプリケーションの数だけ Container が増えることになるため、リソースが限られている時など上記の方法を避けたい場合があります。Serviceを利用しアクセスするためには下記のように設定します。

TCPにバインドするIPアドレス 0.0.0.0 を指定することがポイントです。
これによって、Service経由のホスト名db-cluster-ip で Podにアクセス出来ます。

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: db-deploy
spec:
  replicas: 1
  selector:
    matchLabels:
      app: db
  template:
    metadata:
      labels:
        app: db
    spec:
      containers:
        - image: gcr.io/cloudsql-docker/gce-proxy:1.12
          name: cloudsql-proxy
          ports:
            - containerPort: 3306
          command: ["/cloud_sql_proxy", "--dir=/cloudsql",
                    "-instances=PROJECT_NAME:asia-northeast1:INSTANCE_NAME=tcp:0.0.0.0:3306",
                    "-credential_file=/secrets/cloudsql/credentials.json"]
          volumeMounts:
            - name: cloudsql-instance-credentials
              mountPath: /secrets/cloudsql
              readOnly: true
            - name: ssl-certs
              mountPath: /etc/ssl/certs
            - name: cloudsql
              mountPath: /cloudsql

      volumes:
        - name: cloudsql-instance-credentials
          secret:
            secretName: cloudsql-instance-credentials
        - name: ssl-certs
          hostPath:
            path: /etc/ssl/certs
        - name: cloudsql
          emptyDir:
apiVersion: v1
kind: Service
metadata:
  name: db-cluster-ip
spec:
  type: ClusterIP
  ports:
    - port: 3306
      targetPort: 3306
  selector:
    app: db