Kubernetes Operator を使用して動的に LKE ワーカーノードに Linode Cloud Firewall を適用する

Linode Kubernetes Engine (LKE) のワーカーノードの IP を外部に公開する必要がある場合、ネットワークアクセス制御のためにLinode Firewall を設定します。サービスタイプ：``NodePort`` で起動されたそれぞれのワーカーノードに Firewall を適用する場合、その管理が課題になるケースがあります。Kubernetesはノードのオートスケールに対応しているものの、Firewall の適用を手動で実行する必要がありました。この手動プロセスは、クラスターのオートスケール、アップグレード、サイズ変更、ノードプールのリサイクルで問題となり、メンテナンスの労力とヒューマンエラーのリスクを増加させます。

本文で紹介する Linode Firewall Operator を用いると、クラスターワーカーノードを Linode Firewall インスタンスに自動接続する構成をとれるため、上記の課題に対処し、クラスターのセキュリティ管理を改善します。

![Node firewall with LKE Operator (1).jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/40f4e921-c75a-d37f-8ad6-51b707b43e4e.jpeg)



:::note
ロードバランサーを使うパターンでは、ワーカーノードはロードバランサーの後方に配置されるので、各ワーカーノードに対する Firewall 管理は不要です。NodeBalancer へ Firewall を適用できるからです。LKE クラスターにロードバランサーを適用する場合は下記の資料を参考にしてください。
:::

https://www.linode.com/docs/products/compute/kubernetes/guides/load-balancing/#adding-linode-nodebalancers-to-your-kubernetes-cluster

:::note
ロードバランサーを適用できないパターンに本文で紹介する Firewall Operator は有効です。例えば、ロードバランサーが本原稿執筆時点で対応していない UDP で通信する必要がある場合や、それぞれのワーカーノードが直接パブリック IP を持って外部と通信する必要がある場合です。
:::

# Linode Firewall Operator の動き

## イベント駆動型の自動 Firewall 適用
Operator は Kubernetes クラスターイベントに基づいて、該当する Linode Firewall からワーカーノードを自動的に追加または削除します。ノードが作成または削除されると、現在のノードリストと Firewall のデバイスリストが照合され管理すべきデバイス（ノード）を最新の状態に保ちます。
## クラスター内のノードに同じ Firewall ルールを適用
Linode Firewall Operator はクラスターと Firewall インスタンスの間に 1:1 の関係を想定し、ノードプールに関係なくすべてのノードを平等に扱い、同じ Firewall ルールを適用します。
## ノード削除処理
Linode は削除されたノードを自動的に Firewall のリストから削除します。そして、Operator はこれらの削除を検証し、ログに記録します。

:::note 
将来の開発では、Oprator を使うことなく、Firewall インスタンスに Kubernetes クラスターをマッピングできる予定です。この機能に興味がある方はご連絡ください。
:::

# クイックスタート
以下にあるコードを使って実行します。

https://github.com/gangyi89/deploy-linode-operator

## 前提条件
Linode Firewall インスタンスID、Linode APIキー、LKEインスタンスを準備します。

Firewall ID は次の API Doc を参考に取得してください。

https://techdocs.akamai.com/linode-api/reference/get-firewalls

Linode API キーは下記の記事を参考にしてください。

https://qiita.com/110hideki/items/91491d684cd794632652#linode-%E3%83%9D%E3%83%BC%E3%82%BF%E3%83%AB%E3%81%8B%E3%82%89%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B


## 設定手順
Operator を起動して実行するには 2 つのステップを行います。
### 必要な Custom Resource Definitions(CRD) と Operator を配置する
```
curl -s https://raw.githubusercontent.com/gangyi89/deploy-linode-operator/main/deploy-linode-fw-operator.sh | bash -s -- <MY_NAMESPACE>
```

### Firewall オブジェクトをコピーする
1\. サンプルで提供されている cluster-firewall.yaml をコピーします。
2\. Firewall ID と API-Key を設定します。
![cluster-firewall.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/76dfd5b7-a278-0360-886c-f0f4c03755cf.jpeg)

パラメータの説明です。
| 1 | 2 |
|:-:|:-:|
|ClusterFirewall | オブジェクトを宣言する際に利用可能なパラメータです  |
|  firewallId | クラスタ専用の Firewall インスタンスのIDです  |
| apiKeySecret  | Kubernetes Secretに格納されている 「Firewalls Read/Write 」権限を持つAPIキーです  |
|Interval（オプション |定期的なリコンサイルを実行します|

3\. Firewallオブジェクトをデプロイします。以下は default ネームスペースの例です。
```
kubectl apply -f cluster-firewall.yaml -n default
```

### Kubernetes deployment を確認する (Option)
```
% kubectl get deployments linode-fw-operator
NAME                 READY   UP-TO-DATE   AVAILABLE   AGE
linode-fw-operator   1/1     1            1           9d
```

### Linode Firewall Operator の Pod を確認する
``linode-fw-operator`` で始まる Pod が動作していれば設定は完了しています。
```
% kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGE
linode-fw-operator-XXXXXXXXXXX-XXXds      1/1     Running   0          41m
static-site-deployment-XXXXXXXXXX-9hzvl   1/1     Running   0          35m
static-site-deployment-XXXXXXXXXX-rqxlx   1/1     Running   0          41m
static-site-deployment-XXXXXXXXXX-rxn49   1/1     Running   0          35m
```

# デバッグ方法
## kubectl describe
```
% kubectl describe clusterfirewall
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
Name:         clusterfirewall
Namespace:    default
Labels:       app.kubernetes.io/name=linodeoperator
Annotations:  <none>
API Version:  firewall.operator.linode.io/v1alpha1
Kind:         ClusterFirewall
Metadata:
  Creation Timestamp:  2024-07-19T02:52:29Z
  Generation:          1
  Resource Version:    102467285
  UID:                 16274495-5999-497d-823d-2c4c106385e0
Spec:
  API Key Secret:
    Key:        api-key
    Name:       linode-api-key
  Firewall Id:  6....5
Status:
  Nodes:
    lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b
    lkeZZZZZZ-XXXXXX-YYYYYYYYYYee
    lkeZZZZZZ-XXXXXX-YYYYYY87YY00
    lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb
Events:
  Type    Reason         Age                From                        Message
  ----    ------         ----               ----                        -------
  Normal  DeviceDeleted  58m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-1d915e640000 is removed from firewall ID 6....5
  Normal  DeviceDeleted  40m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb is removed from firewall ID 6....5
  Normal  DeviceAdded    39m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb is added to firewall ID 6....5
  Normal  DeviceDeleted  37m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b is removed from firewall ID 6....5
  Normal  DeviceAdded    36m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b is added to firewall ID 6....5
  Normal  DeviceAdded    34m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYY87YY00 is added to firewall ID 6....5
  Normal  DeviceDeleted  32m                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYee is removed from firewall ID 6....5
  Normal  DeviceAdded    31m (x2 over 37m)  clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYee is added to firewall ID 6....5
  ```
## kubectl logs

```
% kubectl logs linode-fw-operator-XXXXXXXXXXX-XXXds --tail 5
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
2024-07-28T07:04:29Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYee is removed from firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102466194"}, "reason": "DeviceDeleted"}
2024-07-28T07:04:29Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "2f329979-2b25-475b-a622-2226f1a0c38f"}
2024-07-28T07:05:28Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "1b107132-29f7-4c0b-a2de-836f727e828e"}
2024-07-28T07:05:28Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYee is added to firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102466984"}, "reason": "DeviceAdded"}
2024-07-28T07:05:28Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "1b107132-29f7-4c0b-a2de-836f727e828e"}
```

# 動作確認
## Operator が動作していない Node をとめる
まずは、Operator が動作していないノードを止めて、Linode のインスタンスと Firewall の関係がどのように動くのか見てみます。Linode Firewall Operator である ``linode-fw-operator-XXXXXXXXXXX-XXXds`` は ``lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb`` のノードで動いています。
```
 % kubectl get pods -o wide
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
NAME                                      READY   STATUS    RESTARTS   AGE   IP           NODE                            NOMINATED NODE   READINESS GATES
linode-fw-operator-XXXXXXXXXXX-XXXds      1/1     Running   0          64m   10.2.0.4     lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb   <none>           <none>
static-site-deployment-XXXXXXXXXX-9hzvl   1/1     Running   0          57m   10.2.1.3     lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b   <none>           <none>
static-site-deployment-XXXXXXXXXX-rqxlx   1/1     Running   0          64m   10.2.0.2     lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb   <none>           <none>
static-site-deployment-XXXXXXXXXX-rxn49   1/1     Running   0          57m   10.2.1.132   lkeZZZZZZ-XXXXXX-YYYYYY87YY00   <none>           <none>
```
Linode firewall Operator が動作していない ``lkeZZZZZZ-XXXXXX-YYYYYY87YY00`` をリサイクルすると、 Linode Cloud Firewall は次のようにイベントを受け取り、 Linode Cloud Firewall から該当するノードを一旦削除します。
```
% kubectl logs linode-fw-operator-XXXXXXXXXXX-XXXds  --tail 1 -f
2024-07-28T08:18:16Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "a42b650e-a6d8-4e3d-83e7-966cea156438"}
2024-07-28T08:18:16Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYY87YY00 is removed from firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102484424"}, "reason": "DeviceDeleted"}
2024-07-28T08:18:16Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "a42b650e-a6d8-4e3d-83e7-966cea156438"}
```
UI 上でも ``lkeZZZZZZ-XXXXXX-YYYYYY87YY00`` のノードは Firewall のデバイスリストから削除されています。

![firewall-recycle-mark.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/ee50b195-214a-d6e4-31cb-1ba193d3cb5e.jpeg)

しばらくして、``linode-fw-operator-XXXXXXXXXXX-XXXds`` のログを見ると次のように表示されています。``lkeZZZZZZ-XXXXXX-YYYYYY87YY00``のノードがリサイクルされ、再度起動したタイミングで Firewall のデバイスリストに登録されているメッセージが見えます。

```
2024-07-28T08:19:27Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "8f4aaae5-b838-4bb6-8527-eeec2471b729"}
2024-07-28T08:19:28Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYY87YY00 is added to firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102485027"}, "reason": "DeviceAdded"}
2024-07-28T08:19:28Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "8f4aaae5-b838-4bb6-8527-eeec2471b729"}
```
UI 上でも ``lkeZZZZZZ-XXXXXX-YYYYYY87YY00`` のノードが Firewall のデバイスリストに再登録されています。

![firewall-after-recycle-mask.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/a172f751-2def-86ba-fbff-4d460b54f559.jpeg)

このように Operator の仕組みを使うと、デバイスの削除、追加に伴い、登録した動作を自動で行ってくれます。

## Operator が動作している Node を削除する
Operator はいずれかの Node 内の Pod で動きますが、その Node が停止しても、Operator は自動的に別の Node で動きます。

Operator が動作している Node をリサイクルすると、次のように Operator のログは取れなくなります。
```
% kubectl logs linode-fw-operator-XXXXXXXXXXX-XXXds  --tail 1 -f

2024-07-28T08:40:09Z	INFO	Stopping and waiting for non leader election runnables
2024-07-28T08:40:09Z	INFO	Stopping and waiting for leader election runnables
2024-07-28T08:40:09Z	INFO	Shutdown signal received, waiting for all workers to finish	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall"}
2024-07-28T08:40:09Z	INFO	All workers finished	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall"}
2024-07-28T08:40:09Z	INFO	Stopping and waiting for caches
W0728 08:40:09.090703       1 reflector.go:470] pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:232: watch of *v1alpha1.ClusterFirewall ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented the request from succeeding
W0728 08:40:09.090864       1 reflector.go:470] pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:232: watch of *v1.Node ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented the request from succeeding
W0728 08:40:09.090947       1 reflector.go:470] pkg/mod/k8s.io/client-go@v0.30.0/tools/cache/reflector.go:232: watch of *v1.Secret ended with: an error on the server ("unable to decode an event from the watch stream: context canceled") has prevented the request from succeeding
2024-07-28T08:40:09Z	INFO	Stopping and waiting for webhooks
2024-07-28T08:40:09Z	INFO	Stopping and waiting for HTTP servers
2024-07-28T08:40:09Z	INFO	shutting down server	{"name": "health probe", "addr": "[::]:8081"}
2024-07-28T08:40:09Z	INFO	Wait completed, proceeding to shutdown the manager
%
```
Linode Firewall Operator が動いていたノード ``lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb`` は一旦見えなくなります。
```
% kubectl get nodes
NAME                            STATUS   ROLES    AGE   VERSION
lkeZZZZZZ-XXXXXX-YYYYYY87YY00   Ready    <none>   20m   v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYYee   Ready    <none>   94m   v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b   Ready    <none>   99m   v1.29.2
```
しかし、Linode Firewall Operator は別のノードで動いています。
```
% kubectl get pods -o "custom-columns=NAME:.metadata.name,NODENAME:.spec.nodeName"
NAME                                      NODENAME
linode-fw-operator-XXXXXXXXXXX-wqj8p      lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b
static-site-deployment-XXXXXXXXXX-29849   lkeZZZZZZ-XXXXXX-YYYYYYYYYYee
static-site-deployment-XXXXXXXXXX-9hzvl   lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b
static-site-deployment-XXXXXXXXXX-sn9f9   lkeZZZZZZ-XXXXXX-YYYYYY87YY00
```
先ほどまでは、``lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb``で動いていましたが、そのノードを止めた後、``lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b`` で動いていることが確認できます。

```
% kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGE
linode-fw-operator-XXXXXXXXXXX-wqj8p      1/1     Running   0          27s
static-site-deployment-XXXXXXXXXX-29849   1/1     Running   0          25m
static-site-deployment-XXXXXXXXXX-9hzvl   1/1     Running   0          96m
static-site-deployment-XXXXXXXXXX-sn9f9   1/1     Running   0          27s
```
新しい Pod の名前でログを見ます。
```
% kubectl logs linode-fw-operator-XXXXXXXXXXX-wqj8p  -f
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
2024-07-28T08:40:11Z	INFO	setup	starting manager
2024-07-28T08:40:11Z	INFO	starting server	{"name": "health probe", "addr": "[::]:8081"}
I0728 08:40:11.313307       1 leaderelection.go:250] attempting to acquire leader lease default/a7b178ce.operator.linode.io...
I0728 08:40:29.230370       1 leaderelection.go:260] successfully acquired lease default/a7b178ce.operator.linode.io
2024-07-28T08:40:29Z	DEBUG	events	linode-fw-operator-XXXXXXXXXXX-wqj8p_b461bad2-3710-4450-9426-ed5e8bdd4aed became leader	{"type": "Normal", "object": {"kind":"Lease","namespace":"default","name":"a7b178ce.operator.linode.io","uid":"41d508cb-91ac-41fa-a4e0-6dcb62b7680c","apiVersion":"coordination.k8s.io/v1","resourceVersion":"102490618"}, "reason": "LeaderElection"}
2024-07-28T08:40:29Z	INFO	Starting EventSource	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "source": "kind source: *v1alpha1.ClusterFirewall"}
2024-07-28T08:40:29Z	INFO	Starting EventSource	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "source": "kind source: *v1.Node"}
2024-07-28T08:40:29Z	INFO	Starting Controller	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall"}
2024-07-28T08:40:29Z	INFO	Starting workers	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "worker count": 1}
2024-07-28T08:40:29Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "52dde7aa-0419-416e-b4d3-9b9441e15b50"}
2024-07-28T08:40:29Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "52dde7aa-0419-416e-b4d3-9b9441e15b50"}
2024-07-28T08:41:21Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "0205df5f-7d0e-4b69-b07d-f4da3d2f989f"}
2024-07-28T08:41:21Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb is added to firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102490626"}, "reason": "DeviceAdded"}
2024-07-28T08:41:21Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "0205df5f-7d0e-4b69-b07d-f4da3d2f989f"}
```
clusterfirewall にイベントが登録されています。
```
% kubectl describe clusterfirewall
```
```
Events:
  Type    Reason         Age                 From                        Message
  ----    ------         ----                ----                        -------
  Normal  DeviceDeleted  30m                 clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYY87YY00 is removed from firewall ID 6....5
  Normal  DeviceAdded    28m (x3 over 106m)  clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYY87YY00 is added to firewall ID 6....5
  Normal  DeviceAdded    7m3s                clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb is added to firewall ID 6....5
```
このように Operator は動いているノードが削除されても、自律的に API Server 経由でイベントを受け取り、新しいノードで Opertor を継続して動かすことができるので、信頼性ある仕組みを作ることができます。

## 新しいノードを追加する
新しいノードが追加されたときに自動的に Firewall ルールが適用されるかを確認します。ここでは 5 つ目のノードを追加します。ノードが５つ見えます。
```
% kubectl get nodes
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
NAME                            STATUS   ROLES    AGE     VERSION
lkeZZZZZZ-XXXXXX-YYYYYY9dYY00   Ready    <none>   9m59s   v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYY87YY00   Ready    <none>   65m     v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb   Ready    <none>   43m     v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYYee   Ready    <none>   139m    v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b   Ready    <none>   144m    v1.29.2
```
そのときに、Linode Firewall Operator はイベントを補足し、次のイベントを駆動します。
```
2024-07-28T09:15:02Z	INFO	Reconciliation Started	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "1d51b8a5-63dc-484f-8253-c412d01d6fb0"}
2024-07-28T09:15:03Z	DEBUG	events	Node lkeZZZZZZ-XXXXXX-YYYYYY9dYY00 is added to firewall ID 6....5	{"type": "Normal", "object": {"kind":"ClusterFirewall","namespace":"default","name":"clusterfirewall","uid":"16274495-5999-497d-823d-2c4c106385e0","apiVersion":"firewall.operator.linode.io/v1alpha1","resourceVersion":"102490855"}, "reason": "DeviceAdded"}
2024-07-28T09:15:03Z	INFO	Reconciliation Completed	{"controller": "clusterfirewall", "controllerGroup": "firewall.operator.linode.io", "controllerKind": "ClusterFirewall", "ClusterFirewall": {"name":"clusterfirewall","namespace":"default"}, "namespace": "default", "name": "clusterfirewall", "reconcileID": "1d51b8a5-63dc-484f-8253-c412d01d6fb0"}
```
clusterfirewall にも新しいイベントの記録が見えます。
```
  Normal  DeviceAdded    70s                 clusterfirewall-controller  Node lkeZZZZZZ-XXXXXX-YYYYYY9dYY00 is added to firewall ID 6....5
```
UI 上でも次のように自動的に新しいノードに対して Firewall ルールが適用されていることが分かります。
![firewall-5node-mask.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/07cadc5e-dcf4-78fb-554a-d9c85492ab57.jpeg)

このように LKE 内でノードが新規に追加されても、Operator が動的に Linode Firewall を適用してくれます。

## ノードを削減する
ノードが削減されたときにどのように動くのか見てみます。ノードの数を 5 から 3 に削減します。次の結果は削減された後のステータスです。
```
% kubectl get nodes
Warning: Use tokens from the TokenRequest API or manually created secret-based tokens instead of auto-generated secret-based tokens.
NAME                            STATUS   ROLES    AGE    VERSION
lkeZZZZZZ-XXXXXX-YYYYYYYYYYcb   Ready    <none>   58m    v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYYee   Ready    <none>   154m   v1.29.2
lkeZZZZZZ-XXXXXX-YYYYYYYYYY1b   Ready    <none>   159m   v1.29.2
```
UI でも 5 から 3 つのデバイスに変更されていることが分かります。
![firewall-5to3-mask.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/264188/34753b40-5b95-27a2-b34f-517e80f4f904.jpeg)

# Linode Firewall Operator のサンプルルール

Outbound ルールを Drop をデフォルトにすると、内部の通信で問題がおきます。以下にサンプルのルールを置いていますので参考にしてください。

https://github.com/110hideki/linode-firewall-operator-rule/blob/main/README.md



# まとめ
本記事は Linode Kubernetes Engine クラスターで Operator を設定した例です。イベント駆動で、それ自体で回復することができる素晴らしいソリューションです。LKE 内のクラスターノードに動的に Firewall ルールが設定できれば良いのですが、本原稿執筆時点ではその機能が提供されていないため、Operator を使って同様なことを実現しました。Operatorはノード上のPodとして動作し、ノードがリサイクルされると、新しい Operator がすぐに別のノードで実行されます。その後、Operator は新しいノード上で Firewall をアタッチすることができています。Kubernetes Operator はその名の通り、動的なオペレーターとして動かすことができるため、有効な手段として今回は紹介させていただきました。是非ご活用ください。