1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Go 言語Advent Calendar 2023

Day 1

Go API(Gin)をCloud Functionsにデプロイする方法(CLI, Terraform)

Posted at

はじめに

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は以下の通り。
./gin/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にデプロイするために書き換える

  1. "github.com/GoogleCloudPlatform/functions-framework-go/functions"をimport

    import (
    	"net/http"
    
    	"github.com/GoogleCloudPlatform/functions-framework-go/functions"
    	"github.com/gin-gonic/gin"
    )
    
  2. 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)
    }
    
  3. init関数を用意する

    func init() {
    	functions.HTTP("ginHTTP", ginHTTP)
    }
    
  4. go.modファイルの作成
    以下のコマンドを実行する

    ./gin
    $ go mod init example.com/gin
    $ go mod tidy
    
  5. cmd/main.goを作成
    cmdディレクトリを作成し、その中に以下のmain.goを作成する

    ./gin/cmd/main.go
    package 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)
      }
    }
    
    
  6. ローカルでテスト
    以下のコマンドでローカルサーバーが立ち上がることが確認できる

    ./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の設定を行う

デプロイ

./gin
$ 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にデプロイ

前準備

  1. Get Started - Google CloudInstall Terraformまでの章に従ってterafformをinstallする。

  2. Terraform Tutorial (2nd gen)のBefore you beginに従って設定を行う。

  3. 以下のコマンドを用いてgolangコードのzipファイルを生成する。

    ./gin
    zip -r function-source.zip .
    

terraform

main.tfの最初に、GCPで用いるための一般部分を記述する。

./infra/main.tf
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を用いることで使用できる。

./infra/terraform.tfvars
CREDENTIALS="<YOUR CREDENTIALS>"
PROJECT="<YOUR PROJECT>"

続いて、この下にCloud Functionsに関する部分を記述する。

./infra/main.tf
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
}


デプロイ

  1. terraformの初期化

    ./infra
    terraform init
    
  2. デプロイ

    ./infra
    terraform 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
        }
    ]
        
    
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?