仮想化通信

日本仮想化技術株式会社の公式エンジニアブログ

IPv4/IPv6でサービスを動かすコンテナイメージを使ってKubernetesで実行

連載目次

イメージを使ってアプリケーションを実行してみる

前回、テストに利用するイメージを作成してDockerで動作確認しました。 このイメージをKubernetesで使うには、イメージを何らかの方法で「共有」する必要があります。 今回はDocker Hubに登録する方法を試してみます。

Docker Desktopを使ったことがあるユーザーであれば、Dockerのアカウントは作成済みだと思います。 そのアカウントでDocker Hubサイトを開き、ログインしてAccess Tokensを作っておきましょう。 DockerアカウントのTwo-Factor Authenticationが未だ設定されていない場合は、設定しておいたほうが良いでしょう。

ユーザー名とパスワードを聞かれるので、ユーザー名を入力し、パスワードは作成しておいた「Access Tokens」を入力します。

% docker login

ログインできたら、あとは作成しておいたイメージをDocker HubにPushするだけです。 タグを設定時に、Docker Hubユーザー名/イメージ名:タグ名のようにイメージにタグ付けします。

% docker image tag localhost/python-webserver ouruser/python-webserver:latest
% docker image push ouruser/python-webserver:latest

上記は現在実行中のCPUアーキテクチャー向けにコンテナーイメージを作成してイメージを登録する例です。

例えばあなたのマシンがaarch64なマシンであり、上記のようにイメージをビルドして登録した場合、aarch64のCPUを備えたマシンを使っているユーザーしか利用できません。amd64やarm/v7などでも利用できるようにしたい場合は次のドキュメントを参考に、multi-archなイメージを作成します(docker buildxを使うと簡単です)。

作成したイメージ例:

hub.docker.com

作成したイメージを使ってPodを作成します。手元環境で動かす場合は、タグがlatestではない点にご注意ください。 なお、このイメージを使って起こった問題について、筆者は一切責任を負いません。

$ cat testpod2.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ubi9app1
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ubi9app1
  template:
    metadata:
      labels:
        app: ubi9app1
    spec:
      containers:
      - name: ubi9app1
        image:  docker.io/ytooyama/ubi9-pythonweb-multiarch-example:buildx-latest
        ports:
        - containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
  name: ubi9app1-np
  labels:
    app: ubi9app1
spec:
  type: NodePort
  ports:
  - port: 8000
    targetPort: 8000
    nodePort: 31800
  selector:
    app: ubi9app1

Podとサービスを作成します。

$ kubectl create -f testpod2.yaml 

確認します。今回はDeployment APIとService APIを使って、PodとNodePortサービスを作成しています。 それぞれエラーが出ていないことを確認します。

$ kubectl get -f testpod2.yaml 
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ubi9app1   1/1     1            1           17s

NAME                  TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/ubi9app1-np   NodePort   10.96.196.55   <none>        8000:31800/TCP   17s

$ kubectl get pods
NAME                        READY   STATUS    RESTARTS   AGE
ubi9app1-86d6858fc4-vkwbc   1/1     Running   0          2m22s

IPアドレスを確認します。PodにはIPv4とIPv6のアドレスが割り当てられています。

$ kubectl describe pods/ubi9app1-86d6858fc4-vkwbc ... IP: 10.244.78.136 IPs: IP: 10.244.78.136 IP: 2001:db8:42:c5:45e3:dcb6:dab7:e247

ホスト上であれば、curlコマンドを使ってアクセスできます。

$ curl http://10.244.78.136:8000
$ curl http://[2001:db8:42:c5:45e3:dcb6:dab7:e247]:8000

サービスを確認します。

$ kubectl get svc/ubi9app1-np 
NAME          TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
ubi9app1-np   NodePort   10.96.46.56   <none>        8000:31800/TCP   7m39s

何も書かない場合、IPv4のSingleStackでサービスが作られるようです。

$ kubectl describe svc/ubi9app1-np 
Name:                     ubi9app1-np
Namespace:                default
Labels:                   app=ubi9app1
Annotations:              <none>
Selector:                 app=ubi9app1
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv4
IP:                       10.96.46.56
IPs:                      10.96.46.56
Port:                     <unset>  8000/TCP
TargetPort:               8000/TCP
NodePort:                 <unset>  31800/TCP
Endpoints:                10.244.78.136:8000
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

そのため、サービスを通じてアプリケーションのアクセスをする場合、このままではIPv6ではアクセスできません。

IPv6を考慮したKubernetesのサービスの作り方は、Validate IPv4/IPv6 dual-stackにあります。

先程のサービス部分にipFamiliesを追加して、IPv6を指定してみます。

---
apiVersion: v1
kind: Service
metadata:
  name: ubi9app1-np
  labels:
    app: ubi9app1
spec:
  ipFamilies:
  - IPv6
  type: NodePort
  ports:
  - port: 8000
    targetPort: 8000
    nodePort: 31800
  selector:
    app: ubi9app1

サービスを作ると、Cluster IPがIPv6アドレスが設定されていることがわかります。

$ kubectl get -f testpod2.yaml 
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ubi9app1   1/1     1            1           6s

NAME                  TYPE       CLUSTER-IP            EXTERNAL-IP   PORT(S)          AGE
service/ubi9app1-np   NodePort   2001:db8:42:1::d691   <none>        8000:31800/TCP   6s

サービスの詳細を見ると、IPv6のSingleStackなサービスが作られたことがわかります。

$ kubectl describe service/ubi9app1-np
Name:                     ubi9app1-np
Namespace:                default
Labels:                   app=ubi9app1
Annotations:              <none>
Selector:                 app=ubi9app1
Type:                     NodePort
IP Family Policy:         SingleStack
IP Families:              IPv6
IP:                       2001:db8:42:1::d691
IPs:                      2001:db8:42:1::d691
Port:                     <unset>  8000/TCP
TargetPort:               8000/TCP
NodePort:                 <unset>  31800/TCP
Endpoints:                [2001:db8:42:c5:45e3:dcb6:dab7:e24c]:8000
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

NodePortでアクセスすると、IPv4ではアクセスできずにIPv6ではアクセスできることが確認できます。

$ curl -4 http://k8s.example.com:31800
curl: (7) Failed to connect to k8s.example.com port 31800 after 0 ms: Connection refused

$ curl -6 http://k8s.example.com:31800
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Directory listing for /</title>
</head>
<body>
<h1>Directory listing for /</h1>
<hr>
<ul>
<li><a href="afs/">afs/</a></li>
<li><a href="bin/">bin@</a></li>
<li><a href="boot/">boot/</a></li>
<li><a href="dev/">dev/</a></li>
<li><a href="etc/">etc/</a></li>
<li><a href="home/">home/</a></li>
<li><a href="lib/">lib@</a></li>
<li><a href="lib64/">lib64@</a></li>
<li><a href="lost%2Bfound/">lost+found/</a></li>
<li><a href="media/">media/</a></li>
<li><a href="mnt/">mnt/</a></li>
<li><a href="opt/">opt/</a></li>
<li><a href="proc/">proc/</a></li>
<li><a href="root/">root/</a></li>
<li><a href="run/">run/</a></li>
<li><a href="sbin/">sbin@</a></li>
<li><a href="srv/">srv/</a></li>
<li><a href="sys/">sys/</a></li>
<li><a href="tmp/">tmp/</a></li>
<li><a href="usr/">usr/</a></li>
<li><a href="var/">var/</a></li>
</ul>
<hr>
</body>
</html>

-vをつけて実行すると、IPv6アドレスでアクセスされていることが確認できます(必要な部分のみ抜粋)。

$ curl -v -6 http://k8s.example.com:31800
*   Trying fd66:292e:d1ca:e484:f864:d3ff:fe08:7480:31800...
* Connected to k8s.example.com (fd66:292e:d1ca:e484:f864:d3ff:fe08:7480) port 31800 (#0)
> GET / HTTP/1.1
> Host: k8s.example.com:31800

サービス部分をさらに次のように改変してみます。

---
apiVersion: v1
kind: Service
metadata:
  name: ubi9app1-np
  labels:
    app: ubi9app1
spec:
  ipFamilyPolicy: PreferDualStack
  type: NodePort
  ports:
  - port: 8000
    targetPort: 8000
    nodePort: 31800
  selector:
    app: ubi9app1

CLUSTER-IPはIPv4のIPアドレスがみられますが、describeで確認すると、サービスに対してIPv4とIPv6のIPアドレスが設定されていることがわかります。

$ kubectl get -f testpod3.yaml 
NAME                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/ubi9app1   1/1     1            1           4s

NAME                  TYPE       CLUSTER-IP    EXTERNAL-IP   PORT(S)          AGE
service/ubi9app1-np   NodePort   10.96.18.75   <none>        8000:31800/TCP   4s

$ kubectl describe service/ubi9app1-np
Name:                     ubi9app1-np
Namespace:                default
Labels:                   app=ubi9app1
Annotations:              <none>
Selector:                 app=ubi9app1
Type:                     NodePort
IP Family Policy:         PreferDualStack
IP Families:              IPv4,IPv6
IP:                       10.96.18.75
IPs:                      10.96.18.75,2001:db8:42:1::2469
Port:                     <unset>  8000/TCP
TargetPort:               8000/TCP
NodePort:                 <unset>  31800/TCP
Endpoints:                10.244.78.143:8000
Session Affinity:         None
External Traffic Policy:  Cluster
Events:                   <none>

早速アクセスしてみましょう。今度はIPv4とIPv6のアドレスがサービスに設定されているため、どちらも成功するはずです。

$ curl -v -4 http://k8s.example.com:31800
$ curl -v -6 http://k8s.example.com:31800

curlのオプションに-v-4-6をつけて、IPv4とIPv6のアドレスでアプリケーションにアクセスできていることを確認します。

$ curl -v -4 http://k8s.example.com:31800
*   Trying 192.168.205.109:31800...
* Connected to k8s.example.com (192.168.205.109) port 31800 (#0)
> GET / HTTP/1.1
> Host: k8s.example.com:31800
> User-Agent: curl/7.81.0
> Accept: */*
...

$ curl -v -6 http://k8s.example.com:31800
*   Trying fd66:292e:d1ca:e484:f864:d3ff:fe08:7480:31800...
* Connected to k8s.example.com (fd66:292e:d1ca:e484:f864:d3ff:fe08:7480) port 31800 (#0)
> GET / HTTP/1.1
> Host: k8s.example.com:31800
> User-Agent: curl/7.81.0
> Accept: */*
...

今回、IPv4とIPv6アドレスをKubernetesで使う、IPv4/IPv6 dual-stackを試してみました。 今回、パブリックにある既存のイメージを使ってPodを使わなかった理由は、イメージによってIPv6での利用が想定されていないイメージが有り、そのようなイメージでは今回のようなテストができない可能性があるという情報があったためです。イメージの作成については、Red HatのUBI9イメージがPython3をバンドルしてくれていたのでそんなに手間がかからずなんとかなりました。

KubernetesでIPv4とIPv6のDualStack環境を作るのは公式ドキュメントがあるのでそこまで難しくはありませんが、この環境を作るには以下の考慮が必要になると思います。

  • 利用するIPv6アドレスの範囲
  • IPv6アドレスの配布、決定方法
  • 利用するコンテナイメージのIPv6アドレス対応
  • 利用するコンテナネットワークインターフェイス(CNI)プラグインはIPv6対応しているか、DualStackに対応しているか