Contents
1. Go とマイクロサービスアーキテクチャの概要
Go の 軽量バイナリ と 高並行性 は、数百〜数千に及ぶ小規模サービス群を管理する上で大きな利点となります。ここでは、Go がマイクロサービスで採用されやすい3つのポイントと、それがもたらす運用上の効果を整理します。
- 高速起動 & 低フットプリント
- コンパイル時に単一バイナリへ静的リンクできるため、コンテナサイズは 20 ~ 30 MB 程度に抑えられます。
-
起動時間がミリ秒単位であることから、オートスケーリングやサーバーレス環境でも快適です。
-
ネイティブな並行処理
-
goroutineとチャネルにより数十万レベルの軽量スレッドを低コストで扱えるため、I/O バウンドなサービスでも高いスループットが期待できます。 -
充実した標準ライブラリ
net/http,http2,grpc(公式サポートは外部モジュールですが Go のエコシステムに深く統合)など、外部依存を最小化しつつ本格的な通信機構が利用できます。
結論:Go は「デプロイの単純さ」+「実行時パフォーマンス」の両輪でマイクロサービスのスケールアウトと運用コスト削減を支援します。
2. プロジェクト構成と主要コンポーネント実装
この章では、monorepo スタイルのディレクトリ設計例と、認証・設定管理・ロギング・メトリクスといった共通基盤コードをどのように配置すれば保守しやすくなるかを示します。各サブセクションは実装上のポイントを簡潔に解説した後、コード例を提示します。
2.1 ディレクトリ設計と Go モジュール管理
以下の構造は、サービス数が増えても可視性が保たれることを前提に設計されています。internal/ 以下は同一モノレポ内からのみ参照可能で、外部公開用パッケージは pkg/ に集約します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
myapp/ ├─ cmd/ # エントリポイント(各サービスの main) │ ├─ user/ # ユーザーサービス │ └─ order/ ├─ internal/ # ビジネスロジック(モジュール間で非公開) │ ├─ auth/ │ ├─ config/ │ ├─ logger/ │ └─ metrics/ ├─ api/ # protobuf / OpenAPI 定義 ├─ pkg/ # 複数サービスで再利用可能なユーティリティ └─ go.mod # Go 1.22 用モジュール宣言 |
go.modはプロジェクトルートに置き、module github.com/yourorg/myappとし、go 1.22を明示します。- 各サービスは
cmd/<service>/main.goにエントリポイントを配置し、internal/*のコードをインポートするだけで完結します。
注意:この記事で参照しているサンプルリポジトリ(
https://github.com/ebiyy/golang-microservices)は執筆時点では公開されていますが、将来的にアーカイブされたり内容が古くなる可能性があります。実務に導入する際は、最新のコミットやフォークを確認し、自組織向けにカスタマイズしたベースリポジトリを作成すると安全です。
2.2 認証・認可(JWT + ミドルウェア)
JWT を利用した認証ミドルウェアの実装例です。github.com/golang-jwt/jwt/v5 は コミュニティがメンテナンスするデファクトスタンダード であり、Go 標準ライブラリの一部ではありませんが、モジュールとして広く採用されています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
// internal/auth/jwt.go package auth import ( "errors" "net/http" "strings" "github.com/golang-jwt/jwt/v5" ) // 署名検証に使用する鍵取得関数(例:環境変数 or KMS) func keyFunc(token *jwt.Token) (interface{}, error) { // HMAC の場合 if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, errors.New("unexpected signing method") } secret := []byte("your-secret-key") // 本番では安全に管理 return secret, nil } // Middleware はリクエストヘッダーの Authorization から JWT を検証します。 func Middleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { authHeader := r.Header.Get("Authorization") if authHeader == "" { http.Error(w, "missing token", http.StatusUnauthorized) return } // 「Bearer <token>」形式を想定 parts := strings.SplitN(authHeader, " ", 2) if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") { http.Error(w, "invalid token format", http.StatusUnauthorized) return } tokenStr := parts[1] _, err := jwt.Parse(tokenStr, keyFunc) if err != nil { http.Error(w, "invalid token: "+err.Error(), http.StatusUnauthorized) return } // トークンが有効であれば次のハンドラへ next.ServeHTTP(w, r) }) } |
ポイント:
jwt/v5は Go 1.22 のモジュールとして公式に 推奨 されているわけではなく、実務で広く使われていることを根拠に選択しています。Go 標準のcryptoパッケージと組み合わせて自前実装する選択肢もあります。
2.3 設定管理(Viper + 環境変数)
設定ファイルと環境変数を統一的に扱える Viper の基本構成です。Kubernetes の ConfigMap / Secret と相性が良く、デプロイ時の上書きも容易です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
// internal/config/config.go package config import ( "github.com/spf13/viper" ) // Load はカレントディレクトリの ./configs 配下にある設定ファイルを読み込みます。 func Load() (*viper.Viper, error) { v := viper.New() v.SetConfigName("config") // config.yaml など v.AddConfigPath("./configs") v.AutomaticEnv() // 環境変数を上書きとして使用 if err := v.ReadInConfig(); err != nil { return nil, err } return v, nil } |
2.4 ロギング(Zap)
構造化 JSON ログは Loki や Elastic Stack といった集約基盤での検索・可視化が得意です。以下は本番環境向けの最小構成です。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// internal/logger/logger.go package logger import ( "go.uber.org/zap" ) // New は JSON エンコーディングの Production 設定でロガーを生成します。 func New() (*zap.Logger, error) { cfg := zap.NewProductionConfig() cfg.Encoding = "json" return cfg.Build() } |
2.5 メトリクス(Prometheus client)
Prometheus 用メトリクスは Register 関数で一括登録し、HTTP ハンドラに /metrics エンドポイントを付与すれば自動的に取得できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
// internal/metrics/metrics.go package metrics import ( "github.com/prometheus/client_golang/prometheus" ) var ( // HTTP リクエスト数カウンタ(service, method, code ラベル付き) RequestCnt = prometheus.NewCounterVec( prometheus.CounterOpts{ Name: "http_requests_total", Help: "Total number of HTTP requests processed.", }, []string{"service", "method", "code"}, ) ) // Register は全メトリクスを Prometheus に登録します。 func Register() { prometheus.MustRegister(RequestCnt) } |
まとめ:上記の共通基盤はすべて Go のエコシステムで成熟したライブラリです。コードベースが統一されることで、チーム全体の認知コストと保守工数を大幅に削減できます。
3. サービス間通信:gRPC と REST のハイブリッド実装
内部サービスは 高速かつ型安全 な gRPC を、外部向け API は 広くサポートされている REST/JSON に公開する構成が一般的です。ここでは両者を同時に提供するための手順と、選択すべきツールチェーンのバージョン情報を示します。
3.1 gRPC インターフェース定義とサーバ実装
api/proto/user.proto はサービス契約そのものです。コード生成は protoc-gen-go と 最新版 の protoc-gen-go-grpc(2024‑05 時点で v1.5.1 が安定版)を使用します。
|
1 2 3 4 |
# Go 1.22 環境でのインストール例 go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// api/proto/user.proto syntax = "proto3"; package user; service UserService { rpc GetUser (GetUserRequest) returns (GetUserResponse); } message GetUserRequest { string id = 1; } message GetUserResponse { string id = 1; string name = 2; string email = 3; } |
コード生成コマンド(protoc がインストール済みであることが前提):
|
1 2 3 4 5 |
protoc \ --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ api/proto/user.proto |
サーバ実装例
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// internal/service/user/grpc.go package user import ( "context" "github.com/yourorg/myapp/api/proto/user" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) // server は自動生成されたインターフェースを埋め込みます。 type server struct { user.UnimplementedUserServiceServer repo UserRepository } // GetUser はリポジトリからユーザー情報を取得し、gRPC レスポンスに変換します。 func (s *server) GetUser(ctx context.Context, req *user.GetUserRequest) (*user.GetUserResponse, error) { u, err := s.repo.FindByID(req.Id) if err != nil { return nil, status.Errorf(codes.NotFound, "user %s not found", req.Id) } return &user.GetUserResponse{ Id: u.ID, Name: u.Name, Email: u.Email, }, nil } |
ポイント:
protoc-gen-go-grpcのバージョンは常に最新の安定版をgo install ...@latestまたは公式リリースページで確認してください。古いバージョンでは生成コードが Go 1.22 の新機能(例: generics)と衝突することがあります。
3.2 REST エンドポイントへのラップ(grpc‑gateway)
外部クライアント向けに JSON/HTTP を提供するには、grpc-gateway が最も手軽です。以下は UserService の HTTP ラッパー設定例です。
|
1 2 |
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v2.16.0 |
|
1 2 3 4 5 6 7 8 9 10 11 |
// api/proto/user.proto にオプションを追加(省略可) import "google/api/annotations.proto"; service UserService { rpc GetUser (GetUserRequest) returns (GetUserResponse) { option (google.api.http) = { get: "/v1/users/{id}" }; } } |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// cmd/user/main.go(抜粋) package main import ( "context" "log" "net" userpb "github.com/yourorg/myapp/api/proto/user" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" ) func main() { // gRPC サーバ起動 lis, _ := net.Listen("tcp", ":50051") grpcSrv := grpc.NewServer() userpb.RegisterUserServiceServer(grpcSrv, &user.Server{repo: NewRepo()}) go func() { log.Fatal(grpcSrv.Serve(lis)) }() // HTTP/JSON ゲートウェイ起動 ctx := context.Background() mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} err := userpb.RegisterUserServiceHandlerFromEndpoint(ctx, mux, "localhost:50051", opts) if err != nil { log.Fatalf("failed to start gateway: %v", err) } log.Println("HTTP/JSON gateway listening on :8080") log.Fatal(http.ListenAndServe(":8080", mux)) } |
| 項目 | gRPC (内部) | REST / JSON (外部) |
|---|---|---|
| レイテンシ | 非常に低い(HTTP/2 + バイナリ) | やや高め(テキスト+ヘッダーオーバーヘッド) |
| スキーマ管理 | .proto が単一ソースでコード生成可能 |
grpc-gateway の annotation で自動変換 |
| ブラウザ対応 | gRPC‑Web 必要 | そのまま利用可 |
| エコシステム | 高速内部呼び出しに最適 | 外部クライアント・モバイル向けに便利 |
結論:内部サービスは純粋な gRPC、外部公開は grpc-gateway が生成する JSON/HTTP ラッパーでハイブリッド構成を取ると、開発コストと性能のバランスが最適化できます。
4. Kubernetes デプロイと運用基盤
本章では マルチステージ Dockerfile のベストプラクティスから、Kubernetes マニフェスト、そして Service Discovery(K8s DNS と Consul)の併用までを実践的に解説します。特にビルド時の CGO_ENABLED=0 に関する注意点と、Alpine イメージとの相性について詳述します。
4.1 Dockerfile のマルチステージビルド(静的バイナリ)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
# ---------- Build stage ---------- FROM golang:1.22-alpine AS builder WORKDIR /src # モジュールキャッシュを先に取得 COPY go.mod go.sum ./ RUN go mod download # ソースコード全体をコピー COPY . . # CGO を無効化して完全静的リンクを試みるが、Alpine は musl libc です。 # 完全に glibc に依存しないバイナリであれば Alpine 上でも問題なく動作します。 # もし外部 C ライブラリ(例: sqlite3)を利用する場合は # - `musl-dev` をインストールしてビルド # - または `scratch` / `distroless` ベースのイメージへ切り替える ENV CGO_ENABLED=0 GOOS=linux GOARCH=amd64 RUN go build -ldflags="-s -w" -o /app/bin/user ./cmd/user/main.go # ---------- Release stage ---------- FROM alpine:3.20 # musl ベース。static binary が glibc 依存でない限り問題なし RUN apk add --no-cache ca-certificates tzdata COPY --from=builder /app/bin/user /usr/local/bin/ EXPOSE 8080 ENTRYPOINT ["user"] |
- 注意点:
CGO_ENABLED=0は Go の標準ライブラリだけで構成されたバイナリを 完全に静的 にしますが、外部 C ライブラリに依存する場合はmusl-devが必要になるか、scratchへの移行が安全です。 - サイズ:最終イメージは約30 MB 程度に収まり、CI のキャッシュやレジストリ転送コストを削減できます。
4.2 Kubernetes マニフェスト例
以下は user-service をデプロイする際の典型的なリソース定義です。Readiness Probe と Liveness Probe を必ず設定し、自己回復を有効化します。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# deployment.yaml apiVersion: apps/v1 kind: Deployment metadata: name: user-service spec: replicas: 3 selector: matchLabels: app: user template: metadata: labels: app: user spec: containers: - name: user image: ghcr.io/yourorg/user-service:${IMAGE_TAG} ports: - containerPort: 8080 envFrom: - configMapRef: name: user-config - secretRef: name: db-credentials readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 5 periodSeconds: 10 livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 15 periodSeconds: 30 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# service.yaml apiVersion: v1 kind: Service metadata: name: user-service spec: selector: app: user ports: - protocol: TCP port: 80 # クラスタ内で公開するポート targetPort: 8080 # コンテナの実際のリッスンポート type: ClusterIP |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
# ingress.yaml apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: api-ingress spec: rules: - host: api.example.com http: paths: - path: /users pathType: Prefix backend: service: name: user-service port: number: 80 |
|
1 2 3 4 5 6 7 8 9 |
# configmap.yaml apiVersion: v1 kind: ConfigMap metadata: name: user-config data: LOG_LEVEL: "info" METRICS_PORT: "9090" |
|
1 2 3 4 5 6 7 8 9 10 |
# secret.yaml apiVersion: v1 kind: Secret metadata: name: db-credentials type: Opaque stringData: USERNAME: "dbuser" PASSWORD: "s3cr3t!" |
4.3 Service Discovery:Kubernetes DNS と Consul の併用
| 環境 | 主な利点 | 補足 |
|---|---|---|
Kubernetes 内部 DNS (<svc>.<ns>.svc.cluster.local) |
ネイティブで軽量、設定不要 | 同一クラスタ内のサービス間通信に最適 |
| Consul (外部マルチクラスタ・ハイブリッド環境) | 複数 K8s クラスタや VM との名前解決が可能 | *.service.consul ドメインを DNS ポリシーで追加 |
Go 側での切り替えは以下のように実装できます。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// internal/discovery/resolver.go package discovery import ( "context" "net" ) // NewResolver は環境変数 `USE_CONSUL` が true のとき Consul DNS を、そうでなければ K8s DNS を使用します。 func NewResolver() *net.Resolver { if os.Getenv("USE_CONSUL") == "true" { return &net.Resolver{ PreferGo: true, Dial: func(ctx context.Context, network, address string) (net.Conn, error) { d := net.Dialer{} // Consul の DNS エンドポイント(例: consul.service.consul:53) return d.DialContext(ctx, "udp", "consul.service.consul:53") }, } } // デフォルトは OS が提供する K8s DNS return net.DefaultResolver } |
まとめ:マルチステージ Dockerfile と Kubernetes の標準リソースだけで、スケーラブルかつ可観測性の高いデプロイパイプラインが構築できます。CGO 無効化による静的バイナリは musl 互換 に注意しながら使用すれば、Alpine イメージでも問題なく動作します。
5. テスト・CI/CD パイプラインと次のステップ
本節では ユニットテスト / gRPC 契約テスト / E2E テスト の実装例に続き、GitHub Actions を用いたビルド・テスト・デプロイフローを示します。最後に、サンプルリポジトリの管理上の注意点と、導入後に行うべき改善ポイントをまとめます。
5.1 テスト戦略
| レイヤー | 主な目的 | 推奨ツール |
|---|---|---|
| ユニットテスト | ビジネスロジック単体の正当性検証 | testing + testify/assert |
| gRPC 契約テスト | サーバ・クライアント間のインターフェース互換性確認 | bufconn(in‑memory) |
| E2E テスト | 実際の HTTP/gRPC エンドポイントを通したシナリオ検証 | k6、Postman/Newman |
ユニットテスト例
|
1 2 3 4 5 6 7 8 9 10 |
func TestGetUser_OK(t *testing.T) { repo := &mockUserRepo{ users: map[string]*User{"123": {ID: "123", Name: "Alice", Email: "alice@example.com"}}, } srv := &server{repo: repo} resp, err := srv.GetUser(context.Background(), &user.GetUserRequest{Id: "123"}) assert.NoError(t, err) assert.Equal(t, "Alice", resp.Name) } |
gRPC 契約テスト(bufconn)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
func TestGRPCContract_NotFound(t *testing.T) { lis := bufconn.Listen(1024 * 1024) s := grpc.NewServer() userpb.RegisterUserServiceServer(s, &server{repo: &mockUserRepo{}}) go func() { _ = s.Serve(lis) }() ctx := context.Background() conn, err := grpc.DialContext(ctx, "bufnet", grpc.WithContextDialer(func(context.Context, string) (net.Conn, error) { return lis.Dial() }), grpc.WithInsecure()) require.NoError(t, err) client := userpb.NewUserServiceClient(conn) _, err = client.GetUser(ctx, &userpb.GetUserRequest{Id: "nonexistent"}) assert.Error(t, err) // NotFound が返ることを確認 } |
E2E テスト(k6)
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import http from 'k6/http'; import { check, sleep } from 'k6'; export const options = { stages: [{ duration: "30s", target: 20 }], }; export default function () { const res = http.get('http://api.example.com/v1/users/123'); check(res, { 'status is 200': (r) => r.status === 200, 'response has name': (r) => JSON.parse(r.body).name !== '', }); sleep(1); } |
5.2 GitHub Actions による CI/CD パイプライン
以下は ビルド → テスト → Docker イメージ作成 → デプロイ の典型フローです。protoc-gen-go-grpc@v1.5.1 を明示的にインストールし、コード生成が常に最新バージョンで行われるようにしています。
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
name: CI/CD Pipeline on: push: branches: [ main ] pull_request: jobs: # ------------------------------------------------- # 1. ビルド・テスト(ユニット & 契約) # ------------------------------------------------- build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v5 with: go-version: '1.22' - name: Cache Go modules uses: actions/cache@v3 with: path: | ~/.cache/go-build ~/go/pkg/mod key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} # プロトコルバッファのコード生成(最新版を取得) - name: Install protobuf tools run: | go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.33 go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.5.1 sudo apt-get update && sudo apt-get install -y protobuf-compiler - name: Generate proto code run: | protoc --go_out=. --go_opt=paths=source_relative \ --go-grpc_out=. --go-grpc_opt=paths=source_relative \ api/proto/*.proto - name: Build binaries (debug) run: | go build -o bin/user ./cmd/user/main.go go build -o bin/order ./cmd/order/main.go - name: Run unit & contract tests run: go test ./... -v -race # ------------------------------------------------- # 2. Docker イメージの作成とレジストリへのプッシュ # ------------------------------------------------- docker-push: needs: build-test runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Log in to GHCR uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Build & push Docker image run: | IMAGE_TAG=${{ github.sha }} docker build -t ghcr.io/yourorg/user-service:${IMAGE_TAG} . docker push ghcr.io/yourorg/user-service:${IMAGE_TAG}} # ------------------------------------------------- # 3. Kubernetes へローリングデプロイ # ------------------------------------------------- deploy: needs: docker-push runs-on: ubuntu-latest environment: production steps: - name: Install kubectl uses: azure/setup-kubectl@v3 with: version: 'v1.28' - name: Deploy new image (rolling update) env: IMAGE_TAG: ${{ github.sha }} run: | kubectl set image deployment/user-service user=ghcr.io/yourorg/user-service:${IMAGE_TAG} \ --record |
ポイントまとめ
- コード生成は毎回実行 → プラグインバージョンの差異でビルドが壊れるリスクを排除。
- テスト失敗時はデプロイしない →
needs依存関係でフェイルオーバーを防止。 - 画像タグにコミット SHA を使用 → リリースのトレーサビリティが確保でき、ロールバックも簡単。
5.3 サンプルリポジトリの管理上の注意点
- 本稿で紹介した
https://github.com/ebiyy/golang-microservicesは執筆時点では 公開 されていますが、将来的に削除・アーカイブされる可能性があります。 - 最新の依存バージョンや CI 設定を確実に保つため、自組織でフォーク または 社内テンプレートリポジトリ を作成し、
go.modのreplace句で内部ミラーを指すようにすると安全です。 - 定期的(例: 毎月)に CI が走る 依存更新ジョブ(Dependabot 等)を導入し、脆弱性やバージョンのずれを自動検出しましょう。
5.4 次のステップ
- リポジトリをフォーク → 社内 CI に接続し、ビルド・テストがすべて成功することを確認。
- 環境変数/Secret 管理 を Vault や AWS Secrets Manager と連携させ、
Viperのバックエンドを拡張。 - Observability:OpenTelemetry SDK を導入し、トレース・メトリクスを統合的に収集。
- Canary デプロイ:Argo Rollouts などで段階的リリースを試み、問題検知の速度を向上させる。
最終的な目標は「コード → コンテナ → クラスタ」までのフローが 自動化 され、障害時に即座にロールバックできる体制を整えることです。この記事で示した構成・ツールはその土台となりますので、ぜひ実務プロジェクトへ適用してみてください。