🐔

ports なんて飾りです。偉い人にはそれがわからんのですよ。

2023/03/02に公開

ports なんて飾りです。

Kubernetes の Pod のマニフェストには ports と言う項目がある。名前の通り、Pod 内のコンテナがどのポートでリクエストを受け付けるかを指定するためのものである。

指定させるぐらいなんだから、ports に指定されていないポートではリクエストを受け付けられないような気がするのだが、実はそんなことはない。

と言う事で、確かめてみよう。まず ports を一切指定していない nginx の Pod を作成する。

$ kubectl create -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nginx
spec:
  containers:
  - image: nginx
    name: nginx
EOF
pod/nginx created

次に、Pod の IP アドレスを確認する。

$ kubectl get pod/nginx -o wide
NAME    READY   STATUS    RESTARTS   AGE   IP              NODE   NOMINATED NODE   READINESS GATES
nginx   1/1     Running   0          9s    192.168.3.182   wk1    <none>           <none>

準備はできた。コイツに対して実際にリクエストを投げてみよう。

$ kubectl run -i --rm --restart=Never --image=curlimages/curl curl -- curl -s http://192.168.3.182
<!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>
pod "curl" deleted

ちゃんとレスポンスが得られた。

docker run では -p で指定したポート以外はコンテナ外部に公開されないのでリクエストを受け付けられないのだが、kubernetes の ports はそういった類のものではないと言う事だ。

やっぱり ports なんていらんかったんや…

ちなみに、これはちゃんとドキュメントに書いてある。

Not specifying a port here DOES NOT prevent that port from being exposed. Any port which is listening on the default "0.0.0.0" address inside a container will be accessible from the network.

参考:Container v1 core

EXPOSE も飾りです。

ports を見ると DockerfileEXPOSE を思い出す。コイツも特に指定が必要な訳では無いし、コイツを指定したからと言って docker run-p が不要になる訳でもない。

と言う事で試してみよう。

EXPOSE が無いケース

まず、EXPOSE が無い nginx のイメージを作る。

$ docker build - -t nginx-noexpose <<'EOF'
FROM nginx as base
FROM scratch
copy --from=base / /
STOPSIGNAL SIGQUIT
ENTRYPOINT ["nginx", "-g", "daemon off;"]
EOF

これで絶対に EXPOSE は無い。
次にこのイメージを -p 付きで実行する。

$ docker run -d -p 8080:80 --rm --name nginx-noexpose nginx-noexpose
7a94975f98693acf6606f513977c53180fab3856d1236ed2ef2fc3d062080698

特に何の文句も言われず実行できた。
念のため docker ps で状態を見てみる。

$ docker ps
CONTAINER ID   IMAGE            COMMAND                  CREATED         STATUS        PORTS                                   NAMES
7a94975f9869   nginx-noexpose   "nginx -g 'daemon of…"   2 seconds ago   Up 1 second   0.0.0.0:8080->80/tcp, :::8080->80/tcp   nginx-noexpose

ちゃんと PORTS に指定したポートがある。リクエストを受け付けられそうだ。
では、コイツに対して実際にリクエストを投げてみよう。

$ curl http://localhost:8080
<!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>

ちゃんとレスポンスが得られた。

EXPOSE があるケース

nginx の公式イメージには EXPOSE があるのでそのまま使ってもいいが、ここではあえてイメージを作ってみよう。

docker build - -t nginx-expose <<'EOF'
FROM nginx as base
FROM scratch
copy --from=base / /
STOPSIGNAL SIGQUIT
EXPOSE 80
ENTRYPOINT ["nginx", "-g", "daemon off;"]
EOF

これで絶対に EXPOSE がある。
次にこのイメージを -p 無しで実行する。

$ docker run -d --rm --name nginx-expose nginx-expose
92ebcbdd2094e6a2a2472281e02b76550632d9b1ecfcb7a085d8f4d1ac66d39e

当然何の文句も言われず実行できた。
docker ps で状態を見てみる。

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS     NAMES
92ebcbdd2094   nginx-expose   "nginx -g 'daemon of…"   38 seconds ago   Up 37 seconds   80/tcp    nginx-expose

ちゃんと PORTSEXPOSE で指定したポートはあるものの、ホスト側のポート番号が無い。既にリクエストを受け付けらる気がしない。

さて、コイツに対して実際にリクエストを投げたいが、そもそもどのポートに投げればよいだろうか。とりあえず 80 にでも投げてみるか。

$ curl http://localhost
curl: (7) Failed to connect to localhost port 80 after 0 ms: Connection refused

当たり前だが、ダメだ。
せっかくなので実際のポートの状況も調べてみる。

$ sudo ss -antp
State            Recv-Q           Send-Q                     Local Address:Port                        Peer Address:Port           Process
LISTEN           0                4096                           127.0.0.1:35429                            0.0.0.0:*               users:(("containerd",pid=177,fd=15))
LISTEN           0                4096                       127.0.0.53%lo:53                               0.0.0.0:*               users:(("systemd-resolve",pid=172,fd=14))

やはり公開されているポートは無い。

やっぱり EXPOSE なんていらんかったんや…

ちなみにも、これもちゃんとドキュメントに書いてある。

The EXPOSE instruction does not actually publish the port. It functions as a type of documentation between the person who builds the image and the person who runs the container, about which ports are intended to be published. To actually publish the port when running the container, use the -p flag on docker run to publish and map one or more ports, or the -P flag to publish all exposed ports and map them to high-order ports.

…中略…

Regardless of the EXPOSE settings, you can override them at runtime by using the -p flag.

飾りじゃないのよ ports は

ports は飾りと言ったな。あれは嘘だ。

ports にはドキュメンテーション目的以外にもちゃんと役割がある。
ports で指定した名前(name)は ServicetargetPort を指定する際に使用できる。
良く分からない数値を指定されるよりも五万倍は分かりやすい(個人の感想です)。

$ kubectl create -f - <<'EOF'
apiVersion: v1
kind: Pod
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  containers:
  - image: nginx
    name: nginx
    ports:
    - name: http
      containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  ports:
  - port: 80
    targetPort: http
  selector:
    app: nginx
EOF

せっかくなのでちゃんとサービス経由でリクエストを受け付けられることを確認してみる。

$ kubectl run -i --rm --restart=Never --image=curlimages/curl curl -- curl -s http://nginx
<!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>
pod "curl" deleted

当然受け付けられた。

Pod でリクエストを受け付ける場合に Service を作らないなんてことはまず無いと思うので、ports はちゃんと指定しておくのが吉だ。

ちなみに、昔はドキュメントに「飾りだぜ」って書いてあったが「そんなことねぇよ」って Issue が立てられて現在の記載になったようで、ドキュメントにその Issue へのリンクが記載されている。
misleading comments in container.Ports がそれなので興味があれば見てみると良い。

飾りじゃないのよ EXPOSE も

EXPOSE もドキュメンテーション以外に役割がある。

EXPOSE された全ポートの公開

docker run-P なるフラグ(大文字なので注意)があって、これを指定すると EXPOSE で指定された全てのポートが公開される。

試してみよう。

$ docker run -d --rm --name nginx-expose -P nginx-expose
206d93265405d2b29bbb7e7389f70def7a0d6d25534bbf57bc940a6058ac655c

docker ps で状態を見てみる。

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS                                     NAMES
206d93265405   nginx-expose   "nginx -g 'daemon of…"   4 seconds ago   Up 3 seconds   0.0.0.0:32769->80/tcp, :::32769->80/tcp   nginx-expose

ちゃんと公開されている。
念のためアクセスしてみる。

$ curl http://localhost:32769
<!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>

アクセスできた。
ただし、公開されるポートのホスト側ポート番号はランダムなので注意が必要だ。

他のコンテナとのリンク

docker run--link なるフラグがあって、コイツを使うと EXPOSE されているポートは外部に公開していなくとも別のコンテナからアクセスできる。

これも試してみよう。

$ docker run -d --rm --name nginx-expose nginx-expose
ba4e2e2f24a2b941e8933a5a03dd656198a32735a227046cb7cd79776844f360

docker ps で状態を見てみる。

$ docker ps
CONTAINER ID   IMAGE          COMMAND                  CREATED         STATUS         PORTS     NAMES
ba4e2e2f24a2   nginx-expose   "nginx -g 'daemon of…"   5 seconds ago   Up 3 seconds   80/tcp    nginx-expose

-p-P も指定していないので、当然のことながらポートは公開されていない。
しかし、--link を使えば別コンテナからアクセスできる。

$ docker run --rm --link nginx-expose curlimages/curl -s http://nginx-expose
<!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>

アクセスできた。

ちなみに、コンテナ名でアクセスできているが、これは /etc/hosts を小細工してくれているからだ。

$ docker run --rm --link nginx-expose busybox cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      nginx-expose ba4e2e2f24a2
172.17.0.3      e7ef78312b8b

他にもいろんな環境変数が作成されているので見てみると面白いかもしれない。

$ docker run --rm --link nginx-expose busybox env
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=821cd86677e0
TERM=xterm
NGINX_EXPOSE_PORT=tcp://172.17.0.2:80
NGINX_EXPOSE_PORT_80_TCP=tcp://172.17.0.2:80
NGINX_EXPOSE_PORT_80_TCP_ADDR=172.17.0.2
NGINX_EXPOSE_PORT_80_TCP_PORT=80
NGINX_EXPOSE_PORT_80_TCP_PROTO=tcp
NGINX_EXPOSE_NAME=/practical_mclaren/nginx-expose
HOME=/root

飾りであろうがなかろうが書くよね?

どうでもいいことを散々書いてきたが、portsEXPOSE は飾りであろうがなかろうが普通書くよな?
てか頼むから書いてくれ(懇願)。

皆さんも portsEXPOSE をしっかり書いて良いコンテナライフを!(てきとー

Discussion