CKA를 준비해보자 33일차 - Service Networking

0

CKA

목록 보기
33/43

Service Networking

ClusterIP

그럼 이제 서로 다른 node에 있는 pod들끼리 어떻게 통신을 할 수 있는 지 알아보도록 하자.

pod들끼리는 각자의 IP를 통해서 서로 통신이 가능하지만, pod가 죽었다 살아나면 IP가 바뀐다. 따라서, pod의 IP를 통한 통신은 지속적으로 좋지 않은 방법이다. 따라서, 이를 해결하기 위해서 Service라는 kubernetes resource가 있는데, 이는 pod에 대한 접근을 고정된 IP를 가진 service가 받도록 하여 다른 pod들이 service에 요청을 보내도록 하는 것이다. 즉, service의 IP로 요청을 보내면 service에 연결된 pod와 통신이 되는 것이다. 참고로 service는 IP 뿐만 아니라 name으로도 접근이 가능하다.

--------Node1--------                     --------Node2--------
|                   |   ---Service----    |                   |
|   ----pod1----    |   |10.99.13.178|------------pod2----    |
|   |10.244.1.2|    |   --------------    |   |10.244.2.2|    |
|   ------------    |                     |   ------------    |
|                   |                     |                   |
---------------------                     ---------------------
     192.168.1.11                              192.168.1.12

다음과 같이 pod2가 10.99.13.178 IP를 가진 service와 연결되면 Node1에 있는 pod1이 10.99.13.178로 요청을 보내면 pod2에 전달되는 것이다.

이 그림을 통해 알 수 있는 것은 service가 node level이 아니라 cluster level이라는 것이다. 즉, cluster 전체적으로 쓰이고 있기 때문에 서로 다른 node에 배포된 pod들이라도 서로 통신이 가능한 것이다.

그러나, 해당 service IP는 cluster level이므로 cluster 외부에서는 무의미하다. 따라서, 외부에서 10.99.13.178로 요청을 보내어도 응답이 오지 않는다. 이렇게 cluster 내부에서의 유의미한 IP를 가지는 service를 Cluster IP라고 한다.

NodePort

그런데, pod2를 외부에서도 요청을 받도록 사용하고 싶을 수 있다. 이 경우 사용하는 것이 바로 service의 NodePort이다. ClusterIP처럼 cluster내부에서 통용되는 IP를 가져, cluster level로 pod들끼리 통신이 가능하지만, 추가적으로 host port에 forwarding하여 외부와 통신이 가능하다.

                                                 30080
---------------------                     ---------|-----------
|                   |                     |        |          |  
|                   |                     |  ---Service2---   |
|                   |                     |  |10.99.13.178|   |
|                   |                     |  --------------   |
|                   |   ---Service----    |        |          |
|   ----pod1----    |   |10.99.13.178|------------pod2----    |
|   |10.244.1.2|    |   --------------    |   |10.244.2.2|    |
|   ------------    |                     |   ------------    |
|                   |                     |                   |
-------Node1---------                     --------Node2--------
     192.168.1.11                              192.168.1.12

다음 그림은 Node2의 pod2가 NodePort type을 가진 service2와 연결된 것이다. cluster내의 모든 pod들은 service2의 IP와 name으로 접근이 가능하다. 이는 clusterIP와 동일한 특징을 갖지만, 더불어 Service2는 Node2에 30080 port를 사용하여 외부의 traffic을 pod2에 포워딩시키는 동작을 제공한다.

이를 통해서 외부에서 Node230080으로 통신을 시도하면 pod2로 packet이 전달되는 것이다.

Service의 동작

그런데, 어떻게 service는 IP를 받고, node의 특정 pod에 연결되어 배포되었지만 cluster level로 모든 pod들이 접근이 가능하고, node의 특정 port를 열어 외부와 통신이 가능한 것일까?? 누가 이런 것들을 가능하게 해주는 것일까??

우리는 이전에 각 node에 kubelet이 있기 때문에 kube-apiserver로 부터 pod 생성 명령이 해당 node의 kubelet에 떨어져 kubelet이 컨테이너 런타임을 통해 실제 pod를 생성한다고 했다.

마찬가지로, node마다 kube-proxy가 있는데, kube-apiserver로부터 service가 생성되었다는 요청이 오면, service를 생성해주는 것이다.

----------------
|kube-apiserver|-------------------------
----------------                        |
          |                             |
    ----------------            -----------------
    |   kubelet    |            |   kubelet     |
    |  kube-proxy  |            |  kube-proxy   |
    |      |       |            |      |        | 
    | ----------------------------------------- |
    | |                                       | |
    | |                                       | | 
    | ----------------------------------------- |
    |               |           |               |
    |               |           |               |
    |               |           |               |
    ------Node1------           ------Node2------
      192.168.1.11                 192.168.1.11

다음과 같이 kube-proxy에 의해서 만들어진 service들은 Node에 상관없이 cluster level로 서로 접근이 가능한 것이다.

그런데, pod가 만들어지면 network namespace가 만들어지고, virtual interface가 생기고 IP를 할당받는 반면에, service는 그렇지 않다. service는 실제 object가 아니라 virtual한 object로 어떠한 server도 application도 아니다. 즉, namespace도 없고 interface도 없기 때문에 IP도 없다.

그렇다면, 어떻게 service를 통해서 pod에 접근하고 service의 IP와 통신을 가능하게 한 것인가??

service가 만들어질 때, service는 kube-controller-manager의 service manager로 부터 미리 정해진 범위 안에서 IP주소를 할당받는다. kube-proxy는 service에 할당된 IP를 얻고, forwarding rule을 만들어낸다. 그리고 이 forwarding rule을 cluster의 각 node에 뿌려준다.

----------------
|kube-apiserver|-------------------------
----------------                        |
          |                             |
    ----------------            -----------------
    |   kubelet    |            |   kubelet     |
    |  kube-proxy  |            |  kube-proxy   |
    |      |       |            |      |        | 
    | ----------------------------------------- |
    | |      service1(10.99.13.178:80)        | |
    | |                                       | | 
    | ----------------------------------------- |
    |       |       |           |               |
    | ----pod1----  |           |               |
    | |10.244.1.2|  |           |               |
    | ------------  |           |               |
    |               |           |               |
    |               |           |               |
    ------Node1------           ------Node2------
      192.168.1.11                 192.168.1.11

pod1이 만들어지고, service1이 pod1에 연결되면 다음의 forwarding rule이 node1, node2에 모두 전달된다.

Service IPPod IP
10.99.13.178:8010.244.1.2

따라서, 다른 pod가 10.99.13.178:80로 요청을 보내면 10.244.1.2로 전달이 되는 것이다. 이렇게 되면 CNI plugin이 설정한 pod network에 전송되어 pod1에 packet이 전달되는 것이다.

참고로, service에 배정된 IP의 범위는 kube-apiserver에서 설정된 option값을 통해서 볼 수 있다.

kube-api-server --service-cluster-ip-range ipNet (default: 10.0.0.0/24)

따라서, ps aux | grep kube-api-server를 통해서 확인이 가능하다.

그럼 kube-proxy는 어떻게 이러한 동작을 할 수 있는 것일까?? 여기에는 다양한 solution들이 있었지만, 현재 쓰이는 것은 iptables mode이다. 즉, 별다른 것은 없고 iptables를 통해서 service IP로 부터온 packet을 연결된 pod의 IP로 전달하는 것이 끝이다.

따라서, iptables를 통해서 정보를 확인할 수 있다.

iptables -L -t nat | grep db-service
KUBE-SVC-...  tcp  --  anywhere  10.103.132.104  /*  default/db-service:  cluster IP */ tcp dpt: 3306
DNAT          tcp  --  anywhere  anywhere        /*  default/db-service:  /* tcp to:10.244.1.2:3306

위 내용을 간단하게 정리하자면 service인 10.103.132.1043306 port로 전달된 tcp packet을 10.244.1.2:3306로 전달해달라는 것이다. 이렇게 kube-proxy가 간단하게 iptables rule을 만들어 service와 pod를 연결시켜놓는 것이 전부이다.

만약 service의 생성에 대한 log를 보고싶다면 다음의 log파일을 확인하도록 하자.

cat /var/log/kube-proxy.log

문제

  • 해당 cluster에서 node의 network range는?
ip addr | grep eth0
  • service의 ip range는?
ps aux | grep kube-api | grep service

...
--service-cluster-ip-range=10.96.0.0/12
  • kube-proxy의 mode는?
 kubectl logs -n kube-system kube-proxy-5j4wf 
I0726 14:22:32.402794       1 server_linux.go:69] "Using iptables proxy"

iptables이다.

Cluster DNS

kubernetes에서는 DNS service를 제공해주기 위한 core-DNS server pod가 만들어져 있다.

다음과 같이 서로 다른 node에 배치된 두 pod와 service가 있다고 하자.

 ----pod1----
 |10.244.1.5|  
 ------------
      
--web-service--
|10.107.37.188|
---------------
     |
----pod2----
|10.244.2.5|
------------

pod1과 pod2는 사로 다른 node에 배치되었다고 하자. pod2는 service에 연결되어있는데, pod1은 service를 통해서 pod2에 접근이 가능하다.

이때, service의 IP로 접근하는 것 뿐만 아니라 DNS로도 접근할 수 있는데, service가 만들어질 때 kubernetes DNS service가 DNS record를 다음과 같이 만드는 것이다.

hostnameIP address
web-service10.107.37.188

따라서, pod1은 다음의 url을 통해서 service에 접근이 가능한 것이다.

curl http://web-service

그러나 사실 이것은 이들이 서로 같은 namespace에서 접근할 때만 이렇게 사용하는 것이다. 만약 다음과 같이 서로 다른 namespace를 가진 경우를 보자.

-------default-----
|   ----pod1----  |
|   |10.244.1.5|  |
|                 |
-------------------
        
-------apps----------
|  --web-service--  |
|  |10.107.37.188|  |
|  ---------------  |
|         |         |
|    ----pod2----   |
|    |10.244.2.5|   |
|    ------------   |
---------------------

이렇게 서로 다른 namespace에 있을 때는 다음과 같이 DNS record에 namespace도 표시해주어야 한다.

curl http://web-service.apps

또한, 모든 service들은 sub domain으로 svc를 갖는다. 따라서 다음과 같이 요청이 가능하다.

curl http://web-service.apps.svc

마지막으로 cluster그룹을 지정해주는데 default로 cluster.local이다. 따라서, 다음과 같다.

curl http://web-service.apps.svc.cluster.local

이것이 바로 service에 대한 FQDN(fully qualified doamin name)이라는 것이다.

정리하면 다음과 같다.

HostnameNamespaceTypeRootIP Address
web-serviceappssvccluster.local10.107.37.188

사실 service뿐만 아니라 kube dns service는 pod 역시도 DNS record를 만들어주는데, 이 때 pod의 이름을 쓰는 것이 아니라, IP를 쓴다.

가령 10.244.2.5라면 10-244-2-5로 바꾸어 쓰는 것이다. 이 밖에 나머지는 service와 같다.

HostnameNamespaceTypeRootIP Address
10-244-2-5appspodcluster.local10.244.2.5

해당 DNS record로 요청을 보내면 응답이 전달될 것이다.

curl http://10-244-2-5.apps.pod.cluster.local

어떻게 kubernetes는 DNS를 구현했는가?

DNS record를 생성하고 관리하는 가장 쉬운 방법은 각 node마다 dns record를 설정해주는 것이다.

--test pod---        ---web pod---
|10.244.1.5 |        |10.244.2.5 |
-------------        -------------
     |                     |
--/etc/hosts-----    ---/etc/hosts-----
|web  10.244.2.5|    |test  10.244.1.5|
-----------------    ------------------

위와 같이 각 pod가 배치된 node에 pod 이름과 IP에 대한 DNS record를 만드는 것이다.

그런데, 매번 이렇게 pod가 생성될 때마다 모든 node의 record를 추가하고 삭제하고, 업데이트하는 것은 굉장히 어려운 일이다.

그래서, DNS record를 관리하는 하나의 server를 제공하고 pod들이 이 DNS server를 보도록 하는 것이다.

           ------10.96.0.10--------
           |web   10.244.2.5      |
           |test  10.244.1.5      |
           ------------------------
           |                      |
           |                      |
      --test pod---        ---web pod---
      |10.244.1.5 |        |10.244.2.5 |
      -------------        -------------
            |                     |
---/etc/resolve.conf----   ---/etc/resolve.conf----
|nameserver  10.96.0.10|   |nameserver  10.96.0.10|
------------------------   ------------------------

다음과 같이 각 pod마다 DNS설정으로 nameserver10.96.0.10을 보도록 하는 것이다.

이렇게 DNS record를 관리하는 server를 하나 두어 cluster내의 pod들의 생명주기에 따른 DNS 생성, 삭제, 업데이트를 편리하게 관리하는 것이다.

참고로 이전에 보았듯이 pod의 이름이 DNS record에서는 IP이름으로 대체됨으로 다음이 맞다.

      ----------10.96.0.10-----------
      |10-244-2-5   10.244.2.5      |
      |10-244-1-5   10.244.1.5      |
      -------------------------------
           |                      |
           |                      |
      --test pod---        ---web pod---
      |10.244.1.5 |        |10.244.2.5 |
      -------------        -------------
            |                     |
---/etc/resolve.conf----   ---/etc/resolve.conf----
|nameserver  10.96.0.10|   |nameserver  10.96.0.10|
------------------------   ------------------------

pod든 service든 위와 같은 DNS server에 의해서 DNS record들이 만들어지는 것이다.

이러한 DNS server역할을 하는 것이 바로 CoreDNS이다. CoreDNS는 하나의 pod로 cluster에 두개의 replica로 배포된다.

CoreDNS는 configuration fil을 통해서 DNS record를 설정할 수 있는데, 다음과 같다.

  • /etc/coredns/Corefile
.:53 {
      erros
      health
      kubernetes cluster.local in-addr.arpa ip6.arpa {
            pods insecrue
            upstream
            fallthrough in-addr.arpa ip6.arpa
      }
      prometheus :9153
      proxy . /etc/resolve.conf
      cache 30
      reload
}

다음의 설정 파일에서 볼 것은 cluster.local이다. 위에서 본 FQDN에서 가장 최상위 domain이 바로 cluster.local인데, 이것이 coreDNS가 설정해준 것이다.

CoreDNS는 하나의 pod이기 때문에 다른 pod들 처럼 CoreDNS로 접근하기 위한 service를 가지고 있다. 이것이 바로 kube-dns이다.

kubectl get service -n kube-system
NAME        TYPE        CLUSTER-IP ...
kube-dns    ClusterIP   10.96.0.10 ...

pod들이 kube-dns를 통해서 CoreDNS와 통신하여 DNS 이름에 대한 IP를 얻어오고 있던 것이다.

이를 위해서 kubernetes에서는 pod가 생성되면 CoreDNS의 service인 kube-dns의 IP를 DNS server로 설정해준다. 즉, 각 pod마다 처음부터 해당 nameserver를 가지게 되는 것이다.

  • /etc/resolv.conf
nameserver        10.96.0.10

바로 kubelet이 이 일을 해주는 것이다. 따라서kubelet의 configuration을 보면 다음을 볼 수 있다.

  • /var/lib/kubelet/config.yaml
...
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local

다음과 같이 pod안에 들어가서 web-service라는 service의 dns record가 잘 설정되어 있는 지 확인할 수 있다.

host web-service
host web-service.default
host web-service.default.svc
host web-service.default.svc.cluster.local

0개의 댓글