0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ASMを用いた双方向通信アプリケーション(WebSocketで構築)のエンドツーエンド・カナリアリリース

Last updated at Posted at 2025-01-16

本記事はこちらのブログを参考にしています。
翻訳にはアリババクラウドのModelStudio(Qwen)を使用しております。

# トラフィックレーンの概要 著者: Hang Yin

トラフィックレーンは、クラウドネイティブアプリケーション内の複数のサービスをバージョンや他の特徴に基づいて複数の独立した実行環境に分離する機能です。この分離は、Alibaba Cloud Service Mesh (ASM) によってサービス間の東西トラフィックの細粒度な制御を通じて達成されます。トラフィックレーンは、エンドツーエンドのカナリーリリースやアプリケーション開発とテスト用の隔離された環境の構築などのシナリオでよく使用されます。現在、パーミッシブモードのトラフィックレーンは主にHTTPプロトコルで通信するサービスをサポートしています。アプリケーションのコンテキスト通過が適切に設定されている場合、パーミッシブモードのトラフィックレーンはWebSocketで構築された双方向通信アプリケーションにも適用できます。この記事では、WebSocketで構築された双方向通信アプリケーションに対してパーミッシブモードのトラフィックレーンを実装し、エンドツーエンドのカナリーリリース効果を達成する方法について説明します。

概要

Manage End-to-end Traffic Based on Alibaba Cloud Service Mesh (ASM): Traffic Lanes in Strict Mode および Manage End-to-end Traffic Based on Alibaba Cloud Service Mesh (ASM): Traffic Lanes in Permissive Mode で、レーンの概念、エンドツーエンドのカナリーリリース管理にレーンを使用するソリューション、およびASMの厳格モードとパーミッシブモードのトラフィックレーンについて紹介しました。トラフィックレーンは、クラウドネイティブアプリケーション内の複数のサービスをサービスバージョンや他の特徴に基づいて複数の独立した実行環境に分離します。

  • 厳格モードでは、各トラフィックレーンには関連する呼び出しチェーン内のすべてのサービスが含まれ、アプリケーション自体には要求はありません。
  • パーミッシブモードでは、関連する呼び出しチェーン内のすべてのサービスを含むベースラインレーンを作成するだけで十分です。他のレーンは関連する呼び出しチェーン内のすべてのサービスを含む必要はありません。あるレーン内のサービスが互いに呼び出す場合、呼び出されるサービスがそのレーンに存在しない場合、要求はベースラインレーンの同じサービスに転送されます。呼び出されるサービスがレーン内で利用可能になると、要求は再度そのレーンに転送されます。

パーミッシブモードのトラフィックレーンは柔軟なエンドツーエンドのカナリーリリースを達成できますが、アプリケーションが全呼び出しチェーン(E2E通過要求ヘッダー)を通過できる要求ヘッダーを含むことを要求します。WebSocketはHTTPプロトコルを介して通信するため、接続確立時にHTTP要求ヘッダーなどのメタデータを追加することも可能です。Socket.ioは高性能なリアルタイム双方向通信フレームワークであり、クライアントとサーバー間での効率的かつ低遅延のデータ交換を可能にします。この記事では、Socket.ioで開発されたサンプルサービスを例に、このようなアプリケーションに呼び出しチェーンがある場合に、パーミッシブモードのトラフィックレーンを通じてWebSocketアプリケーションにエンドツーエンドのカナリーリリース機能を導入する方法を示します。

シナリオの説明

この記事では、シンプルな呼び出しチェーンを持つSocket.ioで構築されたWebSocketアプリケーションを示し、そのアプリケーションに対してパーミッシブモードのトラフィックレーンを作成して基本的なエンドツーエンドのカナリーリリース効果を達成する方法を示します。アプリケーションのトポロジーは以下の通りです:
! 1

アプリケーションの主要なロジックは以下の通りです:client_socket はクラスター外のクライアントで、ASMゲートウェイを介してクラスター内の wsk サービスとSocket.ioで通信します。wsk サービスが接続されている client_socket クライアントにメッセージを送信するとき、クラスター内の helloworld HTTPサービスに対してHTTP GET要求を行い、helloworld からの応答をメッセージとして client_socket に送信します。図に示すように、helloworld には現在、バックエンドに2つのバージョンのワークロード(helloworld-v1helloworld-v2)がデプロイされています。この例では、期待される効果は、wsk サービスが接続を確立するときに client_socket から提供される要求ヘッダー情報(バージョン要求ヘッダー)に基づいて、後続の wsk 要求の具体的なワークロードバージョンを決定することです。これは典型的でシンプルなエンドツーエンドのカナリーリリースシナリオです:アプリケーションのv2がリリースされるとき、呼び出しチェーンの末端にある helloworld アプリケーションのみがリリースされます。この場合、wsk サービスは単一のバージョン1のままであり、クライアントが接続を開始するときに提供する要求ヘッダーメタデータに基づいて、呼び出す helloworld のターゲットバージョンを決定します。

必要条件

  • エンタープライズエディションまたはアルティメットエディションのASMインスタンスが作成され、インスタンスのバージョンが1.18.2.111以降である。詳細については、ASMインスタンスの作成を参照してください。
  • ACKクラスターがASMインスタンスに追加されている。詳細については、ASMインスタンスへのクラスターの追加を参照してください。
  • 名前が ingressgateway のASMゲートウェイが作成され、ポート30080が作成されている。詳細については、イングレスゲートウェイの作成を参照してください。
  • istio-system 名前空間に名前が ws-gateway のIstioゲートウェイが作成されている。詳細については、Istioゲートウェイの管理を参照してください。
  • デフォルト名前空間での自動サイドカー・プロキシ注入が有効になっている。詳細については、自動サイドカー・プロキシ注入の有効化を参照してください。
apiVersion: networking.istio.io/v1beta1 kind: Gateway metadata: name: ws-gateway namespace: default spec: selector: istio: ingressgateway servers: - hosts: - * port: name: http number: 30080 protocol: HTTP

ステップ1: サンプルアプリケーションのデプロイ

kubectl を使用して ACK クラスターに接続し、次のコマンドを実行してサンプルアプリケーションをデプロイします。
kuebctl apply -f- <<EOF
apiVersion: v1
kind: Service
metadata:
name: wsk-svc
spec:
selector:
app: wsk
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10
ports:
- protocol: TCP
port: 5000
targetPort: 5000
name: http-ws

apiVersion: apps/v1
kind: Deployment
metadata:
name: wsk-deploy
labels:
app: wsk
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: wsk
version: v1
template:
metadata:
labels:
app: wsk
track: stable
version: v1
annotations:
instrumentation.opentelemetry.io/inject-nodejs: true
instrumentation.opentelemetry.io/container-names: websocket-base
spec:
containers:
- name: websocket-base
image: registry-cn-hangzhou.ack.aliyuncs.com/dev/asm-socketio-sample:669297ea
imagePullPolicy: Always
ports:
- name: websocket
containerPort: 5000

apiVersion: v1
kind: Service
metadata:
name: helloworld
labels:
app: helloworld
spec:
ports:

  • port: 5000
    name: http
    selector:
    app: helloworld

apiVersion: v1
kind: ServiceAccount
metadata:
name: helloworld
labels:
account: helloworld

apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v1
labels:
apps: helloworld
version: v1
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v1
template:
metadata:
labels:
app: helloworld
version: v1
spec:
serviceAccount: helloworld
serviceAccountName: helloworld
containers:
- name: helloworld
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/examples-helloworld-v1:1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000

apiVersion: apps/v1
kind: Deployment
metadata:
name: helloworld-v2
labels:
apps: helloworld
version: v2
spec:
replicas: 1
selector:
matchLabels:
app: helloworld
version: v2
template:
metadata:
labels:
app: helloworld
version: v2
spec:
serviceAccount: helloworld
serviceAccountName: helloworld
containers:
- name: helloworld
image: registry-cn-hangzhou.ack.aliyuncs.com/ack-demo/examples-helloworld-v2:1.0
imagePullPolicy: IfNotPresent
ports:
- containerPort: 5000
EOF

helloworld サービスの v1 と v2 がクラスターにデプロイされます。このサービスは /hello エンドポイントへの HTTP GET リクエストに応答し、そのバージョン情報を返します。wsk サービスの v1 もデプロイされます。これはサンプルの Socket.io サービスで、クライアントからのメッセージに応答し、helloworld サービスを呼び出してその出力をクライアントに返します。コードは以下の通りです。javascript
import os from os;
import fetch from node-fetch;
import http from http;
import socketio from socket.io;

const ifaces = os.networkInterfaces();
const makeRequest = async (url, baggage) => {
try {
let headers = {}
if (! !baggage) {
headers[baggage] = baggage
}
const response = await fetch(url, {
headers
});
if (!response.ok) {
throw new Error(HTTP error! status: ${response.status});
}
const data = await response.text(); // or response.json() if you need JSON-formatted data
return data;
} catch (error) {
console.error('Error fetching the data:', error);
return error;
}
}

const privateIp = (() => {
return Object.values(ifaces).flat().find(val => {
return (val.family == 'IPv4' && val.internal == false);
}).address;
})();

const randomOffset = Math.floor(Math.random() * 10);
const intervalOffset = (30 + randomOffset) * Math.pow(10, 3);

// WebSocket Server
const socketPort = 5000;
const socketServer = http.createServer();
const io = socketio(socketServer, {
path: '/'
});

// Handlers
io.on('connection', client => {
console.log('New incoming Connection from', client.id);
client.on('test000', async (message) => {
console.log('Message from the client:', client.id, '->', message);
const response = await makeRequest('http://helloworld:5000/hello', client.handshake.headers['baggage']);
client.emit('hello', response, resp => {
console.log('Response from the client:', client.id, '->', resp);
})
})
});

const emitOk = async () => {
const response = await makeRequest('http://helloworld:5000/hello');
let log0 = I am the host: ${privateIp}. I am healty. Hello message: ${response};
console.log(log0);
io.emit('okok', log0);
}

setInterval(() => {
emitOk();
}, intervalOffset);

// Web Socket listen
socketServer.listen(socketPort);

このコードは、クライアントからメッセージが送信されたときに helloworld サービスを呼び出すようにアプリケーションが構成されていることを示しています。この場合、接続確立時にクライアントによって提供された baggage リクエストヘッダーを読み取り、それを helloworld サービスを呼び出す HTTP リクエストに渡します。これにより、Socket.io アプリケーションは呼び出しチェーンでのコンテキストパススルーを実装します。このコンテキストを使用して、許容モードでトラフィックレーンを構成できます。

Baggageとは?

Baggageは、OpenTelemetryによって開発された標準化されたメカニズムであり、分散システムの呼び出しチェーンのプロセス間でコンテキスト情報を伝達するためのものです。HTTPヘッダーに「Baggage」という名前のHTTPヘッダーを追加できます。Baggageヘッダーの値はキーと値のペア形式です。Baggageヘッダーを使用して、テナントID、トレースID、セキュリティ資格情報などのコンテキストデータを転送できます。例:
baggage: userId=alice,serverNode=DF%2028,isProduction=false

Baggageは、OpenTelemetryコミュニティによって提案された呼び出しチェーンでのコンテキストパススルーの標準化されたメカニズムです。したがって、このメカニズムに基づいて許容モードでトラフィックレーンを構成することも推奨します。次に、ゲートウェイ上に仮想サービスをデプロイして、トラフィックを wsk サービスにリダイレクトし、次のコマンドを実行します。
kubectl apply -f- <<EOF
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: ws
namespace: default
spec:
gateways:
- default/ws-gateway
hosts:
- *
http:
- route:
- destination:
host: wsk-svc
port:
number: 5000
EOF

この仮想サービスは、ゲートウェイ上でルーティングルールを宣言します。この仮想サービスを通じて、ASM ゲートウェイはクライアントからの Socket.io リク

Swimlane Services

Kubernetes Clustersドロップダウンリストから目的のACKクラスターを選択し、Namespaceドロップダウンリストからdefaultを選択します。次に、下のリストからwsk-svcとhelloworldサービスを選択し、iconをクリックして、選択したセクションにサービスを追加します。 2

次に、レーングループ内のv1とv2のレーンを作成して、サービスのv1とv2に対応させます。

  • トラフィックレーンページのトラフィックルール定義セクションで、「スウィムレーンを作成」をクリックします。
  • 「スウィムレーンを作成」ダイアログボックスで、必要なパラメータを設定し、「OK」をクリックします。 3

v1レーンにはwsk-svcとhelloworldサービスが含まれており、v2レーンにはhelloworldサービスのみが含まれています(許容モードのトラフィックレーンでは、レーンがすべてのサービスを含まないことが許可され、wskサービスのv1がサービスのベースラインバージョンとして機能します)。

ステップ3: レーンのパフォーマンスをテストする

次の内容を持つclient-socket.jsファイルを作成します(ASMゲートウェイのIPアドレスを実際のIPアドレスに置き換えてください):javascript
import uuid from 'uuid';
import io from 'socket.io-client';

const client = io('http://{ASMゲートウェイのIPアドレス}:30080', {
reconnection: true,
reconnectionDelay: 500,
transports: ['websocket'],
extraHeaders: {
version: 'v2'
}
});

const clientId = uuid.v4();
let disconnectTimer;

client.on('connect', function() {
console.log('Connected! ', clientId);
setTimeout(function() {
console.log('Sending first message');
client.emit('test000', clientId);
}, 500);
// disconnection timeout clear
clearTimeout(disconnectTimer);
});

client.on('okok', function(message) {
console.log('The server has a message for you:', message);
});

client.on('hello', (arg, callback) => {
console.log('the server respond to your test000: ' + arg);
callback('got it');
});

client.on('disconnect', function() {
console.log('Disconnected! ');
disconnectTimer = setTimeout(function() {
console.log('Not reconnecting in 30s. Exiting...');
process.exit(0);
}, 10000);
});

client.on('error', function(err) {
console.error(err);
process.exit(1);
});

setInterval(function() {
console.log('Sending repeated message');
client.emit('test000', clientId);
}, 5000);

client_socket.jsを実行して、双方向通信の結果を確認します。期待される結果は以下の通りです:plaintext
❯ node client_socket.js
Connected! ffcff9b2-2e41-4334-b64c-60512bdb7c7a
Sending first message
the server respond to your test000: Hello version: v2, instance: helloworld-v2-7b94944d69-wcckk
The server has a message for you: I am the host: 192.168.1.60. I am healty. Hello message: Hello version: v1, instance: helloworld-v1-978dbcf9-vsvw8
Sending repeated message
the server respond to your test000: Hello version: v2, instance: helloworld-v2-7b94944d69-wcckk
Sending repeated message
the server respond to your test000: Hello version: v2, instance: helloworld-v2-7b94944d69-wcckk
Sending repeated message
the server respond to your test000: Hello version: v2, instance: helloworld-v2-7b94944d69-wcckk

期待される結果は、サーバーからの各レスポンスがクライアントに対してv2のhelloworldサービスを呼び出していることを示しています。これは、クライアントが接続を開始する際にversion: v2という追加情報を使用しているためです。wskを通じてhelloworldへのリクエストを送信するときにリクエストコンテキストをコールチェーン上に復元することで、ASMはリクエストを正しいバージョンのhelloworldサービスにルーティングし、エンドツーエンドのカナリアリリースを達成します。特定のクライアントへの応答だけでなく、この例ではサーバーからすべてのクライアントへのメッセージブロードキャストも実装されています。クライアントのログで定期的に以下のような内容を見ることができます:plaintext
The server has a message for you: I am the host: 192.168.1.60. I am healty. Hello message: Hello version: v1, instance: helloworld-v1-978dbcf9-vsvw8

このメッセージは、サーバーからクライアントへのブロードキャストメッセージで、サーバーに接続されているすべてのクライアントが定期的に受信します。ブロードキャストメッセージは実際にはhelloworldサービスのv1を呼び出します。これは、ブロードキャストメッセージが特定のクライアントコンテキストを持たないため、helloworldのベースラインバージョン(つまりv1)が呼ばれるからです。

FAQ: OpenTelemetry Auto-instrumentationを使用しない理由

自動インストルメンテーションについて学ぶには、Injecting Auto-instrumentationを参照してください。この機能は、OpenTelemetry Operatorを使用してアプリケーションに自動インストルメンテーション機能を注入し、アプリケーションコードを変更せずにトレースIDリクエストヘッダーのパススルーを実現します。自動インストルメンテーションを実装するには、前述のコミュニティドキュメントに従ってOpenTelemetry Operatorをインストールし、自動インストルメンテーションを構成し、アプリケーションポッドに注釈を追加する必要があります。OpenTelemetry自動インストルメンテーションは、W3C BaggageやB3など、複数の一般的な分散コールチェーンコンテキストパススルースタンダードをサポートしています。

Socket.ioの場合、自動インストルメンテーションの意味合いが少し異なります。Socket.ioはHTTP(WebSocket)に基づいていますが、主にサービス間の双方向通信を実装しており、クライアントとサーバーはリアルタイムでメッセージを送受信できます。そのため、コミュニティによって実装されたSocket.ioの自動インストルメンテーションは、実際にはメッセージレベルで動作します。HTTPレベルでは、クライアントとサーバー間の接続は大きなHTTPリクエストであり、主に接続時にクライアントによって提供される追加リクエストヘッダーに基づいてコールチェーンコンテキストを決定する必要があります。このシナリオでは、コミュニティの自動インストルメンテーションによって提供されるコンテキストパススルーキャパビリティは限られた意味しかなく、コードの若干の修正が必要となります。詳細については、コミュニティの記事「Instrument Your Node.js Socket.io Apps with OpenTelemetry Like a PRO」を参照してください。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?