はじめに
EDA Server のデプロイ方法として、公式のデプロイメントガイド では Docker Compose と Minikube が案内されています。
その一方で、ひっそりと EDA Server Operator が公開されています。これは、EDA Server をデプロイするための Operator です。
まだ開発中であり、前述のデプロイメントガイドに記載もないことから、完全にサポートされている手段とはまったく言えませんが、本エントリではこれを用いた EDA Server のデプロイと、その後の AWX との連携を、Kubernetes 上での EDA Server の動きを含めて簡単に紹介します。
なお、この Operator の名称は EDA Controller Operator が正しいような気もしており、中の方々の意見を聞く意味も兼ねて Issue と PR を作っていますが、今のところ何ら確証はないので、本エントリでは現時点のドキュメントに従って EDA Server Operator と表記しています。
本エントリ中で利用しているファイルは、GitHub のリポジトリ にも配置しています。
追記(2023/08/12)
追記 (1)
公式のデプロイメントガイド が更新され、推奨されるデプロイ方式が Operator になりました。
追記 (2)
当初、一貫してツール名称を EDA Controller と表記していましたが、アップストリーム版は EDA Server であり、EDA Controller は Ansible Automation Platform に含まれる製品版の名称 とのこと なので、タイトルと本文を修正しました。
Operator の名称も EDA Server Operator で 正しい ようです。
Operator を利用した EDA Server のデプロイ
EDA Server Operator により、EDA Server のインスタンスが Kubernetes の Custom Resources(CR)として管理できるようになります。
手順の全体的な考え方は、AWX を AWX Operator でデプロイするとき とほとんど一緒です。
EDA Server Operator のデプロイ
まずは Operator と Custom Resource Definitions(CRD)をデプロイして、CR がデプロイできる状態を整えます。今回は、Operator のドキュメント も参考にして、次の kustomization.yaml
で EDA Server Operator の 0.0.5
をデプロイします。実際のファイルは GitHub のリポジトリ に配置しています。
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: eda
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: redhat-operators-pull-secret
literals:
- operator=eda
resources:
- github.com/ansible/eda-server-operator/config/default?ref=0.0.5
images:
- name: quay.io/ansible/eda-server-operator
newTag: 0.0.5
途中、redhat-operators-pull-secret
を同時に生成させていますが、これは Operator の Deployment にデフォルトでこの名前の Secret が imagePullSecrets
に指定されていて、存在しないと kubelet がひっそりとエラーを吐くためです。無視できるエラーですし、この後の CR のデプロイ中に同じ名前の Secret を作成する処理があるため、何もしなくてもエラーは解消されますが、そもそもエラーを吐かせない方がきれいなのでこうしています。
この kustomization.yaml
を適用すると、CRD が追加され、Operator が動作し始めます。Namespace は eda
です。
$ kubectl -n eda get all
NAME READY STATUS RESTARTS AGE
pod/eda-server-operator-controller-manager-7bf7578d44-7r87w 2/2 Running 0 12s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/eda-server-operator-controller-manager-metrics-service ClusterIP 10.43.3.124 <none> 8443/TCP 12s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/eda-server-operator-controller-manager 1/1 1 1 12s
NAME DESIRED CURRENT READY AGE
replicaset.apps/eda-server-operator-controller-manager-7bf7578d44 1 1 1 12s
EDA Server のデプロイ
Operator が動作し始めたら EDA Server をデプロイできますが、下準備として、EDA Server の Web UI と API を HTTPS 化するため Ingress に使わせる自己署名証明書を作っておきます。
EDA_HOST="eda.example.com"
openssl req -x509 -nodes -days 3650 -newkey rsa:2048 -out tls.crt -keyout tls.key -subj "/CN=${EDA_HOST}/O=${EDA_HOST}" -addext "subjectAltName = DNS:${EDA_HOST}"
また、EDA Server の管理者の初期パスワード、一緒にデプロイされる PostgreSQL のパスワードなどを Secret で明示的に指定するため、kustomization.yaml
も用意します(Secret を渡さない場合は、デフォルトでランダム文字列で生成されます)。一緒に先の自己署名証明書も Secret にさせます。
---
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: eda
generatorOptions:
disableNameSuffixHash: true
secretGenerator:
- name: eda-secret-tls
type: kubernetes.io/tls
files:
- tls.crt
- tls.key
- name: eda-database-configuration
type: Opaque
literals:
- host=eda-postgres-13
- port=5432
- database=eda
- username=eda
- password=Ansible123!
- type=managed
- name: eda-admin-password
type: Opaque
literals:
- password=Ansible123!
resources:
- eda.yaml
最後に、この中で指定している eda.yaml
として、EDA Server のインスタンスを示す CR である EDA を定義します。この CR の spec
で EDA Server の構成を変更できます。今回は以下の内容です。実際のファイルは GitHub のリポジトリ に配置しています。
---
apiVersion: eda.ansible.com/v1alpha1
kind: EDA
metadata:
name: eda
spec:
admin_user: admin
admin_password_secret: eda-admin-password
ingress_type: ingress
ingress_tls_secret: eda-secret-tls
hostname: eda.example.com
automation_server_url: https://awx.example.com/
automation_server_ssl_verify: no
image: quay.io/ansible/eda-server
image_version: sha-a6e4d66
api:
replicas: 1
resource_requirements:
requests: {}
ui:
replicas: 1
resource_requirements:
requests: {}
worker:
replicas: 2
resource_requirements:
requests: {}
redis:
replicas: 1
resource_requirements:
requests: {}
database:
database_secret: eda-database-configuration
storage_requirements:
requests:
storage: 8Gi
resource_requirements:
requests: {}
各設定の詳しい内容は Operator のドキュメント に(ある程度は)記載がありますが、記載がないものはソースコードを読んで判断が必要です(バージョンも 0.0.5
ですし、ドキュメントの不足は仕方がないですね)。
admin_user: admin
admin_password_secret: eda-admin-password
初期の管理者のユーザ名とパスワードを指定しています。
ingress_type: ingress
ingress_tls_secret: eda-secret-tls
hostname: eda.example.com
Web UI と API のエンドポイントを Ingress にするための設定です。HTTPS 化するために証明書も渡しています。
automation_server_url: https://awx.example.com/
automation_server_ssl_verify: no
AWX の情報を与える部分です。automation_server_url
には、AWX の URL を指定します。手元の AWX は自己署名証明書を使っているため、automation_server_ssl_verify
は no
です(ややこしいですが、true
/ false
の Bool 値ではなく文字列としての yes
/ no
で指定します)。
image: quay.io/ansible/eda-server
image_version: sha-a6e4d66
EDA Server の Pod のイメージとして、デフォルトでは quay.io/ansible/eda-server:main
が使われますが、最近の変更(Worker のクラス名の変更)に 0.0.5
の Operator では対応しておらず起動できない(Issue、PR)ため、少し古いイメージを明示しています。
api:
...
ui:
...
worker:
replicas: 2
...
redis:
...
database:
...
各コンポーネントのレプリカ数やリソース構成を設定する部分です。最小構成を目指して組んでいますが、worker
の replicas
のみ 2
にしています。後述しますが、実装上、EDA Server は Worker の数で様々な処理の並列度が縛られるためです(将来的に default_worker
と activation_worker
、さらに scheduler
に分かれて指定することになるはずですが、その場合も特に *_worker
のレプリカ数は工夫が必要です)。
なお、EDA Server と一緒にデプロイされる PostgreSQL の PV に任意の Storage Class を使わせたい要件があっても、そのための設定である database.postgres_storage_class
が 0.0.5
では機能しません(Issue、PR)。したがって、クラスタのデフォルト(K3s の場合は local-path
)が使われます。
このほか、EDA Server の環境変数をいじる extra_settings
も 0.0.5
では機能しない(Issue、PR)ので、利用したい場合は注意が必要です。
あとはこれを apply
して待つと完了です。進捗は Operator のログで確認できます。
$ kubectl apply -k .
...
$ kubectl -n eda logs -f deployment/eda-server-operator-controller-manager
...
----- Ansible Task Status Event StdOut (eda.ansible.com/v1alpha1, Kind=EDA, eda/eda) -----
PLAY RECAP *********************************************************************
localhost : ok=54 changed=0 unreachable=0 failed=0 skipped=16 rescued=0 ignored=0
$ kubectl -n eda get eda,all,ingress,configmap,secret
NAME AGE
eda.eda.ansible.com/eda 3m50s
NAME READY STATUS RESTARTS AGE
pod/eda-server-operator-controller-manager-7bf7578d44-2wm69 2/2 Running 0 6m29s
pod/eda-redis-7d78cdf7d5-z87kk 1/1 Running 0 3m34s
pod/eda-postgres-13-0 1/1 Running 0 3m25s
pod/eda-ui-647b989ccb-stqkp 1/1 Running 0 2m36s
pod/eda-worker-fd594c44-96d9p 1/1 Running 0 2m32s
pod/eda-worker-fd594c44-7jtcq 1/1 Running 0 2m32s
pod/eda-api-5c467d6c48-88m8z 2/2 Running 0 2m39s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/eda-server-operator-controller-manager-metrics-service ClusterIP 10.43.133.61 <none> 8443/TCP 6m29s
service/eda-redis-svc ClusterIP 10.43.144.67 <none> 6379/TCP 3m36s
service/eda-postgres-13 ClusterIP None <none> 5432/TCP 3m27s
service/eda-api ClusterIP 10.43.89.128 <none> 8000/TCP 2m41s
service/eda-daphne ClusterIP 10.43.12.68 <none> 8001/TCP 2m41s
service/eda-ui ClusterIP 10.43.136.60 <none> 80/TCP 2m38s
service/eda-worker ClusterIP 10.43.201.230 <none> 8080/TCP 2m33s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/eda-server-operator-controller-manager 1/1 1 1 6m29s
deployment.apps/eda-redis 1/1 1 1 3m34s
deployment.apps/eda-ui 1/1 1 1 2m36s
deployment.apps/eda-worker 2/2 2 2 2m32s
deployment.apps/eda-api 1/1 1 1 2m39s
NAME DESIRED CURRENT READY AGE
replicaset.apps/eda-server-operator-controller-manager-7bf7578d44 1 1 1 6m29s
replicaset.apps/eda-redis-7d78cdf7d5 1 1 1 3m34s
replicaset.apps/eda-ui-647b989ccb 1 1 1 2m36s
replicaset.apps/eda-worker-fd594c44 2 2 2 2m32s
replicaset.apps/eda-api-5c467d6c48 1 1 1 2m39s
NAME READY AGE
statefulset.apps/eda-postgres-13 1/1 3m25s
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/eda-ingress traefik eda.example.com 192.168.0.219 80, 443 2m35s
NAME DATA AGE
configmap/kube-root-ca.crt 1 6m29s
configmap/eda-eda-configmap 2 2m43s
configmap/eda-server-operator 0 6m28s
NAME TYPE DATA AGE
secret/redhat-operators-pull-secret Opaque 1 6m29s
secret/eda-admin-password Opaque 1 3m50s
secret/eda-database-configuration Opaque 6 3m50s
secret/eda-secret-tls kubernetes.io/tls 2 3m50s
secret/eda-db-fields-encryption-secret Opaque 1 2m51s
デプロイが完了(Reconciliation Loop が収束)したら、https://eda.example.com/
で Web UI にアクセスできます。

デモ: ルールブックの利用(Webhook の場合)
EDA Server ができたので、デモとしてソースに Webhook(ansible.eda.webhook
)を利用した次のルールブックを実際に動かせるように構成します。 実際のファイルは GitHub のリポジトリ に配置しています。
---
- name: Demo Ruleset for Webhook
hosts: all
sources:
- ansible.eda.webhook:
host: 0.0.0.0
port: 5000
rules:
- name: Run Demo Job Template
condition: event.payload.message == "Hello EDA"
action:
run_job_template:
name: Demo Job Template
organization: Default
中身は、Webhook で投げられたデータの message
フィールドの中身が Hello EDA
だった場合に、AWX で Demo Job Template
を実行するルールセットです。
EDA Server の構成
EDA Server で実際にこれを動かすには、大まかには次の作業が必要です。
- AWX でトークンを発行して EDA Server に登録する
- Decision Environment(DE)を EDA Server に登録する
- ルールブックを含むリポジトリをプロジェクトとして EDA Server に登録する
- ルールブックを有効化(Activate)する
また、ルールセットのソースにした Webhook のエンドポイントを Kubernetes クラスタ外から叩けるようするため、詳細は後述しますが、今回は上記に加えて Ingress も追加でデプロイします。
Ingress 以外に Kubernetes 固有の手順はほとんどないため、以下、ポイントだけ紹介します。
AWX でのトークンの発行と EDA Server への登録
EDA Server が AWX の Job Template を実行できるようにするため、AWX のトークン(Write
権限)を発行して、EDA Server に登録します。AWX のインスタンスに対して kubectl
を使える場合は、コマンドで発行してしまうのが手軽です。
$ kubectl -n awx exec deployment/awx-task -- awx-manage create_oauth2_token --user=admin
4sIZrWXi**************8xChmahb
発行できたら、EDA Server の User details
の Controller Tokens
タブで登録します。
Decision Environment(DE)の EDA Server への登録
詳細は後述しますが、EDA Server でルールブックを有効化すると、新しい Pod が起動 して、その中で ansible-rulebook
コマンドが実行されます。この Pod が Decision Environment(DE)です。
この DE に利用されるコンテナイメージは、あらかじめ EDA Server に登録が必要です。
今回は、最小限の DE として、既成の quay.io/ansible/ansible-rulebook:latest
を Decision Environments
のページで登録します。
ルールブックを含むリポジトリの EDA Server への登録
EDA Server で利用するルールブックは、リモートの Git リポジトリ(の rulebooks
または extensions/eda/rulebooks
ディレクトリ下)に配置されている必要があります。
本エントリで紹介しているルールブックは GitHub のリポジトリ にそのまま使えるよう配置しているため、今回は Projects
のページで https://github.com/kurokobo/awx-on-k3s.git
を登録します。
追加すると、自動でプロジェクトの同期(Git リポジトリのクローン)が(Worker 上で)開始されます。しばらく待って Status
が Completed
になれば正常です。
失敗した場合は、Web UI ではエラーメッセージの表示が不十分なので、Worker の Pod のログを見るとよいでしょう。
ルールブックの有効化(Activate)
ここまでの作業で、当初の目的であるルールブックの起動(ansible-rulebook
の実行)の準備ができました。実際に起動するには、Rulebook Activations
のページで、Project
と Rulebook
、Decision Environment
を指定して、ルールブックを有効化します。このとき、Rulebook activation enabled?
を Enabled
にしていれば、保存した段階でルールブックが起動されます。
しばらく待って、Status
が Running
になれば正常です。
何らかの理由で失敗しても、残念ながら Web UI 上ではエラーメッセージは表示してくれないので、Worker か後述する Activation Job の Pod のログを見るとよいでしょう。
なお、定義した Rulebook Activation には一意の Activation ID が付与されます。Web UI の末尾の数字(例えば http://eda.example.com/eda/rulebook-activations/details/1
なら 1
)で確認しておくと、このあとの探索がしやすくなります(ID が Web UI に表示されるようにする PR を軽率に送ってしまいました、どうなることやら)。
Kubernetes 上でのルールブックの動作の仕方
ここまでで、Kubernetes 上で無事に ansible-rulebook
が実行され、イベントを待ち受ける状態になっています。ここから、少し実装を掘り下げていきます。
Kubernetes 環境でルールブックを有効化すると、EDA Server(の Worker)は次のリソースを Kubernetes 上に作成します。
- Job
- Service(必要な場合のみ)
作成された Job は、Activation ID を使って絞り込むと正確に同定できます(ラベルではなく名前で activation-job-${ACTIVATION_ID}
を get
しても同じです)。
$ ACTIVATION_ID=1
$ kubectl -n eda get job -l activation-id=${ACTIVATION_ID}
NAME COMPLETIONS DURATION AGE
activation-job-1 0/1 4m43s 4m43s
この Job により、新しい Pod が起動されます。この Pod が Activation Job Pod であり、Decision Environment の実体 です。Job から起動された Pod の名前にはランダムな文字列が付与されるので、探すときはラベルを使って絞り込むとラクです。
$ JOB_NAME=activation-job-${ACTIVATION_ID}
$ kubectl -n eda get pod -l job-name=${JOB_NAME}
NAME READY STATUS RESTARTS AGE
activation-job-1-h9kjt 1/1 Running 0 11m
Pod の詳細を見ると、ansible-rulebook
が worker
モードで起動していることがわかります。この ansible-runner
は、Daphne(WebSocket)経由で EDA Server からルールブックの情報などを受け取って動作しています。
$ kubectl -n eda describe pod -l job-name=${JOB_NAME}
...
Containers:
activation-pod-1:
...
Port: 5000/TCP
...
Command:
ansible-rulebook
Args:
--worker
...
また、ルールブック中でルールセットの sources
に host
と port
が指定されている場合は、この Pod は上記の通り指定のポートを待ち受けるように構成されます。さらに、これに対応した Service も EDA Server により作成されます。
$ kubectl -n eda get service -l job-name=${JOB_NAME}
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
activation-job-1-5000 ClusterIP 10.43.221.234 <none> 5000/TCP 11m
つまり、ルールセットで定義したソース(今回は Webhook)を実際に待ち受けているのはこの Pod です。Pod のポートの構成とこの Service の作成が EDA Server により行われることで、ルールセットのソースで指定した待ち受けポートが 公開され、今回であれば Webhook が叩ける状態が実現されています(逆に言えば、Webhook はどうにかしてこの Pod に届くように投げなければいけません)。
ただし、現在の実装では Service の type
は ClusterIP
で固定(正確には、type
が指定されないためデフォルトの ClusterIP
が使われる)のため、公開範囲は Kubernetes クラスタ内に限られます。クラスタ外から叩きたい場合は、何らかの工夫が必要です。
Webhook による動作確認
ここまでで、Webhook を叩ける状態ができています。とはいえ、Kubernetes クラスタ内から叩けることを確認してもあまりおもしろくないので、ここでは前述の Service を Ingress でクラスタ外に公開することを考えます。
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
namespace: eda
name: eda-ingress-webhook
spec:
tls:
- hosts:
- eda.example.com
secretName: eda-secret-tls
rules:
- host: eda.example.com
http:
paths:
- path: /webhooks/demo
pathType: ImplementationSpecific
backend:
service:
name: activation-job-1-5000
port:
number: 5000
今回は、EDA Server の Web UI と同じ host
を指定して、path
で階層を下げました。これにより、Web UI と同じ URL で Webhook にもアクセスできる ようになります。service
には、前述の手順で確認した Service 名を指定しています。
これを適用して、あとは外部から curl
などで適当に JSON を投げるだけです。ルールセットで指定した通り、message
フィールドに Hello EDA
を含めます。
$ curl -k \
-H "Content-Type: application/json" \
-d '{"message": "Hello EDA"}' \
https://eda.example.com/webhooks/demo
正常に動作すれば、EDA Server の Rule Audit
ページや AWX の Jobs
ページなどで、ルール通りにアクションが実行されたことが確認できます。
デモ: ルールブックの利用(MQTT の場合)
Webhook だと個人的にどうしてもイベントドリヴンっぽさがいまいち充分でない気がしてしまうので、MQTT をソースにしたルールセットも試しておきます。実際のファイルは GitHub のリポジトリ に配置しています。
---
- name: Demo Ruleset for MQTT
hosts: all
sources:
- ansible.eda.mqtt:
host: "{{ mqtt_host }}"
port: "{{ mqtt_port }}"
topic: "{{ mqtt_topic }}"
rules:
- name: Run Demo Job Template
condition: event.message == "Hello EDA"
action:
run_job_template:
name: Demo Job Template
organization: Default
ソースは ansible.eda.mqtt
です。お試しなので匿名かつ非暗号化の素の MQTT の想定で作っています。ホスト名やポート番号、トピックには固定値を埋め込んでもよいのですが、今回は ルールブック変数 のデモも兼ねて変数にしています。
ルールは、MQTT でサブスクライブしたメッセージ(JSON 形式)の message
フィールドが Hello EDA
だったときに、AWX で Job Template を起動するものです。
MQTT Broker の構成
何でもよいのでとにかく用意します。インタネット上から丸見えでよければ test.mosquitto.org も使えます。自前で建てたい場合は kustomization.yaml
を用意した のでこれを放り込めば動くはずです(nodePort
の 31883
で待ち受けます)。
EDA Server の構成
Webhook のときとほぼ同じなので、違うところだけ紹介します。
利用する DE の指定
ソースは ansible.eda.mqtt
、と書いたものの、実はまだそれを使えるようにする PR はマージされていません。かつ、そのままだと動きません。そこで、その PR のファイルを少し修正して使えるようにしたイメージ をビルドして docker.io/kurokobo/ansible-rulebook:v1.0.1-mqtt
として公開したので、今回はこれを使います(この修正点は、PR に対する PR として送っています)。
ルールブック変数の指定
Rulebook Activation を作成する際に、Variables
欄でルールブックで使っている変数の実際の値を指定します。例えば以下です。
mqtt_host: mqtt.example.com
mqtt_port: 31883
mqtt_topic: demo
なお、Variables
欄のツールチップには、この欄が ansible-playbook
に対する -e
または --extra-vars
として使われる旨の記載がありますが、これは 誤り(Issue)で、正しくは ansible-rulebook
に対して --vars
で指定するファイルの中身に相当 します(実際にはこの情報は WebSocket 経由で送られるので、本当に --vars
が使われているわけではありません)。
動作の確認
ルールブックが起動できたら、MQTT で JSON 形式のメッセージを送ると動作します。
docker run -it --rm efrecon/mqtt-client pub \
-h mqtt.example.com \
-p 31883 \
-t demo \
-m '{"message": "Hello EDA"}'
補足
いくつか補足です。
プライベート Git リポジトリの利用
プロジェクトとして追加したいリポジトリに認証が必要な場合は、Credentials
で GitHub personal access token
(または GitLab...
)を指定してユーザ名とパスワードを保存し、プロジェクトの追加時に指定します。
名前が GitHub
/ GitLab
なので不安になりますが、実際には特にそれらのサービス固有の実装がされているわけではなく、ユーザ名とパスワードを URL に埋め込んで git clone
に渡しているだけなので、期待通り動作します。
なお、リモートの Git リポジトリが HTTPS の場合、証明書の検証は無効にできない(Issue、PR、PR)ので注意が必要です(がんばりたい場合は Worker のイメージに証明書を埋め込むなどでしょうか……)。
Worker の数と処理の並列度の制限
EDA Server は、プロジェクトの同期やルールブックの実行などの一部の処理を Worker と呼ばれるコンポーネント(Kubernetes 上では *-worker
の Pod)に投げています。ここの実装は RQ(Redis Queue) ですが、大事な点は、Worker は同時に一つのキューしかさばけない ことです。このため、Worker の数がそのまま処理の並列度の上限 になってきます。
今回の実装例のように少し古い EDA Server のイメージを使っている場合、Worker は一種類しかないため、わかりやすいところでは ルールブックの有効化で Worker を使い切っているとプロジェクトの同期が Worker に受け取ってもらえず Pending になる など、直感に反するトラブルを招きます。
最新の EDA Server では、ルールブックの有効化を扱う Activation Worker と プロジェクトの同期などそれ以外を扱う Default Worker の二種類に分けられたため、前述の直感的でない動作不良は発生しにくくなっていますが、それでも 同時に起動できるルールブックの数 が Activation Worker の数に依存する ことになるため、引き続き一定の注意は必要です。
Operator の 0.0.5
ではこの二種類の Worker のデプロイに対応できていないため、今回の実装例では少し古い EDA Server を利用しましたが、将来的には二種類それぞれレプリカ数を別々に指定できるようになるはず(Issue、PR)です。
おわりに
EDA Server を Operator で K3s 上にデプロイして、その動作を確認しました。Operator も EDA Server もまだまだこなれていない感がありますが、Red Hat の中の方々だけでなく、コミュニティも含めてわいわいしていけるとよいですね。