Pourquoi Déployer Go sur GCP ?
Deployer une application Go sur Google Cloud Platform (GCP) est un choix populaire pour les développeurs, car GCP offre des services robustes et scalables qui peuvent gérer la charge de travail d'une application Go efficacement. Voici un contexte réel : imaginez que vous développez une application de microservices backend pour une entreprise croissante. Vous avez besoin d'une infrastructure fiable qui peut s'échelle à mesure du nombre d'utilisateurs et de demandes.
Un cas concret serait la création d'une API de blog. L'application doit être capable de gérer des millions de requêtes par jour, en traitant des opérations CRUD sur les articles de blog. En déployant cette application Go sur GCP, vous bénéficiez d'un stockage fiable et rapide, d'un réseau mondial pour l'accelération du trafic, ainsi que d'une infrastructure auto-géante pour la mise à l'échelle.
Prérequis
Pour suivre ce tutoriel, vous aurez besoin des connaissances suivantes :
- Connaissance de base en Go
- Compréhension des concepts fondamentaux de GCP (Cloud Storage, Cloud Functions, App Engine)
Les outils que vous devez installer sont les suivants :
- Google Cloud SDK v300.0.0 ou ultérieure
- Go v1.16 ou ultérieure
Concepts Fondamentaux
1. Cloud Functions
Cloud Functions est un service qui permet d'exécuter des fonctions serverless sur GCP. Ce service est parfait pour les applications Go qui nécessitent une mise à l'échelle automatique et une infrastructure sans état.
Schema Mental
+-------------------+
| Trigger |
| (HTTP, Pub/Sub, etc.)|
+--------+----------+
|
v
+--------v----------+
| Function |
| (Go Code) |
+-------------------+
|
v
+--------v----------+
| Execution |
| Management |
+-------------------+
go
// main.go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}
func main() {
http.HandleFunc("/", helloHandler)
port := "8080"
if envPort := os.Getenv("PORT"); envPort != "" {
port = envPort
}
log.Printf("Listening on :%s...", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Could not listen on %s: %v\n", port, err)
}
}
2. App Engine
App Engine est un service qui permet de déployer des applications web et mobiles. Il prend en charge les langages Go et offre une mise à l'échelle automatique.
Schema Mental
+-------------------+
| Application |
| (Go Code) |
+--------+----------+
|
v
+--------v----------+
| Deployment |
| Management |
+-------------------+
|
v
+--------v----------+
| Instances |
| (Auto-scaled) |
+-------------------+
go
// app.yaml
runtime: go115
instance_class: F2
automatic_scaling:
target_cpu_utilization: 0.65
min_instances: 1
max_instances: 10
env_variables:
GCP_PROJECT: your-gcp-project-id
go
// main.go
package main
import (
"fmt"
"net/http"
)
func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello from App Engine!")
}
func main() {
http.HandleFunc("/", helloHandler)
port := "8080"
if envPort := os.Getenv("PORT"); envPort != "" {
port = envPort
}
log.Printf("Listening on :%s...", port)
if err := http.ListenAndServe(":"+port, nil); err != nil {
log.Fatalf("Could not listen on %s: %v\n", port, err)
}
}
3. Cloud Storage
Cloud Storage est un stockage d'objets à grande échelle et hautement disponible, qui est idéal pour stocker des fichiers binaires ou textuels.
Schema Mental
+-------------------+
| Bucket |
| (Cloud Storage) |
+--------+----------+
|
v
+--------v----------+
| Objects |
| (Files) |
+-------------------+
|
v
+--------v----------+
| Access |
| Management |
+-------------------+
go
// main.go
package main
import (
"context"
"fmt"
"cloud.google.com/go/storage"
)
func uploadFile(bucketName, objectName, filePath string) error {
ctx := context.Background()
client, err := storage.NewClient(ctx)
if err != nil {
return fmt.Errorf("storage.NewClient: %w", err)
}
defer client.Close()
bucket := client.Bucket(bucketName)
obj := bucket.Object(objectName)
w := obj.NewWriter(ctx)
f, err := os.Open(filePath)
if err != nil {
return fmt.Errorf("os.Open: %w", err)
}
if _, err := io.Copy(w, f); err != nil {
return fmt.Errorf("io.Copy: %w", err)
}
if err := w.Close(); err != nil {
return fmt.Errorf("Writer.Close: %w", err)
}
fmt.Printf("Uploaded %s to gs://%s/%s\n", filePath, bucketName, objectName)
return nil
}
func main() {
bucketName := "your-bucket-name"
objectName := "your-object-name"
filePath := "path/to/your/file"
if err := uploadFile(bucketName, objectName, filePath); err != nil {
fmt.Println("Error:", err)
return
}
}
Mise en Pratique : Projet Fil Rouge
Nous allons créer un gestionnaire de tâches simple en Go qui utilise Cloud Functions pour les exécuter.
Étape 1 : Création du projet et initialisation d'App Engine
- Créez un nouveau projet GCP et notez le nom du projet.
- Activez les services nécessaires (Cloud Functions, App Engine).
gcloud projects create your-gcp-project-id --name "Your Project Name"
gcloud config set project your-gcp-project-id
gcloud services enable cloudfunctions.googleapis.com
gcloud services enable appengine.googleapis.com
- Initialisez un nouveau projet Go.
mkdir task-manager
cd task-manager
go mod init github.com/yourusername/task-manager
Étape 2 : Création de la fonction serverless
Créez un fichier main.go pour votre application Go.
// main.go
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net/http"
"cloud.google.com/go/functions/v2beta1/functionframework"
)
type Task struct {
ID string `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
var tasks = []Task{
{ID: "1", Title: "Buy groceries", Done: false},
{ID: "2", Title: "Clean the house", Done: true},
}
func listTasks(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
json.NewEncoder(w).Encode(tasks)
}
func createTask(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
tasks = append(tasks, task)
w.WriteHeader(http.StatusCreated)
}
func updateTask(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPut {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
for i, t := range tasks {
if t.ID == task.ID {
tasks[i] = task
break
}
}
}
func deleteTask(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodDelete {
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
return
}
var task Task
if err := json.NewDecoder(r.Body).Decode(&task); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
for i, t := range tasks {
if t.ID == task.ID {
tasks = append(tasks[:i], tasks[i+1:]...)
break
}
}
}
func main() {
functionframework.Handle("/tasks", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
listTasks(w, r)
case http.MethodPost:
createTask(w, r)
case http.MethodPut:
updateTask(w, r)
case http.MethodDelete:
deleteTask(w, r)
default:
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
}
})
if err := functionframework.Start(); err != nil {
log.Fatalf("functionframework.Start: %v", err)
}
}
Étape 3 : Déploiement sur Cloud Functions
- Créez une fonction serverless pour gérer les tâches.
gcloud functions deploy taskManager --runtime go115 --trigger-http --allow-unauthenticated --entry-point main
- Testez la fonction en utilisant
curl.
curl -X GET https://REGION-PROJECT_ID.cloudfunctions.net/taskManager/tasks
curl -X POST -H "Content-Type: application/json" -d '{"id": "3", "title": "Walk the dog", "done": false}' https://REGION-PROJECT_ID.cloudfunctions.net/taskManager/tasks
Étape 4 : Déploiement sur App Engine
- Créez un fichier
app.yamlpour configurer App Engine.
## app.yaml
runtime: go115
instance_class: F2
automatic_scaling:
target_cpu_utilization: 0.65
min_instances: 1
max_instances: 10
env_variables:
GCP_PROJECT: your-gcp-project-id
- Déployez l'application sur App Engine.
gcloud app deploy
- Testez l'application en accédant à l'URL affichée par la commande
gcloud app browse.
Erreurs Fréquentes et Debugging
1. Erreur : functionframework: Unable to invoke function
## ❌ Mauvais
func main() {
http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
// Your code here
})
}
## ✅ Correct
func main() {
functionframework.Handle("/tasks", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// Your code here
})
if err := functionframework.Start(); err != nil {
log.Fatalf("functionframework.Start: %v", err)
}
}
2. Erreur : failed to create client: transport: dial tcp [::]:8080: connect: connection refused
## ❌ Mauvais
func main() {
http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
// Your code here
})
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("Could not listen on :8080: %v\n", err)
}
}
## ✅ Correct
func main() {
functionframework.Handle("/tasks", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// Your code here
})
if err := functionframework.Start(); err != nil {
log.Fatalf("functionframework.Start: %v", err)
}
}
3. Erreur : failed to create client: transport: dial tcp [::]:8081: connect: connection refused
## ❌ Mauvais
func main() {
http.HandleFunc("/tasks", func(w http.ResponseWriter, r *http.Request) {
// Your code here
})
if err := http.ListenAndServe(":8081", nil); err != nil {
log.Fatalf("Could not listen on :8081: %v\n", err)
}
}
## ✅ Correct
func main() {
functionframework.Handle("/tasks", func(ctx context.Context, w http.ResponseWriter, r *http.Request) {
// Your code here
})
if err := functionframework.Start(); err != nil {
log.Fatalf("functionframework.Start: %v", err)
}
}
Pour Aller Plus Loins
1. Utiliser Firestore pour le stockage
Firestore est un service de base de données NoSQL sur GCP qui peut être utilisé pour stocker et gérer des données en temps réel.
2. Intégrer Cloud Pub/Sub
Cloud Pub/Sub est un service de messagerie fiable et évolutif, qui peut être utilisé pour publier et abonner des messages à travers votre application.
3. Développer une API GraphQL
GraphQL est un langage de requête pour les APIs et un serveur d'exécution pour traiter ces requêtes avec vos données existantes. Vous pouvez utiliser GraphQL avec Go en utilisant des bibliothèques comme graphql-go.
Challenge Pratique
- Développez une API de blog en utilisant Go, Cloud Functions et Firestore.
- Ajoutez l'intégration de Cloud Pub/Sub pour publier des notifications lorsqu'un nouvel article est créé ou mis à jour.
- Utilisez GraphQL pour exposer votre API de blog.
En suivant ces étapes, vous serez en mesure de créer une application robuste et scalable en utilisant Go sur GCP.