はじめに
AWX の Automation Mesh を構成する機能として、過去に紹介した Execution Node に加えて、Hop Node と Mesh Ingress が最近のリリースで追加されました。
関連する情報は AWX のドキュメント や AWX Operator のドキュメント にもまとまっていますが、本エントリでは Mesh Ingress を中心にこれらの新機能を簡単に紹介します。
Execution Node と Hop Node
Automation Mesh を構成する基本のノードは、Execution Node と Hop Node の二種類です。Execution Node は AWX の 21.7.0 で、Hop Node は 23.0.0 でそれぞれ追加されました。
Execution Node については 過去のエントリ でも触れましたが、まずは基本となるこの二種類を改めて簡単に紹介します。関連する公式のドキュメントは以下です。
概要
Execution Node
Execution Node は、Kubernetes クラスタの外に構成されたスタンドアロンのインスタンスで、AWX で起動されたジョブをクラスタ外のリモートホスト上で実行するために使用します。
ネットワークの都合で AWX からは直接到達できないターゲットノードに対しても、Execution Node を介してプレイブックを実行できます。
Execution Node には、インストールバンドルにより、Receptor と Ansible Runner、Podman が導入されます。AWX でジョブを起動すると、Podman のコンテナとして Execution Environment(EE)が起動し、その中でプレイブックが実行されます。
Hop Node
Hop Node は、AWX と Execution Node の間の通信を中継するためのインスタンスです。Execution Node と同様、Kubernetes クラスタの外にスタンドアロンで構成します。
Execution Node が実装された当初は、Execution Node は AWX から直接到達できる範囲にしか配置できませんでした。Hop Node により、任意の数の Hop Node を AWX と Execution Node の間に配置して、ジョブをさらに遠くのネットワークまで届けられるようになりました。
Hop Node には、インストールバンドルにより Receptor のみが導入されます。Execution Node と異なり Ansible Runner と Podman は導入されず、ジョブの実行機能は持ちません。
構成方法
例として、Execution Node と Hop Node を含んだ Automation Mesh を実際に構成します。
トポロジの設計
まずは Automation Mesh のトポロジを設計します。今回は下図の構成で考えます。Execution Node をふたつ用意し、ひとつは AWX から直接、もうひとつは Hop Node を経由してつながるものとします。
トポロジを設計する際は、ノード間の接続方向(図中の矢印)も設計が必要です。これは、Automation Mesh のバックエンド接続の方向で、ノードの間に ファイアウォールが配置されている場合の穴あけの方向 に相当します。
ただし、AWX からその隣接ノードへ は 必ずアウトバウンド方向 で設計します。AWX 以外のノード間の接続方向は任意 です。これを踏まえて、上図にホスト名とポート番号を加えて少しだけ細かくした図が以下です。
ホストの準備
RHEL ファミリのホストを用意して、ネットワークを設定します。経路上やホスト上でファイアウォールを利用している場合は、前述の接続方向にあわせて穴をあけておきます。
インスタンスの追加
設計したすべてのノードを、AWX の Web UI からインスタンスとして追加します。
Administration
>Instances
>Add
から、Create new Instance
画面を開きます。- 次の通りに構成します。
Host Name
として、そのノードに接続しにくる側(矢印の根元側)で名前解決可能なホスト名(または IP アドレス)を指定します。Listener Port
は、そのノードが 接続を受ける側(矢印の先端側)の場合 に入力します。標準は27199
です。Instance Type
は、そのノードにあわせてExecution
かHop
から選択します。- そのノードが AWX の隣接ノードの場合 は、
Peers from control nodes
にチェック を入れます。
Save
をクリックします。
ノードの数だけこれを繰り返して、全ノードがインスタンスとして登録された状態にします。
ピアの構成
インスタンスを全て追加し終わったら、それぞれのインスタンスの ピア を指定します。つまり、インスタンス同士を設計通りに矢印でつなぐ作業 です。
Administration
>Instances
で、接続しにいく側(矢印の根元側)のインスタンスを選択し、Peers
タブを開きます。Associate
をクリックし、接続を受ける側(矢印の先端側)のインスタンスにチェックを入れてSave
をクリックします。
AWX から直接つながるノード(今回の例では exec01
と hop01
)へのピア設定は、インスタンスの追加時に Peers from control nodes
にチェックをいれたことで完了しています。したがって、今回の構成では hop01.ansible.internal
の Peers
タブで exec02.ansible.internal
を追加すれば完了です。
矢印の数だけこの作業を繰り返せば、Automation Mesh のトポロジが完成します。トポロジは、Administration
> Topology View
で確認できます。この画面でも、接続の方向は矢印で表現 されているため、正しく構成できていることを線のつながりと矢印の方向で確認します。
インストールバンドルの実行
トポロジが設計通りに完成したら、それぞれのインスタンスの詳細ページからインストールバンドルをダウンロードし、inventory.yml
の ansible_user
を修正したうえでプレイブックを実行します。
$ ansible-galaxy collection install -f -r requirements.yml
...
$ ansible-playbook -i inventory.yml install_receptor.yml
...
なお、インストールバンドルにはそのインスタンス専用の証明書やピアの情報が含まれているため、使いまわしはできません。全インスタンスについて個別にインストールバンドルをダウンロードして実行します。
インストール後、しばらくして各インスタンスの Status
が Ready
になれば正常です。
インスタンスグループの追加
インスタンスグループを作成して、Execution Node を追加します。
Administration
>Instance Groups
>Add
>Add instance group
をクリックします。Name
を入力してSave
をクリックします。Instances
タブを開きます。Associate
をクリックし、任意の Execution Node にチェックを入れてSave
をクリックします。
グループに複数の Execution Node が含まれる場合は、ジョブの実行にはいずれかのインスタンスが利用されます。厳密に特定の Execution Node のみでジョブを実行させたい場合は、インスタンスがひとつだけのグループを作成します。
ジョブの実行
作成したインスタンスグループをジョブテンプレートの Instance Groups
で指定することで、ジョブが Execution Node で実行されるようになります。
ジョブを実行して、Execution Node 側の awx
ユーザで Podman の状態を確認すると、Execution Environment が動作している様子が観察できます。
[awx@exec01 ~]$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
a02702fb0b21 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 1 second ago ansible_runner_1
[awx@exec02 ~]$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
40ef61ed9c60 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 1 second ago ansible_runner_2
Mesh Ingress
AWX の 23.8.0 で、Automation Mesh を構成する要素として新しく Mesh Ingress が追加されました。関連ドキュメントは以下です。
- 8. Managing Capacity With Instances — Ansible AWX community documentation
- Mesh Ingress – Ansible AWX Operator Documentation
概要
これまでの Execution Node と Hop Node を使った Automation Mesh では、AWX とそれに隣接するノードの接続は、前述の通り、AWX からみてアウトバウンド方向、つまり、Kubernetes クラスタから出ていく方向でしか構成できません でした。
これを解決するのが Mesh Ingress で、これは Kubernetes クラスタ外からのインバウンド方向の接続を受け入れる Hop Node の一種 です。Hop Node の一種ですが、Hop Node とは異なり Kubernetes クラスタ内の Pod として動作します。
これにより、ネットワークポリシの都合で Kubernetes クラスタからのアウトバウンド方向のピアリングが許容されない場合でも、Automation Mesh を構成できるようになりました。
構成方法
MeshIngress を構成するには、AWX Operator を利用します(がんばれば手動でもできますがまったくおすすめしません)。2.11.0 以降で、新しい CR である AWXMeshIngress
が定義できるようになっています。
なお、CR の詳細や例、デプロイ方法などは AWX Operator のドキュメント にも記載があります。
トポロジの設計
今回は、前述の図の通りの構成を目指します。Mesh Ingress が クラスタ外のノードからインバウンド方向で接続されている 点が重要です。
技術的な実装は後述しますが、Mesh Ingress はバックエンドに(Executiton Node や Hop Node のような TCP ではなく)WebSocket を利用しています。上図をもう少し詳しくしたものが以下です。
以降、Mesh Ingress の構成に絞って紹介します。Mesh Ingress 以外の Execution Node と Hop Node の構成方法は、前述した従来の手順と同じです。
CR の作成
今回の構成に合わせた CR は以下の通りです。これは Traefik 用ですが、Nginx など他の Ingress Controller でも適切に CR をカスタマイズすれば動作します(ドキュメント にいくつか CR の例を 増やす予定 です)。
---
apiVersion: awx.ansible.com/v1alpha1
kind: AWXMeshIngress
metadata:
namespace: awx
name: inbound-hop01
spec:
deployment_name: awx
ingress_type: IngressRouteTCP
ingress_controller: traefik
ingress_class_name: traefik
ingress_api_version: traefik.io/v1alpha1
external_hostname: inbound-hop01.ansible.internal
deployment_name
で Mesh Ingress を追加する AWX のインスタンス名を指定し、external_hostname
でクラスタ外からの接続を受け入れるための FQDN を指定します。併せて、接続点となる Ingress(OpenShift の場合は Route)の構成のため、今回使う Traefik にあわせて ingress_type
と ingress_api_version
を指定しています。
これを作成すると、AWX Operator がいろいろして、デプロイが完了します。
$ kubectl -n awx logs deployments/awx-operator-controller-manager
...
----- Ansible Task Status Event StdOut (awx.ansible.com/v1alpha1, Kind=AWXMeshIngress, inbound-hop01/awx) -----
PLAY RECAP *********************************************************************
localhost : ok=19 changed=3 unreachable=0 failed=0 skipped=5 rescued=0 ignored=0
$ kubectl -n awx get deployment,pod,service,ingressroutetcp.traefik.io
NAME READY UP-TO-DATE AVAILABLE AGE
...
deployment.apps/inbound-hop01 1/1 1 1 35s
NAME READY STATUS RESTARTS AGE
...
pod/inbound-hop01-b858d78cb-89ctn 1/1 Running 0 35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
service/inbound-hop01 ClusterIP 10.43.165.50 <none> 27199/TCP 36s
NAME AGE
ingressroutetcp.traefik.io/inbound-hop01 37s
AWX Operator は、AWX へのインスタンスの追加と AWX からのピアの設定も実施するため、AWX の Web UI ではこの段階で Mesh Ingress のインスタンスが確認できます。名前は CR の name
で指定したもので、Node Type
は hop
です。
Mesh Ingress は CR のデプロイだけで構成が完了 します。Web UI からの操作は不要で、インストールバンドルなども存在しません。したがって、以上で Mesh Ingress の構成は完了です。
他のインスタンスの構成
Mesh Ingress 以外のノードを、前述した従来の手順に従って構成します。Mesh Ingress をピアに持つインスタンスも構成することになりますが、手順は他のインスタンスの場合と同じです。
- ホストの準備
- インスタンスの追加
- ピアの構成
- インストールバンドルの実行
- インスタンスグループの追加
適切に構成できれば、Topology View
でも Mesh Ingress(inbound-hop01
)に矢印が集まっている(インバウンド方向で接続されている)ことが確認できます。
ジョブの実行
インスタンスグループを指定してジョブを起動すると、ジョブが Mesh Ingress を経由して Execution Node まで届けられて実行されたことが、Execution Node の Podman の状態から確認できます。
[awx@exec03 ~]$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
6b149d385677 quay.io/ansible/awx-ee:latest ansible-playbook ... 1 second ago Up 2 seconds ago ansible_runner_3
[awx@exec04 ~]$ podman ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
77c74eec136d quay.io/ansible/awx-ee:latest ansible-playbook ... 2 seconds ago Up 2 seconds ansible_runner_4
参考: 技術的な補足
Mesh Ingress 周辺の技術的ないろいろです。
Mesh Ingress の実装のモチベーション
Mesh Ingress のない Automation Mesh では、AWX とそれに隣接するノードの接続は、AWX からみてアウトバウンド方向 でしか構成できません。これは、コントロールプレーンのレプリカ数が 2 以上のとき に、インバウンド方向のバックエンド接続を維持できなくなる ことが理由です。
Receptor は、複数のノードを仮想的にひとつのノードとして扱うような冗長化の機能を持ちません。すべてのノードは、それぞれが独立した別のノードとしてメッシュに参加します。
ここで、コントロールプレーンのレプリカ数が 2 以上の場合、すなわち、Task の Deployment の replicas
が 2
以上の場合を考えます。Deployment を外部に公開する場合、一般的には図のように Service を構成し、外部からの通信がいずれかの Pod へロードバランスされる状態 にします。
ここで、仕様上、Receptor としては二つの Pod 内の Receptor がそれぞれ別のノードとして扱われる 点に注意します。Receptor としては上図は 3 ノードのメッシュであり、インバウンド方向でピア接続を確立する には、クラスタ外のノードはこの二つの Pod と明示的かつ別々に通信 できなければなりません。つまり、ロードバランスされると通信の到達先が意図せずに切り替わって困る ことになります。
したがって、クラスタ外からのピア接続を受け入れるには、Receptor としては Pod と Service が一対一で対応していることが必須 で、通常の Web アプリケーションなどではたいへん便利な Kubernetes のレプリカ機能も、Receptor とはすこぶる相性が悪いということになります。
こうした背景から、Pod と Service が必ず一対一になるようレプリカ数を 1 に固定した Deployment を作って、それにクラスタ外からの接続を引き受けさせようとして実装されたのが Mesh Ingress です。
Mesh Ingress の仕組み
Mesh Ingress の内部実装を少し掘り下げて紹介します。
Kubernetes 上に作成されるリソース
Mesh Ingress を Kubernetes 上にデプロイすると、AWX Operator によって次のリソースが作成されます。
- Receptor が動作する Deployment(Pod)
- Pod のポートをクラスタ内に公開する Service(
type: ClusterIP
) - Service を外部に公開する Ingress(または Route)
- Deployment を起動する Service Account
- Receptor の設定ファイルを含む ConfigMap
$ kubectl -n awx get deployment,pod,service,ingressroutetcp.traefik.io,serviceaccount,configmap
NAME READY UP-TO-DATE AVAILABLE AGE
...
deployment.apps/inbound-hop01 1/1 1 1 35s
NAME READY STATUS RESTARTS AGE
...
pod/inbound-hop01-b858d78cb-89ctn 1/1 Running 0 35s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
...
service/inbound-hop01 ClusterIP 10.43.165.50 <none> 27199/TCP 36s
NAME AGE
ingressroutetcp.traefik.io/inbound-hop01 37s
NAME SECRETS AGE
...
serviceaccount/inbound-hop01 0 37s
NAME DATA AGE
...
configmap/inbound-hop01-receptor-config 1 36s
Pod で利用されるイメージはデフォルトで quay.io/ansible/awx-ee:latest
です。AWX 本体の CR に control_plane_ee_image
の指定があればそれが利用 されます。
Receptor の設定
Mesh Ingress 用の Receptor の設定ファイルは次の通りです。ConfigMap か Pod 内の /etc/receptor/receptor.conf
で確認できます。
---
- node:
id: inbound-hop01
- log-level: debug
- control-service:
service: control
- ws-listener:
port: 27199
tls: tlsserver
- tls-server:
cert: /etc/receptor/tls/receptor.crt
key: /etc/receptor/tls/receptor.key
name: tlsserver
clientcas: /etc/receptor/tls/ca/mesh-CA.crt
requireclientcert: true
mintls13: false
Listener として ws-listener
が定義されており、WebSocket をポート 27199 で待ち受けている ことがわかります。ここには tls
が指定されているため、実際には WebSocket over TLS(WSS)です。
Receptor 用の証明書
前述の tls-server
の定義の通り、隣接ノードとの接続時には相互に証明書の検証が行われるように設定されています。
Mesh Ingress は、AWX 本体が利用している Receptor の CA 証明書と秘密鍵(Kubernetes 上の Secret リソース)をそのままマウントしています。また、それを使って 自分自身の起動時に自分自身用のサーバ証明書を生成 しています。
このサーバ証明書の有効期限は一年間と比較的短いですが、Pod が再作成されるたびに証明書も再生成されるため、同一の Pod が一年以上動作し続けない限りは失効しません(念のため 延長する是非を伺い中 です)。
証明書のマウントっぷりや証明書を生成するコマンドは、Deployment の定義から確認できます。
$ kubectl -n awx get deployment/inbound-hop01 -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
...
name: inbound-hop01
...
spec:
...
template:
...
spec:
containers:
- args:
- /bin/sh
- -c
- |
internal_hostname=inbound-hop01
external_hostname=inbound-hop01.ansible.internal
receptor --cert-makereq bits=2048 \
commonname=$internal_hostname \
dnsname=$internal_hostname \
nodeid=$internal_hostname \
dnsname=$external_hostname \
outreq=/etc/receptor/tls/receptor.req \
outkey=/etc/receptor/tls/receptor.key
receptor --cert-signreq \
req=/etc/receptor/tls/receptor.req \
cacert=/etc/receptor/tls/ca/mesh-CA.crt \
cakey=/etc/receptor/tls/ca/mesh-CA.key \
outcert=/etc/receptor/tls/receptor.crt \
verify=yes
exec receptor --config /etc/receptor/receptor.conf
image: quay.io/ansible/awx-ee:latest
...
name: inbound-hop01-mesh-ingress
...
volumeMounts:
- mountPath: /etc/receptor/receptor.conf
name: inbound-hop01-receptor-config
subPath: receptor.conf
- mountPath: /etc/receptor/tls/ca/mesh-CA.crt
name: inbound-hop01-receptor-ca
readOnly: true
subPath: tls.crt
- mountPath: /etc/receptor/tls/ca/mesh-CA.key
name: inbound-hop01-receptor-ca
readOnly: true
subPath: tls.key
- mountPath: /etc/receptor/tls/
name: inbound-hop01-receptor-tls
...
volumes:
- emptyDir: {}
name: inbound-hop01-receptor-tls
- name: inbound-hop01-receptor-ca
secret:
defaultMode: 420
secretName: awx-receptor-ca
- configMap:
defaultMode: 420
items:
- key: receptor_conf
path: receptor.conf
name: inbound-hop01-receptor-config
name: inbound-hop01-receptor-config
...
args
で指定されているコマンドの通り、Mesh Ingress が利用するサーバ証明書は、CN だけでなく SAN も使って 内部用と外部用の二つのホスト名の正当性を保証 するように作られます。今回の例では、内部用が inbound-hop01
で、外部用が FQDN の inbound-hop01.ansible.internal
です。
実際、証明書の内容を確認するとそのようになっています。
$ kubectl -n awx exec -it deployment/inbound-hop01 -- openssl x509 -text -in /etc/receptor/tls/receptor.crt -noout
Certificate:
Data:
...
Issuer: CN = awx Receptor Root CA
Validity
Not Before: Feb 16 13:00:59 2024 GMT
Not After : Feb 16 13:00:59 2025 GMT
Subject: CN = inbound-hop01
...
X509v3 extensions:
...
X509v3 Subject Alternative Name:
DNS:inbound-hop01, DNS:inbound-hop01.ansible.internal, othername: 1.3.6.1.4.1.2312.19.1::inbound-hop01
...
ホスト名が二種類用意されているのは、Mesh Ingress に対して隣接ノードが接続する際に 二種類のホスト名が使われる からです。
隣接ノードとの接続
Mesh Ingress は、クラスタ内で AWX からの接続を受け付けるだけでなく、クラスタ外の他のノードからの接続も受け付けます。このとき、クラスタ外の他のノードは Ingress(または Route)を宛先に接続するしかありませんが、AWX は同じクラスタ内にいる ため Service を宛先にして接続 しています。
このため、AWX が Mesh Ingress に接続するときは Service の名称 が、クラスタ外のノードが接続するときは FQDN が利用されます。したがって、Mesh Ingress はこの二種類のホスト名の正当性をどちらも証明できる必要があり、これが証明書に二種類のホスト名が含まれていた背景です。
この二つのホスト名やポート番号の実際の値は、Mesh Ingress のインスタンスの Listener Addresses
タブで確認できます。
Mesh Ingress 用の Ingress リソースを自製するには
Ingress Controller の都合や環境の制約など、何らかの理由で、AWX Operator で作成できる Ingress では不十分な場合は、Ingress のみ自製すれば対応できます。CR で ingress_type
を none
(デフォルト)にすると、AWX Operator は Ingress をデプロイしません。なお、external_hostname
は、Receptor の証明書の生成にも使用されるため、依然として指定は必要です。
---
apiVersion: awx.ansible.com/v1alpha1
kind: AWXMeshIngress
metadata:
name: inbound-hop01
spec:
deployment_name: <awx instance name>
ingress_type: none
external_hostname: inbound-hop01.ansible.internal
自製する Ingress の要件は大まかには次の通りです。type: ClusterIP
の Service が Operator により作成されるので、これをバックエンドに指定します。
- WebSocket に対応していること
- 443 番ポートでアクセスできること
- TLS パススルーを有効にすること(TLS の終端は Receptor が行うため)
- Mesh Ingress 用の Service の 27199 番ポートにルーティングすること
AWXMeshIngress
のexternal_hostname
と同じホスト名を使っていること
Mesh Ingress を削除するには
CR AWXMeshIngress
を kubectl delete
で削除するだけです。
CR の Finalizer が AWX Operator により起動し、Kubernetes 上のリソースの削除だけでなく、AWX からのインスタンスの削除も実行されます。
各ノードを冗長化するには
前述の理由から、Mesh Ingress のレプリカ数は 1 で固定で増やせません。
このため、Mesh Ingress 自体を冗長化したい場合は、別の Mesh Ingress を追加でデプロイして Automation Mesh に参加させることで対応します。
Execution Node や Hop Node を冗長化させたい場合も、隣にもう一台同じ役割のものを追加で用意して Automation Mesh に参加させるだけです。Execution Node は、冗長化させたい複数台を同じインスタンスグループに含めることで、一方で障害が発生しても他方で実行される状態を保てます。
おわりに
AWX で Automation Mesh を構成するための Execution Node、Hop Node、Mesh Ingress のそれぞれを紹介しました。これまで以上に柔軟な構成が取れるようになり、AWX が使える範囲も広がりそうです。
趣味の一環で Mesh Ingress のアーキテクチャの検討 には初期のころから参加していましたが、出した案がわりと素直に採用されておもしろかったです。AWX Operator 側の実装も、Ingress 周辺を少しだけ担当しています。Nginx と Traefik 以外の Ingress Controller はまったくテストできていないので、不具合があったら Issue を作って教えてください。