搭配 Go 使用 App Lifecycle Manager 功能旗標

本指南說明如何將 Go 應用程式與 App Lifecycle Manager 功能旗標整合。您將瞭解如何搭配 flagd 供應商使用 OpenFeature SDK,評估由 App Lifecycle Manager 管理的旗標,以便動態控管 Go 服務中的功能可用性和行為。

本指南假設您已完成下列其中一個快速入門導覽課程,設定必要的 App Lifecycle Manager 資源 (例如 SaaS 產品、單元種類、單元、旗標和推出作業):

必要條件

開始之前,請確認您具備以下項目:

  1. 已安裝 Go:1.23 以上版本。
  2. 已完成 App Lifecycle Manager 功能旗標快速入門:
    • 已成功完成整合式獨立式功能旗標快速入門導覽課程,以佈建目標單元。
    • 您應該會有該快速入門中的環境變數或值,例如 PROJECT_IDLOCATION_1 (您的裝置區域)、UNIT_IDFLAG_KEY
  3. 已驗證 gcloud 應用程式預設憑證 (ADC):Go 應用程式會使用 ADC 向 Google Cloud 服務進行驗證。如果在本機執行 (用於獨立用途或本機 Docker 測試),請確認您已在環境中完成驗證:

    gcloud auth application-default login
    
  4. IAM 權限:執行 Go 應用程式的身分必須在roles/saasconfig.viewer專案中具備 Google Cloud Identity and Access Management 角色,才能讀取旗標設定:

    gcloud projects add-iam-policy-binding YOUR_PROJECT_ID \
        --member="user:YOUR_EMAIL_ADDRESS" \
        --role="roles/saasconfig.viewer"
    

    請視情況替換 YOUR_PROJECT_IDYOUR_EMAIL_ADDRESS

瞭解重要環境變數

Go 應用程式會依據環境變數連線至正確的旗標設定,並評估預期的旗標。

  • FLAGD_SOURCE_PROVIDER_ID:指定要從 App Lifecycle Manager 服務擷取的功能旗標設定。必須是與裝置相關聯的 FeatureFlagConfig 完整資源名稱。
    • 「Format」(形式)projects/PROJECT_ID/locations/LOCATION/featureFlagsConfigs/UNIT_ID
    • 示例projects/my-gcp-project/locations/us-central1/featureFlagsConfigs/my-app-instance-01

設定 Go 專案

執行應用程式前,請先初始化模組,並新增必要的初始化和評估邏輯。

  1. 建立專案目錄:

    mkdir go-featureflag-app
    cd go-featureflag-app
    go mod init go-featureflag-app
    
  2. 建立應用程式程式碼:新增名為 main.go 的檔案,並加入下列內容,初始化供應器及評估旗標。

    package main
    
    import (
        "context"
        "fmt"
        "log"
        "os"
    
        flagd "github.com/open-feature/go-sdk-contrib/providers/flagd/pkg"
        "github.com/open-feature/go-sdk/openfeature"
        "google.golang.org/grpc"
        "google.golang.org/grpc/credentials"
        "google.golang.org/grpc/credentials/oauth"
        "google.golang.org/grpc/metadata"
    )
    
    // GetNewFlagdProvider initializes a flagd provider configured for  App Lifecycle Manager
    func GetNewFlagdProvider(ctx context.Context) (*flagd.Provider, error) {
        providerID := os.Getenv("FLAGD_SOURCE_PROVIDER_ID")
        if providerID == "" {
            return nil, fmt.Errorf("FLAGD_SOURCE_PROVIDER_ID environment variable is not set")
        }
    
        creds, err := oauth.NewApplicationDefault(ctx)
        if err != nil {
            return nil, err
        }
    
        // Interceptor to inject the Unit name into headers for regional routing
        routingInterceptor := func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
            md := metadata.Pairs("x-goog-request-params", fmt.Sprintf("name=%s", providerID))
            ctx = metadata.NewOutgoingContext(ctx, md)
            return streamer(ctx, desc, cc, method, opts...)
        }
    
        options := []flagd.ProviderOption{
            flagd.WithHost("saasconfig.googleapis.com"),
            flagd.WithPort(443),
            flagd.WithInProcessResolver(),
            flagd.WithProviderID(providerID),
            flagd.WithGrpcDialOptionsOverride([]grpc.DialOption{
                grpc.WithTransportCredentials(credentials.NewTLS(nil)),
                grpc.WithPerRPCCredentials(creds),
                grpc.WithStreamInterceptor(routingInterceptor),
            }),
        }
    
        return flagd.NewProvider(options...)
    }
    
    // InitializeFeatureManagement registers the provider globally
    func InitializeFeatureManagement(ctx context.Context) error {
        provider, err := GetNewFlagdProvider(ctx)
        if err != nil {
            return err
        }
        openfeature.SetProvider(provider)
        return nil
    }
    
    func main() {
    ctx := context.Background()
    InitializeFeatureManagement(ctx);
    
    // 1. Get Client
    client := openfeature.NewClient("simple-api")
    
    // 2. Create Evaluation Context
    evalCtx := openfeature.NewEvaluationContext()
    
    // 3. Evaluate Flag
    // Default to false if evaluation fails
    isEnhanced, err := client.BooleanValue(context.Background(), "enhanced-search", false, evalCtx)
    if err != nil {
    log.Printf("Flag evaluation failed: %v", err)
    }
    
    // 4. Return response
    if isEnhanced {
    fmt.Println("Enhanced search feature is enabled.")
    } else {
    fmt.Println("Enhanced search feature is disabled.")
    }
    
    }
    
  3. 建立 go.mod:使用特定版本可確保穩定性。

    module go-featureflag-app
    
    go 1.23
    
    require (
        github.com/open-feature/go-sdk v1.15.1
        github.com/open-feature/go-sdk-contrib/providers/flagd v0.3.0
        google.golang.org/grpc v1.74.2
        golang.org/x/oauth2 v0.22.0
    )
    
  4. 取得依附元件:

    go mod tidy
    go mod download
    

執行 Go 應用程式

您可以透過獨立設定在本機執行應用程式,也可以部署為容器。

獨立執行 (在本機)

  1. 設定環境變數:

    export PROJECT_ID="your-gcp-project-id"
    export LOCATION_1="us-central1"
    export UNIT_ID="my-app-instance-01"
    export FLAGD_SOURCE_PROVIDER_ID="projects/${PROJECT_ID}/locations/${LOCATION_1}/featureFlagsConfigs/${UNIT_ID}"
    
  2. 執行應用程式:

    go run main.go
    

執行整合式 (容器化)

  1. 建立 Dockerfile:

    FROM golang:1.23 as builder
    WORKDIR /app
    COPY go.mod go.sum ./
    RUN go mod download
    COPY . .
    RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /go-featureflag-app .
    
    FROM gcr.io/distroless/static-debian11
    WORKDIR /
    COPY --from=builder /go-featureflag-app /go-featureflag-app
    ENTRYPOINT ["/go-featureflag-app"]
    
  2. 建構及推送映像檔:

    export PROJECT_ID="your-gcp-project-id"
    docker build -t gcr.io/${PROJECT_ID}/my-go-app:latest .
    docker push gcr.io/${PROJECT_ID}/my-go-app:latest
    
  3. 部署服務:更新單元藍圖以參照 gcr.io/${PROJECT_ID}/my-go-app:latest,並使用 App Lifecycle Manager 推出作業部署新版本。

驗證旗標變更

應用程式執行後,請確認應用程式是否正確評估旗標。

  1. 觀察初始值:確認輸出內容記錄的值對應於初始狀態。
  2. 在 App Lifecycle Manager 中更新旗標:使用 Google Cloud 控制台或 gcloud 修改旗標行為。
  3. 驗證更新後的值:重新執行本機編譯,或觀察容器記錄,查看更新後的結果。

後續步驟