はじめに
前回のエントリ では、Receptor によるメッシュネットワークの構成と、メッシュネットワークを介したリモートノードでのコマンド実行、障害からの回復性を紹介しました。
本エントリでは、リモートノードで何らかの処理をさせるもう一つの手段である、Kubernetes クラスタ上でのワークの実行(Kubernetes ワーク)を紹介します。
この機能を利用すると、Receptor の Executor ノード から 任意の Kubernetes クラスタに任意の構成の Pod を作成 できます。Pod で利用するイメージ、そこで実行する コマンドと引数 だけでなく、Pod 自体もカスタマイズでき ます。また、前回のエントリ で紹介したリモートノードでのコマンド実行と同様、任意のペイロードをコマンドの標準入力に渡す ことも引き続き可能なため、非常に柔軟な処理を行えます。
目次
構成例
今回は、図の 4 ノードで構成します。node01
から node03
を前回同様 Docker 上のコンテナとして起動し、node04
は Kubernetes 上の Pod として起動します。
Controller は前回同様 controller01
(node01
)、Executor は controller01
(node01
)と executor01
(node03
)、executor02
(node04
)の 3 ノードです。
必要なファイル群は、前回同様 GitHub のリポジトリに配置 しています。
前提: 基本的な考え方
前回のエントリ で紹介したリモートノードでのコマンド実行(コマンドワーク)では、Executor は指定されたコマンドをそのノード自身で直接実行しました。下図は cat /etc/hostname
をリモートノードで実行させた場合の動作イメージです。
一方で、今回扱う Kubernetes クラスタ上でのワーク(Kubernetes ワーク)では、Executor の役割は Kuberentes 上への Worker と呼ばれる Pod の作成 です。ワークに指定したコマンドや引数は、この Worker Pod の起動コマンド(エントリポイント)として渡されます。したがって、実際のコマンドは Executor 自身ではなくさらにその先の Worker Pod で実行されることになります。下図は cat /etc/hostname
を Kubernetes ワークとして実行させた場合の動作イメージです。
こうした Pod の作成や、Pod の標準入力へのペイロードの連携、標準出力の取得は、技術的にはすべて Kubernetes の API 経由で行われます。
前提: Kubernetes クラスタへの認証
Kubernetes クラスタ上に Pod を作成するには、当然ながら認証(と認可)が必要です。Receptor は、次の 2 種類の認証をサポートしています。
kubeconfig
- 任意の kubeconfig ファイルを利用して認証する方式
- kubeconfig ファイルさえあれば Executor ノードの構成に関わらず任意の Kubernetes クラスタ上に Pod を作成できる
incluster
- Executor ノード自体が Kubernetes クラスタ上の Pod として起動している場合に利用できる方式
- Executor の Pod を動作させている Service Account に認可された範囲で Pod を作成できる
デフォルトは incluster
ですが、本エントリでは両方のパタンを実際に試します。今回の構成で executor02
(node04
)を Kubernetes クラスタ上の Pod として配置しているのは、incluster
を試すためです。
デモ (1): 基本の動きの確認
まずは単純な例として、前回のエントリ でも利用した、5 秒の sleep
を 12 回繰り返すだけの簡単なコマンドを Kubernetes ワークとして動作させます。
設定ファイルの作成
必要なファイルを準備します。実ファイル群は GitHub のリポジトリに配置 しています。
Receptor の Executor 用の設定ファイル
Kubernetes ワークを実行させる Executor として、Docker 上の executor01
(node03
)を利用します。設定ファイルを抜粋します。
...
- work-kubernetes:
worktype: echo-sleep
authmethod: kubeconfig
kubeconfig: /tmp/.kube/config
namespace: receptor
image: quay.io/centos/centos:stream8
command: bash
params: "-c 'for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done'"
...
work-kubernetes
が、Kubernetes ワークを定義する部分で、ここではこのワークにワークタイプ名として echo-sleep
と名前を付けています。
authmethod
は認証方式の指定で、kubeconfig ファイルを利用する kubeconfig
とし、パスを次の設定 kubeconfig
で指定しています。この指定の仕方をする場合、kubeconfig ファイルは Controller 側ではなく Executor 側(つまりこのノード上)に存在している必要があります(少しややこしいですが、後述する receptorctl
に kubeconfig のパスを渡す書き方の場合は Controller 側のパスで記述するため、混同に注意です)。
namespace
、image
、command
、params
が、作成される Pod の構成を変更する部分です。それぞれ、Pod が作成される Namespace、Pod 内のコンテナで利用するイメージ、そのイメージで起動するコマンドとその引数を指定しています。
kubeconfig ファイル
利用する kubeconfig ファイルは、今回は Kubernetes クラスタが K3s のため、/etc/rancher/k3s/k3s.yaml
をコピーして利用します。既定でエンドポイントに 127.0.0.1:6443
が指定されているため、コンテナ内からも到達できるように Docker ホストの実 IP アドレス(今回は 192.168.0.219
)に書き換えます。
cp /etc/rancher/k3s/k3s.yaml .kube/config
sed -i 's/127\.0\.0\.1/192.168.0.219/g' .kube/config
Docker Compose ファイル
用意した kubeconfig ファイルは、Docker Compose ファイルで node03
の /tmp/.kube/config
にマウントさせています。
...
node03:
image: ${RECEPTOR_IMAGE:?err}
container_name: node03
hostname: node03.example.internal
command: "receptor -c /etc/receptor/receptor.conf"
volumes:
- "./conf/node03.yml:/etc/receptor/receptor.conf"
- "./.kube/config:/tmp/.kube/config"
...
実際の動き
Kubernetes クラスタに Namespace receptor
を作成して、コンテナを起動させて動きを確認します。
$ kubectl apply -f manifests/namespace.yml
namespace/receptor created
$ docker compose up -d
$ docker compose exec -it node01 bash
[root@node01 /]#
ワークを実行するコマンドは、前回のエントリ と同じです。
[root@node01 /]# receptorctl work submit \
--node executor01 \
--no-payload \
echo-sleep
Result: Job Started
Unit ID: jBLBH6pV
ワークが実行されたら、Kubernetes クラスタ側の様子を確認します。Namespace に receptor
を指定していたので、その Namespace の Pod を追いかけます。
$ kubectl -n receptor get pod
NAME READY STATUS RESTARTS AGE
echo-sleep-mqnjl 1/1 Running 0 13s
$ kubectl -n receptor get pod echo-sleep-mqnjl -o yaml
apiVersion: v1
kind: Pod
metadata:
...
generateName: echo-sleep-
name: echo-sleep-mqnjl
namespace: receptor
...
spec:
containers:
- args:
- -c
- 'for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done'
command:
- bash
image: quay.io/centos/centos:stream8
imagePullPolicy: IfNotPresent
name: worker
...
Pod echo-sleep-mqnjl
が Namespace receptor
に作成され動作していることがわかります。Pod 名は、ワークの名称(echo-sleep
)から生成されるようです。また、Pod の image
や command
、args
も指定した通りに構成されていることがわかります。
前回のエントリ で紹介した一時ファイルの様子も確認します。
[root@node01 /]# ls -l /tmp/receptor/controller01/jBLBH6pV/
total 8
-rw-------. 1 root root 317 Dec 4 19:46 status
-rw-------. 1 root root 0 Dec 4 19:46 status.lock
-rw-------. 1 root root 0 Dec 4 19:45 stdin
-rw-------. 1 root root 147 Dec 4 19:46 stdout
[root@node01 /]# cat /tmp/receptor/controller01/jBLBH6pV/status
{
"State": 2,
"Detail": "Finished",
"StdoutSize": 147,
...
"ExtraData": {
...
"RemoteUnitID": "zfr7GxnR",
...
}
}
[root@node03 /]# ls -l /tmp/receptor/executor01/zfr7GxnR/
total 8
-rw-------. 1 root root 3292 Dec 4 19:46 status
-rw-------. 1 root root 0 Dec 4 19:46 status.lock
-rw-------. 1 root root 0 Dec 4 19:45 stdin
-rw-------. 1 root root 147 Dec 4 19:46 stdout
[root@node03 /]# cat /tmp/receptor/executor01/zfr7GxnR/status
{
"State": 2,
"Detail": "Finished",
"StdoutSize": 147,
...
"ExtraData": {
"Image": "quay.io/centos/centos:stream8",
"Command": "bash",
"Params": "-c 'for i in $(seq 1 12); do echo ${i}: $(date +%T); sleep 5; done'",
"KubeNamespace": "receptor",
"KubeConfig": "apiVersion: v1\n...",
"KubePod": "",
"PodName": "echo-sleep-mqnjl"
}
}
このあたりの動きも、前回のエントリ で紹介したコマンドワークの時と同じです。status
の ExtraData
に Kubernetes 固有の情報が含まれるくらいで、大きくは変わらないことがわかります。
結果もこれまで通り確認できます。
[root@node01 /]# receptorctl work list --unit_id jBLBH6pV
{
"jBLBH6pV": {
"Detail": "Finished",
"ExtraData": {
...
},
"State": 2,
"StateName": "Succeeded",
"StdoutSize": 147,
...
}
}
[root@node01 /]# receptorctl work results jBLBH6pV
1: 19:45:18
2: 19:45:23
...
11: 19:46:08
12: 19:46:13
ここまでで、Kubernetes ワークの基本的な動作が確認できました。Pod の構成と kubeconfig は Kubernetes ならではですが、見かけ上の動きは コマンドワーク と大きくは変わりません。
ワークはリリースしておきます。
[root@node01 /]# receptorctl work release --all
Released:
(jBLBH6pV, released)
デモ (2): コマンドへの引数と標準入力の連携
前回のエントリ で紹介したコマンドワークへの引数と標準入力の連携は、Kuberentes ワークでも同じように利用できます。このデモでは、次のことを試します(三つ目はオマケです)。
- 実行時に、任意の引数をコマンドへ追加する
- 実行時に、任意のペイロードを標準入力としてコマンドへ渡す
- 実行時に、任意のイメージでコマンドを実行する(参考)
設定ファイルの作成
引き続き、Executor として executor01
(node03
)を利用します。設定ファイルのうち、関連する部分を抜粋します。実ファイル群は GitHub のリポジトリに配置 しています。
...
- work-kubernetes:
worktype: cat-files
authmethod: kubeconfig
kubeconfig: /tmp/.kube/config
namespace: receptor
image: quay.io/centos/centos:stream8
command: grep
params: "-n -H ''"
allowruntimeparams: true
allowruntimecommand: true
- work-kubernetes:
worktype: echo-reply
authmethod: kubeconfig
kubeconfig: /tmp/.kube/config
namespace: receptor
image: quay.io/centos/centos:stream8
command: bash
params: "-c 'while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done'"
...
引数で渡されたファイルを grep
する cat-files
と、標準入力を echo
し返す echo-reply
を定義しています。
cat-files
では、allowruntimeparams
と allowruntimecommand
を true
にしています。allowruntimeparams
が実行時の params
の追加を許可する指定です。allowruntimecommand
は、デモの本筋とは若干逸れますが、実行時の image
の変更を許可する指定です。
実際の動き
コンテナを起動して動きを確認します。
$ docker compose up -d
$ docker compose exec -it node01 bash
[root@node01 /]#
実行時の引数の追加
コマンドワークでは、引数の追加は --param params=<追加したい引数>
でしたが、Kubernetes ワークに引数を追加する場合は、--param kube_params=<追加したい引数>
で指定します。
[root@node01 /]# receptorctl work submit \
--node executor01 \
--rm \
--follow \
--no-payload \
--param "kube_params=/etc/hostname /etc/os-release" \
cat-files
/etc/hostname:1:cat-files-ld5zs
/etc/os-release:1:NAME="CentOS Stream"
/etc/os-release:2:VERSION="8"
/etc/os-release:3:ID="centos"
/etc/os-release:4:ID_LIKE="rhel fedora"
/etc/os-release:5:VERSION_ID="8"
/etc/os-release:6:PLATFORM_ID="platform:el8"
/etc/os-release:7:PRETTY_NAME="CentOS Stream 8"
/etc/os-release:8:ANSI_COLOR="0;31"
/etc/os-release:9:CPE_NAME="cpe:/o:centos:centos:8"
/etc/os-release:10:HOME_URL="https://centos.org/"
/etc/os-release:11:BUG_REPORT_URL="https://bugzilla.redhat.com/"
/etc/os-release:12:REDHAT_SUPPORT_PRODUCT="Red Hat Enterprise Linux 8"
/etc/os-release:13:REDHAT_SUPPORT_PRODUCT_VERSION="CentOS Stream"
(y6UCfqYt, released)
結果から指定したファイルが grep
できていることがわかります。
実際の Pod はすぐに消されてしまいますが、急げば定義も確認できます。
$ kubectl -n receptor get pod
NAME READY STATUS RESTARTS AGE
cat-files-ld5zs 0/1 Completed 0 1s
$ kubectl -n receptor get pod -o yaml
...
- apiVersion: v1
kind: Pod
metadata:
...
generateName: cat-files-
name: cat-files-ld5zs
namespace: receptor
...
spec:
containers:
- args:
- -n
- -H
- ""
- /etc/hostname
- /etc/os-release
command:
- grep
image: quay.io/centos/centos:stream8
imagePullPolicy: IfNotPresent
name: worker
...
Pod の定義から、確かに args
に引数が追加されていることが確認できます。
なお、コマンドワークではワークタイプ名に続けて追加したい引数をベタ書きできました(cat-files -- /etc/hostname /etc/os-release
など)が、Kubernetes ワークではこの表記は機能しません。
実行時の標準入力の利用
標準入力の渡し方は コマンドワーク と全く同じです。--payload
でファイルまたは receptorctl
への標準入力を、--payload-literal
で任意の文字列をそれぞれ渡せます。実行例だけ列挙します。
[root@node01 /]# echo "Hello Receptor!" > /tmp/input.txt
[root@node01 /]# receptorctl work submit \
--node executor01 \
--rm \
--follow \
--payload /tmp/input.txt \
echo-reply
Reply from echo-reply-64l6z: HELLO RECEPTOR!
(mW6CH1D9, released)
[root@node01 /]# echo "Hello Receptor!" | receptorctl work submit \
--node executor01 \
--rm \
--follow \
--payload - \
echo-reply
Reply from echo-reply-jf4bd: HELLO RECEPTOR!
(vmjsnjSd, released)
[root@node01 /]# receptorctl work submit \
--node executor01 \
--rm \
--follow \
--payload-literal "Hello Receptor!" \
echo-reply
Reply from echo-reply-4jzxn: HELLO RECEPTOR!
(MUdlZNqN, released)
実行時のイメージの変更(参考)
デモの本筋とは若干逸れますが、cat-files
では、設定ファイルで allowruntimecommand
を true
にして、実行時の image
の変更を許可していました。これを利用するには、--param kube_image=<イメージ>
を追加します。例えば、ubuntu:22.04
を指定して実行します。
[root@node01 /]# receptorctl work submit \
--node executor01 \
--rm \
--follow \
--no-payload \
--param kube_image=ubuntu:22.04 \
--param "kube_params=/etc/hostname /etc/os-release" \
cat-files
/etc/hostname:1:cat-files-tqwj9
/etc/os-release:1:PRETTY_NAME="Ubuntu 22.04.1 LTS"
/etc/os-release:2:NAME="Ubuntu"
/etc/os-release:3:VERSION_ID="22.04"
/etc/os-release:4:VERSION="22.04.1 LTS (Jammy Jellyfish)"
/etc/os-release:5:VERSION_CODENAME=jammy
/etc/os-release:6:ID=ubuntu
/etc/os-release:7:ID_LIKE=debian
/etc/os-release:8:HOME_URL="https://www.ubuntu.com/"
/etc/os-release:9:SUPPORT_URL="https://help.ubuntu.com/"
/etc/os-release:10:BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
/etc/os-release:11:PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
/etc/os-release:12:UBUNTU_CODENAME=jammy
(jplGpy12, released)
Ubuntu らしい /etc/os-release
の中身が返ってきました。Pod の定義上も image
が変わっていることが確認できます。
$ kubectl -n receptor get pod
NAME READY STATUS RESTARTS AGE
cat-files-tqwj9 0/1 Completed 0 1s
$ kubectl -n receptor get pod -o yaml
...
- apiVersion: v1
kind: Pod
metadata:
...
generateName: cat-files-
name: cat-files-vkgzj
namespace: receptor
...
spec:
containers:
- args:
- -n
- -H
- ""
- /etc/hostname
- /etc/os-release
command:
- grep
image: ubuntu:22.04
imagePullPolicy: IfNotPresent
name: worker
...
デモ (3): Pod のカスタマイズ
これまでのデモで示したように、設定ファイルの work-kubernetes
中の namespace
、image
、command
、params
を活用することで、Executor によって起動される Pod の構成を変更できます。また、allowruntime*
を指定すれば、これらは実行時の --param
で kube_*
により動的にも指定できます(対応表は ドキュメント に掲載されています)。
一方で、この 4 項目だけでは充分なカスタマイズができるとは言えません。例えば、Pod の CPU やメモリを管理したい場合や、プライベートレジストリの認証情報を渡したい場合、PV をマウントしたい場合、Pod で Init コンテナやサイドカーコンテナを動かしたい場合などには対応できません。
こうした要件に対応するため、Executor が起動させる Pod のマニフェストを丸ごと指定できる ようになっています。本デモでは、次のことを試します(三つ目はオマケです)。
- 事前に指定したマニフェストで Pod を作成させる
- 実行時に指定したマニフェストで Pod を作成させる
- 実行時に指定した kubeconfig ファイルで Pod を作成させる(参考)
設定ファイルの作成
必要なファイルを準備します。実ファイル群は GitHub のリポジトリに配置 しています。
マニフェストの作成
作成される Pod のマニフェストでは、少なくとも worker
と名付けられたコンテナが一つ含まれる 必要があります。また、本エントリ公開時点の実装では、複数のコンテナを含めた場合、worker
コンテナがあまりにも早く終了しすぎるとワークがエラーになる(Issue)ほか、worker
コンテナが完了した段階でワークも完了になる(--rm
を付けるとその時点でその他のコンテナの終了処理が始まる)ため、若干の注意が必要です。
また、Pod のマニフェストを指定すると、設定ファイルの image
や command
、params
(やそれに対応する実行時の kube_*
)の指定が許可されない(または効果がなくなる)ので、マニフェストの段階で完全なものを指定しておく必要があります。これらを実行時に都度指定したい要件がある場合は、マニフェストをまず修正し、実行時にマニフェストを指定するパタン(後述)を使う必要があるでしょう。
今回は、任意のマニフェストを読み込ませられることが確認できればよいので、ごく簡単な例として次の要件を考えます。
- ラベル
app: hello-receptor
あり - コンテナに CPU のリクエスト値あり
- Init コンテナ(
sleep
するだけ)あり - CentOS Stream 8 のイメージを利用
- `grep -n -H “” /etc/hostname /etc/os-release” を実行
これに従い、以下の内容で manifests/pod.yml
を作成します。
apiVersion: v1
kind: Pod
metadata:
namespace: receptor
labels:
app: hello-receptor
spec:
initContainers:
- name: init-worker
image: quay.io/centos/centos:stream8
command:
- sleep
args:
- "5"
containers:
- name: worker
image: ubuntu:22.04
command:
- grep
args:
- -n
- -H
- ""
- /etc/hostname
- /etc/os-release
resources:
requests:
cpu: 10m
Receptor の Executor 用の設定ファイル
マニフェストを 事前に設定ファイルで指定 するパタンでは、Executor に executor01
(node03
)を利用します。このパタンでは、マニフェストは Executor 側に配置 する必要があります。設定ファイルの該当箇所は次の通りです。
...
- work-kubernetes:
worktype: custom-predefined-pod
authmethod: kubeconfig
kubeconfig: /tmp/.kube/config
pod: /tmp/pod.yml
allowruntimecommand: true
...
pod
が Pod のマニフェストファイルのパスを指定する箇所です。今回は Docker 環境なので、このパスには Docker ホスト側の ./manifests/pod.yml
を後述の Docker Compose ファイルでマウントさせます。
allowruntimecommand
は本来は不要ですが、本エントリ公開時点では pod
を指定する場合に allowruntimecommand
か allowruntimepod
(後述)が許可されていないとコケる(Issue / PR)ようなので仕方なく足しています(おそらくバグ)。
マニフェストを 実行時に receptorctl
経由で指定 するパタンでは、Executor に controller01
(node01
)を指定します。executor01
を利用しないのは、次の仕様があるからです。
- マニフェストを実行時に指定する場合、マニフェストファイルは Controller 側で読み取られる
- 読み取った内容は Executor に送信 され利用される
- マニフェストは Receptor 的には秘匿対象 である
- 秘匿対象の情報は バックエンドが TLS で暗号化されていないと他のノードに送信できない
今回、TLS をまったく構成していないので、Controller で読み取ったマニフェストは Controller 以外のノードに送れません。逆に言えば他のノードに送らなければ使えるので、今回は Controller 兼 Executor である controller01
に閉じて実行できるように構成します。
なお、TLS を構成して他のノードの Executor を利用する場合、TLS は最低限 Controller から次のノードまでの間で構成されていればよいようです(盗聴対策としてはエンドツーエンドでなければあまり意味がないですが)。
controller01
(node01
)の設定ファイルに次の work-kubernetes
を追加します。
...
- work-kubernetes:
worktype: custom-runtime-pod
authmethod: kubeconfig
allowruntimepod: true
allowruntimeauth: true
...
allowruntimepod
が、実行時に Pod のマニフェストを指定できるようにするオプションです。allowruntimeauth
は本デモの本筋ではないですが、kubeconfig ファイルを実行時に指定する場合のオプションです。
Docker Compose ファイル
node01
には、マニフェストと kubeconfig ファイルを 実行時に指定するため に volumes
にエントリを追加します。node03
にも追加しますが、これはマニフェストと kubeconfig ファイルを 設定ファイルで事前に指定したため です。
services:
node01:
...
volumes:
- "./conf/node01.yml:/etc/receptor/receptor.conf"
- "./.kube/config:/tmp/.kube/config"
- "./manifests/pod.yml:/tmp/pod.yml"
...
node03:
...
volumes:
- "./conf/node03.yml:/etc/receptor/receptor.conf"
- "./.kube/config:/tmp/.kube/config"
- "./manifests/pod.yml:/tmp/pod.yml"
実際の動き
コンテナを起動させて、動きを確認します。
$ docker compose up -d
$ docker compose exec -it node01 bash
[root@node01 /]#
設定ファイルで指定したマニフェストの利用
executor01
に対してワークを実行して、作成された Pod の定義を確認します。
[root@node01 /]# receptorctl work submit \
--node executor01 \
--rm \
--follow \
--no-payload \
custom-predefined-pod
/etc/hostname:1:custom-predefined-pod-rcptp
/etc/os-release:1:PRETTY_NAME="Ubuntu 22.04.1 LTS"
/etc/os-release:2:NAME="Ubuntu"
/etc/os-release:3:VERSION_ID="22.04"
/etc/os-release:4:VERSION="22.04.1 LTS (Jammy Jellyfish)"
/etc/os-release:5:VERSION_CODENAME=jammy
/etc/os-release:6:ID=ubuntu
/etc/os-release:7:ID_LIKE=debian
/etc/os-release:8:HOME_URL="https://www.ubuntu.com/"
/etc/os-release:9:SUPPORT_URL="https://help.ubuntu.com/"
/etc/os-release:10:BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
/etc/os-release:11:PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
/etc/os-release:12:UBUNTU_CODENAME=jammy
(MM4c8e9s, released)
$ kubectl -n receptor get pod -o yaml
...
- apiVersion: v1
kind: Pod
metadata:
...
labels:
app: hello-receptor
name: custom-predefined-pod-rcptp
namespace: receptor
...
spec:
containers:
- args:
- -n
- -H
- ""
- /etc/hostname
- /etc/os-release
command:
- grep
image: ubuntu:22.04
imagePullPolicy: IfNotPresent
name: worker
resources:
requests:
cpu: 10m
...
initContainers:
- args:
- "5"
command:
- sleep
image: quay.io/centos/centos:stream8
imagePullPolicy: IfNotPresent
name: init-worker
...
マニフェストファイルで記述した通り、ラベルやリクエスト値、Init コンテナが構成されていることがわかります。
実行時に指定したマニフェストの利用
実行時にマニフェストを指定するには、receptorctl
に --param secret_kube_pod=@<ファイルパス>
を指定します。前述の通り、これは Controller 側のファイルパス です。@
を付与することで、その中身が読み取られて利用されます(コマンドが長くなりますが、@
なしでマニフェストを直接文字列として渡すことも可能です、この場合は YAML でなく JSON がよいですね)。
同様に、--param secret_kube_config=@<ファイルパス>
で kubeconfig ファイルも実行時に指定できます。
[root@node01 /]# receptorctl work submit \
--node controller01 \
--rm \
--follow \
--no-payload \
--param secret_kube_config=@/tmp/.kube/config \
--param secret_kube_pod=@/tmp/pod.yml \
custom-runtime-pod
/etc/hostname:1:custom-runtime-pod-znh8k
/etc/os-release:1:PRETTY_NAME="Ubuntu 22.04.1 LTS"
/etc/os-release:2:NAME="Ubuntu"
/etc/os-release:3:VERSION_ID="22.04"
/etc/os-release:4:VERSION="22.04.1 LTS (Jammy Jellyfish)"
/etc/os-release:5:VERSION_CODENAME=jammy
/etc/os-release:6:ID=ubuntu
/etc/os-release:7:ID_LIKE=debian
/etc/os-release:8:HOME_URL="https://www.ubuntu.com/"
/etc/os-release:9:SUPPORT_URL="https://help.ubuntu.com/"
/etc/os-release:10:BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
/etc/os-release:11:PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
/etc/os-release:12:UBUNTU_CODENAME=jammy
(Jk6LcMtY, released)
Pod の定義からは、意図通りに構成されていることがわかります。
$ kubectl -n receptor get pod -o yaml
...
- apiVersion: v1
kind: Pod
metadata:
...
labels:
app: hello-receptor
name: custom-runtime-pod-98bmc
namespace: receptor
...
spec:
containers:
- args:
- -n
- -H
- ""
- /etc/hostname
- /etc/os-release
command:
- grep
image: ubuntu:22.04
imagePullPolicy: IfNotPresent
name: worker
resources:
requests:
cpu: 10m
...
initContainers:
- args:
- "5"
command:
- sleep
image: quay.io/centos/centos:stream8
imagePullPolicy: IfNotPresent
name: init-worker
...
デモ (4): In-Cluster Authentication の利用
これまでのデモでは、いずれの Executor も Kubernetes の外で動作しており、Kubernetes への認証には kubeconfig ファイルを利用していました。
一方で、Executor 自身が Kubernetes 上の Pod として動作している場合、その Pod を動作させている Service Account(SA)の権限を使って、kubeconfig ファイルを用意することなく認証することも可能です。これは、いわゆる In-Cluster Authentication などと呼ばれるもので、自動でマ /var/run/secrets/kubernetes.io/serviceaccount
にマウントされる SA のトークンを使って認証するものです。
今回は、Kubernetes 上の Pod として動作させる Executor として executor02
(node04
)を作成し、ここで In-Cluster Authentication を試します。
設定ファイルの作成
必要なファイルを準備して、環境を整えます。実ファイル群は GitHub のリポジトリに配置 しています。
Receptor の Executor 用の設定ファイル
executor02
(node04
)の設定ファイルの抜粋です。
---
- node:
id: executor02
...
- tcp-peer:
address: node02.example.internal:7323
...
- work-kubernetes:
worktype: echo-reply-incluster
authmethod: incluster
namespace: receptor
image: quay.io/centos/centos:stream8
command: bash
params: "-c 'while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done'"
authmethod
を incluster
にしている点が今回のポイントです。それ以外の部分は構成を簡単にするためにシンプルにしていますが、標準入力の動作は確認できるようにしています。実際には、これまでのデモで紹介したような allowruntime*
などももちろん併用可能です。
また、tcp-peer
で指定しているホスト名には Executor の Pod からアクセスが発生するため、上位の DNS で名前解決できるようにしておきます。
Docker Compose ファイル
node04
が接続しにいく対向の node02
側では、Docker Compose ファイルでポート 7323
を外部に公開しています。
...
node02:
...
ports:
- 7323:7323
...
...
SA 関連のマニフェスト
In-Cluster Authentication を使うと、Pod の作成は SA の権限で行われます。つまり、SA が Pod の作成に必要な権限を持っている必要があります。このため、SA だけでなく Role と Role Binding の作成も必要です。
特別な工夫はなく、通常のお作法に従って作ればよいですが、Role には Pod の作成・削除や状態の確認のための pods
への権限だけでなく、worker
コンテナの標準出力を API 経由で取得するための pods/log
、標準入力をコンテナに渡すための pods/attach
も必要なようです。今回は以下の定義としています。
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: receptor
rules:
- apiGroups:
- ""
resources:
- pods
verbs:
- get
- list
- watch
- create
- delete
- apiGroups:
- ""
resources:
- pods/log
verbs:
- get
- apiGroups:
- ""
resources:
- pods/attach
verbs:
- create
また、Executor 自体の Pod のマニフェストも作成します(動作確認程度なので Deployment にはしていません)。以下の定義です。SA を指定しているほか、Receptor の設定ファイルとして ConfigMap をマウントさせています。image
はここでは latest
タグにしていますが、Kustomize で実際には v1.3.0
に置き換えます。
apiVersion: v1
kind: Pod
metadata:
namespace: receptor
name: node04
spec:
hostname: node04
containers:
- name: node04
image: quay.io/ansible/receptor:latest
command:
- receptor
- -c
- /etc/receptor/receptor.conf
volumeMounts:
- name: receptor-conf
mountPath: /etc/receptor/receptor.conf
subPath: receptor.conf
volumes:
- name: receptor-conf
configMap:
name: receptor-conf
items:
- key: node04.yml
path: receptor.conf
restartPolicy: Always
serviceAccountName: receptor
その他、実際のファイルは GitHub のリポジトリ にあるので割愛して、まとめて Kustomize で必要なリソース群を作成します。
$ kubectl apply -k .
namespace/receptor created
serviceaccount/receptor created
role.rbac.authorization.k8s.io/receptor created
rolebinding.rbac.authorization.k8s.io/receptor created
configmap/receptor-conf created
pod/node04 created
Pod が起動しました。
$ kubectl -n receptor get pod
NAME READY STATUS RESTARTS AGE
node04 1/1 Running 0 5s
$ kubectl -n receptor get pod node04 -o yaml
apiVersion: v1
kind: Pod
metadata:
...
name: node04
namespace: receptor
...
spec:
containers:
- command:
- receptor
- -c
- /etc/receptor/receptor.conf
image: quay.io/ansible/receptor:v1.3.0
imagePullPolicy: IfNotPresent
name: node04
...
volumeMounts:
- mountPath: /etc/receptor/receptor.conf
name: receptor-conf
subPath: receptor.conf
- mountPath: /var/run/secrets/kubernetes.io/serviceaccount
name: kube-api-access-b2sck
readOnly: true
...
serviceAccount: receptor
serviceAccountName: receptor
...
volumes:
- configMap:
defaultMode: 420
items:
- key: node04.yml
path: receptor.conf
name: receptor-conf
name: receptor-conf
- name: kube-api-access-b2sck
projected:
...
実際の動き
Docker 側でもコンテナを起動させて、動きを確認します。
$ docker compose up -d
$ docker compose exec -it node01 bash
[root@node01 /]#
まずは、Kubernetes 上で動作させている executor02
が Controller 側で認識され、ワーク echo-reply-incluster
が使える状態であることを確認します。
[root@node01 /]# receptorctl status
...
Known Node Known Connections
controller01 relayer01: 1
executor01 relayer01: 1
executor02 relayer01: 1
relayer01 controller01: 1 executor01: 1 executor02: 1
Route Via
executor01 relayer01
executor02 relayer01
relayer01 relayer01
Node Service Type Last Seen Tags
controller01 control Stream 2022-12-04 22:23:48 {'type': 'Control Service'}
executor01 control Stream 2022-12-04 22:23:19 {'type': 'Control Service'}
executor02 control Stream 2022-12-04 22:22:55 {'type': 'Control Service'}
Node Work Types
controller01 custom-runtime-pod
executor01 echo-sleep, cat-files, echo-reply, custom-predefined-pod
executor02 echo-reply-incluster
ワークを実行します。
[root@node01 /]# echo "Hello Receptor!" | receptorctl work submit \
--node executor02 \
--rm \
--follow \
--payload - \
echo-reply-incluster
Reply from echo-reply-incluster-ql9cp: HELLO RECEPTOR!
(JB484Cvb, released)
Pod が意図通り作成されました。
$ kubectl -n receptor get pod
NAME READY STATUS RESTARTS AGE
node04 1/1 Running 0 107s
echo-reply-incluster-ql9cp 0/1 Completed 0 2s
$ kubectl -n receptor get pod -o yaml
...
- apiVersion: v1
kind: Pod
metadata:
...
generateName: echo-reply-incluster-
name: echo-reply-incluster-ql9cp
namespace: receptor
...
spec:
containers:
- args:
- -c
- 'while read -r line; do echo Reply from $(cat /etc/hostname): ${line^^}; done'
command:
- bash
image: quay.io/centos/centos:stream8
imagePullPolicy: IfNotPresent
name: worker
...
Executor が Pod として動作していて、かつ SA が充分な権限を持っていれば、kubeconfig を使わなくてもワークを実行できることが確認できました。
まとめ
前回のエントリ の続きとして、Kubernetes クラスタ上でのワークの実行を試しました。
Pod が自由に構成でき、ペイロードも渡せるので、相当に柔軟な対応ができそうです。