はじめに
Glolang WebアプリケーションGinを用いたサーバーレスアプリケーションをgcloud CLI, Terraformそれぞれを用いてCloud Functionsにデプロイします
今回用いたコードはGitHubで公開しています。
環境
Golang: 1.20.4
Terraform v1.6.6
ディレクトリ構成
.
├── gin
│ ├── cmd
│ │ └── main.go
│ ├── function-source.zip
│ ├── gin_http.go
│ ├── go.mod
│ └── go.sum
└── infra
├── main.tf
└── terraform.tfvars
Gin
Gin Tutorial
今回はGin Tutorialで作れるRESTful APIをデプロイする。
このTutorialを終えた後のgin_http.goは以下の通り。
package http
import (
"net/http"
"github.com/gin-gonic/gin"
)
// album represents data about a record album.
type album struct {
ID string `json:"id"`
Title string `json:"title"`
Artist string `json:"artist"`
Price float64 `json:"price"`
}
// albums slice to seed record album data.
var albums = []album{
{ID: "1", Title: "Blue Train", Artist: "John Coltrane", Price: 56.99},
{ID: "2", Title: "Jeru", Artist: "Gerry Mulligan", Price: 17.99},
{ID: "3", Title: "Sarah Vaughan and Clifford Brown", Artist: "Sarah Vaughan", Price: 39.99},
}
func main() {
router := gin.Default()
router.GET("/albums", getAlbums)
router.GET("/albums/:id", getAlbumByID)
router.POST("/albums", postAlbums)
router.Run("localhost:8080")
}
// getAlbums responds with the list of all albums as JSON.
func getAlbums(c *gin.Context) {
c.IndentedJSON(http.StatusOK, albums)
}
// postAlbums adds an album from JSON received in the request body.
func postAlbums(c *gin.Context) {
var newAlbum album
if err := c.BindJSON(&newAlbum); err != nil {
return
}
albums = append(albums, newAlbum)
c.IndentedJSON(http.StatusCreated, newAlbum)
}
// getAlbumByID locates the album whose ID value matches the id
func getAlbumByID(c *gin.Context) {
id := c.Param("id")
for _, a := range albums {
if a.ID == id {
c.IndentedJSON(http.StatusOK, a)
return
}
}
c.IndentedJSON(http.StatusNotFound, gin.H{"message": "album not found"})
}
Cloud Functionsにデプロイするために書き換える
-
"github.com/GoogleCloudPlatform/functions-framework-go/functions"をimport
import ( "net/http" "github.com/GoogleCloudPlatform/functions-framework-go/functions" "github.com/gin-gonic/gin" )
-
func mainをfunc ginHTTPに書き換え、以下のように変更する
func ginHTTP(w http.ResponseWriter, r *http.Request) { router := gin.Default() router.GET("/albums", getAlbums) router.GET("/albums/:id", getAlbumByID) router.POST("/albums", postAlbums) router.ServeHTTP(w, r) }
-
init関数を用意する
func init() { functions.HTTP("ginHTTP", ginHTTP) }
-
go.modファイルの作成
以下のコマンドを実行する./gin$ go mod init example.com/gin $ go mod tidy
-
cmd/main.goを作成
cmdディレクトリを作成し、その中に以下のmain.goを作成する./gin/cmd/main.gopackage main import ( "log" _ "example.com/gin" "github.com/GoogleCloudPlatform/functions-framework-go/funcframework" ) func main() { port := "8080" if err := funcframework.Start(port); err != nil { log.Fatalf("funcframework.Start: %v\n", err) } }
-
ローカルでテスト
以下のコマンドでローカルサーバーが立ち上がることが確認できる./gin$ export FUNCTION_TARGET=ginHTTP $ go run cmd/main.go $ curl "http://localhost:8080/albums" [ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ]
CLIを用いてGCPにデプロイ
前準備
Create and deploy an HTTP Cloud Function with Goにある、Before you begin章に従ってgcloud cliの設定を行う
デプロイ
$ gcloud functions deploy go-http-function \
--gen2 \
--runtime=go121 \
--region=<前準備で設定したregion> \
--source=. \
--entry-point=ginHTTP \
--trigger-http \
--allow-unauthenticated
デプロイ後に表示されるurlを用いてアクセスすると、Gin Tutorialと同じレスポンスが返ってくる
$ curl https://<region>.***.cloudfunctions.net/go-http-function/albums
[
{
"id": "1",
"title": "Blue Train",
"artist": "John Coltrane",
"price": 56.99
},
{
"id": "2",
"title": "Jeru",
"artist": "Gerry Mulligan",
"price": 17.99
},
{
"id": "3",
"title": "Sarah Vaughan and Clifford Brown",
"artist": "Sarah Vaughan",
"price": 39.99
}
]
Terraformを用いてGCPにデプロイ
前準備
-
Get Started - Google CloudInstall Terraformまでの章に従ってterafformをinstallする。
-
Terraform Tutorial (2nd gen)のBefore you beginに従って設定を行う。
-
以下のコマンドを用いてgolangコードのzipファイルを生成する。
./ginzip -r function-source.zip .
terraform
main.tfの最初に、GCPで用いるための一般部分を記述する。
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "4.51.0"
}
}
}
variable "CREDENTIALS" {
type = string
}
variable "PROJECT" {
type = string
}
provider "google" {
credentials = file(var.CREDENTIALS)
project = var.PROJECT
region = "<YOUR REGION>"
zone = "<YOUR ZONE>"
}
resource "random_id" "default" {
byte_length = 8
}
resource "google_storage_bucket" "default" {
name = "${random_id.default.hex}-gcf-source"
location = "ASIA"
uniform_bucket_level_access = true
}
上においてCREDENTIALSとPROJECTは環境変数を用いているが、これはterraform.tfvarsファイルを作って以下のように記述し、main.tfでvariableを用いることで使用できる。
CREDENTIALS="<YOUR CREDENTIALS>"
PROJECT="<YOUR PROJECT>"
続いて、この下にCloud Functionsに関する部分を記述する。
data "archive_file" "default" {
type = "zip"
output_path = "../gin/function-source.zip"
source_dir = "../gin"
}
resource "google_storage_bucket_object" "object" {
name = "function-source.zip"
bucket = google_storage_bucket.default.name
source = data.archive_file.default.output_path
}
resource "google_cloudfunctions2_function" "default" {
name = "gin-cloud-functions"
location = "<YOUR REGION>"
description = "Gin server"
build_config {
runtime = "go121"
entry_point = "ginHTTP"
source {
storage_source {
bucket = google_storage_bucket.default.name
object = google_storage_bucket_object.object.name
}
}
}
service_config {
max_instance_count = 1
available_memory = "256M"
timeout_seconds = 60
}
}
resource "google_cloud_run_service_iam_member" "member" {
location = google_cloudfunctions2_function.default.location
service = google_cloudfunctions2_function.default.name
role = "roles/run.invoker"
member = "allUsers"
}
output "function_uri" {
value = google_cloudfunctions2_function.default.service_config[0].uri
}
デプロイ
-
terraformの初期化
./infraterraform init
-
デプロイ
./infraterraform apply
デプロイ後にAPI endpointが表示されます
Apply complete! Resources: 2 added, 0 changed, 0 destroyed. Outputs: function_uri = "https://gin-cloud-functions-***.a.run.app"
実際にAPIを叩くと、Gin Tutorialと同じレスポンスが返ってきます
任意の場所$ curl "https://gin-cloud-functions-***.a.run.app/albums"
[ { "id": "1", "title": "Blue Train", "artist": "John Coltrane", "price": 56.99 }, { "id": "2", "title": "Jeru", "artist": "Gerry Mulligan", "price": 17.99 }, { "id": "3", "title": "Sarah Vaughan and Clifford Brown", "artist": "Sarah Vaughan", "price": 39.99 } ]