目次 [非表示]
EdgeX Foundry 関連エントリ
- 冬休みの自由研究: EdgeX Foundry (1) 概要
- 冬休みの自由研究: EdgeX Foundry (2) 導入とデータの確認
- 冬休みの自由研究: EdgeX Foundry (3) エクスポートサービスによるエクスポート
- 冬休みの自由研究: EdgeX Foundry (4) MQTT デバイスサービスの追加
- 冬休みの自由研究: EdgeX Foundry (5) ルールエンジンによる自動制御
- 冬休みの自由研究: EdgeX Foundry (6) アプリケーションサービスによるエクスポート
- 冬休みの自由研究: EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす
- EdgeX Foundry ハンズオンラボガイド公開
- EdgeX Foundry: Geneva から Hanoi へ
EdgeX Foundry と Kubernetes
これまでの EdgeX Foundry 関連エントリでは、一貫してその動作を Docker Compose に任せていました。公式でも Docker Compose や Snaps を利用して動作させる手順が紹介されています。
が、最近よく Kubernetes(や OpenShift)を触っていることもあり、コンテナなら Kubernetes でも動かせるよね! という気持ちになったので、やってみました。
なお、現段階では、Kubernetes 上で動作させるためのマニフェストファイルは、公式には用意されていません。また、そもそも EdgeX Foundry は HA 構成を明確にはサポートしておらず、実装上も考慮されていないようです。つまり、仮に Kubernetes で動作させられたとしても、レプリカを増やしてロードバランスするような状態での正常な動作は何の保証もないことになります。
というわけで、現状ではあくまで実験程度に捉えておくのがよいと思います。
今回の目標
後述しますが、EdgeX Foundry の Kubernetes 上での動作は、過去にすでに試みられているので、今回はそれらの先行実装で解決されていない以下の 2 点を解消できる実装を目標としました。
- 現時点での Fuji リリースを動作させられること
- マルチノード構成の Kubernetes で動かせること
幸いなことに、自宅の vSphere 環境では Cluster API を使ってマルチノードの Kubernetes 環境がすぐに作れるようになっている ので、この環境を使います。
できあがったマニフェスト
紆余曲折を経てひとまず動くものができたので、リポジトリ に置いてあります。
以下、使い方を簡単に紹介します。リポジトリはあらかじめクローンしておきます。
1 2 | $ git clone https: //github .com /kurokobo/edgex-on-kubernetes .git $ cd edgex-on-kubernetes |
Persistent Volume の用意
PV を 4 つ使うので、PV の実体となるものを用意します。今回は QNAP を NFS ストレージとして使いました。
エクスポートされた NFS 領域を適当な作業用の Linux 端末からマウントして、PV の実体となるサブディレクトリを作っておきます。
1 2 3 | $ mount -t nfs 192.168.0.200: /k8s /mnt/k8s $ for i in $( seq -f %03g 100); do sudo mkdir /mnt/k8s/pv $i; done $ umount /mnt/k8s |
続けて、これらの NFS 領域を Kubernetes の PersistentVolume
として定義します。マニフェストの中身はこんな感じです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ cat persistentvolumes /nfs/pv001-persistentvolume .yaml apiVersion: v1 kind: PersistentVolume metadata: name: pv001 spec: capacity: storage: 512Mi accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Recycle nfs: server: 192.168.0.200 path: /k8s/pv001 |
当然ですが、これはぼくの環境に合わせたものなので、それ以外の環境で動かす場合は適宜修正は必要です。今回は nfs
で書いていますが、hostPath
でも gcePersistentDisk
でも好きなようにしてください。
逆にいえば、Deployment ではボリュームを PVC で割り当てているので、環境に合わせて編集するのは PV だけでどうにかなる気がします。
マニフェストができたら、kubectl create
します。
1 2 3 4 5 6 7 8 9 10 11 12 | $ ls persistentvolumes /nfs/ *.yaml | xargs -n 1 kubectl create -f persistentvolume /pv001 created persistentvolume /pv002 created persistentvolume /pv003 created persistentvolume /pv004 created $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv001 512Mi RWX Recycle Available 22s pv002 128Mi RWX Recycle Available 22s pv003 128Mi RWX Recycle Available 22s pv004 128Mi RWX Recycle Available 22s |
Persistent Volume Claim の用意
PV ができたら、PVC を作ります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | $ ls persistentvolumeclaims/*.yaml | xargs -n 1 kubectl create -f persistentvolumeclaim /consul-config created persistentvolumeclaim /consul-data created persistentvolumeclaim /db-data created persistentvolumeclaim /log-data created $ kubectl get pv,pvc NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume /pv001 512Mi RWX Recycle Bound default /db-data 11m persistentvolume /pv002 128Mi RWX Recycle Bound default /consul-data 11m persistentvolume /pv003 128Mi RWX Recycle Bound default /log-data 11m persistentvolume /pv004 128Mi RWX Recycle Bound default /consul-config 11m NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim /consul-config Bound pv004 128Mi RWX 8s persistentvolumeclaim /consul-data Bound pv002 128Mi RWX 8s persistentvolumeclaim /db-data Bound pv001 512Mi RWX 8s persistentvolumeclaim /log-data Bound pv003 128Mi RWX 7s |
PV の STATUS
が Bound
になって、PVC の VOLUME
で PV が対応付けられていれば大丈夫です。
Service の作成
続けて SVC を作ります。マニフェストファイル内で、外部から触りそうなヤツだけ type: LoadBalancer
を書いて外部 IP アドレスを持たせています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | $ ls services/*.yaml | xargs -n 1 kubectl create -f service/edgex-app-service-configurable-rules created service/edgex-core-command created service/edgex-core-consul created service/edgex-core-data created service/edgex-core-metadata created service/edgex-device-virtual created service/edgex-mongo created service/edgex-support-logging created service/edgex-support-notifications created service/edgex-support-rulesengine created service/edgex-support-scheduler created service/edgex-sys-mgmt-agent created service/edgex-ui-go created $ kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE edgex-app-service-configurable-rules ClusterIP 100.67.112.145 <none> 48100/TCP 8s edgex-core-command LoadBalancer 100.69.10.125 192.168.0.50 48082:31022/TCP 8s edgex-core-consul LoadBalancer 100.71.56.117 192.168.0.51 8400:30675/TCP,8500:32711/TCP 8s edgex-core-data LoadBalancer 100.70.131.230 192.168.0.52 48080:31879/TCP,5563:31195/TCP 8s edgex-core-metadata LoadBalancer 100.68.152.47 192.168.0.53 48081:30265/TCP 8s edgex-device-virtual ClusterIP 100.70.115.198 <none> 49990/TCP 8s edgex-mongo LoadBalancer 100.69.209.129 192.168.0.54 27017:30701/TCP 7s edgex-support-logging ClusterIP 100.65.29.76 <none> 48061/TCP 7s edgex-support-notifications ClusterIP 100.65.127.176 <none> 48060/TCP 7s edgex-support-rulesengine ClusterIP 100.64.125.224 <none> 48075/TCP 7s edgex-support-scheduler ClusterIP 100.70.168.47 <none> 48085/TCP 7s edgex-sys-mgmt-agent LoadBalancer 100.68.113.150 192.168.0.55 48090:32466/TCP 7s edgex-ui-go LoadBalancer 100.64.153.22 192.168.0.56 4000:32252/TCP 7s kubernetes ClusterIP 100.64.0.1 <none> 443/TCP 14m |
外部からの触りっぷりをもうちょっときれいにしたい場合は、Ingress でも作るとよいと思います。
Deployment の作成と Consul の初期化
ここまでできたら、あとは順次マイクロサービスを起動していきます。
最初に、edgex-files
を起動させて、PV 内にファイルを配置します。その後、Consul(edgex-core-consul
)を起動させます。
1 2 3 4 5 6 7 8 9 10 | $ kubectl create -f pods/edgex-files-pod.yaml pod/edgex-files created $ while [[ $(kubectl get pods edgex-files -o "jsonpath={.status.phase}") != "Succeeded" ]]; do sleep 1; done $ kubectl create -f deployments/edgex-core-consul-deployment.yaml deployment.apps/edgex-core-consul created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-core-consul deployment.apps/edgex-core-consul condition met |
Consul が起動したら、edgex-core-config-seed
を動作させて、Consul に初期設定値を登録させます。
1 2 3 4 | $ kubectl create -f pods /edgex-core-config-seed-pod .yaml pod /edgex-core-config-seed created $ while [[ $(kubectl get pods edgex-core-config-seed -o "jsonpath={.status.phase}" ) != "Succeeded" ]]; do sleep 1; done |
edgex-files
と edgex-core-config-seed
は、それぞれ edgex-files
はファイルのコピーが終わったら、edgex-core-config-seed
は Consul に値を登録し終わったら不要になります。そのため、ここではこれらは Deployment リソースとしてではなく Pod リソースとして定義しています。
残りの Deployment の作成
残りのマイクロサービスを起動していきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | $ kubectl create -f deployments /edgex-mongo-deployment .yaml deployment.apps /edgex-mongo created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-mongo deployment.apps /edgex-mongo condition met $ kubectl create -f deployments /edgex-support-logging-deployment .yaml deployment.apps /edgex-support-logging created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-support-logging deployment.apps /edgex-support-logging condition met $ kubectl create -f deployments /edgex-support-notifications-deployment .yaml deployment.apps /edgex-support-notifications created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-support-notifications deployment.apps /edgex-support-notifications condition met $ kubectl create -f deployments /edgex-core-metadata-deployment .yaml deployment.apps /edgex-core-metadata created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-core-metadata deployment.apps /edgex-core-metadata condition met $ kubectl create -f deployments /edgex-core-data-deployment .yaml deployment.apps /edgex-core-data created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-core-data deployment.apps /edgex-core-data condition met $ kubectl create -f deployments /edgex-core-command-deployment .yaml deployment.apps /edgex-core-command created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-core- command deployment.apps /edgex-core-command condition met $ kubectl create -f deployments /edgex-support-scheduler-deployment .yaml deployment.apps /edgex-support-scheduler created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-support-scheduler deployment.apps /edgex-support-scheduler condition met $ kubectl create -f deployments /edgex-support-rulesengine-deployment .yaml deployment.apps /edgex-support-rulesengine created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-support-rulesengine deployment.apps /edgex-support-rulesengine condition met $ kubectl create -f deployments /edgex-app-service-configurable-rules-deployment .yaml deployment.apps /edgex-app-service-configurable-rules created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-app-service-configurable-rules deployment.apps /edgex-app-service-configurable-rules condition met $ kubectl create -f deployments /edgex-device-virtual-deployment .yaml deployment.apps /edgex-device-virtual created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-device-virtual deployment.apps /edgex-device-virtual condition met $ kubectl create -f deployments /edgex-sys-mgmt-agent-deployment .yaml deployment.apps /edgex-sys-mgmt-agent created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-sys-mgmt-agent deployment.apps /edgex-sys-mgmt-agent condition met $ kubectl create -f deployments /edgex-ui-go-deployment .yaml deployment.apps /edgex-ui-go created $ kubectl wait -- for =condition=available --timeout=60s deployment edgex-ui-go deployment.apps /edgex-ui-go condition met |
完成
完成です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | $ kubectl get all,pv,pvc NAME READY STATUS RESTARTS AGE pod /edgex-app-service-configurable-rules-6c794d754d-xj4v4 1 /1 Running 0 92s pod /edgex-core-command-cc8465b6f-kgwhm 1 /1 Running 0 98s pod /edgex-core-config-seed 0 /1 Completed 0 2m13s pod /edgex-core-consul-5b8cdbbfc8-g7j6t 1 /1 Running 0 2m26s pod /edgex-core-data-c7775c56b-t8rdf 1 /1 Running 0 100s pod /edgex-core-metadata-66f86d86b-mfqfg 1 /1 Running 0 102s pod /edgex-device-virtual-597f9d77f-vqlln 1 /1 Running 0 90s pod /edgex-files 0 /1 Completed 0 6m13s pod /edgex-mongo-99c7d8957-fsz6c 1 /1 Running 0 110s pod /edgex-support-logging-8566649bf8-dj8fw 1 /1 Running 0 107s pod /edgex-support-notifications-586cf544f-n8md7 1 /1 Running 0 104s pod /edgex-support-rulesengine-6cfc49f89b-xn8fc 1 /1 Running 0 94s pod /edgex-support-scheduler-7658f985bb-9bmz2 1 /1 Running 0 96s pod /edgex-sys-mgmt-agent-5dd56c65bb-tnqth 1 /1 Running 0 88s pod /edgex-ui-go-54668dcc94-mdx2d 1 /1 Running 0 86s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service /edgex-app-service-configurable-rules ClusterIP 100.67.11.126 <none> 48100 /TCP 7m33s service /edgex-core-command LoadBalancer 100.71.84.78 192.168.0.50 48082:32108 /TCP 7m33s service /edgex-core-consul LoadBalancer 100.66.136.131 192.168.0.51 8400:31442 /TCP ,8500:31806 /TCP 7m32s service /edgex-core-data LoadBalancer 100.64.210.255 192.168.0.52 48080:31808 /TCP ,5563:30664 /TCP 7m32s service /edgex-core-metadata LoadBalancer 100.67.207.51 192.168.0.53 48081:32379 /TCP 7m32s service /edgex-device-virtual ClusterIP 100.69.130.2 <none> 49990 /TCP 7m32s service /edgex-mongo LoadBalancer 100.66.8.30 192.168.0.54 27017:31973 /TCP 7m32s service /edgex-support-logging ClusterIP 100.68.171.73 <none> 48061 /TCP 7m32s service /edgex-support-notifications ClusterIP 100.67.242.204 <none> 48060 /TCP 7m32s service /edgex-support-rulesengine ClusterIP 100.67.203.199 <none> 48075 /TCP 7m32s service /edgex-support-scheduler ClusterIP 100.65.53.193 <none> 48085 /TCP 7m31s service /edgex-sys-mgmt-agent LoadBalancer 100.65.122.248 192.168.0.55 48090:31476 /TCP 7m31s service /edgex-ui-go LoadBalancer 100.70.107.81 192.168.0.56 4000:30852 /TCP 7m31s service /kubernetes ClusterIP 100.64.0.1 <none> 443 /TCP 47m NAME READY UP-TO-DATE AVAILABLE AGE deployment.apps /edgex-app-service-configurable-rules 1 /1 1 1 92s deployment.apps /edgex-core-command 1 /1 1 1 98s deployment.apps /edgex-core-consul 1 /1 1 1 2m26s deployment.apps /edgex-core-data 1 /1 1 1 100s deployment.apps /edgex-core-metadata 1 /1 1 1 102s deployment.apps /edgex-device-virtual 1 /1 1 1 90s deployment.apps /edgex-mongo 1 /1 1 1 110s deployment.apps /edgex-support-logging 1 /1 1 1 107s deployment.apps /edgex-support-notifications 1 /1 1 1 104s deployment.apps /edgex-support-rulesengine 1 /1 1 1 94s deployment.apps /edgex-support-scheduler 1 /1 1 1 96s deployment.apps /edgex-sys-mgmt-agent 1 /1 1 1 88s deployment.apps /edgex-ui-go 1 /1 1 1 86s NAME DESIRED CURRENT READY AGE replicaset.apps /edgex-app-service-configurable-rules-6c794d754d 1 1 1 92s replicaset.apps /edgex-core-command-cc8465b6f 1 1 1 98s replicaset.apps /edgex-core-consul-5b8cdbbfc8 1 1 1 2m26s replicaset.apps /edgex-core-data-c7775c56b 1 1 1 100s replicaset.apps /edgex-core-metadata-66f86d86b 1 1 1 102s replicaset.apps /edgex-device-virtual-597f9d77f 1 1 1 90s replicaset.apps /edgex-mongo-99c7d8957 1 1 1 110s replicaset.apps /edgex-support-logging-8566649bf8 1 1 1 107s replicaset.apps /edgex-support-notifications-586cf544f 1 1 1 104s replicaset.apps /edgex-support-rulesengine-6cfc49f89b 1 1 1 94s replicaset.apps /edgex-support-scheduler-7658f985bb 1 1 1 96s replicaset.apps /edgex-sys-mgmt-agent-5dd56c65bb 1 1 1 88s replicaset.apps /edgex-ui-go-54668dcc94 1 1 1 86s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume /pv001 512Mi RWX Recycle Bound default /db-data 8m11s persistentvolume /pv002 128Mi RWX Recycle Bound default /consul-config 8m11s persistentvolume /pv003 128Mi RWX Recycle Bound default /consul-data 8m10s persistentvolume /pv004 128Mi RWX Recycle Bound default /log-data 8m10s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim /consul-config Bound pv002 128Mi RWX 8m4s persistentvolumeclaim /consul-data Bound pv003 128Mi RWX 8m4s persistentvolumeclaim /db-data Bound pv001 512Mi RWX 8m4s persistentvolumeclaim /log-data Bound pv004 128Mi RWX 8m4s |
動作確認
標準で edgex-device-virtual
が動いているので、edgex-core-data
のログなどを除けば値が取り込まれていることが観察できます。
1 2 3 4 5 | $ kubectl logs edgex-core-data-c7775c56b-t8rdf -- tail 4 level=INFO ts=2020-03-01T07:41:47.454970177Z app=edgex-core-data source =event.go:240 msg= "Putting event on message queue" level=INFO ts=2020-03-01T07:41:47.455410868Z app=edgex-core-data source =event.go:258 msg= "Event Published on message queue. Topic: events, Correlation-id: 4d142e84-f3b2-49cd-a1d4-8be7dc7e5d36 " level=INFO ts=2020-03-01T07:41:47.47515949Z app=edgex-core-data source =event.go:240 msg= "Putting event on message queue" level=INFO ts=2020-03-01T07:41:47.47523845Z app=edgex-core-data source =event.go:258 msg= "Event Published on message queue. Topic: events, Correlation-id: 6e6f7f2a-da7f-4fc7-ac1f-85e5918c31ed " |
適当に MQTT トピックにエクスポートさせると、実際に値が届いていることが確認できます。エクスポート先の MQTT ブローカは、マニフェストファイルで環境変数として定義しているので、適宜修正してください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $ docker run -d --rm --name broker -p 1883:1883 eclipse-mosquitto 598878ed584699c61b4ccb95cdaeb53cc70b3a56793b53c83db1511483dc037a $ kubectl create -f deployments/edgex-app-service-configurable-mqtt-export-deployment.yaml deployment.apps/edgex-app-service-configurable-mqtt-export created $ kubectl wait --for=condition=available --timeout=60s deployment edgex-app-service-configurable-mqtt-export deployment.apps/edgex-app-service-configurable-mqtt-export condition met $ docker run --init --rm -it efrecon/mqtt-client sub -h 192.168.0.100 -t "#" -v edgex-handson-topic {"id":"5a79e954-6c3f-4635-9e60-d93379a2a7ec","device":"Random-UnsignedInteger-Device","origin":1583049398041588772,"readings":[{"id":"529f5003-47b6-45ab-a743-7b4a21d937f4","origin":1583049398026423142,"device":"Random-UnsignedInteger-Device","name":"Uint8","value":"80"}]} edgex-handson-topic {"id":"2cd0b56e-28c8-4035-9351-75028b37afef","device":"Random-UnsignedInteger-Device","origin":1583049398427444993,"readings":[{"id":"0a041b11-76f9-426c-9098-75affe58ea89","origin":1583049398412848451,"device":"Random-UnsignedInteger-Device","name":"Uint64","value":"2542183823530459689"}]} edgex-handson-topic {"id":"00aaffb1-27ca-43ff-8150-ace2eefe2a32","device":"Random-Boolean-Device","origin":1583049398815297954,"readings":[{"id":"7ecc8518-ae56-4c78-8527-dd3442b14c6d","origin":1583049398800419572,"device":"Random-Boolean-Device","name":"Bool","value":"false"}]} ... |
残されている問題
そんなわけで、とりあえずは動いていますが、
- 起動・停止が面倒
- マイクロサービス間に依存関係があり、起動順序に配慮が必要(依存関係自体は Docker Compose でも定義されているし起動順序も公式ドキュメントに書いてあるので無視できない)
- Kubernetes では起動順序を制御しにくい(実装が検討されているっぽい のでそれに期待)
- 現状では、コマンド群をシェルスクリプトにするか、InitContianers などでがんばるのが精々。Kustomize とか Helm でも細かい順序制御はしんどい気がする
- 設定ファイルの流し込みがしにくい
configuration.toml
などをコンテナに渡しにくい- PV を新しく作って InitContainers で
git clone
するなどの工夫が必要
- ヘルスチェックをしていない
- いわゆる Liveness Probe、Rediness Probe の追加をサボった
edgex-sys-mgmt-agent
が部分的に動かないかも(未確認)/var/run/docker.sock
を消したが影響を確認していない
あたりに本気で使うにはハードルがありそうです。将来的には公式に参考実装やベストプラクティスが出てくるといいですね。
マニフェストの作成過程
いきなりできあがったモノを紹介しましたが、作る過程をざっくり書いておきます。
とりあえず Kompose で変換する
Docker Compose ファイルを Kubernetes 用のマニフェストに変換できる Kompose というものがあります。おもむろにこれに突っ込めば完成!
1 2 3 4 5 6 | $ kompose convert -f docker-compose.yml INFO Network edgex-network is detected at Source, shall be converted to equivalent NetworkPolicy at Destination ... INFO Kubernetes file "app-service-rules-service.yaml" created ... INFO Kubernetes file "edgex-network-networkpolicy.yaml" created |
となるはずもなく、このままだと全然動きません。この作業で、
- Services(
*-service.yaml
) - Deployments(
*-deployment.yaml
) - Persistent Volyme Claims(
*-persistentvolumeclaim.yaml
) - Network Policy(
*-networkpolicy.yaml
)
の 4 種類のファイルができあがっています。
このままだと動かないとは言いつつ、ベースとしては充分使えるので、これを編集していく形で実装を考えます。
アーキテクチャを考える
この段階で、テストだからがばがばでよしとすることにして、Network Policy のマニフェストは消しました。
残りのマニフェストで定義された構成を、以下のように考えて組み替えていきます。
- 共有ボリュームの持たせ方
- Docker Compose ファイルだと、ボリュームが無駄に広い範囲で共有されすぎている気がする
- 最新の Nightly Build リリースの Docker Compose ファイルをみると、最小限に絞られていそうだ
- Nightly Build 版での共有っぷりを参考に、今回も共有範囲は最小限に絞ることにする
- 共有ボリュームへのファイルの配置の仕方
edgex-files
のコンテナイメージ内に共有ボリューム上に置くべきファイルが保存されている- Docker Compose と同じようにマウントしてしまうと、既存のファイルがオーバライドされて見えなくなる
- 別のパスにマウントして、
command
とargs
で必要なファイルをコピーさせるようにする - 一度だけ動けばよいので、
restartPolicy
をNever
にした Pod にする
- 設定の配り方
- 先行実装では
edgex-config-seed
を消し去っていた(ローカルの設定ファイルを使わせていた)例がある - とはいえ EdgeX Foundry は Consul ありきでできているフシもあるので、できれば Docker Compose のときと同じようにしたい
- 一度だけ動けばよいので、
restartPolicy
をNever
にした Pod にする
- 先行実装では
- Docker ならではの実装の排除
- Portainer は Docker 前提なので消すことにする
/var/run/docker.sock
のマウントも Docker 前提なので消すことにする
- PV の作り方
- Deployment に直接 PV を持たせるのではなく、お作法通り PVC を使ってマウントさせるようにする
マニフェストの整形
方針が決まったら、Kompose で変換されたマニフェストファイルを実際にきれいにしていきます。
- Services
metadata
を削除、または修正- ほかのマイクロサービスから正しく名前解決できるように
name
を各マイクロサービスのホスト名に修正 selector
を Deployment に合わせて修正- 外部からアクセスしそうなサービスに
type: LoadBalancer
を追加
- Deployments
- API バージョンを修正、併せて必須パラメータ(
selector
)追加 metadata
を削除、または修正- Deployment と Pod の
name
を各マイクロサービスのホスト名に統一 - 不要なボリュームの削除
edgex-core-config-seed
とedgex-files
の削除
- API バージョンを修正、併せて必須パラメータ(
- Pods
edgex-core-config-seed
とedgex-files
を新規に追加restartPolicy
やボリュームの変更、command
とargs
の追加
- Persistent Volume Claims
metadata
を削除、または修正accessModes
はReadWriteMany
に変更- サイズを変更(値は このとき の実使用量を基に決定)
- Persistent Volumes
- 新規に追加
こまごまトラブルシュート
で、だいたいできたと思ったら、一部のマイクロサービスで、REST エンドポイントとして必要な HTTP サーバが起動しない状態になりました。以下のような具合で、起動しようとした直後に停止してしまっています。
1 2 3 4 5 | $ kubectl logs edgex-support-logging-66fc4f6c98-28bk4 ... level=INFO ts=2020-02-25T12:47:50.622662811Z app=edgex-support-logging source=httpserver.go:72 msg="Web server starting (edgex-support-logging:48061)" level=INFO ts=2020-02-25T12:47:50.624106767Z app=edgex-support-logging source=httpserver.go:80 msg="Web server stopped" ... |
いろいろ調べたら、以下のような状態のようでした。
- edgex-go リポジトリで管理されているマイクロサービスで起きる
- 具体的には内部で go-mod-bootstrap の
httpserver
を使っていると起きる
- 具体的には内部で go-mod-bootstrap の
- HTTP サーバは、マイクロサービスの起動時に
<ホスト名>:<ポート番号>
で待ち受けを指示される- 上記のログの例だと
edgex-support-logging:48061
- 待ち受けのホスト名とポート番号は Consul から受け取る
- 上記のログの例だと
- HTTP サーバは、指示された
<ホスト名>
の名前解決を試みる- コンテナ内の
/etc/hosts
を無視して、Kubernetes による名前解決が行われる - 名前解決したいホスト名が Service の名前と一致しているため、Kubernetes の
ClusterIP
に解決される
- コンテナ内の
- Pod 内のプロセスからは
ClusterIP
は利用できない(Pod の IP アドレスしか認識されない)ため、待ち受けできずに死ぬ
Consul 上のホスト名を書き換えるのも、Pod と Service でホスト名を別にするのも、副作用がわからないのでできれば避けたいところです。そもそも Kubernetes の仕様上、コンテナ内の /etc/hosts
には Pod の IP アドレスと Pod のホスト名が正しく書いてあるわけですから、これを参照してくれさえすればよいだけの話です。
が、Go 言語のデフォルトの名前解決の仕組みでは、/etc/nsswitch.conf
が存在しない(または存在していても中で files
が指定されていない)場合に、/etc/hosts
は無視されるようです。この挙動の修正は 1.15 のロードマップに乗っている ものの、要するに、現時点ではそういう仕様ということになります。
そして、問題が起きているマイクロサービスのコンテナイメージは、scratch
や alpine
などがベースになっているので、/etc/nsswitch.conf
が存在しません。
というわけで、いろいろな仕様が重なった結果として、こういう問題が引き起こされています。がんばりましょう。
この解決にあたって、以下の 3 案を考えました。今回はいちばんマニフェストへの取り込みがラクな最初の案を採用しています。
- 環境変数
GODEBUG
に、値netdns=cgo
を与える- 名前解決に Go 標準ではなく CGO の仕組みを使うことを強制するオプション
- CGO では
/etc/nsswitch.conf
が存在していなくても/etc/hosts
が読み取られる
/etc/nsswitch.conf
(中身はhosts: files dns
)をコンテナ内に配置する- コンテナイメージを自製するか、PV や InitContainer などを使ってがんばる必要がある
- Consul の K/V ストアの
/edgex/core/1.0/*/Service/Host
を全部0.0.0.0
にする- Consul を起動させて、手で書き換えてから残りのサービスを起動するようにする
- 副作用が不明
先行実装
冒頭で書いた通り、EdgeX Foundry の Kubernetes 上での動作は、すでに過去に試みられています。現状で確認できているものを簡単に紹介します。
EdgeX on Kubernetes
EdgeX Foundry のブログの 2018 年のエントリで、当時のリリースを Kubernetes で動作させる例が紹介されています。
- Running EdgeX Foundry on Kubernetes
2018 年当時なので、使われているイメージは古く、どうやら Barcelona ベースのようです。
また、共有ボリュームとして利用する PV が hostPath
で定義されているので、シングルノード上での動作が前提になっているものと推測されます。
edgex-kubernetes-support
GitHub の edgexfoundry-holding
配下のリポジトリとして用意されています。比較的新しめです。
- edgex-kubernetes-support
こちらは Edinburgh をベースにしたものと Nightly Build をベースにしたものがありました。Nightly Build とはいえ、半年以上前のそれなので、実際は Fuji 相当に近そうです。Helm チャートも用意されています。
K8s だけでなく K3s も対象としている点はうれしいですが、これも PV が hostPath
で定義されているので、シングルノードでの動作を前提としているようです。
まとめ
EdgeX Foundry の Fuji リリースを、マルチノード構成の Kubernetes 上で動作させるためのマニフェストファイルを作成して、動作を確認しました。
Kompose は便利ですが、やはりそれなりの規模の Docker Compose ファイルだと完全に無編集でそのまま使えるというわけにはいかないですね。よい経験になりました。
EdgeX Foundry も Kubernetes も、本当に日々更新されていっているので、追従し続けるのは相当しんどそうです。このブログの EdgeX Foundry 関連エントリも、来月に予定されている次期バージョン(Geneva)のリリースですぐに時代遅れになるわけです。がんばりましょう。
EdgeX Foundry 関連エントリ
- 冬休みの自由研究: EdgeX Foundry (1) 概要
- 冬休みの自由研究: EdgeX Foundry (2) 導入とデータの確認
- 冬休みの自由研究: EdgeX Foundry (3) エクスポートサービスによるエクスポート
- 冬休みの自由研究: EdgeX Foundry (4) MQTT デバイスサービスの追加
- 冬休みの自由研究: EdgeX Foundry (5) ルールエンジンによる自動制御
- 冬休みの自由研究: EdgeX Foundry (6) アプリケーションサービスによるエクスポート
- 冬休みの自由研究: EdgeX Foundry (7) Kubernetes 上で Fuji リリースを動かす
- EdgeX Foundry ハンズオンラボガイド公開
- EdgeX Foundry: Geneva から Hanoi へ