GCP の Cloud 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 のインスタンス・テーブルは既に作成済みとして話を進めます。
この記事で使用するテーブルなどは以下のとおりです。
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));
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
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
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
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
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