はじめに
GolangのDB接続は内部でGoroutineを使って勝手に簡単にコネクションプールを実現してくれるらしい。
なんと素晴らしい。早速試してみようではないか。
せっかくだから、Auroraと組み合わせてみよう。
なお、Auroraへのアクセス方法については、公式のユーザーガイドが参考になる。
また、接続にはIAM認証を用いる。AuroraへのIAM認証の設定方法は、この記事を参考にしていただきたい。
ファイル構成
今回は、WebサーバのフレームワークからAuroraに接続するサンプルとして、Ginを用いる。
以下のようなファイル構成とする。
.
├── Dockerfile
├── docker-compose.yml
└── src
├── db
│ ├── go.mod
│ └── main.go
├── go.mod
└── main.go
テーブル構成
Auroraのテーブルとユーザは、以下のように定義している前提とする。
CREATE USER app_user IDENTIFIED WITH AWSAuthenticationPlugin AS 'RDS';
GRANT SELECT ON COMPANY.* TO app_user;
CREATE TABLE EMPLOYEE (
id CHAR(5) PRIMARY KEY,
name CHAR(20) NOT NULL,
age INTEGER
);
また、データベース名はCOMPANY
で設定している前提として以降のコードを読んでいただきたい。
Golangのソースコード
mainモジュール
mainは以下のように定義する。
単に動かすだけならhealthCheck()
関数は不要だが、NLBに組み込んだりする際は必要なので定義しておく。
package main
import (
"errors"
"log"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/go-sql-driver/mysql"
"local.packages/db"
)
type employee struct {
ID string
Name string
Age int
}
func healthCheck() gin.HandlerFunc {
return func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{})
}
}
func employeeGet() gin.HandlerFunc {
return func(c *gin.Context) {
result, status, err := getProperty(c.Query("id"))
if err != nil {
log.Println(err)
}
if status == http.StatusOK {
c.JSON(status, result)
} else {
c.JSON(status, gin.H{})
}
}
}
func getProperty(id string) (employee, int, error) {
var (
e employee
)
db := db.DbConn()
err := db.QueryRow("select * from EMPLOYEE where id = ?", id).Scan(&e.ID, &e.Name, &e.Age)
if err != nil {
log.Println(err)
return employee{}, 500, errors.New("Query Error.")
}
return e, 200, nil
}
func initRouter() *gin.Engine {
router := gin.Default()
router.GET("/healthcheck", healthCheck())
router.GET("/employee", employeeGet())
return router
}
func main() {
_, err := db.DbInit()
if err != nil {
log.Println("db.DbInit() Error.")
panic(err)
}
router := initRouter()
router.Run(":8080")
}
module main
go 1.13
require (
github.com/aws/aws-sdk-go v1.40.12
github.com/gin-gonic/gin v1.7.2
github.com/go-sql-driver/mysql v1.6.0
local.packages/db v0.0.0-00010101000000-000000000000
)
replace local.packages/db => ./db
dbモジュール
dbモジュールは以下のように定義する
package db
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/rds/rdsutils"
)
var db *sql.DB
func DbInit() (*sql.DB, error) {
var (
err error
)
dbName := "COMPANY"
dbUser := "app_user"
dbHost := "Auroraの接続先DNS名"
dbPort := 3306
dbEndpoint := fmt.Sprintf("%s:%d", dbHost, dbPort)
region := "ap-northeast-1"
sess := session.Must(session.NewSession())
creds := sess.Config.Credentials
authToken, err := rdsutils.BuildAuthToken(dbEndpoint, region, dbUser, creds)
if err != nil {
log.Println("rdsutils.BuildAuthToken Error.")
panic(err)
}
dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s?tls=true&allowCleartextPasswords=true", dbUser, authToken, dbEndpoint, dbName)
db, err = sql.Open("mysql", dsn)
if err != nil {
log.Println("sql.Open Error.")
panic(err)
}
err = db.Ping()
if err != nil {
log.Println("db.Ping Error.")
panic(err)
}
db.SetMaxIdleConns(100)
db.SetMaxOpenConns(100)
db.SetConnMaxLifetime(0)
return db, err
}
func DbClose() {
if db != nil {
db.Close()
}
log.Println("DB Closed.")
}
func DbConn() *sql.DB {
return db
}
module db
go 1.14
require (
github.com/aws/aws-sdk-go v1.40.12
github.com/go-sql-driver/mysql v1.6.0
)
コネクションプールの設定
前述の通り、Golangでは勝手にコネクションプールを実装してくれているが、デフォルト値では心もとない。
パッケージのドキュメントに書かれている通り、SetMaxIdleConnsのデフォルトは2であるため、接続が不足しても2つまでしかコネクションプールしてくれず、都度接続しにいってしまう。
これを変更しておこう。
※実際、この辺の設定をするとしないとで、スループットが何倍も違った。
コンテナで動かす場合の設定
参考記事にも書いた通り、BuildAuthTokenで接続する場合はAWSの証明書が必要になるため、以下のようにビルド時にコンテナに込めるようにする。
コンテナを使わずにローカル起動する場合はもちろんローカルにwgetしておけば良い。
FROM golang:1.14-alpine3.12 as build
ENV GOPATH /go
RUN apk add --update --no-cache git
COPY ./src /go/src
WORKDIR /go/src
RUN GOARCH=amd64 GOOS=linux CGO_ENABLED=0 go build -o aurora-example-app .
FROM alpine:3.12
RUN mkdir /usr/local/share/ca-certificates
RUN wget https://truststore.pki.rds.amazonaws.com/ap-northeast-1/ap-northeast-1-bundle.pem -P /usr/local/share/ca-certificates
RUN apk add --no-cache ca-certificates && \
update-ca-certificates
RUN mkdir /app
WORKDIR /app
COPY --from=build /go/src/aurora-example-app /app/aurora-example-app
CMD ["/app/aurora-example-app"]
また、ローカル起動する場合は、環境変数でクレデンシャルの情報が必要になる。
docker-compose.ymlを以下のようにして起動しよう。
version: '3'
services:
aurora-example-app:
build: .
image: aurora-example-app
container_name: aurora-example-app
environment:
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
なお、ECS等のAWSサービス上で起動する際にクレデンシャルを設定するのは当然ながら悪手なので、参考記事に記載の通りIAMポリシーとMySQLユーザを紐づけてIAM認証をするようにしよう。
これで、Golangでセキュアかつ高速にAuroraにアクセスできるようになった!