同じpod内にwebコンテナ（nginxとapache2）を起動させてNodePortで外部公開してみるよ～


こんにちは
株式会社クラスアクト インフラストラクチャ事業部の大塚です。

この記事ではタイトルの通り、podの中にnginxとapache2の2つのコンテナを起動させ、そのpodをNodePortを使って外部公開。ローカルPCのcmdからnode(k8sを動かしているサーバの事)に対してcurlを打って、pod内のコンテナにアクセスする的な遊びをやってみたいと思います。
但し、nginxとapache2はともにWebサーバとして稼働しますが、双方とも80,443ポートを使用するので、そのままだとカニバリます。そのため、apache2側のlisten portを90に変更しそれをdocker image化。そのイメージをpod内のコンテナに使用します。

イメージは以下となります。
まずubuntu22.04環境を2つ用意します。片方はdocker。もう片方はk8s（microk8s）です。
docker環境にapache2のdocker imageをpullしコンテナをデプロイ。configファイルを編集しlisten portを90に変更します。
変更しましたら、そのコンテナをdocker commitでイメージ化。docker hubにpushします。
pushしたapache2イメージと最初から用意されているnginxイメージをkubernetesの同一podにデプロイ。そのpodに対してNodePortを設定し80ポートを指定するとnginxコンテナへ接続。90ポートを指定するとapache2コンテナへ接続するようにしたいと思います。

docker imageをhubにpushして、k8s環境でデプロイしている図
![kubernetes-ページ7.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/d17465fc-79a7-e6ca-03bd-3e3d357e78c5.png)

ローカルPCのcmdを起動し、k8sホストに対して80ポートを指定するとpod内のnginxコンテナに接続し、90ポートを指定するとapache2コンテナに接続する図
![kubernetes-ページ8.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/4a42495e-47d1-af45-0177-af4745991b74.png)

# apache2のdocker image準備

apache2は今回の検証で90ポートでlistenしてもらう必要があるので、そのように調整を行います。docker環境のホストはIP:192.168.2.177で動いています。
apache2イメージをdocker hubからpullします。
```
root@docker:~# docker pull httpd:latest 
latest: Pulling from library/httpd 
26c5c85e47da: Already exists 
2d29d3837df5: Pull complete 
2483414a5e59: Pull complete 
e78016c4ba87: Pull complete 
757908175415: Pull complete 
Digest: sha256:a182ef2350699f04b8f8e736747104eb273e255e818cd55b6d7aa50a1490ed0c 
Status: Downloaded newer image for httpd:latest 
docker.io/library/httpd:latest 
root@docker:~# docker image ls 
REPOSITORY                      TAG       IMAGE ID       CREATED         SIZE 
httpd                           latest    4b7fc736cb48   2 weeks ago     145MB 
```

pullしたイメージからコンテナをデプロイします。
```
root@docker:~# docker run -itd --name my-httpd httpd:latest 
10d9f582cffacc03ba4312a609f5e67a448c3dc718ee7bfb1e5e4cf6381c9da1 
root@docker:~# docker ps 
CONTAINER ID   IMAGE                    COMMAND              CREATED         STATUS         PORTS                                                                                            NAMES 
10d9f582cffa   httpd:latest             "httpd-foreground"   4 seconds ago   Up 2 seconds   80/tcp                                                                                           my-httpd 
```

デプロイしたコンテナにアクセス。configファイルを変更し90ポートでlistenする様に変更します。
```
root@docker:~# docker exec -it my-httpd /bin/bash
root@10d9f582cffa:/usr/local/apache2# apt update
root@10d9f582cffa:/usr/local/apache2# apt upgrade
root@10d9f582cffa:/usr/local/apache2# apt-get install vim
root@10d9f582cffa:/usr/local/apache2# cd conf 
root@10d9f582cffa:/usr/local/apache2/conf# ls 
extra  httpd.conf  magic  mime.types  original 
root@10d9f582cffa:/usr/local/apache2/conf# vi httpd.conf
root@10d9f582cffa:/usr/local/apache2/conf# grep Listen httpd.conf 
# Listen: Allows you to bind Apache to specific IP addresses and/or 
# Change this to Listen on specific IP addresses as shown below to 
#Listen 12.34.56.78:80 
Listen 90
```

設定をapache2に認識させるためにrestartを実行します。
```
root@10d9f582cffa:/usr/local/apache2/conf# apachectl restart 
AH00558: httpd: Could not reliably determine the server's fully qualified domain name, using 172.17.0.4. Set the 'ServerName' directive globally to suppress this message
```

本当にこのイメージが90ポートでlistenしているか確認します。
コンテナのイメージ化をまず行います。
```
root@docker:~# docker ps 
CONTAINER ID   IMAGE                    COMMAND              CREATED          STATUS          PORTS                                                                                            NAMES 
10d9f582cffa   httpd:latest             "httpd-foreground"   19 minutes ago   Up 19 minutes   80/tcp                                                                                           my-httpd 
root@docker:~# docker commit my-httpd my-httpd-image 
sha256:a0942229723b2203ad131b5cd521262da6b7bc83c272fcd5f55c0c671391458e
root@docker:~# docker image ls 
REPOSITORY                      TAG       IMAGE ID       CREATED          SIZE 
my-httpd-image                  latest    a0942229723b   14 seconds ago   252MB 
httpd                           latest    4b7fc736cb48   2 weeks ago      145MB 
```

イメージ化したコンテナを削除します
```
root@docker:~# docker stop my-httpd 
my-httpd 
root@docker:~# docker rm my-httpd 
my-httpd
```

イメージ化したものをベースに再度apache2コンテナをデプロイします。
一番最初にデプロイしたコンテナはデフォルトの80ポートでlistenしていますが、今デプロイしたコンテナは設定変更したイメージを元にデプロイしておりますので90ポートでlistenしています。
docker runコマンドのオプションで-pを忘れないようにしましょう。
```
root@docker:~# docker run -itd -p 90:90 --name my-httpd my-httpd-image:latest 
0b09ac7c75729eabf4caed8927e1e0f5ce13975800a8c3d355a7e8bd173c9e34 
root@docker:~# docker ps
root@docker:~# docker ps 
CONTAINER ID   IMAGE                    COMMAND              CREATED         STATUS         PORTS                                                                                            NAMES 
0b09ac7c7572   my-httpd-image:latest    "httpd-foreground"   4 seconds ago   Up 3 seconds   80/tcp, 0.0.0.0:90->90/tcp, :::90->90/tcp        
```
WebブラウザでdockerのホストのIPアドレス:ポート90に対してアクセスしてみます。
動いているッぽいですね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/2db022cd-daf8-4a55-e06d-1e30569bee26.png)

このdocker imageをdocker hubにpushします。
```
root@docker:~# docker login 
Authenticating with existing credentials... 
WARNING! Your password will be stored unencrypted in /root/.docker/config.json. 
Configure a credential helper to remove this warning. See 
https://docs.docker.com/engine/reference/commandline/login/#credentials-store 
Login Succeeded 
root@docker:~# docker tag a0942229723b shotaohtsuka/my-httpd-image 
root@docker:~# docker image ls | grep shotaohtsuka/my-httpd-image 
shotaohtsuka/my-httpd-image     latest    a0942229723b   7 minutes ago   252MB
root@docker:~# docker push shotaohtsuka/my-httpd-image 
Using default tag: latest 
The push refers to repository [docker.io/shotaohtsuka/my-httpd-image] 
4c8603531fe3: Pushed 
355053d0995e: Mounted from library/httpd 
e7bd853ca2e7: Mounted from library/httpd 
16f81c3ee33a: Mounted from library/httpd 
568467bc0db5: Mounted from library/httpd 
ed7b0ef3bf5b: Mounted from library/httpd 
latest: digest: sha256:0e837c33efd33924cc2d6d5126903cdf4f75236e95d00bdea05b5677bcb90253 size: 1578
```

pushした結果です。
![image02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/2e090b23-15ef-6f05-eab4-550a9d08c2af.png)

# yamlを使ってpodをk8sにデプロイする

k8sにpodをデプロイしていきます。なお、今回はシングルノードのk8s環境となります。
用意したyamlファイルは以下です。
podの名前は"web-pod"、コンテナの名前はnginx側が"web-nginx"、apache2側が"web-httpd"となります。ポートはnginxが80で、apache2が90でlistenすることを宣言しています。
```
root@sv-ohtsuka-k8s-master:~/k8s_yaml# cat httpd-nginx.yaml
apiVersion: v1
kind: Pod
metadata:
  name: web-pod
  labels:
    app: web-app
spec:
  containers:
    - name: web-httpd
      image: shotaohtsuka/my-httpd-image
      ports:
        - name: web-httpd
          containerPort: 90
          protocol: TCP
    - name: web-nginx
      image: nginx
      ports:
        - name: web-nginx
          containerPort: 80
          protocol: TCP
```

このyamlファイルをベースにデプロイします。
```
root@sv-ohtsuka-k8s-master:~/k8s_yaml# kubectl create -f httpd-nginx.yaml
pod/web-pod created
root@sv-ohtsuka-k8s-master:~/k8s_yaml# kubectl get pod
NAME      READY   STATUS    RESTARTS   AGE
web-pod   2/2     Running   0          14s
```

kubectl describeでpodの詳細を確認してみます。
containersの部分で2つのコンテナ、web-httpdとweb-nginxがどのdocker imageを元にデプロイされているか、ポートは何番でlistenしているかなどが確認出来ます。
```
root@sv-ohtsuka-k8s-master:~/k8s_yaml# kubectl describe pod  web-pod
Name:         web-pod
Namespace:    default
Priority:     0
Node:         sv-ohtsuka-k8s-master/172.19.0.223
Start Time:   Mon, 01 May 2023 12:14:10 +0000
Labels:       app=web-app
Annotations:  cni.projectcalico.org/podIP: 10.1.114.97/32
              cni.projectcalico.org/podIPs: 10.1.114.97/32
Status:       Running
IP:           10.1.114.97
IPs:
  IP:  10.1.114.97
Containers:
  web-httpd:
    Container ID:   containerd://f9aafc2fd5f838a1dda66768f97fcd12b6ea314c4eddfaa79ad49229cb723149
    Image:          shotaohtsuka/my-httpd-image
    Image ID:       docker.io/shotaohtsuka/my-httpd-image@sha256:0e837c33efd33924cc2d6d5126903cdf4f75236e95d00bdea05b5677bcb90253
    Port:           90/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 01 May 2023 12:14:18 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-wjg99 (ro)
  web-nginx:
    Container ID:   containerd://086395ea3956ff66fe9c3829b7e1060ef7049631af5640352085290490aa064b
    Image:          nginx
    Image ID:       docker.io/library/nginx@sha256:63b44e8ddb83d5dd8020327c1f40436e37a6fffd3ef2498a6204df23be6e7e94
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Mon, 01 May 2023 12:14:19 +0000
    Ready:          True
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-wjg99 (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  kube-api-access-wjg99:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  57s   default-scheduler  Successfully assigned default/web-pod to sv-ohtsuka-k8s-master
  Normal  Pulling    58s   kubelet            Pulling image "shotaohtsuka/my-httpd-image"
  Normal  Pulled     50s   kubelet            Successfully pulled image "shotaohtsuka/my-httpd-image" in 7.313665416s (7.313676676s including waiting)
  Normal  Created    50s   kubelet            Created container web-httpd
  Normal  Started    50s   kubelet            Started container web-httpd
  Normal  Pulling    50s   kubelet            Pulling image "nginx"
  Normal  Pulled     49s   kubelet            Successfully pulled image "nginx" in 1.304934787s (1.304946093s including waiting)
  Normal  Created    49s   kubelet            Created container web-nginx
  Normal  Started    49s   kubelet            Started container web-nginx
```

デプロイしたpodをイメージに起こすと以下になります。
![kubernetes-ページ9.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/120306f2-d9df-234f-91a0-75c28575c792.png)

# yamlを使ってNodePortをk8sにデプロイする

k8sにNodePort serviceをデプロイしていきます。NodePortについての説明は割愛します。ざっくり言うとpodを外部に公開するLayer4のLB的な奴だと思ってます。

今回用意したyamlは以下です。
NodePortの名前は"service-httpd-nginx"、selectorでどのpodをターゲットにするかを指定してします。node(k8sのホストのこと)の30080ポートに対して接続してくるセッションがあった場合はターゲットにしたpodの80ポートに流し、30090ポートに対して接続してくるセッションがあった場合はターゲットにしたpodの90ポートに流すような設定をしています。
```
root@sv-ohtsuka-k8s-master:~/k8s_yaml# cat service-httpd-nginx.yaml
apiVersion: v1
kind: Service
metadata:
  name: service-httpd-nginx
spec:
  type: NodePort
  selector:
    app: web-app
  ports:
    - name: nginx-port
      port: 80
      targetPort: 80
      nodePort: 30080
    - name: httpd-port
      port: 90
      targetPort: 90
      nodePort: 30090
```

このyamlファイルを元にNodePortをデプロイします。
```
root@sv-ohtsuka-k8s-master:~/k8s_yaml# kubectl create -f service-httpd-nginx.yaml
service/service-httpd-nginx created

root@sv-ohtsuka-k8s-master:~/k8s_yaml# kubectl get service
NAME                  TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)                     AGE
kubernetes            ClusterIP   10.152.183.1    <none>        443/TCP                     38h
service-httpd-nginx   NodePort    10.152.183.50   <none>        80:30080/TCP,90:30090/TCP   7s
```

この時のイメージです。
![kubernetes-ページ9.drawio (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/6aae4d0d-55f1-20f4-d634-617da36849b1.png)




# Windows PCのcmdからk8sのホストにcurlする

Windows PCのcmdを起動し、上記の環境を構築したk8sホストに対して30080ポートと30090ポートを指定してcurlをかけてみます。k8sのホストのIPは192.168.2.30になります。

結果のlogは以下となります。30080ポートを指定した場合は"Welcome to nginx!"とあるようにnginxのHTMLファイルを取得していることが分かります。
一方で30090ポートを指定すると"It works!"とapache2のHTMLファイルを取得していることが分かります。（最もこれだけだとapache2だとわかりにくいですが・・・）

```
C:\Users\otsuka-shota>curl 192.168.2.30:30080
<!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>

C:\Users\otsuka-shota>curl 192.168.2.30:30090
<html><body><h1>It works!</h1></body></html>
```

# 詰まったところ

この記事の最初の方にちらっと書いたのですが、k8s環境にubuntu22.04とmicrok8sを使用しました。
ubuntuのfirewallを成業するものとしてufwがありますが、これはデフォルトでinactiveとなっており、つまり全てのポートを開放していることになります。
k8sのNodePortのポート制約として30000以降のポートしか指定できないということがあるのですが、cmdから30080や30090を指定してもなぜかpodに接続出来ないような事象が散見されました。

k8s環境はESXiを使用して構築していたのですが、どうもそのESXiで30000以降のポートをセキュリティの観点からはじくような設定がされていた様子（定かではありまんが・・・）。トラブルシュートできず上手く出来ず1日無駄にしました。完全に盲点でした。なんでufwがinactiveなのにportがバグってんのかわかりませんでした汗。

![kubernetes-ページ10.drawio.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/315dfa11-404b-abc2-de4c-796c3d055425.png)

~~また、今回はNodePortというL4LBでpodに接続したためcurlでアクセスは出来ますが、Webブラウザではアクセス出来ません。
アクセスする為には、microk8sの場合metallbを有効にしてLoadBalancerと呼ばれるL7LBをデプロイしないといけないと思います。多分。~~
嘘です。Webブラウジング出来ました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/92b87568-f0a2-61a5-a3dd-b700d5127c62.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3219385/6574232c-122d-3ff9-477d-a031418ad2f0.png)