11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Cloud SQL Proxy を Golang のパッケージとして使用する

Posted at

GCPCloud SQL へアプリケーションから接続する場合、 アプリケーションを配置しているホストのIPアドレスをホワイトリストに追加するか、 Cloud SQL Proxy を使用するかと思います。

しかし、アプリケーションが配置されるホストのIPアドレスが不定(GKE などを使っており、配置されるホストが不定など)の場合は、 Cloud SQL Proxy を使うことになると思います。
もし、そのアプリケーションを Golang で書いている場合は、 Cloud SQL Proxy の Github の README に

If your program is written in Go you can use the Cloud SQL Proxy as a library, avoiding the need to start the Proxy as a companion process.

と書かれている通り、 Cloud SQL Proxy を Golang のパッケージとして使用できます。
そのため、別途 Cloud SQL Proxy を用意する必要なく、アプリケーションから直接データベースへ接続ができるようになります。

それでは、実際にやってみましょう。
なお、 Cloud SQL のインスタンス・テーブルは既に作成済みとして話を進めます。
この記事で使用するテーブルなどは以下のとおりです。

MySQL
CREATE DATABASE mydb;
USE mydb;
CREATE TABLE guestbook (guestName VARCHAR(255) NOT NULL, content VARCHAR(255) NOT NULL, date DATETIME, entryID INT NOT NULL AUTO_INCREMENT, PRIMARY KEY(entryID));
PostgreSQL
CREATE TABLE guestbook (guestName VARCHAR(255) NOT NULL, content VARCHAR(255) NOT NULL, date TIMESTAMP NOT NULL, entryID SERIAL PRIMARY KEY);
挿入レコード
INSERT INTO guestbook (guestName, content, date) values ('first guest', 'I got here!', '2017-08-06 12:00:00');
INSERT INTO guestbook (guestName, content, date) values ('second guest', 'Me too!', '2017-08-06 13:00:00');

パッケージ取得

Cloud SQL Proxy のパッケージを取得します。

$ go get github.com/GoogleCloudPlatform/cloudsql-proxy

次にデータベースのドライバパッケージを取得します。
インスタンスを MySQL にしている場合は

$ go get github.com/go-sql-driver/mysql

PostgreSQL にしている場合は

$ go get github.com/lib/pq

を取得します。

サービスアカウント作成

Cloud SQL Proxy を使用する場合は、適切な権限が付与されたサービスアカウントの作成が必要です。
ここでは Cloud SQL Proxy 用のサービスアカウントを作成して使用します。
IAMページに移動し、サービスアカウントを作成して秘密鍵をダウンロードしてください。
この秘密鍵はアプリケーション実行時に必要になります。

サービスアカウントに必要な権限は以下のとおりです。

  • Cloud SQL クライアント
  • Cloud SQL 編集者

ソースコード

MySQL

main.go
package main

import (
	"context"
	"fmt"
	"log"
	"time"

	cloudsqlproxy "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql"
	"github.com/go-sql-driver/mysql"
)

func main() {
	ctx := context.Background()

	if err := showRecords(ctx, "project:region:instance-name", "mydb", "user", "user_password"); err != nil {
		log.Fatal(err)
	}
}

func showRecords(ctx context.Context, dbAddress, dbName, dbUser, dbPassword string) error {
	// 接続するだけなら以下のコードになります。
	// db, err := cloudsqlproxy.DialPassword(dbAddress, dbUser, dbPassword)

	// 詳細に設定したい場合は以下のコードになります。
	db, err := cloudsqlproxy.DialCfg(&mysql.Config{
		Addr:      dbAddress,  // インスタンス接続名
		DBName:    dbName,     // データベース名
		User:      dbUser,     // ユーザ名
		Passwd:    dbPassword, // ユーザパスワード
		Net:       "cloudsql", // Cloud SQL Proxy で接続する場合は cloudsql 固定です
		ParseTime: true,       // DATE/DATETIME 型を time.Time へパースする
		TLSConfig: "",         // TLSConfig は空文字を設定しなければなりません
	})
	if err != nil {
		return err
	}
	defer db.Close()

	rows, err := db.QueryContext(ctx, "SELECT * FROM guestbook")
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var guestName, content string
		var date time.Time
		var entryID int64
		if err := rows.Scan(&guestName, &content, &date, &entryID); err != nil {
			return err
		}
		fmt.Printf("%s\t%s\t%s\t%d\n", guestName, content, date.Format(time.RFC3339), entryID)
	}

	return nil
}

PostgreSQL

main.go
package main

import (
	"context"
	"database/sql"
	"fmt"
	"log"
	"time"

	_ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres"
)

func main() {
	ctx := context.Background()

	if err := showRecords(ctx, "project:region:instance-name", "postgres", "user", "user_password"); err != nil {
		log.Fatal(err)
	}
}

func showRecords(ctx context.Context, dbAddress, dbName, dbUser, dbPassword string) error {
	// sslmode は必ず disable にする必要があります。
	dsn := fmt.Sprintf("host=%s user=%s dbname=%s password=%s sslmode=disable", dbAddress, dbUser, dbName, dbPassword)
	db, err := sql.Open("cloudsqlpostgres", dsn)
	if err != nil {
		return err
	}
	defer db.Close()

	rows, err := db.QueryContext(ctx, "SELECT * FROM guestbook")
	if err != nil {
		return err
	}
	defer rows.Close()

	for rows.Next() {
		var guestName, content string
		var date time.Time
		var entryID int64
		if err := rows.Scan(&guestName, &content, &date, &entryID); err != nil {
			return err
		}
		fmt.Printf("%s\t%s\t%s\t%d\n", guestName, content, date.Format(time.RFC3339), entryID)
	}

	return nil
}

ビルド・実行

GOOGLE_APPLICATION_CREDENTIALS環境変数には、作成したサービスアカウントの秘密鍵へのパスをセットしてください。
ここでセットした秘密鍵が、 Cloud SQL Proxy パッケージ内で認証情報として使用されます。

$ go build -o example-cloudsqlproxypackage main.go
$ GOOGLE_APPLICATION_CREDENTIALS=PATH_TO_CREDENTIAL_FILE ./example-cloudsqlproxypackage
first guest     I got here!     2017-08-06T12:00:00Z    1
second guest    Me too! 2017-08-06T13:00:00Z    2

サービスアカウント秘密鍵の読み込みについて

Cloud SQL Proxy で使用されるサービスアカウントの秘密鍵は、デフォルトではGOOGLE_APPLICATION_CREDENTIALS環境変数にセットされているファイルを使用します。
もし、アプリケーション内で独自に読み込みをしたい場合は、以下のように処理を追加します。

パッケージ追加
$ go get golang.org/x/oauth2

MySQL

main.go
package main

import (
	"context"
	"fmt"
+	"io/ioutil"
	"log"
+	"net/http"
	"time"

	cloudsqlproxy "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/mysql"
+	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
	"github.com/go-sql-driver/mysql"
+	goauth "golang.org/x/oauth2/google"
)

func main() {
	ctx := context.Background()

+	// 秘密鍵を読み込んで、 Proxy で使用する Client をセット。
+	client, err := clientFromCredentials(ctx, "path_to_credential_file")
+	if err != nil {
+		log.Fatal(err)
+	}
+	proxy.Init(client, nil, nil)

	if err := showRecords(ctx, "project:region:instance-name", "mydb", "root", "password"); err != nil {
		log.Fatal(err)
	}
}

func showRecords(ctx context.Context, dbAddress, dbName, dbUser, dbPassword string) error {
	// 省略
	return nil
}

+// 参考: https://github.com/GoogleCloudPlatform/cloudsql-proxy/blob/master/tests/dialers_test.go#L89
+// 秘密鍵より Proxy で使用する Client を作成。
+func clientFromCredentials(ctx context.Context, file string) (*http.Client, error) {
+	const SQLScope = "https://www.googleapis.com/auth/sqlservice.admin"
+	var client *http.Client
+
+	all, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, err
+	}
+
+	cfg, err := goauth.JWTConfigFromJSON(all, SQLScope)
+	if err != nil {
+		return nil, err
+	}
+
+	client = cfg.Client(ctx)
+
+	return client, nil
+}

PostgreSQL

main.go
package main

import (
	"context"
	"database/sql"
	"fmt"
+	"io/ioutil"
	"log"
+	"net/http"
	"time"

	_ "github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/dialers/postgres"
+	"github.com/GoogleCloudPlatform/cloudsql-proxy/proxy/proxy"
+	goauth "golang.org/x/oauth2/google"
)

func main() {
	ctx := context.Background()

+	// 秘密鍵を読み込んで、 Proxy で使用する Client をセット。
+	client, err := clientFromCredentials(ctx, "path_to_credential_file")
+	if err != nil {
+		log.Fatal(err)
+	}
+	proxy.Init(client, nil, nil)

	if err := showRecords(ctx, "project:region:instance-name", "postgres", "user", "user_password"); err != nil {
		log.Fatal(err)
	}
}

func showRecords(ctx context.Context, dbAddress, dbName, dbUser, dbPassword string) error {
	// 省略
	return nil
}

+// 参考: https://github.com/GoogleCloudPlatform/cloudsql-proxy/blob/master/tests/dialers_test.go#L89
+// 秘密鍵より Proxy で使用する Client を作成。
+func clientFromCredentials(ctx context.Context, file string) (*http.Client, error) {
+	const SQLScope = "https://www.googleapis.com/auth/sqlservice.admin"
+	var client *http.Client
+
+	all, err := ioutil.ReadFile(file)
+	if err != nil {
+		return nil, err
+	}
+
+	cfg, err := goauth.JWTConfigFromJSON(all, SQLScope)
+	if err != nil {
+		return nil, err
+	}
+
+	client = cfg.Client(ctx)
+
+	return client, nil
+}

サンプルコードのリポジトリ

最終的なサンプルコードはこちらのリポジトリに上げています。
hirsim/example-cloudsqlproxypackage

参考

11
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?