こんにちは。金融ソリューション事業部の多田です。
本記事は 電通国際情報サービス Advent Calendar 2023 22日目の記事となります。
前日の記事は星野将吾さんの「若手こそ受けておきたい!IPA システムアーキテクト試験」でした。
はじめに
みなさん、microservices-demoをご存知でしょうか。
Googleが提供しているクラウドを前提としたマイクロサービスのデモアプリケーションです。
今回はこのmicroservices-demoの一部をRust実装に置き換えてAmazon EKS(以降、EKS)にデプロイしてみました。
筆者は社内ワーキンググループの活動としてソフトウェアアーキテクチャの技術調査などを行っており
その一環としてRust/gRPC/GraphQLを用いたマイクロサービスアーキテクチャを検討しております。
本記事はその検討にあたって実施した内容の一部をまとめたものになります。
microservices-demoについて
ECサイトを模しており、商品の閲覧やカートへ入れる、購入するといったことが可能です(決済などはモックです)。
技術的にはKubernetes、GKE、Istio、Stackdriver、gRPCなどを使用しています。
少し設定を加えればGKEなどに作成したKubernetesクラスター上にデプロイ可能となっています。
アーキテクチャと各サービスの言語、役割は以下のようになっています。
今回はこの中のproductcatalogserviceをRust実装に置き換えます。
(役割は筆者の意訳です。詳細はREADMEをご確認ください)
サービス | 言語 | 役割 |
---|---|---|
frontend | Go | HTTPサーバ。ユーザーと直接やり取りするのはココ |
cartservice | C# | 商品の保存や取得(ショッピングカート) |
productcatalogservice | Go | 商品一覧と各商品情報(商品名、概要)の提供、検索 |
currencyservice | Node.js | 各通貨の変換 |
paymentservice | Node.js | クレジットカード(モック)で決済 |
shippingservice | Go | 送料の計算と住所(モック)への発送 |
emailservice | Python | 購入確認メール(メールアドレスはモック)の送信 |
checkoutservice | Go | カートの情報を元に注文準備や支払い、発送、メール通知を調整 |
recommendationservice | Python | カートにある商品を元に別の商品を推薦 |
adservice | Java | テキスト広告の提供 |
loadgenerator | Python/Locust | EC2の利用フローを模したリクエストを継続的に実施(おそらく負荷テスト用) |
productcatalogserviceをRustで実装する
productcatalogserviceの内容を確認
サービス同士の通信にはgRPCを使用しています。
Protocol Buffersは既にあるため、それを元にサービスを実装します。
demo.proto
productcatalogserviceのインターフェースは以下です。
(Productなどのメッセージ定義は省略しています)
service ProductCatalogService { rpc ListProducts(Empty) returns (ListProductsResponse) {} rpc GetProduct(GetProductRequest) returns (Product) {} rpc SearchProducts(SearchProductsRequest) returns (SearchProductsResponse) {} }
microservices-demo上ではGoで実装されています。
コードを見てみるとproducts.json
に商品情報があり、これを元に商品一覧や各商品情報、検索機能を提供しているようです。
この実装を参考にRustで実装します。
準備
以下を参考にRustのインストールをお願いします。
https://www.rust-lang.org/ja/tools/install
tonicについて
tonicというクレートを使用して実装します。
tonicはRustネイティブのgRPC実装です。用途によりコンポーネントが用意されており、それらを組み合わせて使用します。
今回はtonic-buildというコンポーネントを使用してprotoファイルを元にスケルトンとスタブを生成し、それを元にgRPCサーバーとクライアントを実装しました。
デモアプリケーション上ではクライアントは不要ですがテストのために実装しました。
tonicを利用するにはprotocのインストールが必要になります。
お使いの環境にあわせてインストールしてください。
https://github.com/hyperium/tonic?tab=readme-ov-file#dependencies
Windowsの場合、上記に加えてPROTOCという環境変数を用意しprotoc.exeへのパスを設定する必要があります。
(筆者はここで「コンパイルできない(; ;)」と30分ほど無駄にしました)
Rustによる実装
コードの全量は以下になります。
productcatalogservice
構成は以下のとおりです。
productcatalogservice |-- Cargo.toml |-- Dockerfile ... EKSへデプロイするためのイメージ作成で使用(後述) |-- build.rs ... スケルトン、スタブ生成用のコード |-- pb | |-- demo.proto ... 前述のproductcatalogserviceのインターフェースを定義 |-- products.json ... 商品情報リスト |-- src |-- client.rs ... gRPCクライアントのコード |-- server.rs ... gRPCサーバーのコード
本記事ではbuild.rs
とserver.rs
について解説します。
その他は実際のコードをご確認ください。
まずはbuild.rs
です。
fn main() -> Result<(), Box<dyn std::error::Error>> { tonic_build::configure() .type_attribute(".", "#[derive(serde::Deserialize)]") .type_attribute(".", "#[serde(rename_all = \"camelCase\")]") .compile(&["./pb/demo.proto"], &["./pb"])?; Ok(()) }
build.rs
はビルド時にprotoファイルを読み込んでスケルトンとスタブのコードを生成します。
type_attribute(...)
は生成されるスケルトンとスタブに属性を付与したい場合に使用します。
今回はproduct.json
から自動生成されたコード(商品の構造体)に直接変換するため、そのための属性を付与しました。
(実開発においては直接変換ではなく間にDTOなどを挟むと思います)
ビルドに成功するとtarget配下にコードが生成されます。
続いてserver.rs
です(一部省略しています)。
// 生成されたコードの読み込み pub mod hipstershop { // demo.protoのpackageを指定 tonic::include_proto!("hipstershop"); } // 省略 #[derive(Default)] pub struct ProductCatalogServiceImpl {} // サーバ用のコードを実装 #[tonic::async_trait] impl ProductCatalogService for ProductCatalogServiceImpl { async fn list_products( &self, _request: Request<Empty> ) -> Result<Response<ListProductsResponse>, Status> { let products = read_catalog_file().await; let reply: ListProductsResponse = ListProductsResponse{ products: products }; Ok(Response::new(reply)) } async fn get_product( &self, request: Request<GetProductRequest> ) -> Result<Response<Product>, Status> { //省略 } async fn search_products( &self, request: Request<SearchProductsRequest> ) -> Result<Response<SearchProductsResponse>, Status> { //省略 } } #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { // 省略 let addr = "0.0.0.0:3550".parse().unwrap(); let product_catalog_service = ProductCatalogServiceImpl::default(); println!("HealthServer + ProductCatalogServiceServer listening on {}", addr); Server::builder() .add_service(health_service) .add_service(ProductCatalogServiceServer::new(product_catalog_service)) .serve(addr) .await?; Ok(()) }
pub mod hipstershop {...}
では自動生成されたコードをモジュールとして定義しています。
tonic::include_proto!("hipstershop")
は自動生成されたコードを読み込むためのマクロです。
引数にはprotoファイルのpackageを指定します。
impl ProductCatalogService for ProductCatalogServiceImpl {...}
はサービスの実装です。
自動生成されたProductCatalogServiceトレイトを実装しています。
product.json
から商品情報を読み込み、それをそのまま返したり、中身を検索したりしています。
main()
はgRPCサーバーを起動するための実装です。
ポートやサービスの設定などをしています。
gRPCサーバーの動作確認
サーバーは以下のコマンドで起動します。
cargo run --bin server
クライアントを実行して動作確認をしてみます。
cargo run --bin client
成功すると以下のような結果が表示されます。
RESPONSE=Response { metadata: MetadataMap { headers: {"content-type": "application/grpc", "date": "Tue, 19 Dec 2023 08:02:30 GMT", "grpc-status": "0"} }, message: ListProductsResponse { products: [Product { id: "OLJCESPC7Z", name: "Sunglasses", description: "Add a modern touch to your outfits with these sleek aviator sunglasses.", picture: "/static/img/products/sunglasses.jpg", price_usd: Some(Money { currency_code: "USD", units: 19, nanos: 990000000 }), ...省略
これでRustによる実装ができましたので、次はEKSへデプロイします。
EKSへのデプロイ
準備
EKSへデプロイするためにAWSアカウントの作成とAWS CLIのインストール、認証情報の設定をしてください。
また、イメージ作成にはDockerを使用しますのでお使いの環境にあわせてインストールしてください。
https://docs.docker.jp/desktop/install.html
コンテナイメージの準備 & ECRへの登録
EKSへデプロイするためにproductcatalogserviceのイメージを作成しECRへイメージを登録します。
まず以下を参考にAWSコンソールからECRにプライベートリポジトリを作成します(リポジトリ名はproductcatalogservice)。
https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/repository-create.html
次にイメージを作成しECRへpushするため、以下のコマンドを実行します。
AWSのアカウント番号は適宜置き換えてください。
リージョンは東京リージョンにしています。
# イメージ作成 docker build -t productcatalogservice . # タグ付けする docker tag productcatalogservice:latest <AWSのアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/productcatalogservice:latest # dockerクライアントの認証 aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin <AWSのアカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com # push docker push <アカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/productcatalogservice:latest
AWS CloudShellの準備
EKSへのデプロイにはAWS CloudShellを利用します。
まずはkubectlを使えるようにします。
kubectlはkubernetes用のコマンドラインツールです。
マイクロサービスのデプロイに使用します。
curl -O https://s3.us-west-2.amazonaws.com/amazon-eks/1.28.3/2023-11-14/bin/linux/amd64/kubectl chmod +x ./kubectl mkdir -p $HOME/bin && cp ./kubectl $HOME/bin/kubectl && export PATH=$PATH:$HOME/bin echo 'export PATH=$PATH:$HOME/bin' >> ~/.bashrc kubectl version --client
続いてeksctlを使えるようにします。
eksctlはEKS上でKubernetesクラスターを作成・管理するためのコマンドラインツールです。
curl --silent --location "https://github.com/weaveworks/eksctl/releases/latest/download/eksctl_$(uname -s)_amd64.tar.gz" | tar xz -C /tmp sudo mv /tmp/eksctl /usr/local/bin eksctl version
AWS上にEKSクラスター/ノードグループを作成する
以下のコマンドでクラスターを作成できます(10分ほどで完了します)。
name
はonlineboutique
、region
は東京リージョンとしています。
eksctl create cluster --name onlineboutique --region ap-northeast-1 --without-nodegroup
クラスターの作成が完了したらノードグループを作成します(2分ほどで完了します)。
cluster
はonlineboutique
、ノード数は4としています。
eksctl create nodegroup --cluster onlineboutique --region ap-northeast-1 --nodes 4 --nodes-min 4 --nodes-max 4
microservices-demoのデプロイ
クラスターが作成できたらいよいよデプロイです。
microservices-demoをクローンし、ECRへpushしたイメージを使うようマニフェストを修正します。
★でproductcatalogserviceのimage
を以下のように修正してください。
- 修正前: gcr.io/google-samples/microservices-demo/productcatalogservice:<バージョン>
- 修正後:<アカウント番号>.dkr.ecr.ap-northeast-1.amazonaws.com/productcatalogservice:latest
その後、kubectl apply
でデプロイします。
# microservices-demoをクローン git clone https://github.com/GoogleCloudPlatform/microservices-demo.git # リポジトリへ移動 cd microservices-demo # 書き換え(★) vi ./release/kubernetes-manifests.yaml # デプロイ kubectl apply -f ./release/kubernetes-manifests.yaml
サービスの一覧を確認するとfrontendにELBのDNS名が割り当てられています。
kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE adservice ClusterIP 10.100.8.99 <none> 9555/TCP 2m34s cartservice ClusterIP 10.100.167.3 <none> 7070/TCP 2m35s checkoutservice ClusterIP 10.100.155.33 <none> 5050/TCP 2m35s currencyservice ClusterIP 10.100.81.172 <none> 7000/TCP 2m34s emailservice ClusterIP 10.100.116.249 <none> 5000/TCP 2m35s frontend ClusterIP 10.100.23.148 <none> 80/TCP 2m35s frontend-external LoadBalancer 10.100.167.89 a0f6456281124412b8f095f94c6e242a-2020525330.ap-northeast-1.elb.amazonaws.com 80:31538/TCP 2m35s kubernetes ClusterIP 10.100.0.1 <none> 443/TCP 17m paymentservice ClusterIP 10.100.154.183 <none> 50051/TCP 2m35s productcatalogservice ClusterIP 10.100.20.37 <none> 3550/TCP 2m35s recommendationservice ClusterIP 10.100.90.209 <none> 8080/TCP 2m35s redis-cart ClusterIP 10.100.32.58 <none> 6379/TCP 2m34s shippingservice ClusterIP 10.100.194.29 <none> 50051/TCP 2m34s
ブラウザでアクセスして画像のような画面が表示されれば成功です。
後始末
削除は以下の順でコマンドを実行します。
グループ名はマネジメントコンソールなどでご確認ください。
# podを削除 kubectl delete -f ./release/kubernetes-manifests.yaml # ノードグループを削除 eksctl delete nodegroup --cluster=onlineboutique --region ap-northeast-1 --name=<グループ名> --wait # クラスターを削除 eksctl delete cluster --name onlineboutique --region ap-northeast-1 --wait
まとめ
Googleが提供しているmicroservices-demoの一部をRustに置き換えてEKSにデプロイしてみました。
tonicを用いることで簡単にRustで実装できました。
他のサービスもRustで置き換える場合、共通部分をライブラリクレートとして抜き出して各サービスで利用する(自動生成されたコードのモジュールを定義している部分など)、といったことも検討できそうです。
今回はRustを利用しましたが、gRPCが利用できれば他の言語でも可能ですのでご興味あればぜひお試しください。
デプロイ環境についてもKuberntesクラスターであればどの環境でもデプロイ可能です。
microservices-demoのクイックスタートや開発ガイドにGKEやローカルを利用する方法が載っていますので、こちらもぜひお試しください。
ここまでお読みいただきありがとうございました。
私たちは一緒に働いてくれる仲間を募集しています!
募集職種一覧執筆:@tada.keisuke、レビュー:@takeda.hideyuki
(Shodoで執筆されました)