개요
https://seversky.atlassian.net/wiki/spaces/CHT/pages/527925259/Creating+a+single+control-plane+cluster+with+kubeadm#Testing-a-simple-Nginx-deployment 에서 실습했던 내용에서 조금 더 확장하여 이해하여 본다.
여기에서 사용되는 config 파일은 https://github.com/snetsystems/K8s-Objects/tree/master/tests 에 업로드 되어 있다.
기본적으로 pod는 내부 IP가 할당되며 외부에서 접근이 불가능 하다.
또한, Pod는 언제든 죽을 수 있다는 것이 k8s의 철학이며 pod가 다시 만들어 질 경우 pod에 할당된 IP가 변경되게 된다. 해서, 내부 Pod간 통신을 위해서도 전통적 방식인 IP addr을 통한 통신으로는 불가능하다.
즉, Service discovery 방식으로 해결하게 되는데, 즉 도메인 방식으로 접근한다.
기본적으로 K8s를 Initialize하게 되면, coredns pod가 생성되며, 생성된 모든 pod는 여기 등록되어 아래와 같이 kube-dns
서비스로 dns 쿼리하여 pod IP를 알아낸 후 통신을 연결하게 된다.
$ kubectl get svc -A NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 13d kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 13d
정리하자면, 각각의 Pod는 언제든 IP가 변경될 수 있으며 할당된 IP는 label selector로 지정된 서비스를 통해 도메인으로 통신하게 된다.
이제 아래 예제를 통해 이해를 높여보자.
외부에서의 Pod 접속 테스트
nginx-deployment.yaml
편의를 위해 test namespace를 생성한다.
apiVersion: v1 kind: Namespace metadata: name: test
nginx pod를 2개(replicas: 2
)로 생성한다.
apiVersion: apps/v1 kind: Deployment metadata: name: nginx namespace: test labels: app: nginx spec: replicas: 2 selector: matchLabels: app: nginx # 이 label을 Service object에 지정해야 서비스 도메인을 통한 통신 연결이 수행됨. template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:stable-alpine3.17-slim ports: - containerPort: 80
각각의 타입 별 테스트를 위해,
ClusterIP, LB, NodePort 타입의 서비스들을 각각 생성하여 본다.
apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: test spec: selector: app: nginx # 위 matchLabels에 지정되었던 label 지정. type: ClusterIP ports: - name: http port: 80 # targetPort를 지정하지 않을 경우, targetPort=port 설정 값. externalIPs: - 10.20.2.235 - 10.20.2.236 --- apiVersion: v1 kind: Service metadata: name: nginx-svc-lb namespace: test spec: selector: app: nginx # 위 matchLabels에 지정되었던 label 지정. type: LoadBalancer ports: - name: http port: 8080 protocol: TCP targetPort: 80 #nodePort: 30036 # 생략 시, 임의값 지정됨.(30000-32767) externalIPs: - 10.20.2.235 - 10.20.2.236 --- apiVersion: v1 kind: Service metadata: name: nginx-svc-np namespace: test spec: selector: app: nginx # 위 matchLabels에 지정되었던 label 지정. type: NodePort ports: - name: http port: 8081 protocol: TCP targetPort: 80 #nodePort: 30037 # 생략 시, 임의값 지정됨.(30000-32767) externalIPs: - 10.20.2.235 - 10.20.2.236
모두 문제없이 생성된다면, 아래와 같을 것이다.
$ kubectl get po -n test -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-6c8cf466cd-mpfz4 1/1 Running 0 129m 10.244.1.15 test-k8s-worker01-centos8.snetsystems.com <none> <none> nginx-6c8cf466cd-xgbd7 1/1 Running 0 129m 10.244.2.14 test-k8s-worker02-centos8.snetsystems.com <none> <none>
$ kubectl get svc -n test NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx-svc ClusterIP 10.105.132.127 10.20.2.235,10.20.2.236 80/TCP 45h nginx-svc-lb LoadBalancer 10.110.214.153 10.20.2.235,10.20.2.236 8080:31558/TCP 45h nginx-svc-np NodePort 10.97.16.76 10.20.2.235,10.20.2.236 8081:31511/TCP 45h
위에서 externalIPs
를 각각 지정하여 두었기 때문에, 각각의 externalIPs
를 통하여 각 80, 8080 포트로 접속이 가능하다.
가능한 조합의 curl 테스트:
$ curl http://10.20.2.235 $ curl http://10.20.2.236 $ curl http://10.20.2.235:8080 $ curl http://10.20.2.236:8080 $ curl http://10.20.2.236:8081 $ curl http://10.20.2.236:8081 # 물론 LoadBalancer와 NodePort Type 서비스는 nodeport로도 접근이 가능하다. $ curl http://10.20.2.235:31558 $ curl http://10.20.2.236:31558 $ curl http://10.20.2.235:31511 $ curl http://10.20.2.236:31511 <!DOCTYPE html> <html> <head> <title>Welcome to nginx!</title> ... </html>
또한, 각각의 서비스는 label selector app: nginx
로 지정하였기 때문에 deploy로 생성한 2개의 nginx pod에 Load balancing 된다.
nginx-6c8cf466cd-xgbd7 로그:
10.244.2.1 - - [27/Jun/2023:08:30:40 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:31:58 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:14 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:18 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:19 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:20 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:20 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:40 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
nginx-6c8cf466cd-mpfz4 로그:
10.244.2.1 - - [27/Jun/2023:08:30:40 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:31:58 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:14 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:18 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:19 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:20 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:20 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-" 10.244.2.1 - - [27/Jun/2023:08:34:40 +0000] "GET / HTTP/1.1" 200 615 "-" "curl/7.61.1" "-"
내부 Pod간 통신
앞서 개요에서 언급하였듯이, 모든 Pod들은 IP 대신 도메인으로 통신할 수 있다.
즉, Pod간 internal 통신을 위해서는 서비스명으로 access한다.
관련 테스트를 위해 test namespace에 아래와 같은 pod를 하나 생성하자.
apiVersion: apps/v1 kind: Deployment metadata: name: busybox namespace: test labels: app: busybox spec: replicas: 1 selector: matchLabels: app: busybox template: metadata: labels: app: busybox spec: containers: - name: busybox image: busybox command: - "/bin/sh" - "-c" - "sleep inf"
정상 생성되면 아래와 같을 것이다.
$ kubectl get po -n test -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES busybox-9ff887cc8-f5m46 1/1 Running 0 46h 10.244.1.16 test-k8s-worker01-centos8.snetsystems.com <none> <none> nginx-6c8cf466cd-mpfz4 1/1 Running 0 46h 10.244.1.15 test-k8s-worker01-centos8.snetsystems.com <none> <none> nginx-6c8cf466cd-xgbd7 1/1 Running 0 46h 10.244.2.14 test-k8s-worker02-centos8.snetsystems.com <none> <none>
busybox 내부 쉘로 진입하여 각 pod ip로 접근해보면, 당연하게도 overlay networking을 통해 잘 접근된다는 것을 볼 수 있다.
# kubectl exec -n test -it busybox-9ff887cc8-f5m46 -- sh / # ping 10.244.2.14 PING 10.244.2.14 (10.244.2.14): 56 data bytes 64 bytes from 10.244.2.14: seq=0 ttl=62 time=1.243 ms 64 bytes from 10.244.2.14: seq=1 ttl=62 time=0.887 ms ^C --- 10.244.2.14 ping statistics --- 2 packets transmitted, 2 packets received, 0% packet loss round-trip min/avg/max = 0.887/1.065/1.243 ms --- 10.244.2.15 ping statistics --- 7 packets transmitted, 0 packets received, 100% packet loss / # / # ping 10.244.1.15 PING 10.244.1.15 (10.244.1.15): 56 data bytes 64 bytes from 10.244.1.15: seq=0 ttl=64 time=0.322 ms ^C --- 10.244.1.15 ping statistics --- 1 packets transmitted, 1 packets received, 0% packet loss round-trip min/avg/max = 0.322/0.322/0.322 ms
CRI-O runtime의 경우 보안 상 pod 내부에서 ping이 나가는 것이 막혀있다.
각 K8s 클러스터 노드들에 /etc/crio/crio.conf.d/default_capabilities.conf
를 생성하여,
아래 옵션(“NET_RAW
")을 넣어준다.[crio.runtime]
default_capabilities = [
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"SETGID",
"SETUID",
"SETPCAP",
"NET_BIND_SERVICE",
"KILL",
"NET_RAW",
]
이후, systemctl restart crio를 통해 crio를 재시작하고,
ping을 사용하려는 pod(“busybox”)도 재생성해야 한다.
자세한 내용은 https://flavono123.github.io/posts/crio-capabilities-bug/ 을 참고 하기 바란다.
그러나, IP는 언제든 바뀔 수 있으므로, 이번에는 미리 만들어둔 Service를 통해 접근해보도록 하자.
당연한 말이지만, Service는 Pod가 아니라 pod의 특정 포트를 cluster ip와 포트로 연결해주는 일종의 정책이므로, 위의 ping은 도달하지 않는다.
아래와 같이 telnet을 통해 서비스 포트로 접근이 가능하다는 것을 확인할 수 있다.
/ # telnet nginx-svc 80 Connected to nginx-svc / # telnet nginx-svc-lb 8080 Connected to nginx-svc-lb # telnet nginx-svc-np 8081 Connected to nginx-svc-np
그럼 어떻게 pod에서 서비스 포트로 접근이 가능할까?
nslookup을 통해 도메인 찾기를 해보자.
/ # nslookup nginx-svc Server: 10.96.0.10 Address: 10.96.0.10:53 ** server can't find nginx-svc.svc.cluster.local: NXDOMAIN Name: nginx-svc.test.svc.cluster.local Address: 10.105.132.127
위의 10.96.0.10:53
은 kube-dns
서비스의 CLUSTER-IP
이다.(kubectl get svc -n kube-system
명령으로 확인할 수 있다.)
10.105.132.127
는 nginx-svc
서비스의 CLUSTER-IP
이다.
즉, CLUSTER-IP: 10.105.132.127
에 대하여 nginx-svc.test.svc.cluster.local
으로 도메인이 등록되어 있음을 알 수 있으며, nginx-svc
는 label selector로 각 pod와 연결된다는 것을 추정할 수 있을 것이다.
참고로, 다른 domain의 pod에 접근하기 위해서는 서비스명.도메인명
으로 사용하면 된다.
/ # telnet kube-dns.kube-system 53 Connected to kube-dns.kube-system Connection closed by foreign host
정리
이 실습들을 통해 모던 어플리케이션 시스템을 어떻게 K8s 상에 구축할 것 인가에 대한 통상의 개념을 알아보았다.
다음에는 아래와 같은 완성된 형태의 어플리케이션 서비스의 Server side를 deploy 해볼 것이다.
0 Comments