Opensourcetechブログ

OpensourcetechによるNGINX/Kubernetes/Zabbix/Neo4j/Linuxなどオープンソース技術に関するブログです。

kubernetes環境における送信元IPアドレスの保持(externalTrafficPolicy: Local)


LinuCエヴァンジェリスト・Open Source Summit Japan 2022ボランティアリーダー鯨井貴博@opensourcetechです。


はじめに
今回は、kubernetes環境における送信元IPアドレスの保持について確認してみます。
以下の記事にあるService(LoadBalancer)環境を使って確認していきます。
https://www.opensourcetech.tokyo/entry/20230316/1678966960


試験環境の構築
まず、試験用の環境構築。

namespace(名前空間)の作成。

kubeuser@master01:~/20231008$ cat ns.yaml 
apiVersion: v1
kind: Namespace
metadata:
  name: seminar

kubeuser@master01:~/20231008$ kubectl apply -f ns.yaml 
namespace/seminar created

kubeuser@master01:~/20231008$ kubectl get ns | grep seminar
seminar                Active   15s


Webサーバ用のconfigmap・Deploymentの作成。

kubeuser@master01:~/20231008$ cat configmap_html.yaml 
apiVersion: v1
data:
  index.html: |
    <!DOCTYPE html>
    <html>
    <head>
     <title>20231008 k8sセミナー</title>
     <style>
      html { color-scheme: dark; }
      body { width: 35em; margin: 0 auto;
      font-family: Tahoma, Verdana, Arial, sans-serif; }
     </style>
    </head>
    <body>
     <h1>Thnak you for joining today's seminer!</h1>
    </body>
    </html>
kind: ConfigMap
metadata:
  name: indexhtml
  namespace: seminar

kubeuser@master01:~/20231008$ kubectl apply -f configmap_html.yaml 
configmap/indexhtml created

kubeuser@master01:~/20231008$ kubectl get cm -n seminar
NAME               DATA   AGE
indexhtml          1      6s
kube-root-ca.crt   1      71s

kubeuser@master01:~/20231008$ cat deployment.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: webserver
  name: webserver
  namespace: seminar
spec:
  replicas: 2
  selector:
    matchLabels:
      app: webserver
  strategy: {}
  template:
    metadata:
      labels:
        app: webserver
    spec:
      containers:
      - image: nginx:latest
        name: nginx
        volumeMounts:
        - name: index-volume
          mountPath: /usr/share/nginx/html
      volumes:
      - name: index-volume
        configMap:
          name: indexhtml

kubeuser@master01:~/20231008$ kubectl apply -f deployment.yaml 
deployment.apps/webserver created

kubeuser@master01:~/20231008$ kubectl get deployments.apps -n seminar
NAME        READY   UP-TO-DATE   AVAILABLE   AGE
webserver   2/2     2            2           27s

kubeuser@master01:~/20231008$ kubectl get pods -n seminar
NAME                         READY   STATUS    RESTARTS   AGE
webserver-68c554c9f6-ck6h2   1/1     Running   0          30s
webserver-68c554c9f6-nlhk2   1/1     Running   0          30s

kubeuser@master01:~/20231008$ kubectl get pods -n seminar -o wide
NAME                         READY   STATUS    RESTARTS   AGE     IP           NODE       NOMINATED NODE   READINESS GATES
webserver-68c554c9f6-ck6h2   1/1     Running   0          8m31s   10.0.30.88   worker02   <none>           <none>
webserver-68c554c9f6-nlhk2   1/1     Running   0          8m31s   10.0.5.8     worker01   <none>           <none>


Serviceの作成。

kubeuser@master01:~/20231008$ cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: webserver
  name: webserver
  namespace: seminar
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: webserver
  externalTrafficPolicy: Cluster
  type: LoadBalancer

kubeuser@master01:~/20231008$ kubectl apply -f service.yaml 
service/webserver created

kubeuser@master01:~/20231008$ kubectl get svc -n seminar
NAME        TYPE           CLUSTER-IP    EXTERNAL-IP    PORT(S)        AGE
webserver   LoadBalancer   10.1.246.94   192.168.1.56   80:32305/TCP   7s



デフォルト値(externalTrafficPolicy: Cluster)の場合
では、この状態でClientからの送信元IPアドレスがPodから見るとどのようになるか確認します。
確認には、sternを使います。

Serviceで公開されたIPアドレス(192.168.1.56)&ポート番号(TCP80)にアクセスすると、
送信元はNode(worker01、192.168.1.45)になっています。

kubeuser@master01:~/20231008$ kubectl get pods -n seminar
NAME                         READY   STATUS    RESTARTS   AGE
webserver-68c554c9f6-ck6h2   1/1     Running   0          3m17s
webserver-68c554c9f6-nlhk2   1/1     Running   0          3m17s

kubeuser@master01:~/20231008$ stern webserver-68c554c9f6 -n seminar
+ webserver-68c554c9f6-ck6h2 › nginx
+ webserver-68c554c9f6-nlhk2 › nginx
.
.
.
.
webserver-68c554c9f6-ck6h2 nginx 10.0.5.0 - - [15/Oct/2023:04:23:57 +0000] "GET / HTTP/1.1" 200 292 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"
webserver-68c554c9f6-ck6h2 nginx 2023/10/15 04:23:57 [error] 29#29: *1 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 10.0.5.0, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "192.168.1.56", referrer: "http://192.168.1.56/"
webserver-68c554c9f6-ck6h2 nginx 10.0.5.0 - - [15/Oct/2023:04:23:57 +0000] "GET /favicon.ico HTTP/1.1" 404 153 "http://192.168.1.56/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"
webserver-68c554c9f6-nlhk2 nginx 192.168.1.45 - - [15/Oct/2023:04:23:58 +0000] "GET / HTTP/1.1" 200 292 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"
webserver-68c554c9f6-nlhk2 nginx 2023/10/15 04:23:58 [error] 29#29: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 192.168.1.45, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "192.168.1.56", referrer: "http://192.168.1.56/"
webserver-68c554c9f6-nlhk2 nginx 192.168.1.45 - - [15/Oct/2023:04:23:58 +0000] "GET /favicon.ico HTTP/1.1" 404 153 "http://192.168.1.56/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"


kubeuser@master01:~/20231008$ kubectl get nodes -o wide
NAME       STATUS   ROLES           AGE    VERSION   INTERNAL-IP    EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION      CONTAINER-RUNTIME
master01   Ready    control-plane   214d   v1.27.0   192.168.1.41   <none>        Ubuntu 22.04.2 LTS   5.15.0-67-generic   containerd://1.6.18
worker01   Ready    <none>          214d   v1.27.0   192.168.1.45   <none>        Ubuntu 22.04.2 LTS   5.15.0-67-generic   containerd://1.6.18
worker02   Ready    <none>          214d   v1.27.0   192.168.1.46   <none>        Ubuntu 22.04.2 LTS   5.15.0-67-generic   containerd://1.6.18


これはexternalTrafficPolicy:がCluster(デフォルト値)となっているためで、
ClientのIPアドレス -----> Service -----> Node(worker01) でSNAT(送信元IPNAT)---> Pod という動作をしているからです。

つまり、ClientのIPアドレスは不明ということです。


externalTrafficPolicy: Localの場合
では、externalTrafficPolicyをLocalとして動作を見てみましょう。
externalTrafficPolicy: LocalとServiceの設定を変更します。

kubeuser@master01:~/20231008$ cat service.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: webserver
  name: webserver
  namespace: seminar
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: webserver
  externalTrafficPolicy: Local
  type: LoadBalancer

kubeuser@master01:~/20231008$ kubectl apply -f service.yaml 
service/webserver configured


改めてクライアントからアクセスすると、
送信元IP(192.168.1.122)が表示されています。

kubeuser@master01:~/20231008$ stern webserver-68c554c9f6 -n seminar
+ webserver-68c554c9f6-ck6h2 › nginx
+ webserver-68c554c9f6-nlhk2 › nginx
.
.
.
.
.
webserver-68c554c9f6-nlhk2 nginx 192.168.1.122 - - [15/Oct/2023:04:29:41 +0000] "GET / HTTP/1.1" 200 292 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"
webserver-68c554c9f6-nlhk2 nginx 192.168.1.122 - - [15/Oct/2023:04:29:41 +0000] "GET /favicon.ico HTTP/1.1" 404 153 "http://192.168.1.56/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/118.0" "-"
webserver-68c554c9f6-nlhk2 nginx 2023/10/15 04:29:41 [error] 30#30: *3 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 192.168.1.122, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "192.168.1.56", referrer: "http://192.168.1.56/"



externalTrafficPolicyについて
externalTrafficPolicyをLocalに設定した場合の動作ですが、
・ノードに到達したリクエスト通信が他のノードに転送されなくなる
・クライアントの送信元IPアドレスを保持する(SNATされない)
というようになります。

なお、公式ドキュメントでは以下に記載があります。
https://kubernetes.io/docs/tutorials/services/source-ip/
https://kubernetes.io/ja/docs/tutorials/services/source-ip/


おわりに
externalTrafficPolicy: Localを使うことでPodログにてクライアントの送信元IPアドレスが確認出来る事が分かりました。

これを利用する上では本家ドキュメントにあるような留意点や、
kubernetes環境における通信を制御するkube-proxyなども併せて深堀する必要がありそうです。
kube-proxyのmodeをiptablesからipvsに変更する

Opensourcetech by Takahiro Kujirai