からCloudSQLに接続してみた(CloudSQL Proxy - PublicIP - ServiceAccount)


最近、業務の関係でGKEを触る機会が増え、その中でCloudSQLにGKEから接続する必要があったため、その際の接続メモを個人的に残しておきます。
※まだまだ勉強し始め、かつ個人的な検証メモなのでこれが正しいわけではありません。

基本的には下記公式から追って行きましたが、色々不親切だったりはするので補足程度になれば幸いです。

公式:https://cloud.google.com/sql/docs/mysql/connect-kubernetes-engine?hl=ja#proxy-with-service-account-key

構成

今回は、GKEのクラスタからCloudSQLに接続する際は下記構成になります。

・CloudSQL:Public IPでの接続
 Private接続が業務上使えないので、まずはPublic IPでの接続を試しています。

・GKEからの接続:CloudSQL ProxyをSideCar形式で使用する方式で接続
 直接でも何とかすればいけるかもですが、公式おすすめの方法を採用。
 ※すごく取っ付きが難しかったですが、慣れると確かに楽ですし、セキュリティ向上になるなら良いかなと。

・プロキシへのサービスアカウントの提供方法:サービスアカウントキーファイルによる連携
 Workload Identifyを使用する方法もあるようですが、共有は必要なく個別で良い。
 またおそらく権限上使えない気がしたので、この方法を採用。

クラスタの作成

とりあえずGKEのクラスタがないと始まらないのでクラスタを作成。
検証なので少しでも安くしたいので下記構成でクラスタを作成。

  • ノード数:2
  • マシンシリーズ:E2
  • マシンタイプ:e2-micro
  • ディスク:20GB
  • プリエンプティブルノードを有効 (1日しか持たないVM)

あとは適当に作成(どうせ検証終わったら消しますし)

CloudSQLの作成

同じくとりあえずCloudSQLがないと始まらないのでCloudSQLを作成。
同じく検証なので少しでも安くしたいので下記構成で作成

  • 種類:MySQL 8.0
  • 接続:Public IPを有効
  • マシンタイプ:db-f1-micro
  • ストレージ:10GB
  • バックアップ:なし

そして、DBとユーザー、パスワードは適当に使いたいものを作っておきましょう。

IAMのサービスアカウントの作成

今回、サービスアカウントを使用して認証を通しますので、事前にサービスアカウントを作成しておきます。

Google Cloudコンソールに接続し、IAMと管理 -> サービスアカウントを開きます。

画面上部にある「+サービスアカウントを作成」をクリック
必要な情報を入力しサービスアカウントを作成します。
 
 ①は任意で大丈夫です。
 ②はCloud SQL クライアント、Cloud SQL 編集者、Cloud SQL 管理者の中で必要な権限を割り振ってください
 ③は今回は指定はしていません。

Cloud SQL Admin APIの有効化

CloudSQL Proxyから接続する際はAPIの有効化が必要になります。
下記のページ等から事前に有効化しておいてください。

Cloud Shellにログイン

この後の作業は基本的にCloud Shellで行なえます。
Cloud Shellはユーザーに無料で割り当てられており、中にはgit,docker,docker-compose,kubectl,minikubeなど様々な作業に必要なコマンドが事前に用意されています。
ディスク容量が5GBという制約はありますが、ちょっとした検証やbuildする環境であれば、ほぼGCP内の操作はここで十分かなと個人的に思っています。

ログインはGoogle Cloudコンソールの検索の横にある|>_|というアイコンから実行できます。

GKEのテスト用イメージの作成

とりあえずmysqlコマンドでCloudSQL Proxy経由でCloudSQLのMySQLにログインしたいので、
CentOS8のmysqlコマンドが使えるimageを事前に用意しておきます。

ログインしたCloudShellに適当にディレクトリでも作ってDockerfileを用意します。
とりあえず日本語環境にしてmariadb入れてmysqlコマンド使えるようにしているだけです。
ここは環境に合わせて用意されたらいいと思います。

Dockerfile
FROM centos:8

RUN dnf -y update && dnf clean all

ENV TZ='Asia/Tokyo'
RUN dnf -y install langpacks-ja && \
    dnf clean all && \
    cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime

ENV LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"

RUN dnf -y install mariadb && \
    dnf clean all
CMD ["/sbin/init"]

次にDockerfileからイメージを作成します。
今回はGCPのContainer Resistryに後ほどpushしたいので、それ用のタグをつけてbuildしています。

先程のDockerfileがあるディレクトリで下記コマンドを実行。
PROJECT ID部分は自身のプロジェクトのIDに変換してください。
分からなければ、CloudShell上で"gcloud projects list"と打てばわかると思います。

$ docker build -t gcr.io/<PROJECT ID>/<任意のイメージ名>:<任意のタグ> .

Container Resistoryへのpush

Container Resistoryに作成したイメージをpushします。
CloudShell上で下記のようなコマンドでpushできます。

$ gcloud docker -- push gcr.io/<PROJCET ID>/<任意のイメージ名>:<任意のタグ>

実行後、GCP CloudコンソールのContainer Resistoryにイメージがあることを確認すればOKです。

DBの認証情報をGKEのSecretに登録する

ついに本題に入ってきました。
各コンテナに認証情報を連携するためにSecretという機能を使用します。

CloudShell上で下記のようなコマンドでSecretを登録します。

$ kubectl create secret generic <任意のSecret名> \
  --from-literal=username=<CloudSQLで作成したユーザ名> \
  --from-literal=password=<CloudSQLで作成したパスワード> \
  --from-literal=database=<CloudSQLで作成したデータベース名>

コマンドが成功したらGoogle CloudコンソールのKubernetes Engine -> 構成を開き、
任意でつけた名前のSecretが作成されていればOKです。

サービスアカウントキーの作成とSecret登録

次にサービスアカウントの情報も連携する必要があるので、それをSecretに登録します。
ここでは一旦keyファイルを作成した後、それをSecretに登録しています。

下記のようなコマンドでkeyファイルを作成します。

$ gcloud iam service-accounts keys create <任意のキー名>.json \
  --iam-account <サービスアカウントのメール>

<サービスアカウントのメール>は作成したサービスアカウントをIAM管理ページで見た際に、
メール列にある情報です。

そして、keyファイルを作成できたら、そのkeyファイルをsecretに変換します。
下記のようなコマンドで変換します。

kubectl create secret generic <任意のSecret名> \
--from-file=service_account.json=key.json

GKEのPODの作成

これで下準備は大体終わったので、事前にContainer Resistoryにpushしたイメージを使用して、
CloudSQLに接続できるかを試します。

適当な作業フォルダで下記のようなyamlファイルを作成します。

kubernetes.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: <任意の名前>
spec:
  selector:
    matchLabels:
      app: <任意の名前>
  template:
    metadata:
      labels:
        app: <任意の名前>
    spec:
      containers:
      - name: <任意のコンテナ名>
        image: <事前にpushしたContainer Resistoryのイメージ名>
        env:
        - name: DB_USER
          valueFrom:
            secretKeyRef:
              name: <DB用のSecret名>
              key: username
        - name: DB_PASS
          valueFrom:
            secretKeyRef:
              name: <DB用のSecret名>
              key: password
        - name: DB_NAME
          valueFrom:
            secretKeyRef:
              name: <DB用のSecret名>
              key: database
      - name: cloud-sql-proxy
        image: gcr.io/cloudsql-docker/gce-proxy:1.17
        command:
          - "/cloud_sql_proxy"
          - "-instances=<CloudSQLのインスタンス接続名>=tcp:3306"
          - "-credential_file=/secrets/service_account.json"
        securityContext:
          runAsNonRoot: true
        volumeMounts:
        - name: <サービスアカウントのSecret名>
          mountPath: /secrets/
          readOnly: true
      volumes:
      - name: <サービスアカウントのSecret名>
        secret:
          secretName: <サービスアカウントのSecret名>

yamlファイルを作成できたら、そのファイルを使用してGKEにPODを作成します。

$ kubectl apply -f kubernetes.yaml

少し待ってPODがErrorなくRunningになっていればOKです。

$ kubectl get pods
NAME      READY   STATUS    RESTARTS   AGE
<POD名>   2/2     Running   0          86m

もし上手く起動しない場合等はlogを表示させて見て確認してみてください。
※私はCloud SQL Admin APIを有効化してなかったため、エラーになっていました。

$ kubectl logs <POD名> -c cloud-sql-proxy

PODにログインしCloudSQLに接続できるかを確認する

正常にここまで立ち上がっていれば、Cloud SQL ProxyがSideCar形式で立ち上がっているはずなので、
PODにログインすれば127.0.0.1:3306へのmysqlアクセスでCloudSQLにアクセスできるはずです。

CloudShellからPODにログインします。

$ kubectl exec -it <POD名> -- /bin/bash

ログイン後、DBのSecretに登録した環境変数を使用してCloudSQLにアクセスします。

$ mysql -h 127.0.0.1 -P 3306 -u$DB_USER -p$DB_PASS $DB_NAME

エラーなくmysqlのプロンプトが表示され、作成したCloudSQLの情報が見えればOKだと思います。