NGINXの運用をちょっとだけ楽にするKubernetes Controllerを実装してみた
Motivation
以前[Controller編] Server-Side Applyを試すの記事にて、Server-Side Apply(以降SSA)を試してみました。
そこで、今回はSSAを利用して、NGINXの運用をちょっとだけ楽にするControllerを実装してみました。また、Controllerで利用可能なさまざまな機能も色々と盛り込んでいるので、今後のチュートリアル的な出来になったかなと思います。
コードの場所
以下のリポジトリにコードを配置しています。
機能紹介
以下の作業を自動化します。
- 以下のkuberndtes Resourceを作成する。
- ConfigMap: default.confとindex.html
- Deployment: NGINXサーバー
- Service: NGINXへのルーティングを行う
- Ingress: 外部からのアクセスをServiceにルーティングする
- Secret: IngressのSSL終端に必要なCA証明書、サーバー証明書、サーバー証明書の秘密鍵が含まれる
- Secret: Ingressへのアクセスに必要なクライアント証明書と秘密鍵が含まれる
- Resource名の変更
- 名称変更後、旧Resourceを削除
- Resource定義の変更
- default.conf変更時の自動再読み込み (inotifywaitで監視)
kuberndtes Resourceの自動作成
まずCustomResource定義を準備します。
apiVersion: ssanginx.jnytnai0613.github.io/v1
kind: SSANginx
metadata:
name: ssanginx-sample
namespace: ssa-nginx-controller-system
spec:
# Deployment定義
deploymentName: nginx
deploymentSpec:
replicas: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 30%
maxUnavailable: 30%
template:
spec:
containers:
- name: nginx
image: nginx:latest
# ConfigMap定義
# NGINX設定ファイルdefault.confと静的コンテンツの定義を記載する
configMapName: nginx
configMapData:
default.conf: |
server {
listen 80 default_server;
listen [::]:80 default_server ipv6only=on;
root /usr/share/nginx/html;
index index.html index.htm mod-index.html;
server_name localhost;
}
mod-index.html: |
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
# Serviceの定義
serviceName: nginx
serviceSpec:
type: ClusterIP
ports:
- protocol: TCP
port: 80
targetPort: 80
# Ingressの定義
ingressName: nginx
ingressSpec:
rules:
- host: nginx.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: nginx
port:
number: 80
# Ingress TLSを有効にするかどうか
ingressSecureEnabled: true
CustomResourceを適用することで、以下のように各Resourceが自動作成されます。
$ kubectl -n ssa-nginx-controller-system get all,cm,secret,ingress
NAME READY STATUS RESTARTS AGE
pod/nginx-686484b946-bnhgc 1/1 Running 0 32m
pod/nginx-686484b946-fhx4c 1/1 Running 0 32m
pod/nginx-686484b946-kzgpc 1/1 Running 0 32m
pod/ssa-nginx-controller-controller-manager-5658b4d48f-j7kn9 2/2 Running 0 33m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/nginx ClusterIP 10.96.81.98 <none> 80/TCP 32m
service/ssa-nginx-controller-controller-manager-metrics-service ClusterIP 10.96.44.121 <none> 8443/TCP 33m
service/ssa-nginx-controller-webhook-service ClusterIP 10.96.245.134 <none> 443/TCP 33m
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nginx 3/3 3 3 32m
deployment.apps/ssa-nginx-controller-controller-manager 1/1 1 1 33m
NAME DESIRED CURRENT READY AGE
replicaset.apps/nginx-686484b946 3 3 3 32m
replicaset.apps/ssa-nginx-controller-controller-manager-5658b4d48f 1 1 1 33m
NAME DATA AGE
configmap/kube-root-ca.crt 1 71m
configmap/nginx 2 32m
configmap/ssa-nginx-controller-manager-config 1 33m
NAME TYPE DATA AGE
secret/ca-secret Opaque 3 32m
secret/cli-secret Opaque 2 32m
secret/webhook-server-cert kubernetes.io/tls 3 33m
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/nginx nginx nginx.example.com 10.96.168.155 80, 443 32m
IngressのSSL終端
CustomResourceの.spec.ingressSecureEnabledフィールドをtrueとすることで、以下のSecretが自動作成されます。
NAME TYPE DATA AGE
secret/ca-secret Opaque 3 32m
secret/cli-secret Opaque 2 32m
また、Ingressにも以下TLS設定が自動で追加されます。
- 以下のannotationsを追加し、クライアント認証を有効にします。
各annotationの説明は以下のページの通りです。
https://github.com/kubernetes/ingress-nginx/blob/main/docs/user-guide/nginx-configuration/annotations.md#client-certificate-authentication
$ kubectl -n ssa-nginx-controller-system get ingress nginx -ojson | jq '.metadata.annotations'
{
"nginx.ingress.kubernetes.io/auth-tls-secret": "ssa-nginx-controller-system/ca-secret",
"nginx.ingress.kubernetes.io/auth-tls-verify-client": "on",
"nginx.ingress.kubernetes.io/rewrite-target": "/"
}
- .spec.tlsフィールドを追加します。
Secret ca-secretはControllerによって自動作成されたものが、自動指定されます。
また、hostsはCustomResourceの.spec.ingressSpec.rules[].hostに指定されている値が自動で使用されます。
$ kubectl -n ssa-nginx-controller-system get ingress nginx -ojson | jq '.spec.tls'
[
{
"hosts": [
"nginx.example.com"
],
"secretName": "ca-secret"
}
]
Ingressを利用した接続
まずSecret cli-secretよりクライアント証明書と秘密鍵をダウンロードします。
$ kubectl -n ssa-nginx-controller-system get secrets cli-secret -ojsonpath='{.data.client\.crt}' | base64 -d > client.crt
$ kubectl -n ssa-nginx-controller-system get secrets cli-secret -ojsonpath='{.data.client\.key}' | base64 -d > client.key
その後、以下のコマンドにてアクセス可能です。
curl --key client.key --cert client.crt https://nginx.example.com:443/ --resolve nginx.example.com:443:<IP Address> -kv
なお、各種証明書と秘密鍵は以下goファイルの関数をControllerから呼び出して生成しています。
curlに-vオプションを与えていますので、certificate.goで指定しているCNが確認できます。
* subject: C=JP; O=Example Org; OU=Example Org Unit; CN=server
* start date: Sep 26 03:21:24 2022 GMT
* expire date: Dec 31 00:00:00 2031 GMT
* issuer: C=JP; O=Example Org; OU=Example Org Unit; CN=ca
Resourceの名前変更
CustomResourceのXXXXXNameを変更し、再適用することでResourceの名前変更が可能です。
変更した名前で新Resourceが作成され、変更前の旧Resourceは削除される流れとなっています。
ちなみにOwnerReferenceからIndexを貼って、絞り込んでList化して、目的の旧Resourceを探して削除しています。
// add IndexOwnerKey index to deployment object which SSANginx resource owns
if err := mgr.GetFieldIndexer().IndexField(ctx, &appsv1.Deployment{}, constants.IndexOwnerKey, func(obj client.Object) []string {
// grab the deployment object, extract the owner...
deployment := obj.(*appsv1.Deployment)
owner := metav1.GetControllerOf(deployment)
if owner == nil {
return nil
}
if owner.APIVersion != apiGVStr || owner.Kind != constants.CrKind {
return nil
}
return []string{owner.Name}
}); err != nil {
return err
}
Resource定義の変更
CustomResourceのXXXXXSpecを変更することで定義変更が可能です。
差分計算はClient-Sideで行い、定義変更を検知しています。
また、差分計算はequality.Semantic.DeepEqual()を利用することで実現しています。
default.conf変更時の自動再読み込み
NGINX Pod内では以下のshellスクリプトが実行され、inotifywaitにてdefault.confの変更を監視しています。
oldcksum=`cksum /etc/nginx/conf.d/default.conf`
inotifywait -e modify,move,create,delete -mr --timefmt '%Y/%m/%d %H:%M:%S' --format '%T' /etc/nginx/conf.d/ | \
while read date time; do
newcksum=`cksum /etc/nginx/conf.d/default.conf`
if [ "${newcksum}" != "${oldcksum}" ]; then
echo "At ${time} on ${date}, config file update detected."
oldcksum=${newcksum}
service nginx restart
fi
done
ConfigMapの変更などでdefault.confの変更が検知されると、以下のようにNGINXプロセスが再起動され、変更が反映されます。
At 03:24:21 on 2022/09/26, config file update detected.
2022/09/26 03:24:21 [notice] 301#301: signal 15 (SIGTERM) received from 315, exiting
2022/09/26 03:24:21 [notice] 302#302: exiting
2022/09/26 03:24:21 [notice] 303#303: exiting
2022/09/26 03:24:21 [notice] 303#303: exit
2022/09/26 03:24:21 [notice] 302#302: exit
2022/09/26 03:24:21 [notice] 301#301: signal 17 (SIGCHLD) received from 302
2022/09/26 03:24:21 [notice] 301#301: worker process 302 exited with code 0
2022/09/26 03:24:21 [notice] 301#301: signal 29 (SIGIO) received
2022/09/26 03:24:21 [notice] 301#301: signal 17 (SIGCHLD) received from 303
2022/09/26 03:24:21 [notice] 301#301: worker process 303 exited with code 0
2022/09/26 03:24:21 [notice] 301#301: exit
2022/09/26 03:24:21 [notice] 317#317: using the "epoll" event method
2022/09/26 03:24:21 [notice] 317#317: nginx/1.23.1
2022/09/26 03:24:21 [notice] 317#317: built by gcc 10.2.1 20210110 (Debian 10.2.1-6)
2022/09/26 03:24:21 [notice] 317#317: OS: Linux 5.10.104-linuxkit
2022/09/26 03:24:21 [notice] 317#317: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2022/09/26 03:24:21 [notice] 318#318: start worker processes
2022/09/26 03:24:21 [notice] 318#318: start worker process 319
2022/09/26 03:24:21 [notice] 318#318: start worker process 320
Restarting nginx: nginx.
最後に
今回実装したControllerには以下を盛り込んでいます。
- Server-Side Apply
- Client-Sideでの差分計算
- Owner Referenceを利用したResouce間の親子関係の構築
- Indexを利用したResouceのList化
- Admission Webhook(Validation)
これらはControllerを実装する上でも多用する機能です。
もし、今後Controllerを実装する際には参考になれば幸いです。
Discussion