最初に
本記事の内容は、サーバーサイドにほとんど触れてこなかったUnityエンジニアに向けて、最近の主流構成を学びながら少しずつサーバーの知識を身につけられるハンズオン形式で進めます。この記事が、サーバーサイドに対する理解を深める一助になれば幸いです。
本記事の対象者は以下の通りです。
- A Tour of Goを一通り終わらせたが次に何をするべきかわからない初心者サーバーエンジニア
- gRPCについて学びたいが、Unityとの関連性も知りたい初学者
- なんとなくGoやgRPCについては理解できるけど、具体的な仕組みについて知りたいUnityエンジニアの方
また、今回のハンズオン記事は全7章が存在します。
こちらから他の章も見ていただけると嬉しいです!
「第一章、環境構築」
「第二章、Cloud Runへのデプロイ」
「第三章、CI/CDパイプライン構築」
「第四章、Firebaseプロジェクトの作成とFirebaseSDK導入」
「第五章、FirebaseSDKを用いたアカウント作成とログイン実装」
今回の環境
- OS : MacOS
- IDE : GoLand
- Gitクライアント : Fork
前提
- A Tour of Goを一通り終わらせていること
- gRPCの基本的な概要を理解していること
- こちらの本がおすすめです(Chapter 02まで読んでおけば本記事の内容についていけます):作ってわかる!はじめてのgRPC
- 簡単なUnityの操作は行えること
- Git、GitHubを使用したことがあること
また、記事の内容を自由に学習できるよう、GitHub上に公開リポジトリを作成しました。各ステップに対応したバージョンをReleasesとして登録しているので、興味のあるステップから学習を始められます。
上記リポジトリ内容を使用して特定のステップから始める場合はリポジトリをForkするか、手元にDownloadなどをしてから進めるようお願いします。
0. なぜバックエンドでGoを選択するか
バックエンドの技術選定は、開発効率や運用性にも大きく影響を与えます。では、なぜソーシャルゲームプロジェクトではバックエンドにgRPCとGoの組み合わせが選択されることが多いのでしょうか?
私がこれまで関わってきたプロジェクトでは少なくとも上記の選択が行われてました…。
高速で効率的
Goは低レイテンシかつ高スループットな処理を実現します。コンパイル言語として、ランタイムオーバーヘッドが少なく、パフォーマンスも安定して出ます。特にGoのゴルーチンは、非常に軽量なスレッドのようなもので、数万単位のゴルーチンを同時に実行しても、システムリソースへの負担が少なくすみます。
大量の同時接続が発生するソーシャルゲームプロジェクト開発では、プレイヤーからの大量のリクエストをリアルタイムで処理する必要があるため、この特徴はかなりの強みと言えます。
シンプルさと柔軟性
Goは設計がシンプルにでき、柔軟性も高いです。また、マイクロサービスアーキテクチャとの相性が良く、サービスをスケールアウトする際にも柔軟に対応できます。サーバーサイドに特化したライブラリやフレームワーク(gRPC含む)も豊富にあるため、複雑な処理の構築も行いやすいです。
学習コストの低さ
ゲームプロジェクトでは、バックエンド開発者とクライアント開発者が異なるスキルセットを持つケースが一般的で、Goはバックエンドエンジニアにとって学習コストが低いことも強みです。
これらの点を踏まえて、Goはサーバーサイドのパフォーマンスや運用効率を最大化したい場合に選択されているのだと思います。
以下のDeNAの記事も参考にしてみてください!
1. Gitリポジトリ作成
まずは、このハンズオン用のGitリポジトリを自分で作成していきましょう。
GitHubにログインして新しいリポジトリを作成してください。
リポジトリ名、説明(Description)、公開設定などは自由に設定してOKです。
今回は「grpc-chat-app-sample」とします。
.gitignore
ファイルは、Goのテンプレートを選択してください。
Goの.gitignoreテンプレートに含まれる主な項目は?
-
/bin/
:Goのビルドで生成されるバイナリファイルを除外する -
/pkg/
:ビルドキャッシュや依存関係のパッケージを除外する -
.exe
、.dll
、.so
:ビルド結果の実行ファイルを除外する -
.test
:テスト用の実行ファイルを除外する
リポジトリが作成できたら、任意の場所にCloneする用の新規ディレクトリ「chat-app」を作成しましょう。
本記事ではコマンド操作で用意していきます。
1.ディレクトリ作成
mkdir chat-app
2.ディレクトリ内へ移動
cd chat-app/
3.先ほど作成したリポジトリをClone
git clone {リポジトリのクローン用URL}
コマンド結果:
Cloning into 'grpc-chat-app-sample'...
remote: Enumerating objects: 4, done.
remote: Counting objects: 100% (4/4), done.
remote: Compressing objects: 100% (4/4), done.
remote: Total 4 (delta 0), reused 0 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (4/4), done.
2. Goのインストール
タグv0.1に対応しています。
Go言語を使用するために、公式サイトからGoの最新版をダウンロードしてインストールしましょう。
安定して動作させたい場合は、Go 1.23.0
をインストールしてください。
Go公式ダウンロードページ
(MacユーザーでM1チップ以降をお使いの方は、arm64版をダウンロードしてください)
ダウンロードが完了したら、インストールを進めてください。
インストール後、ターミナル(またはコマンドプロンプト)を開き、以下のコマンドを使用してGoが正しくインストールされているか確認しましょう。
go version
コマンド結果:
go version go1.23.0 darwin/arm64
3. Goプロジェクトの初期化
先ほど作成したリポジトリを、新しいGoのプロジェクトとして初期化していきましょう。
まずはリポジトリのあるディレクトリへ移動します。
cd grpc-chat-app-sample/
以下のコマンドを実行してください。
このコマンドを実行すると、プロジェクトのルートディレクトリに go.mod
というファイルが作成され、外部ライブラリや依存関係の管理ができるようになります。
go mod init grpc-chat-app-sample
コマンド結果:
go: creating new go.mod: module grpc-chat-app-sample
次に、以下のコマンドを実行してください。
これはGo用のgRPCライブラリをインストールするコマンドで、GoでgRPCサーバーやクライアントを実装する際に必要となります。
go get google.golang.org/grpc
コマンド結果:
go: added golang.org/x/net v0.25.0
go: added golang.org/x/sys v0.20.0
go: added golang.org/x/text v0.15.0
go: added google.golang.org/genproto/googleapis/rpc v0.0.0-20240528184218-531527333157
go: added google.golang.org/grpc v1.65.0
go: added google.golang.org/protobuf v1.34.1
go.modファイル、go.subファイルについてもっと詳しく!
-
go.mod
ファイル:
プロジェクトで使用するGoのバージョンや依存するパッケージのバージョンを記録します。 -
go.sum
ファイル:
go.mod
に記載された依存パッケージの具体的なバージョンや、そのチェックサム(パッケージの整合性を確認するためのもの)を記録します。
4. Dockerのインストール
続いて、Dockerを導入していきます…が、その前にDockerとは何かを簡単におさらいしましょう。
すでにDockerを知っている方は、この部分を飛ばして構いません。
Dockerとは?
Dockerは、アプリケーションとその依存関係を一つの「コンテナ」にまとめて実行できるプラットフォームです。
この「コンテナ」というのは、軽量でポータブルな仮想環境のようなものです。
主な特徴は以下の通りです。
-
コンテナ化:
Dockerでは、アプリケーションとそれに必要なすべてのライブラリや設定ファイルを一つのパッケージ(コンテナ)にまとめます。コンテナは、WindowsやMacなど、どんな環境でも同じように動作が可能です。 -
軽量:
Dockerコンテナは、従来の仮想マシンよりも軽量で、高速に起動します。コンテナはホストマシンのOSカーネルを共有するため、リソースを効率的に使用できます。 -
再現性:
Dockerを使えば、特定の環境や依存関係を簡単に再現できます。これにより、開発者間で「自分の環境では動いた」という問題が減ります。
次に、Dockerで使われる基本的な概念や用語を見ていきましょう。
-
Dockerfile:
Dockerイメージを作成するための「設計図」です。アプリケーションの環境設定や必要なソフトウェアのインストール手順を記述したテキストファイルで、この内容に基づいてDockerイメージが作られます。 -
Dockerイメージ:
アプリケーションとその依存関係をパッケージ化したもので、コンテナを実行するための「実行可能なパッケージ」です。イメージは、Dockerfileという「設計図」から作成されます。 -
Dockerコンテナ:
Dockerイメージを実行して動作するインスタンスを指します。コンテナは、アプリケーションの動作環境を提供します。 -
Docker Hub:
Dockerイメージを共有するためのオンラインリポジトリです。公式イメージや、他の開発者が作成したイメージをここから取得できます。
一気に色々書いても頭に入らないかもしれません。
Dockerに関する書籍は色々出ているので、一度、どれか一冊を通して読むことをお勧めします。
ちなみに私のおすすめは「仕組みと使い方がわかる Docker&Kubernetesのきほんのきほん」という本です。
図解が多くて非常にわかりやすいので、初心者のかたにおすすめです。
最後に、使い方の流れについて簡単におさらいです。
- イメージの取得 or イメージの作成
- コンテナの実行
- コンテナの管理
また、コマンドを使用することで、実行中のコンテナを確認したり、停止したりすることができます。
(例: docker ps
, docker stop <コンテナID>
)
今回は、上記の説明をなんとなくでも良いので理解しておいてもらえれば十分です。
Dockerのインストール
それでは実際に公式サイトからDocker Desktopをダウンロードしましょう。
Docker Desktopとは?
Dockerは元々、Linux環境での使用を前提としていますが、WindowsやMacOSで使うためには、特別なセットアップが必要です。しかし、それではDockerを使用する難易度が上がってしまいます。Docker Desktopは最新版のDockerと、各OSでの実行に必要なツールをパッケージ化しており、簡単にセットアップできます。
ダウンロードが終わったらインストールを進めてください。
実際に立ち上げてみましょう。
以下の画面が表示されたら「Continue without signing in」をクリックしてください。
これでDockerを使う準備ができました。
尚、Dockerアップデートなどの更新お知らせがある場合は右下のベルマークから通知が来るので、もしも通知があれば適宜確認してみてください。
5. protoファイルの作成
タグv0.2に対応しています。
一通り、必要最低限の環境構築が完了しました。
ここからはgRPCコードを生成するために、.proto
ファイルを作成します。
.proto
ファイルは、 Protocol Buffers(プロトコルバッファ) を使用して、クライアントとサーバー間でやり取りする用のメッセージやメソッドを定義するために使います。.proto
ファイルの内容を元に、gRPCが使うコードが自動生成され、クライアントとサーバーが同じルールで通信を行えるようになります。
Protocol Buffersって?
Protocol Buffersは、Googleが開発したデータのシリアライズ(エンコードとデコード)形式です。プレーンテキストよりもデータをコンパクトに保存・送信でき、多くの言語やプラットフォームで使えるため、効率的にデータをやり取りできます。
どうして自動生成するの?
クライアントとサーバーの通信コードを手動で書くのは大変で、ミスも起こりやすくなります。自動生成を利用することで、定義に基づいた一貫したコードが生成され、クライアントとサーバーがスムーズに通信できるようになります。
新たにprotoというフォルダを作成し、その中にhelloworld.proto
ファイルを作成しましょう。
helloworld.proto
の中身は以下のようになります。
// Protocol Bufferのバージョンを指定
// 3はバージョン2よりも簡潔で使いやすい構文を提供する
syntax = "proto3";
// Protocol Buffersのパッケージ名を指定
// 名前空間だと思ってください
package helloworld;
// 生成するGoコードをどのパッケージに配置するか指定
// 実はディレクトリ構造も色々議論の余地があったり(後述)
option go_package = "gen/api/helloworld";
// Greeterという名前のサービスを定義
// ここで定義されたサービスはRPCインターフェース(後述)として扱われる
// gRPCは、このインターフェースの定義に基づいて通信を行うgRPCコードを自動生成する
service Greeter {
// クライアントからHelloRequestというメッセージを受け取り、
// それに基づいてHelloReplyというメッセージを返す、
// SayHelloというRPC(後述)を提供
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// クライアントから送られてくるメッセージの定義(後述)
message HelloRequest {
string name = 1;
}
// サーバーからクライアントに返すメッセージの定義(後述)
message HelloReply {
string message = 1;
}
コードの中に出てきた用語のいくつかを追加で説明します。
RPC(Remote Procedure Call)とは
RPCは、あるコンピュータ(クライアント)から別のコンピュータ(サーバー)上のメソッドを呼び出すための技術です。
gRPCはこの概念を取り入れ、ネットワーク越しにメソッドを実行し、その結果をクライアントに返します。
SayHello
はこのRPCの一例で、クライアントがHelloRequest
をサーバーに送り、サーバーがHelloReply
を返すという流れになります。
RPCインターフェースとは
RPCインターフェースは、クライアントがサーバー上で定義されたメソッドをリモートで呼び出す際に、どのメソッドを使用し、どのデータを送受信するかを決定するインターフェースです。
通常、proto
ファイルのservice
ブロックで定義されます。gRPCでは、サービスとして定義されたメソッドが、クライアントとサーバー間の通信エンドポイントになります。
エンドポイントって?
通信の入り口とも言えます。
クライアントがサーバーにリクエストを送る際に、サーバー上のどの機能やリソースにアクセスするかを指定するために使われます。エンドポイントは、通常、URLやURI(Uniform Resource Identifier)として表されます。
gRPCでは、各メソッドがエンドポイントとして機能します。エンドポイントには、リクエストの送信先と、そのリクエストに対応するメソッドが含まれます。
メッセージとは
メッセージは、gRPCを通じて送受信されるデータの形式です。
ここで定義されているHelloRequest
とHelloReply
は、それぞれクライアントから送られるリクエストと、サーバーから返されるレスポンスのデータ形式を表しています。
メッセージの各フィールドには、データの型と順序を指定する番号が付けられています。これにより、データが正確にエンコード・デコードされます。
ディレクトリ構造について
ディレクトリ構成には様々なパターンがあり、プロジェクトの規模や目的に応じて最適な構成が異なります。
以下に、それぞれのディレクトリ構成の意味と、今回のプロジェクトに最適な構成を説明します。
-
pkg/proto
:- この構成では、
proto
ディレクトリにProtocol Buffers関連のコードを配置します。 -
pkg
は再利用可能なコードをまとめる場所で、他のプロジェクトでも使い回せることを意図しています。
- この構成では、
-
pkg/grpc
:- この構成では、
grpc
ディレクトリにgRPCサービス関連のコードを配置します。通常、gRPCのサービスコードやインターフェースがここに置かれます。 - gRPCサービスを中心に据えたアプリケーションや、gRPC特化のプロジェクトでよく使われます。
- この構成では、
-
gen/api
:-
gen
は「generated(生成された)」の略で、通常、コード生成プロセスによって自動生成されたファイルを格納するためのディレクトリです。 -
api
はAPI定義やその関連コードを意味し、gen/api
はProtocol BuffersやgRPCを使用して自動生成されたAPI関連のコードを格納する場所として使用されます。
-
今回はシンプルに gen/api
を使用する形にしています。
6. gRPCのコード自動生成
protoファイルの準備ができたので、早速gRPCコードを自動生成していきましょう。
protoc
というProtocol Buffersのコンパイラを使用して、Go言語用のコードをコマンドから生成します。そのためにも、まずは protoc
と protoc-gen-go
プラグイン、合わせて protoc-gen-go-grpc
プラグインをインストールする必要があります。
今回もコマンドで実行を行います。
まずは以下のコマンドでprotoc
をインストールします。
今回はHomebrewを使ってインストールしますが、もしもHomebrewを使わないのであれば以下の記事に沿ってインストールを行うのが良いかもしれません。
brew install protobuf
問題なくinstallができたか、バージョンチェックのコマンドを打ってみましょう。
protoc --version
コマンド結果:
libprotoc 27.3
続いて、protoc-gen-go
プラグインをinstallします。
このプラグインは.proto
ファイルに定義されたメッセージ(例えば、HelloRequest
やHelloReply
)を、Goで扱える構造体に変換します。これにより、Goのコードで簡単にProtocol Buffersを使ったデータのシリアライズ(エンコード)やデシリアライズ(デコード)ができるようになります。
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
gRPCのコード生成のためには、protoc-gen-go-grpc
も必要です。
このプラグインは.proto
ファイルに定義されたgRPCサービス(例えば、Greeter
サービスやSayHello
メソッド)に対応するGoのサーバー側とクライアント側のコードを生成します。
以下のコマンドでインストールします。
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
プラグイン側の準備が整ったところで、gRPCコードを自動生成してみましょう。
プロジェクトルートディレクトリをワーキングディレクトリとした状態で、以下のコマンドを実行してください。
protoc --go_out=. --go-grpc_out=. ./proto/helloworld.proto
以下のように2つのgoファイルが新たに生成されていれば成功です!
上記2つのファイルについては以下の通りです。
-
helloworld.pb.go
:
このファイルには、Protocol Buffersのメッセージ定義に基づいたGoのデータ構造が含まれています。これはクライアントとサーバーの両方で使用されます。 -
helloworld_grpc.pb.go
:
このファイルには、gRPCサービスのコードが含まれています。具体的には、サーバー側のインターフェースとクライアント側のコードの両方が含まれています。
helloworld_grpc.pb.go
には、サーバー側のインターフェースと一緒にGo言語で書かれたクライアント側のコードも含まれていますが、実際にはUnity側のコードがクライアントとなるため、使用しません。
ただ、サーバー側だけを生成するコマンドは無く、クライアント実装だけを削除するのは少し難易度が高いので、ここでは一旦、自動生成されたクライアントコードは無視することにします。
7. サーバー側の実装
タグv0.3に対応しています。
それでは、サーバーをGoで実装していきましょう。
まず、ルートディレクトリに cmd
フォルダと server
フォルダを作成してください。
そのあとに、serverフォルダにmain.go
というファイルを作成します。
作成後のディレクトリ構造は以下のようになります。
grpc-chat-app-sample/
│
├── cmd/
│ └── server/ ← サーバーのエントリーポイントを格納するディレクトリ
│ └── main.go ← gRPCサーバーのエントリーポイント
│
├── gen/
│ └── api/
│ ├── helloworld.pb.go
│ └── helloworld_grpc.pb.go
│
├── proto/
│ └── helloworld.proto
│
├── go.mod
└── go.sum
cmdディレクトリって?
cmd
は「command」の略で、コマンドラインアプリケーションやサーバーアプリケーションのエントリーポイントとなるコードを格納するために使用されます。
このディレクトリには、通常プロジェクトの中で実行可能なアプリケーションを構築するためのmain.go
ファイルが置かれます。複数プロジェクトを含む場合はcmdの下にさらにプロジェクト名を持ったディレクトリを作成し、その中にmain.go
ファイルを置くことが多いようです。
早速main.go
に実装を追加していきます。
// 実行可能なGoアプリケーションであることを宣言する
package main;
// どの言語でも大抵お馴染みのパッケージインポート
import (
// クライアントからサーバーへのリクエストのスコープやキャンセル、タイムアウトの管理に使用する
"context"
// プログラムの実行中にログ(メッセージ)を記録するためのパッケージ
"log"
// ネットワーク関連の機能を提供
// ここでは、サーバーがリクエストを受け付けるためのネットワークリスナーを作成するためにimport
"net"
// gRPCサーバーを作成するためのパッケージ
"google.golang.org/grpc"
// 生成されたgRPCコードをインポート
// pbはこのパッケージを参照するための別名
pb "grpc-chat-app-sample/gen/api/helloworld"
)
const (
// ポート番号を定義
// クライアントはこのポートに接続してリクエストを送信する
port = ":50051"
)
// サーバー構造体の定義
// gRPCで定義されたサービスを実装する
type server struct {
// gRPCのサービスを実装する際に必要なデフォルトの設定を提供(後述)
pb.UnimplementedGreeterServer
}
// 関数の定義
// server型に属すメソッドで、inという名前の引数(*pb.HelloRequest型のポインタ)を受け取り
// HelloReply型のポインタと、エラー情報を戻り値としている
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
// サーバーが指定したポート番号(ここでは50051)でTCP接続を待ち受けるリスナーを作成
// リスナーは、クライアントからの接続を待機する
// ちなみに := は変数の宣言と初期化を同時に行う「短縮変数宣言」
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// 新しいgRPCサーバーを作成
// このサーバーがクライアントからのリクエストを処理する
s := grpc.NewServer()
// GreeterサービスをgRPCサーバーに登録
// ここで、&server{}は先ほど定義したserver構造体のインスタンスを渡している
pb.RegisterGreeterServer(s, &server{})
// Addr()メソッドは、net.Listenerインターフェースに定義されているメソッド
// リスナーが現在待ち受けているアドレス(IPアドレスとポート番号の組み合わせ)を返す
// これにより、サーバーがどのアドレスで接続を待機しているかを確認できる
log.Printf("server listening at %v", lis.Addr())
// s.Serve(lis)でサーバーを起動し、クライアントからの接続をリスナーを通じて受け付ける
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
protoファイルのpackage名と、main.goのpackage名の違いは?
-
proto
ファイルのpackage helloworld
:
Protocol Buffersのメッセージやサービスが属する名前空間を定義し、生成されるGoコードがどのパッケージに属するかを決定します。 -
main.go
のpackage main
:
Goプログラムのエントリーポイントを定義し、実行可能なプログラムとして動作させるためのものです。
こちらでも、コードの中に出てきた用語を追加で説明します。
UnimplementedGreeterServer
UnimplementedGreeterServer
は、gRPCサービスのすべてのメソッドに対してデフォルトの未実装のバージョンを提供します。
これにより、サービスを部分的に実装する際に役立ちます。例えば、あるメソッドだけを実装して、他のメソッドは未実装のままにしておくことができます。
pb.UnimplementedGreeterServer
を構造体server
に埋め込むことで、GreeterServer
インターフェースの、すべてのメソッドに対してデフォルトの未実装の実装を提供します。
main.go
の実装が済んだら手元で実行してみましょう。
ワーキングディレクトリをルートディレクトリにした状態で以下のコマンドを実行してください。
go run cmd/server/main.go
以下の表示がされれば成功です。
2024/08/29 14:44:12 server listening at [::]:50051
念のため、確認ができた時点で、上記コマンドを実行したターミナルを終了するか、Ctrl + C
を押下してください。
8. Dockerコンテナ立ち上げ・実行
タグv0.4に対応しています。
次に、Dockerのセットアップを行い、実際にDockerコンテナを立ち上げて、先ほどのサーバーをコンテナ上で実行してみましょう。
Dockerの特徴である「コンテナ化」については、以下の通りです。
Dockerでは、アプリケーションとそれに必要なすべてのライブラリや設定ファイルを一つのパッケージ(コンテナ)にまとめます。コンテナは、WindowsやMacなど、どんな環境でも同じように動作が可能です。
コンテナを実行するには、まずDockerイメージを作成する必要があります。
Dockerイメージを作成するためのDockerfile(Dockerイメージの「設計図」)をルートディレクトリに作成しましょう。
grpc-chat-app-sample/
│
├── cmd/
│ └── server/
│ └── main.go
│
├── gen/
│ └── api/
│ ├── helloworld.pb.go
│ └── helloworld_grpc.pb.go
│
├── proto/
│ └── helloworld.proto
│
├── Dockerfile ← ココに配置されるよう作成!
├── go.mod
├── go.sum
└── README.md
中身は以下の通りとなります。
# ベースイメージとしてGoの公式イメージを使用
FROM golang:1.20
# コンテナ内での作業ディレクトリを設定
# 以降のコマンドは、このディレクトリ内で実行される
WORKDIR /app
# ホストマシン(あなたのPC)上のプロジェクトディレクトリにあるGoモジュールと依存関係を
# コンテナの作業ディレクトリにコピーする
COPY go.mod .
COPY go.sum .
# プロジェクト全体のコピー
# アプリケーションのソースコードやその他のファイルがすべてコンテナ内に入る
COPY . .
WORKDIR /app/cmd/server
# go.modとgo.sumに基づいて、Goプロジェクトに必要な依存パッケージをダウンロードし、
# コンテナ内にインストール
RUN go mod download
# アプリケーションをビルド
# -o /grpc-chat-serverは、ビルドされた実行ファイルの名前と出力先を指定している
# この実行ファイルが、後でコンテナ内で実行されるgRPCサーバーになる
RUN go build -o /grpc-chat-server
# コンテナがリッスンするネットワークポートを指定
# これにより、コンテナ外部から50051番ポートを通じてgRPCサーバーにアクセスできるようになる
EXPOSE 50051
# CMDは、コンテナが起動したときに実行されるコマンドを指定するもの
# ここでは、先ほどビルドした/grpc-chat-serverという実行ファイルを実行するよう指定している
# これにより、コンテナが起動すると、自動的にgRPCサーバーが立ち上がり、リクエストを受け付ける状態になる
CMD ["/grpc-chat-server"]
Dockerfileの準備ができたら、以下のコマンドを実行して、Dockerfileをビルドし、Dockerイメージを作成しましょう。
docker build -t grpc-chat-server .
コマンド結果:
[+] Building 5.8s (13/13) FINISHED docker:desktop-linux
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 230B 0.0s
=> [internal] load metadata for docker.io/library/golang:1.23 0.8s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 2B 0.0s
=> [1/8] FROM docker.io/library/golang:1.23@sha256:{ランダムな文字列} 0.0s
=> [internal] load build context 0.0s
=> => transferring context: 6.46kB 0.0s
=> CACHED [2/8] WORKDIR /app 0.0s
=> CACHED [3/8] COPY go.mod . 0.0s
=> CACHED [4/8] COPY go.sum . 0.0s
=> CACHED [5/8] RUN go mod download 0.0s
=> [6/8] COPY . . 0.0s
=> [7/8] WORKDIR /app/cmd/server 0.0s
=> [8/8] RUN go build -o /grpc-chat-server 4.7s
=> exporting to image 0.2s
=> => exporting layers 0.2s
=> => writing image sha256:{ランダムな文字列} 0.0s
=> => naming to docker.io/library/grpc-chat-server 0.0s
これでDockerイメージが作成されました。
このDockerイメージを使ってコンテナ(アプリケーションの実行環境)を立ち上げてみましょう。もしもこの時点でgo run
しているターミナルがまだ存在している場合は該当のターミナルを終了するか、該当のターミナル上でCtrl + C
を押下してください。
Dockerコンテナを立ち上げるコマンドは以下の通りとなります。
docker run -p 50051:50051 grpc-chat-server
コマンド結果:
2024/08/29 08:18:53 server listening at [::]:50051
まとめ
今回の第一章では、gRPCとGoの環境構築、そしてDockerでのコンテナ化を体験しました。
今回のハンズオンでは以下のことを学びました。
- Goのインストールとプロジェクトの初期化
- Dockerのインストールとその基本概念の理解
- Protocol Buffersと.protoファイルの作成方法
- protocおよび関連プラグインを使用したコードの自動生成
- サーバーのエントリーポイントとなるmain.goの実装
- UnimplementedGreeterServerを活用した部分的なサービス実装
- Dockerfileの作成とビルド
- Dockerイメージを使ったコンテナの実行
これで、次のステップであるCI/CDやCloud Runの設定に進む準備が整いました。
次回は、Cloud Runの設定に挑戦していきます。
もしも記事の中で進められない箇所があったら、Xなどでご連絡ください。