Service Catalogを使って、KubernetesからGoogle Cloud SQLを作成して使ってみた

Google Kubernetes Engineの中からGoogle Cloud SQLを作成し、サンプルAPIを動作させてみました
2018.12.29

はじめに

おはようございます、加藤です。Google Kubernetes Engine(以降、GKE)の中からGoogle Cloud SQL(以降、Cloud SQL)を作成し、サンプルAPIを動作させてみました。

前提

  • GCPのアカウントがセットアップされている
  • 検証用の新規GCPプロジェクトを作成している
  • 検証用のGKEクラスタを作成している
    • n1-standard-1 * 3 (リソース不足にならないようにこれぐらい確保しましょう)
  • gcloudが認証済み(ログイン済み)である
  • kubectlコマンドでgkeと通信ができる

環境

  • macOS Mojave 10.14.2(18C54
  • Homebrew 1.8.6
  • go version go1.11 darwin/amd64
    • 1.10以上
  • Google Cloud SDK 228.0.0
  • GKE
    • 1.11.5-gke.5
    • n1-standard-1(vCPU x 1、メモリ 3.75 GB)
    • サイズ 3
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"13", GitVersion:"v1.13.1", GitCommit:"eec55b9ba98609a46fee712359c7b5b365bdd920", GitTreeState:"clean", BuildDate:"2018-12-13T19:44:19Z", GoVersion:"go1.11.2", Compiler:"gc", Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"10+", GitVersion:"v1.10.9-gke.5", GitCommit:"d776b4deeb3655fa4b8f4e8e7e4651d00c5f4a98", GitTreeState:"clean", BuildDate:"2018-11-08T20:33:00Z", GoVersion:"go1.9.3b4", Compiler:"gc", Platform:"linux/amd64"}

cfssl, cfssljson のインストール

cloudflare/cfssl: CFSSL: Cloudflare's PKI and TLS toolkit
cfsslをインストールするには、Go 1.10以上が必要です。

# インストール
go get -u github.com/cloudflare/cfssl/cmd/...
# PATHが通っていることを確認する
which cfssl
which cfssljson

cluster-adminロール

k8sクラスタに対してcluster-adminのロールをgcloudが使用するアカウントに付与します。

# ロールの付与
kubectl create clusterrolebinding cluster-admin-binding --clusterrole=cluster-admin --user=$(gcloud config get-value account)

gcloud Beta Commands のインストールと認証

Beta CommandsのインストールとGoogle Cloud SDK の認証を行います。

gcloud components install beta
gcloud auth login
gcloud auth application-default login

Service Catalog(sc)のインストール

ローカル端末でscコマンドを実行できるようにします。

# インストール
go get -u github.com/GoogleCloudPlatform/k8s-service-catalog/installer/cmd/sc
# 確認
sc check | tail -n 1
# Dependency check passed. You are good to go.

k8sクラスタに対してscをインストールします。

# インストール
sc install
# 確認(全てがAVAILABLE)
kubectl get deployment -n service-catalog
#NAME                          DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
#apiserver                     1         1         1            1           1m
#controller-manager            1         1         1            1           1m
#etcd-cluster-backup-sidecar   1         1         1            1           1m
#etcd-operator                 1         1         1            1           1m

# Service Broker を Kubernetes Service Catalog に登録
# APIの有効化などを行います、時間がかかります
sc add-gcp-broker
# 確認
kubectl get clusterservicebrokers -o 'custom-columns=BROKER:.metadata.name,STATUS:.status.conditions[0].reason'
# BROKER       STATUS
# gcp-broker   FetchedCatalog

cloudservicesにオーナー役割(roles/owner)を付与して、IAMの発行を行えるようにします。

GCP_PROJECT_ID=$(gcloud config get-value project)
GCP_PROJECT_NUMBER=$(gcloud projects describe $GCP_PROJECT_ID --format='value(projectNumber)')
gcloud projects add-iam-policy-binding ${GCP_PROJECT_ID} \
    --member serviceAccount:${GCP_PROJECT_NUMBER}@cloudservices.gserviceaccount.com \
    --role=roles/owner

Service Catalog CLIのインストール

作業時の確認などでsvcatを使用するので、インストールします。

# インストール
brew update
brew install kubernetes-service-catalog-client
# 確認
svcat get classes

やってみた

サンプルAPIの構築

ネームスペースの作成

作業用ネームスペースの作成します。削除を簡単に行う為に作成しましょう。

kubectl create namespace cloud-mysql

Cloud SQL(MySQL)の作成

k8sのカスタムリソースServiceInstanceを使用して、Cloud SQLを作成します。
ServiceInstanceの役割はリソース作成であって情報の取得は別途行う必要があります。ここではDBを作成していますが、その接続情報は受け取りません。

cloud_sql.yaml

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: cloudsql-instance
  namespace: cloud-mysql
spec:
  clusterServiceClassExternalName: cloud-sql-mysql
  clusterServicePlanExternalName: beta
  parameters:
    instanceId: practice-dbinstance
    region: us-central1
    databaseVersion: MYSQL_5_7
    settings:
      tier: db-n1-standard-1
# Cloud SQL(MySQL)の作成
kubectl apply -f cloud_sql.yaml
# StatusがReadyになることを確認
# watchコマンドを使って見ると便利です
# watch -n 10 svcat describe instance --namespace cloud-mysql cloudsql-instance
svcat get instance --namespace cloud-mysql cloudsql-instance

Cloud SQLが作成されています。

Service Accountの作成

ServiceInstanceを使用して、Service Accountを作成します。
Cloud SQLの場合と同じく、情報の取得は別途行います。

service-account.yaml

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceInstance
metadata:
  name: service-account
  namespace: cloud-mysql
spec:
  clusterServiceClassExternalName: cloud-iam-service-account
  clusterServicePlanExternalName: beta
  parameters:
    accountId: cloud-sql-user-service-account
    displayName: "A service account for Cloud SQL sample"
kubectl apply -f service-account.yaml
# StatusがReadyになることを確認
svcat get instance --namespace cloud-mysql service-account

サービスアカウントが作成されています。

Service Accountの取得

k8sのカスタムリソースServiceBindingを使用して、Service Accountの情報を取得します。
取得した情報はSecretに格納されます。この時名前は省略する事が可能です、省略するとmetadata.nameで指定した名前が使用されます。
下記のYAMLはSecret: service-accountに、Service Accountの情報を格納します。

service-account-binding.yaml

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceBinding
metadata:
  name: service-account
  namespace: cloud-mysql
spec:
  instanceRef:
    name: service-account
kubectl apply -f service-account-binding.yaml
# StatusがReadyになることを確認
svcat get binding --namespace cloud-mysql service-account
# base64エンコードされたprivateKeyDataとserviceAccountが確認できます
# user-deployment.yamlで、privateKeyDataを利用します
kubectl get secret --namespace cloud-mysql service-account -oyaml

Secretの内容をbase64デコードして確認してみました。

privateKeyData

{
  "type": "service_account",
  "project_id": "k8s-practice-******",
  "private_key_id": "************************************",
  "private_key": "-----BEGIN PRIVATE KEY-----\***...***\n-----END PRIVATE KEY-----\n",
  "client_email": "cloudsql-user-service-account@k8s-practice-******.iam.gserviceaccount.com",
  "client_id": "******************",
  "auth_uri": "https://accounts.google.com/o/oauth2/auth",
  "token_uri": "https://oauth2.googleapis.com/token",
  "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
  "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/cloudsql-user-service-account%40k8s-practice-******.iam.gserviceaccount.com"
}
cloudsql-user-service-account@k8s-practice-******.iam.gserviceaccount.com

Cloud SQL 接続情報の取得

ServiceBindingを使用して、Cloud SQLの接続情報を取得します。
作成したCloud SQL(cloudsql-instance)の情報をcloudsql-user-service-accountの権限を使って取得しています。
Secretの名前はcloudsql-credentialsで定義しました。

cloud_sql-binding.yaml

# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: servicecatalog.k8s.io/v1beta1
kind: ServiceBinding
metadata:
  name: cloudsql-binding
  namespace: cloud-mysql
spec:
  instanceRef:
    name: cloudsql-instance
  secretName: cloudsql-credentials
  parameters:
    roles:
      - roles/cloudsql.client
    serviceAccount: cloud-sql-user-service-account
kubectl apply -f cloud_sql-binding.yaml
# StatusがReadyになることを確認
svcat get binding --namespace cloud-mysql cloudsql-binding
# base64エンコードされたconnectionNameとserviceAccountが確認できます
# user-deployment.yamlで、connectionNameを利用します
kubectl get secret --namespace cloud-mysql -o yaml cloudsql-credentials

同様にbase64デコードしてみました。

connectionName

k8s-practice-******:us-central1:my-service-broker-practice
cloudsql-user-service-account@k8s-practice-******.iam.gserviceaccount.com

ここでSecretcloudsql-credentialsに登録されている内容に認証情報が無いことを意識しておいてください。
実際に接続する際には、cloudsql-credentialsだけでは不十分です。

サンプルAPIの作成

Cloud SQLにデータを読み書きするサンプルAPIを作成して、正しくCloud SQLにアクセスできるか確認します。

Container: web は自身でCloud SQLへの書き込み権限を持たず、Container: cloudsql-proxy経由でアクセスします。
cloudsql-proxyには環境変数にconnectionNameが格納され、/secrets/cloudsqlにService Accountの情報がマウントされます。

user-deployment.yaml

# Copyright 2018 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: musicians
  namespace: cloud-mysql
  labels:
    app: musicians
spec:
  selector:
    matchLabels:
      app: musicians
  template:
    metadata:
      labels:
        app: musicians
    spec:
      containers:
        - name: web
          image: gcr.io/google-samples/service-catalog/cloud-sql-mysql-user:latest
          ports:
            - containerPort: 8080
          env:
            - name: DB_HOST
              value: 127.0.0.1:3306
            # These secrets are required to start the pod.
            - name: DB_USER
              value: root
            - name: DB_PASSWORD
              value: ""
            - name: DB_DBNAME
              value: musicians
        - name: cloudsql-proxy
          image: gcr.io/cloudsql-docker/gce-proxy:1.11
          env:
            - name: CONNECTION_NAME
              valueFrom:
                secretKeyRef:
                  name: cloudsql-credentials
                  key: connectionName
          command:
            [
              "/cloud_sql_proxy",
              "-instances=$(CONNECTION_NAME)=tcp:3306",
              "-credential_file=/secrets/cloudsql/privateKeyData",
            ]
          volumeMounts:
            - name: service-account
              mountPath: /secrets/cloudsql
              readOnly: true
      volumes:
        - name: service-account
          secret:
            secretName: service-account
        - name: cloudsql
          emptyDir:

---
apiVersion: v1
kind: Service
metadata:
  name: cloudsql-user-service
  namespace: cloud-mysql
  labels:
    app: musicians
spec:
  selector:
    app: musicians
  ports:
    - port: 80
      targetPort: 8080
  type: LoadBalancer

デプロイします。

kubectl apply -f user-deployment.yaml
# EXTERNAL-IPが表示されるのを待ちます
kubectl get service --namespace cloud-mysql
# EXTERNAL-IPを環境変数に格納します
IP=$(kubectl get service --namespace cloud-mysql cloudsql-user-service -o=jsonpath='{.status.loadBalancer.ingress[0].ip}')

動作をテストします。

# Create (or reset) musicians table:
curl -X POST http://${IP}/reset

# Query the musician information (will return empty result):
curl http://${IP}/musicians

# Create new musicians:
curl http://${IP}/musicians -d '{"name": "John", "instrument": "Guitar"}'
curl http://${IP}/musicians -d '{"name": "Paul", "instrument": "Bass Guitar"}'
curl http://${IP}/musicians -d '{"name": "Ringo", "instrument": "Drums"}'
curl http://${IP}/musicians -d '{"name": "George", "instrument": "Lead Guitar"}'

# Query the musicians again:
curl http://${IP}/musicians

ハマったこと

  • Cloud SQLのインスタンス名が削除しても最大1週間は使用できないという事を知らなかった
    • 試行錯誤しながらだったので詰まった...

あとがき

存在は知っていたものの、実際に触ったことの無かったService Catalogを使ってのパブリッククラウドへのリソース作成をやってみました。
自分が慣れていない事もあり、「あれ?この名前ってどこで定義した奴を入れればいいんだ?」と参考文献をさまよいながらの作業でした。
まだベータなので仕方ないと思いますが、気になったポイントは作成したマニフェストファイルは完了を待ってから適用していく必要がある事です。例えばCloudSQLが作成完了(Readyになる)前にServiceBinding使用とするとSTATUS: ErrorInstanceNotReadyとなりリトライが行われません。

参考