こんにちは
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
この記事ではタイトルの通り、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環境でデプロイしている図
ローカルPCのcmdを起動し、k8sホストに対して80ポートを指定するとpod内のnginxコンテナに接続し、90ポートを指定するとapache2コンテナに接続する図
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に対してアクセスしてみます。
動いているッぽいですね。
この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
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
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
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がバグってんのかわかりませんでした汗。
また、今回はNodePortというL4LBでpodに接続したためcurlでアクセスは出来ますが、Webブラウザではアクセス出来ません。
アクセスする為には、microk8sの場合metallbを有効にしてLoadBalancerと呼ばれるL7LBをデプロイしないといけないと思います。多分。
嘘です。Webブラウジング出来ました。