Goクラウドネイティブマイクロサービス開発・デプロイ実践
なぜGoがクラウドネイティブマイクロサービスに最適な言語なのか?
Go言語はコンパイル型のパフォーマンス、極小バイナリサイズ、ネイティブ並行性モデル、クラウドエコシステムとの親和性により、クラウドネイティブマイクロサービスの事実上の標準言語となっています。Docker、Kubernetes、etcd、Consul、Prometheusなどの中核的なクラウドネイティブプロジェクトはすべてGoで書かれています。
Go vs 他言語比較
| 次元 | Go | Java (Spring Boot) | Node.js | Rust |
|---|---|---|---|---|
| 起動速度 | <10ms | 2-5s | 200-500ms | <10ms |
| メモリ使用量 | 10-30MB | 200-500MB | 50-150MB | 10-30MB |
| バイナリサイズ | 5-15MB | JVM必要 | Nodeランタイム必要 | 5-15MB |
| 並行性 | goroutine(軽量) | 仮想スレッド | イベントループ | async/await |
| コールドスタート | 極速 | 遅い(JIT) | 中程度 | 極速 |
| エコシステム | クラウドネイティブ強 | エンタープライズ強 | フロントエンド/フルスタック | システムレベル |
| 開発速度 | 高い | 高い | 非常に高い | 中程度 |
Goマイクロサービスのコア利点
- 単一バイナリデプロイ:コンパイル後は静的リンクファイル1つ、ランタイム依存なし
- goroutine並行性:単一マシンで百万級の同時接続を処理
- 高速コンパイル:増分ビルドが秒単位、スムーズな開発体験
- 組み込みツールチェーン:
go fmt、go test、go vet、競合検出器がすぐ使える - クラウドネイティブDNA:Docker・K8sエコシステムと自然に統合
プロジェクト構造ベストプラクティス
Clean Architectureのレイヤー設計を採用し、ビジネスロジックと技術実装の結合を解消します:
user-service/
├── cmd/
│ └── server/
│ └── main.go # エントリポイント
├── internal/
│ ├── domain/ # ドメイン層:エンティティとインターフェース
│ │ ├── user.go
│ │ └── repository.go
│ ├── application/ # アプリケーション層:ユースケース/サービス
│ │ └── user_service.go
│ ├── infrastructure/ # インフラ層:実装
│ │ ├── persistence/
│ │ │ └── postgres_repo.go
│ │ ├── grpc/
│ │ │ └── user_handler.go
│ │ └── cache/
│ │ └── redis_cache.go
│ └── interfaces/ # インターフェース層:アダプター
│ └── rest/
│ └── user_controller.go
├── api/
│ └── proto/
│ └── user/v1/user.proto # Protobuf定義
├── configs/
│ └── config.yaml
├── deployments/
│ ├── docker/
│ │ └── Dockerfile
│ └── k8s/
│ ├── deployment.yaml
│ └── service.yaml
├── pkg/ # 再利用可能な公開パッケージ
│ ├── logger/
│ └── middleware/
├── go.mod
├── go.sum
└── Makefile
💡 JSONフォーマッターツールで設定ファイル構造を確認・デバッグできます。
gRPC vs REST比較
コアの違い
| 次元 | gRPC | REST |
|---|---|---|
| プロトコル | HTTP/2 + Protobuf | HTTP/1.1 + JSON |
| シリアライズ | バイナリProtobuf(小さく高速) | テキストJSON(大きく遅い) |
| ストリーミグ | ✅ 双方向ストリーミグ | ❌ リクエスト-レスポンスのみ |
| コード生成 | ✅ 自動生成クライアント/サーバー | ❌ 手動またはSwagger |
| パフォーマンス | RESTより5-10倍高速 | ベースライン |
| ブラウザサポート | ❌ gRPC-Webが必要 | ✅ ネイティブサポート |
| デバッグ難易度 | 高い(バイナリ) | 低い(読みやすいJSON) |
| サービス間通信 | ✅ 最適な選択 | 使用可能だが最適ではない |
| 外部API | gRPC-Webプロキシが必要 | ✅ 最適な選択 |
選択ガイドライン
- サービス間通信 → gRPC(高性能、強い型付け、ストリーミグ)
- フロントエンド/外部向けAPI → REST(互換性良好、デバッグ容易)
- 本番実践 → デュアルプロトコル:内部gRPC + 外部RESTゲートウェイ
完全なGoマイクロサービスの実装
1. Protobuf定義
// api/proto/user/v1/user.proto
syntax = "proto3";
package user.v1;
option go_package = "github.com/example/user-service/api/proto/user/v1;v1";
service UserService {
rpc GetUser(GetUserRequest) returns (GetUserResponse);
rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
rpc ListUsers(ListUsersRequest) returns (stream User);
}
message User {
string id = 1;
string name = 2;
string email = 3;
int64 created_at = 4;
}
message GetUserRequest {
string id = 1;
}
message GetUserResponse {
User user = 1;
}
message CreateUserRequest {
string name = 1;
string email = 2;
}
message CreateUserResponse {
User user = 1;
}
message ListUsersRequest {
int32 page_size = 1;
string page_token = 2;
}
2. ドメイン層
// internal/domain/user.go
package domain
import (
"context"
"time"
)
type User struct {
ID string
Name string
Email string
CreatedAt time.Time
}
type UserRepository interface {
GetByID(ctx context.Context, id string) (*User, error)
Create(ctx context.Context, user *User) (*User, error)
List(ctx context.Context, pageSize int32, pageToken string) ([]*User, string, error)
}
type UserService interface {
GetUser(ctx context.Context, id string) (*User, error)
CreateUser(ctx context.Context, name, email string) (*User, error)
ListUsers(ctx context.Context, pageSize int32, pageToken string) ([]*User, string, error)
}
3. アプリケーション層の実装
// internal/application/user_service.go
package application
import (
"context"
"fmt"
"time"
"github.com/example/user-service/internal/domain"
)
type userService struct {
repo domain.UserRepository
}
func NewUserService(repo domain.UserRepository) domain.UserService {
return &userService{repo: repo}
}
func (s *userService) GetUser(ctx context.Context, id string) (*domain.User, error) {
user, err := s.repo.GetByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("get user %s: %w", id, err)
}
return user, nil
}
func (s *userService) CreateUser(ctx context.Context, name, email string) (*domain.User, error) {
user := &domain.User{
Name: name,
Email: email,
CreatedAt: time.Now(),
}
created, err := s.repo.Create(ctx, user)
if err != nil {
return nil, fmt.Errorf("create user: %w", err)
}
return created, nil
}
func (s *userService) ListUsers(ctx context.Context, pageSize int32, pageToken string) ([]*domain.User, string, error) {
return s.repo.List(ctx, pageSize, pageToken)
}
4. gRPCハンドラー
// internal/infrastructure/grpc/user_handler.go
package grpc
import (
"context"
pb "github.com/example/user-service/api/proto/user/v1"
"github.com/example/user-service/internal/domain"
)
type UserHandler struct {
pb.UnimplementedUserServiceServer
svc domain.UserService
}
func NewUserHandler(svc domain.UserService) *UserHandler {
return &UserHandler{svc: svc}
}
func (h *UserHandler) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.GetUserResponse, error) {
user, err := h.svc.GetUser(ctx, req.GetId())
if err != nil {
return nil, err
}
return &pb.GetUserResponse{User: toProto(user)}, nil
}
func (h *UserHandler) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.CreateUserResponse, error) {
user, err := h.svc.CreateUser(ctx, req.GetName(), req.GetEmail())
if err != nil {
return nil, err
}
return &pb.CreateUserResponse{User: toProto(user)}, nil
}
func toProto(u *domain.User) *pb.User {
return &pb.User{
Id: u.ID,
Name: u.Name,
Email: u.Email,
CreatedAt: u.CreatedAt.Unix(),
}
}
5. メインエントリポイント
// cmd/server/main.go
package main
import (
"log"
"net"
"os"
"os/signal"
"syscall"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "github.com/example/user-service/api/proto/user/v1"
grpcHandler "github.com/example/user-service/internal/infrastructure/grpc"
"github.com/example/user-service/internal/application"
"github.com/example/user-service/internal/infrastructure/persistence"
)
func main() {
repo, err := persistence.NewPostgresRepo("postgres://localhost:5432/users")
if err != nil {
log.Fatalf("connect db: %v", err)
}
svc := application.NewUserService(repo)
handler := grpcHandler.NewUserHandler(svc)
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterUserServiceServer(s, handler)
reflection.Register(s)
go func() {
log.Printf("gRPC server listening on :50051")
if err := s.Serve(lis); err != nil {
log.Fatalf("serve: %v", err)
}
}()
quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("shutting down...")
s.GracefulStop()
}
サービスディスカバリ:ConsulとEtcd
Consulサービス登録
package discovery
import (
"fmt"
"log"
"github.com/hashicorp/consul/api"
)
type ConsulRegistry struct {
client *api.Client
}
func NewConsulRegistry(addr string) (*ConsulRegistry, error) {
config := api.DefaultConfig()
config.Address = addr
client, err := api.NewClient(config)
if err != nil {
return nil, fmt.Errorf("create consul client: %w", err)
}
return &ConsulRegistry{client: client}, nil
}
func (r *ConsulRegistry) Register(serviceName, serviceID, host string, port int) error {
registration := &api.AgentServiceRegistration{
ID: serviceID,
Name: serviceName,
Address: host,
Port: port,
Check: &api.AgentServiceCheck{
GRPC: fmt.Sprintf("%s:%d", host, port),
Interval: "10s",
Timeout: "5s",
DeregisterCriticalServiceAfter: "30s",
},
}
return r.client.Agent().ServiceRegister(registration)
}
func (r *ConsulRegistry) Deregister(serviceID string) error {
return r.client.Agent().ServiceDeregister(serviceID)
}
Etcdサービスディスカバリ
package discovery
import (
"context"
"fmt"
"time"
clientv3 "go.etcd.io/etcd/client/v3"
)
type EtcdRegistry struct {
client *clientv3.Client
}
func NewEtcdRegistry(endpoints []string) (*EtcdRegistry, error) {
client, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: 5 * time.Second,
})
if err != nil {
return nil, fmt.Errorf("create etcd client: %w", err)
}
return &EtcdRegistry{client: client}, nil
}
func (r *EtcdRegistry) Register(ctx context.Context, serviceName, addr string, ttl int64) error {
lease, err := r.client.Grant(ctx, ttl)
if err != nil {
return fmt.Errorf("create lease: %w", err)
}
key := fmt.Sprintf("/services/%s/%s", serviceName, addr)
_, err = r.client.Put(ctx, key, addr, clientv3.WithLease(lease.ID))
if err != nil {
return fmt.Errorf("register service: %w", err)
}
ch, err := r.client.KeepAlive(ctx, lease.ID)
if err != nil {
return fmt.Errorf("keep alive: %w", err)
}
go func() {
for range ch {
}
}()
return nil
}
Consul vs Etcd選定
| 次元 | Consul | Etcd |
|---|---|---|
| サービスディスカバリ | ✅ ネイティブサポート | カスタム実装が必要 |
| ヘルスチェック | ✅ 内蔵HTTP/gRPC/TCP | 外部実装が必要 |
| KVストア | ✅ | ✅ 強一貫性 |
| マルチデータセンター | ✅ | ❌ |
| K8s統合 | インストールが必要 | ✅ コアコンポーネント |
| 推奨場面 | 従来のマイクロサービスアーキテクチャ | K8sネイティブ環境 |
サーキットブレーカーパターン
sony/gobreakerの使用
package middleware
import (
"context"
"fmt"
"github.com/sony/gobreaker"
)
type CircuitBreakerClient struct {
cb *gobreaker.CircuitBreaker
}
func NewCircuitBreakerClient(name string) *CircuitBreakerClient {
settings := gobreaker.Settings{
Name: name,
MaxRequests: 5,
Interval: 10 * time.Second,
Timeout: 30 * time.Second,
ReadyToTrip: func(counts gobreaker.Counts) bool {
failureRatio := float64(counts.TotalFailures) / float64(counts.Requests)
return counts.Requests >= 10 && failureRatio >= 0.6
},
OnStateChange: func(name string, from gobreaker.State, to gobreaker.State) {
log.Printf("circuit breaker %s: %s -> %s", name, from, to)
},
}
return &CircuitBreakerClient{
cb: gobreaker.NewCircuitBreaker(settings),
}
}
func (c *CircuitBreakerClient) Execute(ctx context.Context, fn func() (interface{}, error)) (interface{}, error) {
result, err := c.cb.Execute(func() (interface{}, error) {
return fn()
})
if err != nil {
return nil, fmt.Errorf("circuit breaker: %w", err)
}
return result, nil
}
サーキットブレーカーの3状態遷移
成功率回復 連続失敗が閾値超え
┌──────────────────┐ ┌──────────────────┐
│ │ │ │
▼ │ ▼ │
Closed ──────► Open ──────► HalfOpen ─────┘
│ │ │
│ 通常リクエスト │ 拒否リクエスト │ 少量リクエスト許可
│ │ │
└─────────────┴──────────────┘
Dockerマルチステージビルド
# ステージ1:ビルド
FROM golang:1.22-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
go build -ldflags="-s -w" -o /app/server ./cmd/server
# ステージ2:ランタイム
FROM gcr.io/distroless/static-debian12:nonroot
COPY --from=builder /app/server /server
COPY --from=builder /app/configs /configs
EXPOSE 50051
ENTRYPOINT ["/server"]
ビルド最適化のヒント
# キャッシュ最適化:go.mod/go.sumを先にコピーし、Dockerレイヤーキャッシュを活用
# イメージサイズ:distroless約2MB + バイナリ、合計<20MB
# セキュリティ:nonrootユーザーで実行、シェルなし、攻撃面極小
Makefile共通コマンド
.PHONY: build run test docker-build docker-push
build:
go build -o bin/server ./cmd/server
run:
go run ./cmd/server
test:
go test -v -race -coverprofile=coverage.out ./...
docker-build:
docker build -t user-service:latest .
docker-push:
docker tag user-service:latest registry.example.com/user-service:latest
docker push registry.example.com/user-service:latest
proto:
protoc --go_out=. --go-grpc_out=. api/proto/user/v1/*.proto
lint:
golangci-lint run ./...
Kubernetesデプロイ
Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
namespace: production
labels:
app: user-service
spec:
replicas: 3
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: registry.example.com/user-service:latest
ports:
- containerPort: 50051
name: grpc
- containerPort: 8080
name: http
env:
- name: DB_HOST
valueFrom:
secretKeyRef:
name: user-service-secrets
key: db-host
- name: CONSUL_ADDR
value: "consul:8500"
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
cpu: 500m
memory: 256Mi
livenessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
exec:
command: ["/bin/grpc_health_probe", "-addr=:50051"]
initialDelaySeconds: 5
periodSeconds: 10
terminationGracePeriodSeconds: 30
Service
apiVersion: v1
kind: Service
metadata:
name: user-service
namespace: production
spec:
type: ClusterIP
ports:
- port: 50051
targetPort: 50051
name: grpc
- port: 8080
targetPort: 8080
name: http
selector:
app: user-service
HorizontalPodAutoscaler
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: user-service-hpa
namespace: production
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: user-service
minReplicas: 3
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
💡 YAMLフォーマッターツールでK8s設定ファイルを検証・フォーマットできます。
観測可能性:OpenTelemetryトレーシング
Tracerの初期化
package telemetry
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.24.0"
)
func InitTracer(ctx context.Context, serviceName, collectorAddr string) (func(), error) {
exporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint(collectorAddr),
otlptracegrpc.WithInsecure(),
)
if err != nil {
return nil, err
}
res, err := resource.New(ctx,
resource.WithAttributes(
semconv.ServiceNameKey.String(serviceName),
semconv.ServiceVersionKey.String("1.0.0"),
),
)
if err != nil {
return nil, err
}
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.ParentBased(
sdktrace.TraceIDRatioBased(0.1),
)),
)
otel.SetTracerProvider(provider)
return func() {
if err := provider.Shutdown(ctx); err != nil {
log.Printf("shutdown tracer: %v", err)
}
}, nil
}
gRPCインターセプターでトレーシング注入
package middleware
import (
"go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc"
"google.golang.org/grpc"
)
func WithTracing() grpc.ServerOption {
return grpc.UnaryInterceptor(otelgrpc.UnaryServerInterceptor())
}
// 使用方法
s := grpc.NewServer(
WithTracing(),
grpc.StreamInterceptor(otelgrpc.StreamServerInterceptor()),
)
ビジネスコードにSpanを追加
func (s *userService) GetUser(ctx context.Context, id string) (*domain.User, error) {
ctx, span := otel.Tracer("user-service").Start(ctx, "GetUser")
defer span.End()
span.SetAttributes(attribute.String("user.id", id))
user, err := s.repo.GetByID(ctx, id)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return nil, err
}
return user, nil
}
よくあるエラーと解決策
1. gRPC接続リーク
// ❌ 誤り:接続が閉じられていない
conn, _ := grpc.Dial("localhost:50051", grpc.WithInsecure())
client := pb.NewUserServiceClient(conn)
// ✅ 正しい:接続を確実に閉じる
conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
if err != nil {
return err
}
defer conn.Close()
2. Contextタイムアウト未設定
// ❌ 誤り:タイムアウトなし、永久にブロックされる可能性
resp, err := client.GetUser(context.Background(), req)
// ✅ 正しい:適切なタイムアウトを設定
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
resp, err := client.GetUser(ctx, req)
3. goroutineリーク
// ❌ 誤り:goroutineが終了できない
go func() {
for {
doWork()
}
}()
// ✅ 正しい:contextでgoroutineのライフサイクルを制御
go func(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
default:
doWork()
}
}
}(ctx)
4. Protobufゼロ値の曖昧さ
// ❌ 誤り:ゼロ値で「未設定」と「値が0」を区別できない
if user.GetAge() == 0 {
// 未設定か、年齢が0か?
}
// ✅ 正しい:wrapper型またはポインタを使用
import "google/protobuf/wrappers.proto";
message User {
google.protobuf.Int32Value age = 1; // nullと0を区別可能
}
5. 競合状態
// ❌ 誤り:mapの並行読み書き
var cache = make(map[string]string)
// 複数goroutineが同時に読み書き → panic
// ✅ 正しい:sync.Mapまたはロックを使用
var cache sync.Map
cache.Store("key", "value")
val, ok := cache.Load("key")
パフォーマンスチューニング
1. 接続プール設定
import "google.golang.org/grpc"
conn, err := grpc.Dial(addr,
grpc.WithDefaultServiceConfig(`{
"methodConfig": [{
"name": [{"service": "user.v1.UserService"}],
"retryPolicy": {
"maxAttempts": 3,
"initialBackoff": "0.1s",
"maxBackoff": "1s",
"backoffMultiplier": 2,
"retryableStatusCodes": ["UNAVAILABLE"]
}
}]
}`),
grpc.WithKeepaliveParams(keepalive.ClientParameters{
Time: 10 * time.Second,
Timeout: 3 * time.Second,
PermitWithoutStream: true,
}),
)
2. データベース接続プール
db, err := sql.Open("pgx", dsn)
db.SetMaxOpenConns(25)
db.SetMaxIdleConns(10)
db.SetConnMaxLifetime(5 * time.Minute)
db.SetConnMaxIdleTime(2 * time.Minute)
3. Goランタイムチューニング
import "runtime"
runtime.GOMAXPROCS(runtime.NumCPU())
// Linuxコンテナでは注意:Go 1.21+はcgroup CPU制限を自動検知
// 旧バージョンは手動設定またはautomaxprocsライブラリを使用
import _ "go.uber.org/automaxprocs"
4. パフォーマンスベンチマーク
| 指標 | 最適化前 | 最適化後 | 最適化手法 |
|---|---|---|---|
| gRPC QPS | 8,000 | 25,000 | 接続プール + keepalive |
| P99レイテンシ | 120ms | 35ms | タイムアウト制御 + サーキットブレーカー |
| メモリ使用量 | 256MB | 80MB | オブジェクトプール + sync.Pool |
| コールドスタート | 2s | 0.3s | distrolessイメージ |
FAQ
Q1: GoマイクロサービスにはgRPCとRESTどちらを使うべきか?
サービス間通信にはgRPC、外部/フロントエンド向けAPIにはRESTを使用します。本番では通常両プロトコルが共存し、grpc-gatewayでgRPCをRESTに自動変換します。
Q2: サービスディスカバリはConsulかK8s Serviceか?
K8s上で実行する場合、K8s Service + DNSを直接使用し、Consulは不要です。ハイブリッド環境(K8s + ベアメタル)やマルチデータセンターの場合のみConsulを検討してください。
Q3: 大きなgRPCメッセージの処理方法は?
gRPCのデフォルトメッセージサイズ制限は4MBです。大きなメッセージには:1)ストーミグRPCでチャンク転送;2)grpc.MaxRecvMsgSize(16*1024*1024)で制限を増加;3)大きなファイルはS3に保存し、参照IDのみ渡す。
Q4: Goマイクロサービスのグレースフルシャットダウン方法は?
SIGINT/SIGTERMシグナルをリッスンし、grpc.Server.GracefulStop()を呼び出し、処理中のリクエストの完了を待ちます。K8sではterminationGracePeriodSecondsとpreStopフックを組み合わせてトラフィックドレイナージを確保します。
Q5: どのGoマイクロサービスフレームワークを選ぶべきか?
- 軽量級:標準ライブラリ + grpc + 手動ワイヤリング(推奨、最も柔軟)
- マイクロフレームワーク:go-zero(コード生成、サーキットブレーカー、レート制限内蔵)
- フル機能:Kratos(protobuf中心、Bilibiliがオープンソース化)
- エンタープライズ:go-micro / micro v4(プラグインアーキテクチャ)
関連ツール
- JSONエンコード/デコード — REST APIリクエスト・レスポンスのデバッグ
- YAMLエンコード/デコード — K8s/Docker設定ファイルのフォーマット
- ハッシュ計算 — サービスヘルスチェックとデータ検証
まとめ
Goはコンパイル型パフォーマンス、極小バイナリ、ネイティブ並行性、クラウドエコシステム親和性により、クラウドネイティブマイクロサービス構築に最適です。Clean Architectureプロジェクト構造から始め、内部通信にgRPC + 外部APIにREST、Consul/Etcdでサービスディスカバリ、gobreakerでサーキットブレーカー保護、Dockerマルチステージビルドで極小イメージ、K8s宣言的デプロイとオートスケーリング、OpenTelemetryでフルチェーントレーシング——この組み合わせはマイクロサービスの開発から本番までの完全なライフサイクルをカバーします。コア原則:シンプルに保つ、標準ライブラリを活用する、漸進的に強化する。
ブラウザローカルツールを無料で試す →