Opensourcetechブログ

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

Dual Stack環境のkubernetesにおけるServiceの挙動


LinuCエヴァンジェリストの鯨井貴博@opensourcetechです。


はじめに
Dual Stack環境のkubernetes(v1.26.0)におけるServiceの挙動(IPv4とIPv6のどっちが有効になるの?)に関するメモです。
なお、Dual Stack環境のkubernetesクラスターはこちらで構築したものを使っています。


やってみること
以下のようにkubernetes上にnginxのDeploymentsがあるので、
それを公開(Service)して挙動を確かめます。
※kubectl create deployments nginx --image=nginx:1.23.2

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


kubeuser@master01:~$ kubectl get deployments.apps 
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
nginx   1/1     1            1           147m

kubeuser@master01:~$ kubectl get deployments.apps nginx -o yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  annotations:
    deployment.kubernetes.io/revision: "1"
  creationTimestamp: "2023-03-14T13:50:35Z"
  generation: 1
  labels:
    app: nginx
  name: nginx
  namespace: default
  resourceVersion: "1156"
  uid: 9083fb18-721d-4102-ae2d-100ebae18536
spec:
  progressDeadlineSeconds: 600
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: nginx
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx:1.23.2
        imagePullPolicy: IfNotPresent
        name: nginx
        resources: {}
        terminationMessagePath: /dev/termination-log
        terminationMessagePolicy: File
      dnsPolicy: ClusterFirst
      restartPolicy: Always
      schedulerName: default-scheduler
      securityContext: {}
      terminationGracePeriodSeconds: 30
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2023-03-14T13:50:59Z"
    lastUpdateTime: "2023-03-14T13:50:59Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  - lastTransitionTime: "2023-03-14T13:50:35Z"
    lastUpdateTime: "2023-03-14T13:50:59Z"
    message: ReplicaSet "nginx-759fd59899" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  observedGeneration: 1
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1

kubeuser@master01:~$ kubectl get pods -o wide
NAME                     READY   STATUS    RESTARTS   AGE    IP           NODE       NOMINATED NODE   READINESS GATES
nginx-759fd59899-59sgs   1/1     Running   0          147m   10.0.30.65   worker02   <none>           <none>

kubeuser@master01:~$ kubectl get pods nginx-759fd59899-59sgs -o go-template --template='{{range .status.podIPs}}{{printf "%s\n" .ip}}{{end}}'
10.0.30.65
fd12:b5e0:383e:0:7bf:50a7:b256:1e40

kubeuser@master01:~$ curl http://[fd12:b5e0:383e:0:7bf:50a7:b256:1e40]
<!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>



ipFamilies(IPv4/IPv6)指定なし
まずは、以下のService用のマニュフェストを使います。
特に、どのipFamilies(IPv4/IPv6)を指定していません。

kubeuser@master01:~$ cat svc_nginx_ipv4.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx


結果:IPv4がアサインされる

kubeuser@master01:~$ kubectl apply -f svc_nginx_ipv4.yaml 
service/nginx configured

kubeuser@master01:~$ kubectl get svc nginx 
NAME    TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.1.179.244   <none>        80/TCP    158m

kubeuser@master01:~$ kubectl get svc nginx -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"ports":[{"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"nginx"}}}
  creationTimestamp: "2023-03-14T13:55:35Z"
  labels:
    app: nginx
  name: nginx
  namespace: default
  resourceVersion: "15706"
  uid: 97bbff5e-a1ad-4410-a9cb-267defd83fc3
spec:
  clusterIP: 10.1.179.244
  clusterIPs:
  - 10.1.179.244
  - fd12:b5e0:383f::2261
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: PreferDualStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}



ipFamilies(IPv6指定) & ipFamilyPolicy: SingleStack
まずは、以下のService用のマニュフェストを使います。
ipFamiliesでIPv6を指定します。

kubeuser@master01:~$ cat svc_nginx_ipv6.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ipFamilies: 
  - IPv6
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx


結果:IPv6がアサインされる

kubeuser@master01:~$ kubectl apply -f svc_nginx_ipv6.yaml 
service/nginx created

kubeuser@master01:~$ kubectl get svc nginx
NAME    TYPE        CLUSTER-IP             EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   fd12:b5e0:383f::f75b   <none>        80/TCP    5s

kubeuser@master01:~$ kubectl get svc nginx -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"ipFamilies":["IPv6"],"ipFamilyPolicy":"SingleStack","ports":[{"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"nginx"}}}
  creationTimestamp: "2023-03-14T16:37:41Z"
  labels:
    app: nginx
  name: nginx
  namespace: default
  resourceVersion: "16046"
  uid: 9b2b69ec-fc71-4873-813b-9ab59fbdcc95
spec:
  clusterIP: fd12:b5e0:383f::f75b
  clusterIPs:
  - fd12:b5e0:383f::f75b
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv6
  ipFamilyPolicy: SingleStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}



ipFamilies(IPv4/IPv6指定) & ipFamilyPolicy: PreferDualStack
まずは、以下のService用のマニュフェストを使います。
ipFamiliesでIPv4/IPv6を指定します。
また、ipFamilyPolicy: PreferDualStackとしています。

kubeuser@master01:~$ cat svc_nginx_dual_prefer.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ipFamilies: 
  - IPv4
  - IPv6
  ipFamilyPolicy: PreferDualStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx


結果:IPv4/IPv6(Dual Stack)となる

kubeuser@master01:~$ kubectl apply -f svc_nginx_dual_prefer.yaml 
service/nginx created

kubeuser@master01:~$ kubectl get svc nginx 
NAME    TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   10.1.49.119   <none>        80/TCP    5s

kubeuser@master01:~$ kubectl get svc nginx -o yaml
apiVersion: v1
kind: Service
metadata:
  annotations:
    kubectl.kubernetes.io/last-applied-configuration: |
      {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"ipFamilies":["IPv4","IPv6"],"ipFamilyPolicy":"PreferDualStack","ports":[{"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"nginx"}}}
  creationTimestamp: "2023-03-14T16:41:24Z"
  labels:
    app: nginx
  name: nginx
  namespace: default
  resourceVersion: "16387"
  uid: 164a6826-54ea-41f2-8820-75a6ca0fb1c1
spec:
  clusterIP: 10.1.49.119
  clusterIPs:
  - 10.1.49.119
  - fd12:b5e0:383f::f2e2
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: PreferDualStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx
  sessionAffinity: None
  type: ClusterIP
status:
  loadBalancer: {}



ipFamilies(IPv4/IPv6指定) & ipFamilyPolicy: RequireDualStack
まずは、以下のService用のマニュフェストを使います。
ipFamiliesでIPv4/IPv6を指定します。
また、ipFamilyPolicy: RequireDualStackとしています。

kubeuser@master01:~$ cat svc_nginx_dual_reequire.yaml 
apiVersion: v1
kind: Service
metadata:
  labels:
    app: nginx
  name: nginx
spec:
  ipFamilies: 
  - IPv4
  - IPv6
  ipFamilyPolicy: RequireDualStack
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: nginx


結果:IPv4/IPv6(Dual Stack)となる

kubeuser@master01:~$ kubectl apply -f svc_nginx_dual_reequire.yaml 
service/nginx created

kubeuser@master01:~$ kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.1.0.1       <none>        443/TCP   3h12m
nginx        ClusterIP   10.1.223.177   <none>        80/TCP    5s

kubeuser@master01:~$ kubectl get svc -o yaml
apiVersion: v1
items:
- apiVersion: v1
  kind: Service
  metadata:
    creationTimestamp: "2023-03-14T13:45:53Z"
    labels:
      component: apiserver
      provider: kubernetes
    name: kubernetes
    namespace: default
    resourceVersion: "199"
    uid: 55782f7e-062c-4cfb-acca-4e4a0d8177a3
  spec:
    clusterIP: 10.1.0.1
    clusterIPs:
    - 10.1.0.1
    internalTrafficPolicy: Cluster
    ipFamilies:
    - IPv4
    ipFamilyPolicy: SingleStack
    ports:
    - name: https
      port: 443
      protocol: TCP
      targetPort: 6443
    sessionAffinity: None
    type: ClusterIP
  status:
    loadBalancer: {}
- apiVersion: v1
  kind: Service
  metadata:
    annotations:
      kubectl.kubernetes.io/last-applied-configuration: |
        {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"labels":{"app":"nginx"},"name":"nginx","namespace":"default"},"spec":{"ipFamilies":["IPv4","IPv6"],"ipFamilyPolicy":"RequireDualStack","ports":[{"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"nginx"}}}
    creationTimestamp: "2023-03-14T16:58:24Z"
    labels:
      app: nginx
    name: nginx
    namespace: default
    resourceVersion: "17914"
    uid: 699a5e0f-bf6d-4941-85c9-ca0a1eade1b5
  spec:
    clusterIP: 10.1.223.177
    clusterIPs:
    - 10.1.223.177
    - fd12:b5e0:383f::e44e
    internalTrafficPolicy: Cluster
    ipFamilies:
    - IPv4
    - IPv6
    ipFamilyPolicy: RequireDualStack
    ports:
    - port: 80
      protocol: TCP
      targetPort: 80
    selector:
      app: nginx
    sessionAffinity: None
    type: ClusterIP
  status:
    loadBalancer: {}
kind: List
metadata:
  resourceVersion: ""



ServiceのipFamilyPolicyとipFamiliesについて
ipFamilyPolicy
・SingleStack:IPv4 or IPv6のどちらか
・PreferDualStack:DualStack or SingleStackのどちらか
・RequireDualStack:DualStack(それ以外は失敗する)
ipFamilies
・IPv4/IPv6のどちらか、もしくは両方の指定が可能
・IPv4/IPv6のどれが適用されるかは、クラスターの構成とipFamilyPolicyで決定される

kubeuser@master01:~$ kubectl explain svc.spec.ipFamilyPolicy
KIND:     Service
VERSION:  v1

FIELD:    ipFamilyPolicy <string>

DESCRIPTION:
     IPFamilyPolicy represents the dual-stack-ness requested or required by this
     Service. If there is no value provided, then this field will be set to
     SingleStack. Services can be "SingleStack" (a single IP family),
     "PreferDualStack" (two IP families on dual-stack configured clusters or a
     single IP family on single-stack clusters), or "RequireDualStack" (two IP
     families on dual-stack configured clusters, otherwise fail). The ipFamilies
     and clusterIPs fields depend on the value of this field. This field will be
     wiped when updating a service to type ExternalName.

kubeuser@master01:~$ kubectl explain svc.spec.ipFamilies
KIND:     Service
VERSION:  v1

FIELD:    ipFamilies <[]string>

DESCRIPTION:
     IPFamilies is a list of IP families (e.g. IPv4, IPv6) assigned to this
     service. This field is usually assigned automatically based on cluster
     configuration and the ipFamilyPolicy field. If this field is specified
     manually, the requested family is available in the cluster, and
     ipFamilyPolicy allows it, it will be used; otherwise creation of the
     service will fail. This field is conditionally mutable: it allows for
     adding or removing a secondary IP family, but it does not allow changing
     the primary IP family of the Service. Valid values are "IPv4" and "IPv6".
     This field only applies to Services of types ClusterIP, NodePort, and
     LoadBalancer, and does apply to "headless" services. This field will be
     wiped when updating a Service to type ExternalName.

     This field may hold a maximum of two entries (dual-stack families, in
     either order). These families must correspond to the values of the
     clusterIPs field, if specified. Both clusterIPs and ipFamilies are governed
     by the ipFamilyPolicy field.


Opensourcetech by Takahiro Kujirai