Skip to end of metadata
Go to start of metadata

You are viewing an old version of this content. View the current version.

Compare with Current View Version History

« Previous Version 6 Current »

개요

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:53kube-dns 서비스의 CLUSTER-IP이다.(kubectl get svc -n kube-system 명령으로 확인할 수 있다.)

10.105.132.127nginx-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 해볼 것이다.

  • No labels