Azure IoT Edgeを自動更新で壊さないために、世代固定しながらPatchだけ適用する
Azure IoT Edgeを24時間365日の無人端末で運用する場合、
Security Updateは適用したい
でも
検証していないVersionへ
勝手に上がってほしくない
という問題があります。
すべてのUpdateを止めれば構成は変わりにくくなりますが、Security FixやBug Fixも取り込めません。
逆に自動Updateへ任せると、
Update
↓
Package Version変更
↓
依存関係変更
↓
検証していない構成へ変化
↓
現場で突然動かなくなる
というリスクがあります。
そこで今回は、
Azure IoT Edge / Container Runtimeは検証済みの「世代」を固定し、同一世代内のPatchだけを条件付きで適用する
という構成にしました。
この記事では、
- Ubuntuの自動Update
- Azure IoT Edgeの世代固定
- APT Pinning
- Update前のDry-run
- 世代変更の検知
- Update後のHealth Check
- Reboot時間の分離
まで整理します。
結論:Version完全固定ではなく「世代固定」にする
今回の方針はシンプルです。
Ubuntu Security Update
↓
許可
Ubuntu Stable Update
↓
許可
Azure IoT Edge
1.6.x → 1.6.x
↓
条件付きで許可
1.6.x → 1.7.x / 2.x
↓
自動では許可しない
moby-containerd
2.2.x → 2.2.x
↓
条件付きで許可
moby-engine
29.x → 29.x
↓
条件付きで許可
つまり、
特定の1Versionを永遠に固定するのではなく、検証済みのVersion範囲を固定する
という考え方です。
なぜAzure IoT Edgeの自動Updateを慎重にするのか
Azure IoT Edgeは単独で動くApplicationではありません。
今回の構成では、おおまかに次のLayerがあります。
Device
↓
Edge Application
↓
Container
↓
edgeHub / edgeAgent
↓
IoT Edge Security Subsystem
↓
Container Runtime
↓
Ubuntu
↓
Network
↓
Azure IoT Hub
そのため、
IoT EdgeだけUpdateした
つもりでも、Container Runtimeや依存Packageとの組み合わせによってSystem全体へ影響する可能性があります。
一般的なPCなら、
Update
↓
問題発生
↓
ユーザーが気付く
↓
修正
という対応ができます。
しかし無人Edge Gatewayでは、
Update
↓
Componentの一部だけ異常
↓
Data転送停止
↓
現場には誰もいない
という状態になる可能性があります。
そのため、
IoT Edge / Container Runtimeは、通常のOS Packageより一段厳しくUpdateを管理する
ことにしました。
Ubuntuの自動Update自体は止めない
重要なのは、
Versionを固定する
=
Updateを全部止める
ではないことです。
Security Updateまで止めると、長期運用では別のリスクになります。
そのためUbuntu側では、
Ubuntu Update
Ubuntu Security Update
↓
許可
IoT Edge / Container Runtime
検証していない世代への変更
↓
制限
と分けました。
Ubuntuでは unattended-upgrades を利用しています。
例えば、
APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Download-Upgradeable-Packages "1";
APT::Periodic::AutocleanInterval "7";
APT::Periodic::Unattended-Upgrade "1";
とし、
Ubuntu
Ubuntu Security
Ubuntu Updates
ESM Apps Security
ESM Infra Security
を自動Updateの対象としています。
考え方としては、
Ubuntu 24.04 LTS
│
├─ Security Update
│ ↓
│ 適用
│
├─ 通常Update
│ ↓
│ 適用
│
└─ IoT Edge / Runtime
│
├─ 同一世代Patch
│ ↓
│ 条件付き適用
│
└─ 世代変更
↓
自動適用しない
です。
UpdateとRebootを分離する
もう一つ重要なのが、
Package UpdateとRebootを別の処理にする
ことです。
unattended-upgrades では、
Unattended-Upgrade::Automatic-Reboot "false";
としています。
つまり、
Update完了
↓
Rebootが必要
↓
その場で即Reboot
とはしません。
代わりに、
Package Update
↓
/var/run/reboot-required
を確認
↓
必要な場合のみ
Maintenance時間にReboot
としています。
今回の構成では、OS側のReboot判定を 02:30 に行います。
Azure IoT Edgeは「1.6.x」の範囲を維持する
今回検証済みの基準は、
Azure IoT Edge 1.6系
moby-containerd 2.2系
moby-engine 29系
です。
IoT Edgeなら、
1.6.0
↓
1.6.1
↓
1.6.x
のような同一世代内のPatchは候補にできます。
一方、
1.6.x
↓
1.7.x
や、
1.6.x
↓
2.x
のような世代変更は自動では実施しません。
Container Runtimeも同じです。
moby-containerd
2.2.x
↓
2.2.x
OK
moby-engine
29.x
↓
29.x
OK
という範囲を維持します。
APT Pinningで更新可能なVersion範囲を制限する
まずAPT側で、移動してよいVersion範囲を制限します。
IoT Edgeでは例えば、
Package: aziot-edge aziot-identity-service aziot-keyd aziot-certd aziot-tpmd aziot-edged
Pin: version 1.5*
Pin-Priority: -10
Package: aziot-edge aziot-identity-service aziot-keyd aziot-certd aziot-tpmd aziot-edged
Pin: version 1.6*
Pin-Priority: 1001
Package: aziot-edge aziot-identity-service aziot-keyd aziot-certd aziot-tpmd aziot-edged
Pin: version 1.7*
Pin-Priority: -10
Package: aziot-edge aziot-identity-service aziot-keyd aziot-certd aziot-tpmd aziot-edged
Pin: version 2.*
Pin-Priority: -10
としています。
意図は、
1.5系
↓
自動移行させない
1.6系
↓
使用する
1.7系
↓
自動移行させない
2.x系
↓
自動移行させない
です。
Container Runtimeも同様に、
moby-containerd 2.2*
↓
許可
とし、
1.*
2.0*
2.1*
2.3*
3.*
など、今回の検証範囲外へ自動的に移動しないようにします。
moby-engine についても、
29*
↓
許可
30*
↓
自動移行させない
という考え方です。
APT PinningだけではなくInstalled / Candidate Versionも比較する
Pinningだけで終わりにはしていません。
専用のUpdate Scriptで、
現在InstallされているVersion
と
APTが提示しているCandidate Version
を比較します。
例えば、
Installed
1.6.3
Candidate
1.6.4
なら、
同じ1.6系
↓
Patch候補
と判断できます。
一方、
Installed
1.6.x
Candidate
1.7.x
なら、
世代変更
↓
自動Updateしない
と判断します。
つまり、
APTがUpdate可能と言っているかではなく、自分たちが許可した世代の範囲内かを見る
ようにしています。
新しい世代を検知したらFlagを残す
検証済みの範囲を超えるVersionが見つかった場合も、単に無視するだけにはしません。
/var/lib/mdlinkage/pending-genupgrade
というFlagを作成します。
処理としては、
Candidate Version確認
↓
検証済み世代を超えている
↓
自動Updateしない
↓
pending-genupgrade作成
です。
これによって、
新しい世代が公開されていること自体は検知する
ようにしています。
その後、
新Versionを検証環境で評価
↓
問題なし
↓
許可するVersion範囲を変更
↓
本番端末へ展開
という運用にできます。
PatchでもすぐにInstallせず、まずDry-runする
Candidate Versionが同一世代だったとしても、
Patchだから安全
とは判断しません。
実際のUpdate前に、
apt-get -s install --only-upgrade ...
を実行してSimulationします。
ここで確認するのは、
新しいPackageがInstallされないか
既存PackageがRemoveされないか
Downgradeが発生しないか
指定していないPackageまで
Upgradeされないか
です。
今回のUpdate Scriptでは、この条件を
CHAIN-ZERO
として扱っています。
指定したPackage以外に影響するならUpdateしない
例えばIoT EdgeのPatchを適用しようとして、
Dry-runの結果が、
IoT Edge更新
+
別Packageを新規Install
+
別PackageもUpgrade
+
別PackageをRemove
となった場合、そのUpdateは実行しません。
狙っている状態は、
変更対象
↓
指定したIoT Edge / Runtime Packageだけ
です。
判断の流れは、
同一世代か
↓
依存関係に変化がないか
↓
対象外Packageへ波及しないか
↓
問題なければUpdate
です。
重要なのは、
Version番号だけではなく、APTが実際に何を変更しようとしているかを見る
ことです。
IoT Edge / Mobyだけ専用のUpdate Routeを作る
今回、通常のUbuntu Packageとは別に、IoT Edge系専用のUpdate処理を用意しています。
対象は例えば、
aziot-edge
aziot-identity-service
aziot-keyd
aziot-certd
aziot-tpmd
aziot-edged
moby-engine
moby-containerd
moby-runc
です。
処理フローは、
Installed Version確認
↓
Candidate Version確認
↓
世代確認
↓
世代変更ならBlock
↓
同一世代PatchならDry-run
↓
CHAIN-ZERO確認
↓
対象PackageだけUpdate
↓
Health Check
としています。
この処理を systemd timer から 01:30 に実行します。
また、
RandomizedDelaySec=300
も設定し、複数処理が同時刻に集中しにくいようにしています。
世代変更候補が一つでもあればさらに慎重にする
Update対象の中に一つでも、
世代変更候補
があった場合、通常のIoT Edge / Moby Updateをまとめて実行しません。
世代変更候補あり
↓
通常Update Route停止
↓
pending-genupgrade作成
↓
IoT Edgeの同世代Patchだけ抽出
↓
再度Dry-run
↓
対象外への影響がゼロならPatch
↓
それ以外はUpdateをSkip
としています。
一部のComponentで想定外のVersion変更が見えている状態で、
関連Package全部Update
としてしまわないためです。
Update後は「apt-getが成功した」だけでは終わらせない
Package Updateが成功しても、
Edge Gatewayとして正常
とは限りません。
そこでUpdate直後にHealth Checkを実行します。
確認しているのは例えば、
aziot-edged が active
docker が active
iotedge system status に
fatal
failed
inactive
stopped
unhealthy
などがない
edgeAgent が Up
edgeHub が Up
です。
さらに、edgeHub Container内部からAzure IoT HubのFQDNをDNS解決できるかも確認します。
Update
↓
Service確認
↓
IoT Edge状態確認
↓
edgeAgent確認
↓
edgeHub確認
↓
Container内部からDNS確認
という流れです。
異常があった場合には、
journalctl -u aziot-edged
journalctl -u docker
docker ps -a
などのDiagnostic情報も取得するようにしています。
Cloud側へのHTTPS / WebSockets通信も確認する
RuntimeやContainerが正常でも、
Cloudへ通信できない
のであればEdge Gatewayとしては正常とはいえません。
今回の環境では施設NetworkによってAMQPがBlockされるケースを考慮し、Cloud通信にはHTTPS / WebSockets 443を使用しています。
そのため、
iotedge check --verbose
などを利用して、
module network can connect to upstream HTTPS / WebSockets
が正常か確認します。
最終的には、
Package Update成功
↓
Runtime正常
↓
Container正常
↓
DNS正常
↓
Cloud通信正常
まで確認して、
Update成功
と判断します。
IoT Edge Update直後にもRebootしない
IoT Edge / MobyをUpdateした場合も、その場で即Rebootしません。
IoT Edge / Moby Update
↓
pending-iotedge-reboot
↓
待機
↓
03:30
↓
FlagがあればReboot
という流れです。
さらに、
/var/run/reboot-required
がすでに存在する場合は、IoT Edge側のRebootを実行しません。
OS RebootとIoT Edge側のRebootが短時間に連続して発生することを防ぎます。
Maintenance処理の時間も分ける
今回の構成では、
01:30
IoT Edge / Moby Patch Update
02:30
OS Updateによる
reboot-required確認
03:30
IoT Edge / Moby更新Flag確認
というように処理時間を分離しています。
これは、
Updateを管理する
だけではなく、
Maintenance処理同士を衝突させない
ためです。
一つ一つの処理に問題がなくても、
Update
Reboot
Network Maintenance
Container Restart
が同じ時間に集中すると、それ自体が障害原因になる可能性があります。
今回のUpdateフロー全体
最終的なIoT Edge Update処理をまとめると、
APT Update
↓
Installed Version取得
↓
Candidate Version取得
↓
検証済み世代か確認
↓
┌─────────────────┐
│ 世代変更あり │
└─────────────────┘
↓
Updateしない
↓
pending-genupgrade
┌─────────────────┐
│ 同一世代Patch │
└─────────────────┘
↓
apt-get -s
↓
Dependency確認
↓
CHAIN-ZERO確認
↓
対象PackageだけUpdate
↓
Health Check
↓
pending-iotedge-reboot
↓
Maintenance時間
↓
Reboot
という構成です。
Version管理はTroubleshootingにも効く
Version固定は安定稼働だけのためではありません。
複数拠点へEdge PCを展開した場合、
端末A
IoT Edge Version A
端末B
IoT Edge Version B
端末C
IoT Edge Version C
となっていると、障害発生時に、
Hardware?
Ubuntu?
Kernel?
IoT Edge?
Container Runtime?
Application?
と確認対象が増えます。
逆に、
Ubuntu
IoT Edge
Container Runtime
Application
の基準をそろえておけば、端末間の差分を減らせます。
例えば、
昨日まで正常
↓
今日から異常
という障害でも、
IoT Edge世代は変わっていない
Container Runtime世代も変わっていない
ことが分かれば、
Network
Cloud
外部Device
Application
Hardware
電源
などへ調査範囲を絞りやすくなります。
Version管理は、安定稼働だけでなくTroubleshootingのためでもあります。
まとめ
24時間365日の無人Edge端末では、
最新版にする
ことそのものが目的ではありません。
必要なのは、
Securityを維持しながら、検証済みの構成を不用意に変えないこと
だと考えています。
今回の構成では、
Ubuntu Security Update
↓
継続する
Azure IoT Edge / Runtime
↓
検証済み世代を維持
同一世代Patch
↓
条件付きで適用
世代変更
↓
検知するが自動適用しない
Update前
↓
Dry-run / Dependency確認
Update後
↓
Runtime / Container / DNS /
Cloud通信をHealth Check
Reboot
↓
Maintenance時間に分離
としました。
特に重要だと考えているのは、
Patchは取り込む
+
世代変更は止める
+
依存関係の変化も確認する
+
Update後に動作確認する
という4点です。
無人Edge Gatewayでは、
「Updateできたか」ではなく、「Update後も同じ役割を果たせているか」
まで確認して初めてUpdate完了と考える必要があります。
次回:24/365無人Edge端末をどう安定稼働させるか
VersionとUpdateを管理できても、24時間365日の無人運用はそれだけでは完成しません。
実際には、
Network断
DNS異常
Cloud接続断
Container停止
時刻ずれ
Reboot後に一部Serviceだけ復旧しない
電源断後に正常状態まで戻らない
といった障害も考える必要があります。
次回は、
24時間365日、人が操作しないEdge端末を安定稼働させるためのSystem全体の設計
として、
定期Maintenance
↓
systemd timerによる処理分散
↓
状態確認
↓
異常時の自己回復
↓
それでも復旧できない場合は外部監視
という構成を整理します。