Akri(アクリ)ではじめる AzureでのIoTデバイス管理
Akriとは
Akriは、エッジデバイス(OPC UAデバイス/カメラ/IoTセンサー/その他周辺機器など)を Kubernetes クラスター内のリソースとして公開できるようにする オープンソース プロジェクトです。Akriは、GPU や FPGA などの組み込みハードウェア リソースの公開をサポートします。Akriは、デバイスにアクセスできるノードを継続的に検出し、それらに基づいてワークロードをスケジュールします。現時点では、OPC UA/ONVIF/udevプロトコルがサポートされています。
AkriはCloud Native Computing Foundation(CNCF)のサンドボックスプロジェクトで、Microsoftが開発を主導しています。またAzure IoT Akriは、Akriの商用バージョンで、執筆時点(2024/05/07)では、プレビュー提供中です。
Akriの機能
Akri にはいくつかのコア機能が用意されています。
-
デバイスの検出
OPC UA Serverなどネットワークエンドポイント内のリーフデバイスを検出するHandler -
動的プロビジョニング
USB カメラ/OPC UA Serverなどの動的なプロビジョニング機能
今回はAkriの動作を確認するため、非常にシンプルな構成を作成して、デバイスの検出からデータ取得するまでを確認します。
Akriを使用したOPC UA Serverの管理
OPC UAは、OPC Foundationで策定されている国際標準規格で、産業用オートメーション用の通信プロトコルです。アクセス方法が標準化されることにより、異なるメーカや機器との接続時に、ベンダー特有のプログラム改変を行うことなくアクセスできる規格として開発されています。
OPC UA Serverの作成
まずは動作検証のため、OPC PLC serverを用意します。デバイスを用意するのは大変なので、Azure Container Instancesで動くOPC PLC serverを使用します。Azure Container Instancesは、Azureが提供するDockerコンテナホスティングービスです。今回は、これをオンプレに置かれたエッジサーバに見立てて利用します。
OPC PLC serverのGitHubリポジトリの[Deploy to Azure]をクリックします。
ここで、[テンプレートの編集] をクリックし、以下の通り変更します。
■ 変更前
...中略...
"command": [
"/bin/sh",
"-c",
"[concat('./opcplc --pn=50000 --autoaccept --unsecuretransport --sph --wp=80 --alm --ses --gn=5 --sn=', parameters('numberOfSlowNodes'), ' --sr=', parameters('slowNodeRate'), ' --st=', parameters('slowNodeType'), ' --fn=', parameters('numberOfFastNodes'), ' --fr=', parameters('fastNodeRate'), ' --ft=', parameters('fastNodeType'), ' --ph=', variables('aciPlc'), add(copyIndex(), 1), '.', resourceGroup().location, '.azurecontainer.io')]"
],
■ 変更後
...中略...
"command": [
"/bin/sh",
"-c",
"[concat('./opcplc --pn=50000 --sph --fn=1 --fr=1 --ft=uint --ftl=65 --ftu=85 --ftr=True --aa --sph --ftl=65 --ftu=85 --ftr=True --ut', ' --ph=', variables('aciPlc'), add(copyIndex(), 1), '.', resourceGroup().location, '.azurecontainer.io')]"
],
このOPC PLC serverは、パラメータを設定することでさまざまなシナリオをシミュレーションできます。今回設定する値は次の通りです。
オプション | 説明 | 今回の設定値 |
---|---|---|
--sph |
OPC Publisher 設定ファイルをダンプする | - |
--fn |
高速ノードの数 | 1 |
--fr |
高速ノードを変更する間隔(s) | 1 |
--ft |
高速ノードのデータ型 (UInt/Double/Bool/UIntArray) | uint |
--aa |
接続が確立されると、すべての証明書を信頼する | - |
--ut |
unsecured transportを有効にする | - |
--ftu |
高速ノードのデータ型の上限 | 85 |
--ftl |
高速ノードのデータ型の下限 | 65 |
--ftr |
高速ノード値のランダム化 | True |
--ph |
PLC の完全修飾ホスト名 | Azure Container InstancesのFQDN |
ここで、シミュレーションしたい数をNumber Of Simulations
に設定します。今回は2に設定します。お金持ちの方は、もっと多くのサーバを立ち上げてシミュレーションをしてみてください。
[確認と作成] ボタンをクリックして、Azure Container Instancesを立ち上げます。デプロイが完了すると、 [リソースに移動] をクリックし、OPC UAサーバのエンドポイント(FQDN)を確認します。
データの確認
作成したOPC UAサーバーに接続して、データを確認します。OPC UAサーバーに接続するためのクライアントアプリケーションはいくつかありますが、今回はUAExpertを使用します。
UAExpertを起動して、OPC UAサーバーのエンドポイント(FQDN)を入力します。例えば、aci-contoso-xxx-plc1.eastus.azurecontainer.ioの場合、以下のように入力します。
opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000
[Connect] ボタンをクリックし、OPC UAサーバーに接続します。
テレメトリデータの確認は、[Telemetry]-[Fast]-[FastUInt1] を選択して、メインウインドウにドラッグアンドドロップします。データが更新されるたびに、値が変わっていることが確認できます。
ここでは、[NamespaceIndex]=3 の [value] の値を取得します。1秒間隔で65~85のランダムな値が取得されてることを確認します。2台のOPC UA Serverから温度データが上がってきていると、妄想してください。
KubernetesクラスタをAzure Arcに接続
作成したOPC UA ServerをAkriで管理するために、Kubernetesクラスタを作成し、Azure Arc-enabled Kubernetesに接続します。
Azure Arc-enabled Kubernetesは、クラウドのみならずオンプレ/エッジ環境など任意の場所で実行されているKubernetesクラスターをAzureで管理および構成するためのサービスです。Azure Arc-enabled Kubernetesに接続することで、AzureポータルやAzure CLIを使用して、クラスターの監視/更新/ポリシー適用などを行うことができます。
手順は公式サイト:クイックスタート: Azure IoT Operations プレビューを Arc 対応 Kubernetes クラスターにデプロイするの通りなので、詳しくは説明しませんが、今回はオンプレサーバに見立てた、Azure上のLinux仮想マシンにK3sをセットアップして利用します。K3sは、軽量なKubernetesディストリビューションで、IoTエッジデバイスに適しています。
次のコマンドを実行して、仮想マシンにK3sをインストールします。
curl -sfL https://get.k3s.io | sh -
mkdir ~/.kube
sudo KUBECONFIG=~/.kube/config:/etc/rancher/k3s/k3s.yaml kubectl config view --flatten > ~/.kube/merged
mv ~/.kube/merged ~/.kube/config
chmod 0600 ~/.kube/config
export KUBECONFIG=~/.kube/config
kubectl config use-context default
kubectlのシェル自動補完も設定しておきましょう。
source <(kubectl completion bash)
echo "source <(kubectl completion bash)" >> ~/.bashrc
alias k=kubectl
complete -F __start_kubectl k
ノードの状態を確認しておきます。
k get no
NAME STATUS ROLES AGE VERSION
iot-operations Ready control-plane,master 5h1m v1.29.3+k3s1
次に、Linuxのinotify.max_user_watches
および inotify.max_user_instances
の制限を増やしておきます。inotifyはLinuxでファイルの変更を検知するツールです。
echo fs.inotify.max_user_instances=8192 | sudo tee -a /etc/sysctl.conf
echo fs.inotify.max_user_watches=524288 | sudo tee -a /etc/sysctl.conf
echo fs.file-max = 100000 | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
これでKubernetesクラスタの準備は整いました。次に、Azure Arcに接続します。
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
az login --use-device
変数LOCATION
にサポートされているリージョン"eastus", "eastus2", "westus", "westus2", "westus3", "westeurope", or "northeurope"のいずれかを設定します。
export LOCATION=eastus
export CLUSTER_NAME=iot-operations
export RESOURCE_GROUP=iot-operations
次に、クラスタをAzure Arcに接続します。
az connectedk8s connect \
-n $CLUSTER_NAME \
-l $LOCATION \
-g $RESOURCE_GROUP
export OBJECT_ID=$(az ad sp show --id bc313c14-388c-4e7d-a58e-70017303ee3b --query id -o tsv)
echo $OBJECT_ID
az connectedk8s enable-features \
-n $CLUSTER_NAME \
-g $RESOURCE_GROUP \
--custom-locations-oid $OBJECT_ID \
--features cluster-connect custom-locations
接続が完了すると、azure-arc名前空間にリソースが生成されます。
k get ns
NAME STATUS AGE
kube-system Active 8m36s
kube-public Active 8m36s
kube-node-lease Active 8m36s
default Active 8m36s
azure-arc-release Active 3m11s
azure-arc Active 79s
Azure Arc-enabled Kubernetesそのものの説明とDeep Diveは後日あらためて。
Akriの実行
これで、Kubernetesクラスタの環境が準備できたので、OPC UAサーバーをAkriで管理します。KubernetesクラスタにAkriをデプロイするため、Helmを使用します。
まず、Helmをインストールします。
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh
Akriのリポジトリを追加し、アップデートします。
helm repo add akri-helm-charts https://project-akri.github.io/akri/
helm repo update
HelmでAkriをデプロイします。ここで、opcua.configuration.discoveryDetails.discoveryUrls[0]
とopcua.configuration.discoveryDetails.discoveryUrls[0]
は、Azure Container Instancesで立てたOPC UAサーバーのエンドポイントに変更してください。
helm install akri akri-helm-charts/akri \
--set opcua.discovery.enabled=true \
--set opcua.configuration.enabled=true \
--set opcua.configuration.name=akri-opcua-monitoring \
--set opcua.configuration.brokerPod.image.repository="ghcr.io/project-akri/akri/opcua-monitoring-broker" \
--set opcua.configuration.brokerPod.image.tag="latest-dev" \
--set opcua.configuration.brokerProperties.IDENTIFIER='FastUInt1' \
--set opcua.configuration.brokerProperties.NAMESPACE_INDEX='3' \
--set opcua.configuration.discoveryDetails.discoveryUrls[0]="opc.tcp://aci-contoso-xxxxx-plc1.eastus.azurecontainer.io:50000/" \
--set opcua.configuration.discoveryDetails.discoveryUrls[1]="opc.tcp://aci-contoso-xxxxx-plc2.eastus.azurecontainer.io:50000/"
Azure上でAkriを素早く利用したいときはAkriの商用サービスであるAzure IoT Akriを利用すると便利です。が、Azure IoT Akriはマネージドなため、今回は機能をいろいろ見たかったのでオープンソースのAkriを利用しています。
動作確認
Akriをデプロイすると、以下のようなPodとServiceが作成されます。
$ k get po,svc
NAME READY STATUS RESTARTS AGE
pod/akri-agent-daemonset-lzl79 1/1 Running 0 12s
pod/akri-controller-deployment-86cf9c5cf-2zdt6 1/1 Running 0 12s
pod/akri-opcua-discovery-daemonset-gq7mt 1/1 Running 0 12s
pod/akri-webhook-configuration-69d9bd7d89-5tqwv 1/1 Running 0 12s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.43.0.1 <none> 443/TCP 15m
service/akri-webhook-configuration ClusterIP 10.43.246.85 <none> 443/TCP 12s
DaemonSetをみてみましょう。
$ k get daemonset
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
akri-agent-daemonset 1 1 1 1 1 kubernetes.io/os=linux 3m5s
akri-opcua-discovery-daemonset 1 1 1 1 1 kubernetes.io/os=linux 3m5s
akri-opcua-discovery
という名前のDaemonSetがOPC UAサーバーを検出します。
ログを確認してみます。エッジのOPC UA Serverに見立てた、Azure Container Instanceをopc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/
とopc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/
を検出しているのが確認できます。
$ k logs akri-opcua-discovery-daemonset-gq7mt
[2024-05-02T23:54:48Z INFO opcua_discovery_handler] main - opcua discovery handler started
[2024-05-02T23:54:48Z TRACE akri_discovery_utils::discovery::discovery_handler] run_discovery_handler - registering with Agent with uds endpoint
[2024-05-02T23:54:48Z INFO akri_discovery_utils::registration_client] register_discovery_handler - entered
[2024-05-02T23:54:48Z TRACE akri_discovery_utils::registration_client] register_discovery_handler - sleeping for 10 seconds and trying again
[2024-05-02T23:54:48Z INFO akri_discovery_utils::discovery::server] internal_run_discovery_server - entered
[2024-05-02T23:54:58Z INFO akri_opcua::discovery_handler] discover - called for OPC UA protocol
[2024-05-02T23:54:58Z INFO akri_opcua::discovery_impl] do_standard_discovery - for DiscoveryUrls ["opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/", "opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/"]
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_urls - Server at opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/ responded with 1 Applications
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - found server : OpcPlc
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - server has [UAString { value: Some("opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/") }] DiscoveryUrls
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_urls - Server at opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/ responded with 1 Applications
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - found server : OpcPlc
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_impl] get_discovery_url_from_application - server has [UAString { value: Some("opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/") }] DiscoveryUrls
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_handler] discover - found OPC UA server at DiscoveryURL opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/
[2024-05-02T23:55:16Z TRACE akri_opcua::discovery_handler] discover - found OPC UA server at DiscoveryURL opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/
......
OPC UA Serverが検出されたら、まもなくOPC UA Serverのデータを取得するためのBroker Podがデプロイされます。
k get po
NAME READY STATUS RESTARTS AGE
akri-agent-daemonset-lzl79 1/1 Running 0 5m29s
akri-controller-deployment-86cf9c5cf-2zdt6 1/1 Running 0 5m29s
akri-opcua-discovery-daemonset-gq7mt 1/1 Running 0 5m29s
akri-webhook-configuration-69d9bd7d89-5tqwv 1/1 Running 0 5m29s
iot-operations-akri-opcua-monitoring-47ef33-pod 1/1 Running 0 4m51s
iot-operations-akri-opcua-monitoring-07aa54-pod 1/1 Running 0 4m52s
Broker Podiot-operations-akri-opcua-monitoring-47ef33
のログを確認します。これをみると、まずApplication Configurationを作成し、OPC UA Serverのエンドポイントを検出し、セッションを作成し、OPC UA Serverのノードをブラウズしています。その後、1秒間隔でデータを取得しているのがわかります。
k logs iot-operations-akri-opcua-monitoring-47ef33-pod
.NET Core OPC UA Console Client Start
1 - Create an Application Configuration.
2 - Discover endpoints of opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/.
info: Microsoft.Hosting.Lifetime[0]
Now listening on: http://[::]:8083
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: /app
Selected endpoint uses: None
3 - Create a session with OPC UA server.
4 - Browse the OPC UA server namespace.
DisplayName, BrowseName, NodeClass
Server, Server, Object
+ ServerArray, ServerArray, Variable next
+ NamespaceArray, NamespaceArray, Variable next
+ UrisVersion, UrisVersion, Variable next
+ ServerStatus, ServerStatus, Variable next
+ ServiceLevel, ServiceLevel, Variable next
+ EstimatedReturnTime, EstimatedReturnTime, Variable next
+ LocalTime, LocalTime, Variable next
+ ServerCapabilities, ServerCapabilities, Object next
+ ServerDiagnostics, ServerDiagnostics, Object next
+ VendorServerInfo, VendorServerInfo, Object next
+ ServerRedundancy, ServerRedundancy, Object next
+ Namespaces, Namespaces, Object next
+ GetMonitoredItems, GetMonitoredItems, Method next
+ ResendData, ResendData, Method next
+ RequestServerStateChange, RequestServerStateChange, Method next
+ ServerConfiguration, ServerConfiguration, Object next
+ Quantities, Quantities, Object next
+ DefaultHAConfiguration, DefaultHAConfiguration, Object next
+ DefaultHEConfiguration, DefaultHEConfiguration, Object next
+ PublishSubscribe, PublishSubscribe, Object next
+ Dictionaries, Dictionaries, Object next
+ Resources, Resources, Object next
Aliases, Aliases, Object
+ FindAlias, FindAlias, Method next
+ LastChange, LastChange, Variable next
+ TagVariables, TagVariables, Object next
+ Topics, Topics, Object next
Locations, Locations, Object
DeviceSet, 2:DeviceSet, Object
+ DeviceFeatures, 2:DeviceFeatures, Object next
NetworkSet, 2:NetworkSet, Object
DeviceTopology, 2:DeviceTopology, Object
+ OnlineAccess, 2:OnlineAccess, Variable next
Boilers, 4:Boilers, Object
+ Boiler #2, 4:Boiler #2, Object next
+ Boiler #1, 4:Boiler #1, Object next
OpcPlc, 3:OpcPlc, Object
+ Telemetry, 3:Telemetry, Object next
+ Methods, 3:Methods, Object next
+ SimulatorConfiguration, 3:SimulatorConfiguration, Object next
ReferenceTest, 6:ReferenceTest, Object
+ Scalar, 6:Scalar, Object next
+ DataAccess, 6:DataAccess, Object next
+ References, 6:References, Object next
+ AccessRights, 6:AccessRights, Object next
+ NodeIds, 6:NodeIds, Object next
+ Methods, 6:Methods, Object next
+ Views, 6:Views, Object next
+ Locales, 6:Locales, Object next
+ Attributes, 6:Attributes, Object next
5 - Create a subscription with publishing interval of 1 second.
6 - Add node FastUInt1 to the subscription.
7 - Add the subscription to the session.
8 - Running...Press Ctrl-C to exit...
FastUInt1: 79, 05/02/2024 23:56:03, Good
FastUInt1: 84, 05/02/2024 23:56:04, Good
FastUInt1: 75, 05/02/2024 23:56:05, Good
FastUInt1: 73, 05/02/2024 23:56:06, Good
FastUInt1: 74, 05/02/2024 23:56:07, Good
FastUInt1: 68, 05/02/2024 23:56:08, Good
FastUInt1: 75, 05/02/2024 23:56:09, Good
FastUInt1: 81, 05/02/2024 23:56:10, Good
FastUInt1: 77, 05/02/2024 23:56:11, Good
FastUInt1: 65, 05/02/2024 23:56:12, Good
FastUInt1: 69, 05/02/2024 23:56:13, Good
FastUInt1: 84, 05/02/2024 23:56:14, Good
....
すばらしい✨✨✨✨
OPC UA Serverからデータを取得できました。
めでたしめでたし。
Akriの基本動作と内部アーキテクチャ
一般的にエッジ環境では、デバイスそのもののトラブルや、ネットワークの瞬断などで、デバイスの状態が頻繁に変化します。自動車の車載器などをイメージしてもらうとわかりやすいかもしれません。そもそも車が運転されていないのか、故障中なのか、走っているけどたまたまトンネルにいるので、通信が途切れているのか、など、状況によってデバイスの状態が変わります。
これが、車数台であればなんとか頑張れそうな気もします。しかし、数百台、数千台となると、デバイスの状態を管理するのが大変です。また、24時間365日、デバイスの状態を監視し続けるのも大変です。これらのデバイスを管理するため、クラウドなどのサーバで集約する仕組みも必要でブローカを置く必要がありますが、なにぶん規模が大きくなればなるほど、デバイスを極限までコスパ良く管理したくなるものです。
Kubernetesは、コンテナのデプロイメント、スケーリング、ネットワークの設定、ロードバランシング、ロギング、モニタリングなどを自動化できるコンテナオーケストレーションツールです。主に分散システムでの利用を想定されていて、障害時のオートヒール機能やスケーリングなどの機能を備えています。このしくみに乗っかる形でエッジデバイスを管理していこうというのが、Akriのコンセプトです。
Akriは刻々と変わるデバイスの状態が変化するたびに、デバイスを探し出し、その変化をKubernetesに通知し、デバイスからデータを取得したり管理したりしてくれるオープンソースソフトウェアです。
で、それをもう少し厳密に説明すると、Akriは以下の流れでデバイスを管理しています。
-
Akri Configuration でデバイスを定義する。Akri discoveryHandlerがデバイスを検索し、見つかったら Akri Agent へ通知する
-
デバイスが見つかったらAkri AgentがAkri Instance(CRD)を作成する
-
Akri Controller が Akri Instance(CRD)に対応した Broker Pod をデプロイする
-
検出されたデバイスとBroker Pod デバイスが接続される
これらを実現するため、Akriは次のアーキテクチャで実装されています。
このように複数の登場人物が、 複雑な系を維持するために各々に与えられた仕事を淡々とやっていくわけなのですが、主要なコンポーネントの役割と動きを説明します。
Akri Configuration CRD
デバイスを管理するには、何はともあれまずはデバイスの情報を定義する必要があります。Akri Configurationは、検出したいデバイスの情報と、リソースを検出するノードにデプロイする必要があるPodを定義するためのCRD(Custom Resource Definitions)です。
k get Configuration
NAME CAPACITY AGE
akri-opcua-monitoring 1 26m
discoveryHandler
でデバイスを検出するハンドラーを指定し、brokerSpec
でデバイスのデータを取得するためのBroker Podを指定します。OPC UAの場合、discoveryDetails
でデバイスのエンドポイントを指定します。
今回の場合だと、 「お忙しいところすいませんが、opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000
と opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000
を探しに行って、見つかったらghcr.io/project-akri/akri/opcua-monitoring-broker
をBroker Podとしてデプロイしてくださいね。あ、デバイスからのデータはFastUInt1
の3
でとれますので!」を設定しています。
apiVersion: akri.sh/v0
kind: Configuration
metadata:
name: akri-opcua-monitoring
spec:
brokerProperties:
IDENTIFIER: FastUInt1
NAMESPACE_INDEX: "3"
brokerSpec:
brokerPodSpec:
containers:
- image: ghcr.io/project-akri/akri/opcua-monitoring-broker:latest-dev
name: akri-opcua-monitoring-broker
capacity: 1
discoveryHandler:
name: opcua
discoveryDetails: "opcuaDiscoveryMethod: \n standard:\n discoveryUrls:\n -
opc.tcp://aci-contoso-xxx-plc1.eastus.azurecontainer.io:50000/\n - opc.tcp://aci-contoso-xxx-plc2.eastus.azurecontainer.io:50000/\napplicationNames:\n
\ action: Exclude\n items: []\n"
この情報は、Kubernetesのetcdに保存され、Akri Agentが定期的に監視しています。
Akri Controller
Akri Controllerは、大きく2つの役割があります。
- デバイスへのクラスターアクセスの有効化
- デバイスの消失の処理
Akri Controllerはクラスター内のマスターノードで実行されます。
KubernetesのControllerはクラスターの状態を常時監視し、必要に応じて変更を加えたり要求したりする制御ループです。Kubernetesの特徴ともいえるこの機能のおかげて、クラスター内のリソースが常に正しい状態に保たれます。
いわゆる 「Kubernetesがよしなにやってくれるやつ」 です。
Akri Discovery Handlers
どこからデバイスを見つけてきて、見つかったらどうしてほしいかを定義したら、実際にデバイスを検索するハンドラーが必要になります。Akri Discovery Handlersは、検出されたすべてのデバイスをAkri Agentにアドバタイズします。Akri Discovery Handlersは、discovery.proto
で定義されているDiscoveryHandler
を実装します。Akri Discovery Handlersの詳細は、Hemlで設定できます。
Heml key | 説明 | デフォルト値 |
---|---|---|
opcua.configuration.discoveryDetails.discoveryUrls | OPC UA ServerのURL | ["opc.tcp://localhost:4840/"] |
opcua.configuration.discoveryDetails.applicationNames.action | OPC UA アプリケーションに対して実行するフィルターアクション | Exclude |
ほかにも資格情報の設定やブローカの設定などを細かく指定できます。詳細はこちらを参照してください。
Akri Agent
検出されたデバイス用のKubernetes Device-Pluginsで、次を実行します。
- Configurationの変更を監視して、どのリソースを検索するかを決定
- リソースの可用性を監視し、どのリソースをアドバタイズするか決定
- 変更に応じて、Kubernetes にリソースの正常性と可用性を通知
Akri Agentはデバイスの状態(Waiting
/ Active
/ Offline(Instant)
) を保持しています。また、Akri AgentはDaemonSetとして動作します。DaemoSetは、Kubernetesのノード上で必ず1つだけ動くPodのことで、もしAkri Agentがクラッシュした場合、Kubernetesが自動的に再起動します。
デバイスからのデータ取得と異常値検出
取得したデータを使って異常値検出をするサンプルアプリがあるので、試してみます。
インストールは、以下のコマンドで行います。
kubectl apply -f https://raw.githubusercontent.com/project-akri/akri/main/deployment/samples/akri-anomaly-detection-app.yaml
PodとServiceが作成されていることを確認します。ServiceはNodePortで公開されているので、クラスタにアクセスできる環境からブラウザでアクセスします。
$ k get po,svc
NAME READY STATUS RESTARTS AGE
pod/akri-anomaly-detection-app-7f989d6d4b-7759d 1/1 Running 0 25s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/akri-anomaly-detection-app NodePort 10.43.180.49 <none> 80:32509/TCP 25s
$ k get no -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
iot-operations Ready control-plane,master 3h11m v1.29.4+k3s1 10.0.0.4 <none> Ubuntu 22.04.4 LTS 6.5.0-1019-azure containerd://1.7.15-k3s1
たとえば、NodeのInternal-IPが10.0.0.4
、akri-anomaly-detection-app
に割り当てられたNodePortが32509
の場合、htttp://10.0.0.4:32509/
でアクセスできます。
このサンプルの実装は、gRPC Serverから温度値を取得しこの値がデータセットの外れ値であるかどうかを判断しています。データセットは、70~80 の数値がランダムにセットされたCSVで、この範囲外は外れ値(赤色)と見なされます。 Web アプリケーションはすべての温度値とその値を送信した OPC UA Serverのアドレスを表示します。
なお、今回はたまたまこのサンプルをKubernetesクラスタ上にデプロイしていますが、実際には、gRPCが喋れるクライアントから異常値を検出するアプリケーションを作成することができます。
まとめ
Akriを使うとKubernetes上でデバイスの管理ができることが分かりました。内部アーキテクチャもざっくり確認しましたが、Kubernetes上で管理されているので、独自のHanderやBrokerを実装したい!となったときも参考になりそうと思いました。
IoTではハードウエアの実装/対応しているプロトコル/デバイスの仕様などな複雑な技術要素を考慮しての設計が必要となり、総合格闘技的な力業が必要になるシーンもあるのではと思っています。
ただし、決して簡単な仕組みではないため本番環境で利用するには、十分な検証が必要だなとおもいました(小並感)
Discussion