본문으로 건너뛰기
Kreath Archive
TechProjectsBooksAbout
TechProjectsBooksAbout
TechProjectsBooksAbout
© 2026 Kreath. All rights reserved.
홈TechProjectsBooksAbout
//
  1. 홈
  2. 테크
  3. 10장: gRPC와 마이크로서비스 통신
2026년 8월 30일·프로그래밍·

10장: gRPC와 마이크로서비스 통신

Protocol Buffers 정의부터 Go에서의 gRPC 서버/클라이언트 구현, 유니어리/스트리밍 통신, 인터셉터, gRPC-Gateway를 통한 REST 변환, 헬스 체킹과 서비스 디스커버리를 다룹니다.

12분923자10개 섹션
concurrencyperformancedesign-patternstestinginfrastructure
공유
go-backend10 / 11
1234567891011
이전9장: 테스트 전략 - 단위, 통합, 벤치마크다음11장: 실전 프로젝트 - Go 백엔드 API 서버 구축

학습 목표

  • Protocol Buffers를 정의하고 Go 코드를 생성할 수 있다
  • gRPC의 유니어리 및 스트리밍 통신을 구현할 수 있다
  • 인터셉터를 활용한 횡단 관심사(로깅, 인증) 처리를 할 수 있다
  • gRPC-Gateway로 REST API를 동시에 제공할 수 있다
  • 헬스 체킹과 서비스 디스커버리 패턴을 적용할 수 있다

Protocol Buffers

Protobuf란

Protocol Buffers(Protobuf)는 구글이 개발한 언어 중립적인 직렬화 형식입니다. JSON과 비교하여 직렬화/역직렬화 속도가 빠르고 바이너리 크기가 작습니다. gRPC의 기본 메시지 형식이며, 서비스 인터페이스를 정의하는 IDL(Interface Definition Language) 역할도 합니다.

proto/user/v1/user.proto
protobuf
syntax = "proto3";
 
package user.v1;
 
option go_package = "github.com/myapp/gen/user/v1;userv1";
 
import "google/protobuf/timestamp.proto";
 
// 사용자 메시지 정의
message User {
  int64 id = 1;
  string name = 2;
  string email = 3;
  Role role = 4;
  google.protobuf.Timestamp created_at = 5;
}
 
enum Role {
  ROLE_UNSPECIFIED = 0;
  ROLE_USER = 1;
  ROLE_ADMIN = 2;
}
 
// 서비스 정의
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse);
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse);
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse);
  rpc UpdateUser(UpdateUserRequest) returns (UpdateUserResponse);
  rpc DeleteUser(DeleteUserRequest) returns (DeleteUserResponse);
}
 
message GetUserRequest {
  int64 id = 1;
}
 
message GetUserResponse {
  User user = 1;
}
 
message ListUsersRequest {
  int32 page_size = 1;
  string page_token = 2;
}
 
message ListUsersResponse {
  repeated User users = 1;
  string next_page_token = 2;
}
 
message CreateUserRequest {
  string name = 1;
  string email = 2;
  string password = 3;
}
 
message CreateUserResponse {
  User user = 1;
}

코드 생성

Protobuf 코드 생성
bash
# 도구 설치
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
 
# Go 코드 생성
protoc --go_out=. --go_opt=paths=source_relative \
       --go-grpc_out=. --go-grpc_opt=paths=source_relative \
       proto/user/v1/user.proto
Info

buf 도구를 사용하면 protoc 대신 더 현대적인 방식으로 Protobuf를 관리할 수 있습니다. 린팅(Linting), 호환성 검사(Breaking Change Detection), 의존성 관리를 통합적으로 지원합니다.


gRPC 서버 구현

유니어리 RPC

유니어리(Unary) RPC는 클라이언트가 하나의 요청을 보내고 서버가 하나의 응답을 반환하는 가장 기본적인 통신 방식입니다.

grpc-server.go
go
import (
    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    userv1 "github.com/myapp/gen/user/v1"
)
 
type userServer struct {
    userv1.UnimplementedUserServiceServer
    repo UserRepository
}
 
func (s *userServer) GetUser(ctx context.Context, req *userv1.GetUserRequest) (*userv1.GetUserResponse, error) {
    if req.Id <= 0 {
        return nil, status.Errorf(codes.InvalidArgument, "유효하지 않은 사용자 ID: %d", req.Id)
    }
 
    user, err := s.repo.FindByID(ctx, req.Id)
    if err != nil {
        if errors.Is(err, ErrNotFound) {
            return nil, status.Errorf(codes.NotFound, "사용자를 찾을 수 없습니다: ID %d", req.Id)
        }
        return nil, status.Errorf(codes.Internal, "사용자 조회 실패: %v", err)
    }
 
    return &userv1.GetUserResponse{
        User: toProtoUser(user),
    }, nil
}
 
func (s *userServer) CreateUser(ctx context.Context, req *userv1.CreateUserRequest) (*userv1.CreateUserResponse, error) {
    if req.Name == "" || req.Email == "" {
        return nil, status.Error(codes.InvalidArgument, "이름과 이메일은 필수입니다")
    }
 
    user := &User{
        Name:  req.Name,
        Email: req.Email,
    }
 
    if err := s.repo.Create(ctx, user); err != nil {
        return nil, status.Errorf(codes.Internal, "사용자 생성 실패: %v", err)
    }
 
    return &userv1.CreateUserResponse{
        User: toProtoUser(user),
    }, nil
}
 
// 서버 시작
func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatal("리슨 실패:", err)
    }
 
    grpcServer := grpc.NewServer()
    userv1.RegisterUserServiceServer(grpcServer, &userServer{repo: repo})
 
    slog.Info("gRPC 서버 시작", "addr", ":50051")
    if err := grpcServer.Serve(lis); err != nil {
        log.Fatal("서버 실행 실패:", err)
    }
}

gRPC 에러 처리

gRPC는 자체적인 상태 코드 체계를 가지고 있습니다.

gRPC 상태 코드 매핑
text
codes.OK              -> 200 OK
codes.InvalidArgument -> 400 Bad Request
codes.Unauthenticated -> 401 Unauthorized
codes.PermissionDenied-> 403 Forbidden
codes.NotFound        -> 404 Not Found
codes.AlreadyExists   -> 409 Conflict
codes.Internal        -> 500 Internal Server Error
codes.Unavailable     -> 503 Service Unavailable
codes.DeadlineExceeded-> 504 Gateway Timeout

스트리밍 RPC

서버 스트리밍

서버가 여러 응답을 순차적으로 전송합니다.

서버 스트리밍 정의
protobuf
service EventService {
  rpc StreamEvents(StreamEventsRequest) returns (stream Event);
}
server-streaming.go
go
func (s *eventServer) StreamEvents(
    req *eventv1.StreamEventsRequest,
    stream eventv1.EventService_StreamEventsServer,
) error {
    ticker := time.NewTicker(time.Second)
    defer ticker.Stop()
 
    for {
        select {
        case <-stream.Context().Done():
            return nil
        case t := <-ticker.C:
            event := &eventv1.Event{
                Id:        uuid.New().String(),
                Type:      "heartbeat",
                Timestamp: timestamppb.New(t),
            }
            if err := stream.Send(event); err != nil {
                return status.Errorf(codes.Internal, "이벤트 전송 실패: %v", err)
            }
        }
    }
}

양방향 스트리밍

클라이언트와 서버가 동시에 메시지를 주고받습니다.

양방향 스트리밍 정의
protobuf
service ChatService {
  rpc Chat(stream ChatMessage) returns (stream ChatMessage);
}
bidirectional-streaming.go
go
func (s *chatServer) Chat(stream chatv1.ChatService_ChatServer) error {
    for {
        msg, err := stream.Recv()
        if err == io.EOF {
            return nil
        }
        if err != nil {
            return status.Errorf(codes.Internal, "메시지 수신 실패: %v", err)
        }
 
        // 메시지 처리 후 응답
        response := &chatv1.ChatMessage{
            UserId:  "system",
            Content: fmt.Sprintf("수신 확인: %s", msg.Content),
        }
 
        if err := stream.Send(response); err != nil {
            return status.Errorf(codes.Internal, "응답 전송 실패: %v", err)
        }
    }
}

인터셉터

인터셉터(Interceptor)는 gRPC 버전의 미들웨어로, 요청 처리 전후에 횡단 관심사를 처리합니다.

유니어리 인터셉터

unary-interceptor.go
go
// 로깅 인터셉터
func LoggingInterceptor(
    ctx context.Context,
    req interface{},
    info *grpc.UnaryServerInfo,
    handler grpc.UnaryHandler,
) (interface{}, error) {
    start := time.Now()
 
    // 핸들러 실행
    resp, err := handler(ctx, req)
 
    duration := time.Since(start)
    statusCode := status.Code(err)
 
    slog.Info("gRPC 요청 처리",
        "method", info.FullMethod,
        "status", statusCode.String(),
        "duration_ms", duration.Milliseconds(),
    )
 
    return resp, err
}
 
// 인증 인터셉터
func AuthInterceptor(jwtSvc *JWTService) grpc.UnaryServerInterceptor {
    return func(
        ctx context.Context,
        req interface{},
        info *grpc.UnaryServerInfo,
        handler grpc.UnaryHandler,
    ) (interface{}, error) {
        // 인증이 필요 없는 메서드 건너뛰기
        if isPublicMethod(info.FullMethod) {
            return handler(ctx, req)
        }
 
        md, ok := metadata.FromIncomingContext(ctx)
        if !ok {
            return nil, status.Error(codes.Unauthenticated, "메타데이터 없음")
        }
 
        tokens := md.Get("authorization")
        if len(tokens) == 0 {
            return nil, status.Error(codes.Unauthenticated, "인증 토큰 없음")
        }
 
        claims, err := jwtSvc.ValidateToken(strings.TrimPrefix(tokens[0], "Bearer "))
        if err != nil {
            return nil, status.Error(codes.Unauthenticated, "유효하지 않은 토큰")
        }
 
        ctx = context.WithValue(ctx, userClaimsKey, claims)
        return handler(ctx, req)
    }
}
 
// 서버에 인터셉터 적용
grpcServer := grpc.NewServer(
    grpc.ChainUnaryInterceptor(
        LoggingInterceptor,
        AuthInterceptor(jwtSvc),
        RecoveryInterceptor,
    ),
)

gRPC-Gateway

gRPC-Gateway는 gRPC 서비스를 RESTful HTTP API로 자동 변환합니다. 하나의 서비스 정의로 gRPC와 REST를 동시에 제공할 수 있습니다.

gateway 어노테이션 추가
protobuf
import "google/api/annotations.proto";
 
service UserService {
  rpc GetUser(GetUserRequest) returns (GetUserResponse) {
    option (google.api.http) = {
      get: "/api/v1/users/{id}"
    };
  }
 
  rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
    option (google.api.http) = {
      post: "/api/v1/users"
      body: "*"
    };
  }
 
  rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
    option (google.api.http) = {
      get: "/api/v1/users"
    };
  }
}
gateway-setup.go
go
func runGateway(ctx context.Context, grpcAddr, httpAddr string) error {
    mux := runtime.NewServeMux(
        runtime.WithMarshalerOption(
            runtime.MIMEWildcard,
            &runtime.JSONPb{
                MarshalOptions: protojson.MarshalOptions{
                    UseProtoNames: true,
                },
            },
        ),
    )
 
    opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
 
    err := userv1.RegisterUserServiceHandlerFromEndpoint(ctx, mux, grpcAddr, opts)
    if err != nil {
        return fmt.Errorf("게이트웨이 등록 실패: %w", err)
    }
 
    slog.Info("HTTP 게이트웨이 시작", "addr", httpAddr)
    return http.ListenAndServe(httpAddr, mux)
}
Tip

gRPC-Gateway를 사용하면 내부 서비스 간에는 gRPC의 성능 이점을 활용하고, 외부 클라이언트(웹 브라우저, 모바일 앱)에는 REST API를 제공하는 패턴을 쉽게 구현할 수 있습니다.


헬스 체킹

gRPC 헬스 체킹 프로토콜은 로드밸런서와 오케스트레이션 도구가 서비스 상태를 확인하는 표준 방법입니다.

health-check.go
go
import "google.golang.org/grpc/health"
import healthpb "google.golang.org/grpc/health/grpc_health_v1"
 
func main() {
    grpcServer := grpc.NewServer()
 
    // 헬스 체크 서비스 등록
    healthServer := health.NewServer()
    healthpb.RegisterHealthServer(grpcServer, healthServer)
 
    // 서비스 상태 설정
    healthServer.SetServingStatus("user.v1.UserService",
        healthpb.HealthCheckResponse_SERVING)
 
    // 서비스 중지 시
    // healthServer.SetServingStatus("user.v1.UserService",
    //     healthpb.HealthCheckResponse_NOT_SERVING)
}

서비스 디스커버리

마이크로서비스 환경에서 서비스 인스턴스를 동적으로 찾는 패턴입니다.

service-discovery.go
go
// gRPC 리졸버를 통한 서비스 디스커버리
conn, err := grpc.Dial(
    "dns:///user-service.default.svc.cluster.local:50051",
    grpc.WithDefaultServiceConfig(`{
        "loadBalancingConfig": [{"round_robin": {}}]
    }`),
    grpc.WithTransportCredentials(insecure.NewCredentials()),
)
Info

Kubernetes 환경에서는 DNS 기반 서비스 디스커버리가 가장 간단합니다. gRPC의 내장 리졸버(Resolver)가 DNS SRV 레코드를 조회하여 서비스 인스턴스를 자동으로 찾고, 라운드 로빈 등의 로드밸런싱 정책을 적용합니다.


정리

이번 장에서 살펴본 핵심 내용을 정리합니다.

  • Protocol Buffers는 gRPC의 메시지 형식이자 서비스 인터페이스 정의 언어입니다
  • gRPC는 유니어리, 서버 스트리밍, 클라이언트 스트리밍, 양방향 스트리밍 네 가지 통신 방식을 지원합니다
  • 인터셉터는 gRPC의 미들웨어로, 로깅, 인증, 복구 등 횡단 관심사를 처리합니다
  • gRPC-Gateway로 하나의 서비스 정의로 gRPC와 REST를 동시에 제공할 수 있습니다
  • 헬스 체킹과 서비스 디스커버리는 마이크로서비스 운영의 필수 요소입니다

다음 장 미리보기

11장에서는 실전 프로젝트로 이 시리즈의 모든 내용을 종합합니다. Chi 기반 API 서버, PostgreSQL(pgx), JWT 인증, 미들웨어 스택, 구조화된 로깅, 테스트, Docker 빌드, CI/CD 파이프라인까지 완전한 백엔드 서비스를 구축합니다.

이 글이 도움이 되셨나요?

관련 글

프로그래밍

9장: 테스트 전략 - 단위, 통합, 벤치마크

Go의 testing 패키지를 중심으로 테이블 기반 테스트, testify, httptest, 목(Mock) 생성, testcontainers-go 통합 테스트, 벤치마크, 퍼징 테스트 전략을 다룹니다.

2026년 8월 28일·14분
프로그래밍

11장: 실전 프로젝트 - Go 백엔드 API 서버 구축

시리즈의 모든 내용을 종합하여 Chi 기반 REST API 서버, PostgreSQL(pgx), JWT 인증, 미들웨어 스택, 구조화된 로깅, 테스트, Docker 빌드, CI/CD 파이프라인을 갖춘 완전한 백엔드를 구축합니다.

2026년 9월 2일·16분
프로그래밍

8장: 미들웨어, 인증, API 설계

Go 백엔드의 미들웨어 패턴, JWT 인증 구현, CORS 설정, 레이트 리미팅, 요청 유효성 검사, OpenAPI/Swagger 문서 생성, log/slog를 활용한 구조화된 로깅을 다룹니다.

2026년 8월 25일·11분
이전 글9장: 테스트 전략 - 단위, 통합, 벤치마크
다음 글11장: 실전 프로젝트 - Go 백엔드 API 서버 구축

댓글

목차

약 12분 남음
  • 학습 목표
  • Protocol Buffers
    • Protobuf란
    • 코드 생성
  • gRPC 서버 구현
    • 유니어리 RPC
    • gRPC 에러 처리
  • 스트리밍 RPC
    • 서버 스트리밍
    • 양방향 스트리밍
  • 인터셉터
    • 유니어리 인터셉터
  • gRPC-Gateway
  • 헬스 체킹
  • 서비스 디스커버리
  • 정리
  • 다음 장 미리보기