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?

#10. Azure IoT Edgeを自動更新で壊さないために、世代固定しながらPatchだけ適用する

0
Posted at

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による処理分散
↓
状態確認
↓
異常時の自己回復
↓
それでも復旧できない場合は外部監視

という構成を整理します。

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?