오늘의 주제는 "Pod 와 서비스 간 통신" 이라는 쿠버네티스가 해결한 4가지 주제중 한 가지 입니다. 서비스 오브젝트를 생성하면 내부적으로 어떻게 동작하는지 살펴볼 예정입니다.
쿠버네티스는 배포한 파드에 대하여 내/외부 통신을 도와줍니다. 파드는 쿠버네티스에서 동적인 생명 주기를 갖기 때문에 이와 관련하여 안정적인 네트워크를 지원할 수 있어야 하고, 이를 Kube Proxy가 해결합니다.
이전 글 목차
https://dev-whoan.xyz/111, 네트워크로 시작하는 쿠버네티스 — 내가 데이터를 보낸다면
https://dev-whoan.xyz/112, 네트워크로 시작하는 쿠버네티스 - Network Namespace
https://dev-whoan.xyz/113, 네트워크로 시작하는 쿠버네티스 - Docker Network
https://dev-whoan.xyz/114, 네트워크로 시작하는 쿠버네티스 - iptables, ipvs, ipip, vxlan
사전 준비물
앞으로 쿠버네티스에서 실습을 하기 위한 클러스터를 구성합니다.
- network-study 네임스페이스 생성
- CNI를 설치합니다.
- 저의 경우 Calico를 선택했습니다.
- Load Balancer 설치
- 저의 경우 베어메탈 서버를 이용하고 있으며, MetalLB를 설치해 홈 네트워크의 아이피 대역을 이용할 수 있도록 했습니다.
Pod IP의 비일관성
현재 시스템에서 Kube Proxy는 iptables 모드로 동작하고 있습니다.
$ kubectl get pod -n kube-system -o wide | grep "kube-proxy"
kube-proxy-fc578 1/1 Running 0 48s 10.108.10.102 worker2 <none> <none>
kube-proxy-lqrjn 1/1 Running 0 58s 10.108.10.103 worker3 <none> <none>
kube-proxy-nrks7 1/1 Running 0 50s 10.108.10.101 worker1 <none> <none>
kube-proxy-vg52m 1/1 Running 0 54s <none> master1 <none> <none>
$ kubectl logs -n kube-system kube-proxy-nrks7 | grep "Using"
I1215 12:44:47.895588 1 server_linux.go:66] "Using iptables proxy"
I1215 12:44:49.285601 1 server_linux.go:169] "Using iptables Proxier"
I1215 12:44:49.357815 1 proxier.go:278] "Using iptables mark for masquerade" ipFamily="IPv4" mark="0x00004000"
I1215 12:44:49.362095 1 proxier.go:278] "Using iptables mark for masquerade" ipFamily="IPv6" mark="0x00004000"
쿠버네티스는 클러스터 구성시 설정한 --pod-network-cidr, 그리고 kube proxy에 설정된 --cluster-cidr을 통해 파드의 IP CIDR를 구성할 수 있습니다.
이 옵션을 통해 설정된 값을 바탕으로 kube proxy는 Cluster IP를 제공하며, 여기에 iptables 혹은 ipvs를 사용하여 트래픽을 처리하게 됩니다. Pod IP의 비일관성을 해결하기 위해 서비스 오브젝트에 대한 Cluster IP를 할당하고, 여기에 대상의 파드를 할당함으로써 일관된 Cluster IP를 제공합니다.
--- # nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-study
namespace: network-study
spec:
replicas: 1
selector:
matchLabels:
type: nginx-study
template:
metadata:
name: nginx-study-app
labels:
type: nginx-study
spec:
containers:
- image: nginx
name: nginx-container
ports:
- name: http
containerPort: 80
protocol: TCP
nodeSelector:
kubernetes.io/hostname: worker1
## HostOS 혹은 쿠버네티스 구성이 가능한 시스템에서 수행합니다.
# 생성한 쿠버네티스의 Service CIDR, Pod Network CIDR은 다음과 같습니다.
$ kubectl get cm -n kube-system kubeadm-config -o yaml
apiVersion: v1
data:
ClusterConfiguration: |
...
networking:
podSubnet: 192.168.0.0/16
serviceSubnet: 172.16.0.0/12
...
...
$ kubectl apply -f nginx.yaml
deployment.apps/nginx-study created
$ kubectl get pod -n network-study
NAME READY STATUS RESTARTS AGE
nginx-study-b5996c7b9-nfvqd 1/1 Running 0 92s
$ kubectl expose deploy nginx-study -n network-study --type=ClusterIP --port=80 --target-port=80
service/nginx-study exposed
$ kubectl describe svc -n network-study nginx-study
Name: nginx-study
Namespace: network-study
Labels: <none>
Annotations: <none>
Selector: type=nginx-study
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 172.30.103.10
IPs: 172.30.103.10
Port: <unset> 80/TCP
TargetPort: 80/TCP
Endpoints: 192.168.235.152:80
Session Affinity: None
Events: <none>
## worker1에서 iptables을 통해 확인해 봅시다.
# 서비스를 expose 하기 전에는 nginx-study와 관련된 iptables 규칙이 없습니다.
root@worker1:~# iptables -t nat -L -n -v | grep "nginx-study"
root@worker1:~#
# nginx-study를 서비스로 노출시키면, 아래와 같이 관련된 규칙이 나타나게 됩니다.
# 이 정보는 worker2 등 다른 시스템에서 확인하더라도 동일하게 확인할 수 있습니다.
root@worker1:~# iptables -t nat -L -n -v | grep "nginx-study"
0 0 KUBE-MARK-MASQ all -- * * 192.168.235.152 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.235.152:80
0 0 KUBE-SVC-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 172.30.103.10 /* network-study/nginx-study cluster IP */
0 0 KUBE-MARK-MASQ tcp -- * * !192.168.0.0/16 172.30.103.10 /* network-study/nginx-study cluster IP */
0 0 KUBE-SEP-LJ5QOTZF6XC46AXQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.235.152:80 */
## worker2 에서 확인한 값
root@worker2:~# iptables -t nat -L -n -v | grep "nginx-study"
0 0 KUBE-MARK-MASQ all -- * * 192.168.235.152 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.235.152:80
0 0 KUBE-SVC-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 172.30.103.10 /* network-study/nginx-study cluster IP */
0 0 KUBE-MARK-MASQ tcp -- * * !192.168.0.0/16 172.30.103.10 /* network-study/nginx-study cluster IP */
0 0 KUBE-SEP-LJ5QOTZF6XC46AXQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.235.152:80 */
Kube Proxy 파드의 로그를 확인해 보면 생성한 Service를 바탕으로 iptables 규칙이 생성된 것을 확인할 수 있습니다.
$ kubectl logs -n kube-system kube-proxy-nrks7 | grep "nginx-study"
I1215 12:44:49.823887 1 servicechangetracker.go:106] "Service updated ports" service="network-study/nginx-study" portCount=1
I1215 12:44:50.073028 1 config.go:124] "Calling handler.OnEndpointSliceAdd" endpoints="network-study/nginx-study-9rjfb"
I1215 12:44:50.140306 1 servicechangetracker.go:211] "Adding new service port" portName="network-study/nginx-study" servicePort="172.18.189.139:80/TCP"
I1215 12:44:50.143013 1 endpointslicecache.go:303] "Setting endpoints for service port name" portName="network-study/nginx-study" endpoints=["192.168.235.152:80"]
tcpdump를 이용하여 worker1 노드에서 해당 서비스로 트래픽을 흘려보냈을 때 어떻게 동작하는지 확인해 보겠습니다.
## 쿠버네티스 노드 중 아무곳에서나 요청을 보내봅니다.
root@master1:~# curl 172.18.189.139
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to "nginx.org".
Commercial support is available at "nginx.com".
Thank you for using nginx.
## worker1 노드에서 tcpdump로 확인해 봅니다.
root@worker1:~# tcpdump -i any port 80
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
12:48:58.161637 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [S], seq 2298058529, win 64240, options [mss 1460,sackOK,TS val 1768291727 ecr 0,nop,wscale 7], length 0
12:48:58.162053 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [S], seq 2298058529, win 64240, options [mss 1460,sackOK,TS val 1768291727 ecr 0,nop,wscale 7], length 0
12:48:58.162217 cali675956f83bb In IP 192.168.235.152.http > 192.168.137.64.61145: Flags [S.], seq 3030732140, ack 2298058530, win 64260, options [mss 1440,sackOK,TS val 147421508 ecr 1768291727,nop,wscale 7], length 0
12:48:58.162284 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [S.], seq 3030732140, ack 2298058530, win 64260, options [mss 1440,sackOK,TS val 147421508 ecr 1768291727,nop,wscale 7], length 0
12:48:58.163329 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1768291729 ecr 147421508], length 0
12:48:58.163421 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 1768291729 ecr 147421508], length 0
12:48:58.165280 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [P.], seq 1:79, ack 1, win 502, options [nop,nop,TS val 1768291731 ecr 147421508], length 78: HTTP: GET / HTTP/1.1
12:48:58.165439 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [P.], seq 1:79, ack 1, win 502, options [nop,nop,TS val 1768291731 ecr 147421508], length 78: HTTP: GET / HTTP/1.1
12:48:58.165564 cali675956f83bb In IP 192.168.235.152.http > 192.168.137.64.61145: Flags [.], ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 0
12:48:58.165612 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [.], ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 0
12:48:58.166314 cali675956f83bb In IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 1:239, ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 238: HTTP: HTTP/1.1 200 OK
12:48:58.166375 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 1:239, ack 79, win 502, options [nop,nop,TS val 147421512 ecr 1768291731], length 238: HTTP: HTTP/1.1 200 OK
12:48:58.167083 cali675956f83bb In IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 239:854, ack 79, win 502, options [nop,nop,TS val 147421513 ecr 1768291731], length 615: HTTP
12:48:58.167148 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [P.], seq 239:854, ack 79, win 502, options [nop,nop,TS val 147421513 ecr 1768291731], length 615: HTTP
12:48:58.167352 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 1768291733 ecr 147421512], length 0
12:48:58.167454 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 1768291733 ecr 147421512], length 0
12:48:58.168204 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 1768291734 ecr 147421513], length 0
12:48:58.168575 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 1768291734 ecr 147421513], length 0
12:48:58.174186 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [F.], seq 79, ack 854, win 501, options [nop,nop,TS val 1768291740 ecr 147421513], length 0
12:48:58.174398 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [F.], seq 79, ack 854, win 501, options [nop,nop,TS val 1768291740 ecr 147421513], length 0
12:48:58.175491 cali675956f83bb In IP 192.168.235.152.http > 192.168.137.64.61145: Flags [F.], seq 854, ack 80, win 502, options [nop,nop,TS val 147421521 ecr 1768291740], length 0
12:48:58.175622 tunl0 Out IP 192.168.235.152.http > 192.168.137.64.61145: Flags [F.], seq 854, ack 80, win 502, options [nop,nop,TS val 147421521 ecr 1768291740], length 0
12:48:58.176467 tunl0 In IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 1768291742 ecr 147421521], length 0
12:48:58.176610 cali675956f83bb Out IP 192.168.137.64.61145 > 192.168.235.152.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 1768291742 ecr 147421521], length 0
그런데 이상합니다. 분명 172.18.189.139의 서비스 오브젝트의 Cluster IP로 요청을 보냈는데 tcpdump를 확인해 보니 알 수 없는 대역 192.168.137.64로부터 요청이 들어옵니다. 왜 그런지 한번 직접 확인해 보시기 바랍니다.
- 이때까지의 글을 모두 읽으셨다면, 확인하실 수 있습니다.
Kube Proxy는 기본적으로 iptables 모드를 이용하고 있고, 이에 따라 우리가 살펴봤던 IPVS의 로드 밸런싱을 비교적 똑똑하게 처리하지 못합니다. 이와 관련하여 IPVS에 대한 설정은 직접 수행해 보시기 바랍니다.
트래픽 분산 및 로드밸런싱
- 시작하기에 앞서, 앞에서 배포한 nginx 디플로이먼트의 레플리카 수를 3개로 늘려주세요.
- nginx-study 서비스 또한 다시 배포해 주세요.
Kube Proxy는 iptables, ipvs를 이용하여 레플리카로 구성된 서비스에 대해 트래픽 분산을 수행합니다. 우리가 디플로이먼트 혹은 레플리카셋 등을 통해 하나의 애플리케이션을 둘 이상의 파드를 갖도록 구성하고, 서비스를 할당할 경우 트래픽 분산을 처리하게 되는 것이죠.
다만 iptables와 ipvs의 모드에 따라 서비스 Endpoints 연결에 대한 차이가 발생합니다. iptables는 임의의 라우팅 규칙을 확률에 따라 선택하게 됩니다.
root@worker1$:~# iptables -t nat -L -v -n | grep "nginx-study"
0 0 KUBE-MARK-MASQ all -- * * 192.168.182.4 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.182.4:80
0 0 KUBE-MARK-MASQ all -- * * 192.168.189.69 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.189.69:80
0 0 KUBE-MARK-MASQ all -- * * 192.168.235.135 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.235.135:80
# 172.27.83.52, Cluster IP로 들어오는 요청은 KUBE-SEP-...으로 처리됩니다.
# 주의할 점은 라우팅은 순서대로 처리된다는점 입니다.
0 0 KUBE-SVC-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 172.27.83.52 /* network-study/nginx-study cluster IP */
# 192.168.0.0/16이 아니라는 표시는 출발지가 쿠버네티스의 Pod CIDR이 아닌경우 입니다.
0 0 KUBE-MARK-MASQ tcp -- * * !192.168.0.0/16 172.27.83.52 /* network-study/nginx-study cluster IP */
0 0 KUBE-SEP-3GSDTNPYCMTOPFUF all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.182.4:80 */ statistic mode random probability 0.33333333349
0 0 KUBE-SEP-ILIVPKL7LONIRUSF all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.189.69:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-SIBCELYMSJMZUCJP all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.235.135:80 */
# 첫 번째 엔드포인트 확률에 걸릴 경우
root@worker1:~# iptables -t nat -L KUBE-SEP-3GSDTNPYCMTOPFUF -n -v
Chain KUBE-SEP-3GSDTNPYCMTOPFUF (1 references)
pkts bytes target prot opt in out source destination
# KUBE-SEP-3GSDTNPYCMTOPFUF로 들어오는 요청에 대해 SNAT, DNAT 처리하는 것을 확인할 수 있습니다.
# 목적지: TCP 192.168.182.4:80
0 0 KUBE-MARK-MASQ all -- * * 192.168.182.4 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.182.4:80
각 레플리카의 Pod IP (엔드포인트)에 대해 라우팅 규칙이 설정되어 있는 것을 확인할 수 있습니다. 그런데, 제일 마지막 엔드포인트에 대해서는 확률이 지정되어 있지 않은 것을 확인할 수 있습니다. 즉, 앞의 두 규칙에 라우팅 되지 않은 트래픽은 모두 192.168.235.135로 라우팅 됩니다.
// probability 계산 코드.
// <https://github.com/kubernetes/kubernetes/blob/cb93d6ee69b8d4ca8701336e4f7cb278751f34e4/pkg/proxy/iptables/proxier.go#L503>
// n번째 엔드포인트에 대해 1/N을 수행합니다.
// 우리의 경우 총 3개 이므로, 1/3, 1/2, 1이 됩니다.
// 그렇기 때문에 제일 마지막 iptables 규칙은 random probability가 지정되지 않는것을 알 수 있습니다.
func computeProbability(n int) string {
return fmt.Sprintf("%0.10f", 1.0/float64(n))
}
// This assumes proxier.mu is held
func (proxier *Proxier) precomputeProbabilities(numberOfPrecomputed int) {
if len(proxier.precomputedProbabilities) == 0 {
proxier.precomputedProbabilities = append(proxier.precomputedProbabilities, "")
}
for i := len(proxier.precomputedProbabilities); i <= numberOfPrecomputed; i++ {
proxier.precomputedProbabilities = append(proxier.precomputedProbabilities, computeProbability(i))
}
}
// This assumes proxier.mu is held
func (proxier *Proxier) probability(n int) string {
if n >= len(proxier.precomputedProbabilities) {
proxier.precomputeProbabilities(n)
}
return proxier.precomputedProbabilities[n]
}
iptables 모드로 kube proxy를 구성하고, 레플리카 오브젝트에 대해 서비스 오브젝트로 연결한다면, 확률에 따라 선택한 엔드포인트의 파드가 죽어있는 상태라면, 해당 요청은 Fail 하게 됩니다.
파드를 사용 불가 상태로 만든 뒤, 직접 서비스로 요청을 보내 Fail을 확인해 보겠습니다. 저의 경우 worker3가 33% 확률로 라우팅 규칙이 선택되기 때문에, 192.168.182.4에 대해 패킷 로스 100%로 설정하겠습니다.
$ kubectl get pods -n network-study -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-study-6cbc845ff5-86bnt 1/1 Running 0 27m 192.168.235.135 worker1 <none> <none>
nginx-study-6cbc845ff5-fbxxv 1/1 Running 0 28m 192.168.189.69 worker2 <none> <none>
nginx-study-6cbc845ff5-lffzb 1/1 Running 0 28m 192.168.182.4 worker3 <none> <none>
- 아이피를 보고 눈치채신 분이 계실 수 있습니다. 각 노드에서 운영하는 NIC의 대역에 따라 파드 아이피를 할당하며 이를 바탕으로 배포되어 있는 노드를 유추할 수 있습니다.
## worker3에서 tc 패키지를 이용해 임의로 패킷 로스 100%로 설wㅓㅇ합니다.
root@worker3:~# sudo tc qdisc add dev tunl0 root netem loss 100%
root@worker3:~#
## 쿠버네티스를 구성하는 다른 노드에서 192.168.182.4로 요청을 보내봅니다.
# 패킷 로스 설정 전
root@master1:~# curl 192.168.182.4
Welcome to nginx!
If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.
For online documentation and support please refer to "nginx.org".
Commercial support is available at "nginx.com".
Thank you for using nginx.
# 설정 후
root@master1:~# curl 192.168.182.4
curl: (28) Failed to connect to 192.168.182.4 port 80 after 130379 ms: Connection timed out
root@master1:~# tcpdump -i any port 80
tcpdump: data link type LINUX_SLL2
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on any, link-type LINUX_SLL2 (Linux cooked v2), snapshot length 262144 bytes
# 192.168.182.4로 가는 요청은 처리되지 않는 것을 확인할 수 있습니다.
14:16:23.213340 tunl0 Out IP master1.26045 > 192.168.182.4.http: Flags [S], seq 944653463, win 64240, options [mss 1460,sackOK,TS val 2004030469 ecr 0,nop,wscale 7], length 0
14:16:24.239302 tunl0 Out IP master1.26045 > 192.168.182.4.http: Flags [S], seq 944653463, win 64240, options [mss 1460,sackOK,TS val 2004031495 ecr 0,nop,wscale 7], length 0
14:16:34.823831 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [S], seq 1458160175, win 64240, options [mss 1460,sackOK,TS val 2004042080 ecr 0,nop,wscale 7], length 0
14:16:34.825215 tunl0 In IP 192.168.189.69.http > master1.25757: Flags [S.], seq 3313784763, ack 1458160176, win 64260, options [mss 1440,sackOK,TS val 521483265 ecr 2004042080,nop,wscale 7], length 0
14:16:34.825729 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 1, win 502, options [nop,nop,TS val 2004042082 ecr 521483265], length 0
14:16:34.827183 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [P.], seq 1:77, ack 1, win 502, options [nop,nop,TS val 2004042083 ecr 521483265], length 76: HTTP: GET / HTTP/1.1
14:16:34.828059 tunl0 In IP 192.168.189.69.http > master1.25757: Flags [.], ack 77, win 502, options [nop,nop,TS val 521483268 ecr 2004042083], length 0
14:16:34.828761 tunl0 In IP 192.168.189.69.http > master1.25757: Flags [P.], seq 1:239, ack 77, win 502, options [nop,nop,TS val 521483268 ecr 2004042083], length 238: HTTP: HTTP/1.1 200 OK
14:16:34.828902 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 239, win 501, options [nop,nop,TS val 2004042085 ecr 521483268], length 0
14:16:34.829632 tunl0 In IP 192.168.189.69.http > master1.25757: Flags [P.], seq 239:854, ack 77, win 502, options [nop,nop,TS val 521483269 ecr 2004042085], length 615: HTTP
14:16:34.829742 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 854, win 501, options [nop,nop,TS val 2004042086 ecr 521483269], length 0
14:16:34.834683 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [F.], seq 77, ack 854, win 501, options [nop,nop,TS val 2004042091 ecr 521483269], length 0
14:16:34.835959 tunl0 In IP 192.168.189.69.http > master1.25757: Flags [F.], seq 854, ack 78, win 502, options [nop,nop,TS val 521483276 ecr 2004042091], length 0
14:16:34.836167 tunl0 Out IP master1.25757 > 192.168.189.69.http: Flags [.], ack 855, win 501, options [nop,nop,TS val 2004042092 ecr 521483276], length 0
분명 쿠버네티스는 Fault Tolerance 한 시스템을 제공하는 것으로 알고 있는데, 이 경우 장애가 발생하면 정상적인 처리가 불가능할 것으로 예상됩니다. 어떻게 해야 이를 예방할 수 있을까요?
마찬가지로 IPVS에 대한 구성을 직접 수행해 보시기 바랍니다.
서비스 디스커버리
Kube Proxy는 쿠버네티스에 배포된 서비스에 대해 트래픽을 라우팅 합니다. 예시로 다른 파드에서 우리가 배포한 nginx-study 서비스에 ping을 보내보겠습니다.
# 혹은 nginx-study.network-study.svc.cluster.local
/app # ping nginx-study.network-study.svc
PING nginx-study.network-study.svc (172.27.83.52): 56 data bytes
...
위 로그를 확인해 보면, 172.27.83.52에 대한 Service IP를 정상적으로 받아오는 것을 알 수 있습니다. 이는 쿠버네티스에 배포되어 있는 CoreDNS (버전, 혹은 설정에 따라 그 종류가 다를 수 있습니다.)가 정상적으로 수행하고 있음을 알 수 있습니다.
Core DNS는 쿠버네티스 API와의 통신을 통해 서비스에 대한 DNS 레코드를 관리하게 됩니다. 이를 통해 서비스 오브젝트에 저장되어 있는 아이피를 획득할 수 있고, 우리가 위에서 살펴본 바와 같이 iptables 혹은 ipvs의 규칙을 바탕으로 FQDN → Service IP → Target Pod로의 서비스 디스커버리가 동작합니다.
12:53:45.462231 tunl0 In IP 192.168.235.139.47399 > 192.168.189.73.domain: 36509+ A? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.462741 cali881bb8ca45a Out IP 192.168.235.139.47399 > 192.168.189.73.domain: 36509+ A? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.462806 tunl0 In IP 192.168.235.139.47399 > 192.168.189.73.domain: 39023+ AAAA? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.463022 cali881bb8ca45a Out IP 192.168.235.139.47399 > 192.168.189.73.domain: 39023+ AAAA? nginx-study.network-study.svc.svc.cluster.local. (65)
12:53:45.464267 cali881bb8ca45a In IP 192.168.189.73.domain > 192.168.235.139.47399: 39023 NXDomain*- 0/1/0 (158)
12:53:45.464421 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.47399: 39023 NXDomain*- 0/1/0 (158)
12:53:45.465107 cali881bb8ca45a In IP 192.168.189.73.domain > 192.168.235.139.47399: 36509 NXDomain*- 0/1/0 (158)
12:53:45.465270 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.47399: 36509 NXDomain*- 0/1/0 (158)
12:53:45.466494 tunl0 In IP 192.168.235.139.58856 > 192.168.189.73.domain: 4431+ A? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.466788 cali881bb8ca45a Out IP 192.168.235.139.58856 > 192.168.189.73.domain: 4431+ A? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.466876 tunl0 In IP 192.168.235.139.58856 > 192.168.189.73.domain: 8064+ AAAA? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.467002 cali881bb8ca45a Out IP 192.168.235.139.58856 > 192.168.189.73.domain: 8064+ AAAA? nginx-study.network-study.svc.cluster.local. (61)
12:53:45.467974 cali881bb8ca45a In IP 192.168.189.73.domain > 192.168.235.139.58856: 8064*- 0/1/0 (154)
12:53:45.468059 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.58856: 8064*- 0/1/0 (154)
12:53:45.468910 cali881bb8ca45a In IP 192.168.189.73.domain > 192.168.235.139.58856: 4431*- 1/0/0 A 172.27.83.52 (120)
12:53:45.468998 tunl0 Out IP 192.168.189.73.domain > 192.168.235.139.58856: 4431*- 1/0/0 A 172.27.83.52 (120)
클러스터 외부 통신
Kbue Proxy는 서비스 오브젝트의 Node Port, LoadBalancer 타입을 지원합니다. 이를 통해 클러스터에 배포된 애플리케이션에 대해 외부 접근을 처리합니다.
NodePort
nginx-study를 지우고 NodePort를 아래와 설정하여 재배포 한 뒤, iptables 규칙의 변경 사항을 확인해 보겠습니다.
apiVersion: v1
kind: Service
metadata:
name: nginx-study
namespace: network-study
spec:
ports:
- port: 80
targetPort: 80
nodePort: 30002
selector:
type: nginx-study
type: NodePort
---
Name: nginx-study
Namespace: network-study
Labels: <none>
Annotations: <none>
Selector: type=nginx-study
Type: NodePort
IP Family Policy: SingleStack
IP Families: IPv4
IP: 172.26.210.120
IPs: 172.26.210.120
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 30002/TCP
Endpoints: 192.168.182.7:80,192.168.189.71:80,192.168.235.138:80
Session Affinity: None
External Traffic Policy: Cluster
Events: <none>
## kube-proxy log입니다
I1218 13:32:14.265314 1 config.go:218] "Calling handler.OnServiceAdd"
I1218 13:32:14.267558 1 servicechangetracker.go:106] "Service updated ports" service="network-study/nginx-study" portCount=1
I1218 13:32:14.272867 1 servicechangetracker.go:211] "Adding new service port" portName="network-study/nginx-study" servicePort="172.26.210.120:80/TCP"
I1218 13:32:14.277897 1 proxier.go:828] "Syncing iptables rules" ipFamily="IPv4"
I1218 13:32:14.284552 1 iptables.go:361] "Running" command="iptables-save" arguments=["-t","nat"]
I1218 13:32:14.394584 1 proxier.go:1547] "Reloading service iptables data" ipFamily="IPv4" numServices=10 numEndpoints=12 numFilterChains=6 numFilterRules=12 numNATChains=4 numNATRules=20
I1218 13:32:14.394850 1 iptables.go:426] "Running" command="iptables-restore" arguments=["-w","5","-W","100000","--noflush","--counters"]
I1218 13:32:14.514618 1 service_health.go:124] "Existing healthcheck" service="nginx-gateway/ngf-nginx-gateway-fabric" port=31653
I1218 13:32:14.514949 1 cleanup.go:67] "Deleting conntrack stale entries for services" IPs=[]
I1218 13:32:14.515048 1 cleanup.go:73] "Deleting conntrack stale entries for services" nodePorts=[]
I1218 13:32:14.515113 1 proxier.go:822] "SyncProxyRules complete" ipFamily="IPv4" elapsed="243.417367ms"
I1218 13:32:14.515236 1 bounded_frequency_runner.go:296] sync-runner: ran, next possible in 1s, periodic in 1h0m0s
I1218 13:32:14.553788 1 config.go:124] "Calling handler.OnEndpointSliceAdd" endpoints="network-study/nginx-study-kslhj"
I1218 13:32:14.555003 1 endpointslicecache.go:303] "Setting endpoints for service port name" portName="network-study/nginx-study" endpoints=["192.168.182.7:80","192.168.189.71:80","192.168.235.138:80"]
I1218 13:32:14.555301 1 proxier.go:828] "Syncing iptables rules" ipFamily="IPv4"
I1218 13:32:14.557159 1 iptables.go:361] "Running" command="iptables-save" arguments=["-t","nat"]
I1218 13:32:14.670538 1 proxier.go:1547] "Reloading service iptables data" ipFamily="IPv4" numServices=10 numEndpoints=15 numFilterChains=6 numFilterRules=10 numNATChains=9 numNATRules=35
I1218 13:32:14.670977 1 iptables.go:426] "Running" command="iptables-restore" arguments=["-w","5","-W","100000","--noflush","--counters"]
I1218 13:32:14.809056 1 proxier.go:1576] "Network programming" ipFamily="IPv4" endpoint="network-study/nginx-study" elapsed=0.808519384
I1218 13:32:14.810226 1 service_health.go:124] "Existing healthcheck" service="nginx-gateway/ngf-nginx-gateway-fabric" port=31653
I1218 13:32:14.810415 1 cleanup.go:67] "Deleting conntrack stale entries for services" IPs=[]
I1218 13:32:14.810493 1 cleanup.go:73] "Deleting conntrack stale entries for services" nodePorts=[]
I1218 13:32:14.810549 1 proxier.go:822] "SyncProxyRules complete" ipFamily="IPv4" elapsed="255.851106ms"
I1218 13:32:14.810644 1 bounded_frequency_runner.go:296] sync-runner: ran, next possible in 1s, periodic in 1h0m0s
그럼 노드에서 iptables 규칙을 확인해 봅시다.
## 어느 노드에서 수행해도 상관없습니다.
root@worker1:~# iptables -t nat -L KUBE-NODEPORTS -vn
Chain KUBE-NODEPORTS (1 references)
pkts bytes target prot opt in out source destination
0 0 KUBE-EXT-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 127.0.0.0/8 /* network-study/nginx-study */ nfacct-name localhost_nps_accepted_pkts
0 0 KUBE-EXT-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */
0 0 KUBE-EXT-UZSA3IJO67ISGJBK tcp -- * * 0.0.0.0/0 127.0.0.0/8 /* nginx-gateway/ngf-nginx-gateway-fabric:http */ nfacct-name localhost_nps_accepted_pkts
0 0 KUBE-EXT-UZSA3IJO67ISGJBK tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* nginx-gateway/ngf-nginx-gateway-fabric:http */
0 0 KUBE-EXT-RAQOCCT45E5XYVU5 tcp -- * * 0.0.0.0/0 127.0.0.0/8 /* nginx-gateway/ngf-nginx-gateway-fabric:https */ nfacct-name localhost_nps_accepted_pkts
0 0 KUBE-EXT-RAQOCCT45E5XYVU5 tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* nginx-gateway/ngf-nginx-gateway-fabric:https */
## kube proxy의 로그를 확인해보아도 마찬가지입니다.
root@master1:~# kubectl logs -n kube-system kube-proxy-nrks7 | grep "30002"
root@master1:~#
이상합니다. 분명 iptables에 노드포트를 바탕으로 :30002 혹은 이에 매칭되는 포트 번호와 관련된 규칙이 있을 것 같은데, 보이지 않습니다. 그렇다면 Node Port의 30002는 어떻게 처리되는 걸까요? 한번 nginx가 배포된 노드에서 삽질을 시작해 봅시다.
root@worker1:~# crictl ps
CONTAINER IMAGE CREATED STATE NAME ATTEMPT POD ID POD
8c9654ead5fb4 docker.io/library/nginx@sha256:3d696e8357051647b844d8c7cf4a0aa71e84379999a4f6af9b8ca1f7919ade42 About an hour ago Running nginx-container 1 54b9c701fc362 nginx-study-6cbc845ff5-86bnt
...
root@worker1:~# crictl inspect 8c9654 | jq -r '.info.runtimeSpec.linux.namespaces[] |select(.type=="network") | .path'
/var/run/netns/1984a9ac-c383-48fd-8b8a-b4028b416c0f
root@worker1:~# ip netns list
...
1984a9ac-c383-48fd-8b8a-b4028b416c0f (id: 2)
...
root@worker1:~# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f netstat -ntlp
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 1973/nginx: master
tcp6 0 0 :::80 :::* LISTEN 1973/nginx: master
## iptables 중 192.168.235.138에 해당하는 정보를 확인했습니다.
root@worker1:/etc/cni/net.d# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0@if6: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1480 qdisc noqueue state UP group default qlen 1000
link/ether 2a:e2:1d:8f:be:3a brd ff:ff:ff:ff:ff:ff link-netns 77503e0a-7e63-47e8-b1a6-a67aade60f3d
inet 192.168.235.138/32 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::28e2:1dff:fe8f:be3a/64 scope link
valid_lft forever preferred_lft forever
3: tunl0@NONE: <NOARP> mtu 1480 qdisc noop state DOWN group default qlen 1000
link/ipip 0.0.0.0 brd 0.0.0.0
root@worker1:~# ip netns exec 1984a9ac-c383-48fd-8b8a-b4028b416c0f iptables -L -nv
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
pkts bytes target prot opt in out source destination
이는 쿠버네티스 버전에 따라 동작이 다르게 나타날 수 있습니다. 과거 버전의 경우 기존의 iptables 혹은 ipvs를 통해 구성이 되어 아래와 같이 **tcp dpt:{{ NODE_PORT }}**가 명시되어 있을 수 있습니다.
KUBE-SVC-XXX tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:30002
그러나 쿠버네티스는 성능 등의 이유로 iptables를 nftables로 점진적 전환하는 상태이고, 1.31 버전에서는 NodePort 규칙의 경우 nftables를 통해 구성되어 있습니다.
root@worker1: ~# nft list table ip nat | grep "30002"
meta l4proto tcp ip daddr 127.0.0.0/8 tcp dport 30002 # nfacct-name localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
meta l4proto tcp tcp dport 30002 counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
마찬가지로 nftables를 통해 iptables에 존재하는 동일한 체인을 확인할 수 있습니다.
root@worker1:~# nft list table ip nat | grep "KUBE-EXT-ZTGPJZYBPOKDS2TX" --before 5 --after 5
...
counter packets 45020 bytes 2739046 jump KUBE-POSTROUTING
counter packets 44874 bytes 2729543 jump cali-POSTROUTING
}
chain KUBE-NODEPORTS {
meta l4proto tcp ip daddr 127.0.0.0/8 tcp dport 30002 # nfacct-name localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
meta l4proto tcp tcp dport 30002 counter packets 0 bytes 0 jump KUBE-EXT-ZTGPJZYBPOKDS2TX
meta l4proto tcp ip daddr 127.0.0.0/8 tcp dport 30927 # nfacct-name localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-UZSA3IJO67ISGJBK
meta l4proto tcp tcp dport 30927 counter packets 0 bytes 0 jump KUBE-EXT-UZSA3IJO67ISGJBK
meta l4proto tcp ip daddr 127.0.0.0/8 tcp dport 32288 # nfacct-name localhost_nps_accepted_pkts counter packets 0 bytes 0 jump KUBE-EXT-RAQOCCT45E5XYVU5
meta l4proto tcp tcp dport 32288 counter packets 0 bytes 0 jump KUBE-EXT-RAQOCCT45E5XYVU5
}
--
chain KUBE-SEP-FSYDGJCKWB55G5AI {
ip saddr 192.168.189.72 counter packets 0 bytes 0 jump KUBE-MARK-MASQ
meta l4proto tcp counter packets 0 bytes 0 dnat to 192.168.189.72:9443
}
chain KUBE-EXT-ZTGPJZYBPOKDS2TX {
counter packets 0 bytes 0 jump KUBE-MARK-MASQ
counter packets 0 bytes 0 jump KUBE-SVC-ZTGPJZYBPOKDS2TX
}
chain KUBE-SVC-ZTGPJZYBPOKDS2TX {
...
LoadBalancer
이제 최종장입니다. 노드포트는 노드의 포트를 노출한다는 점으로 인해 보안성이 떨어지고 포트의 범위가 한정적이며 로드 밸런싱의 성능 등을 쿠버네티스에 의존한다는 단점이 존재합니다. 이를 해결하기 위해 실질적으로 우리는 서비스 오브젝트의 클라우드에서 지원하는 로드밸런서 혹은 MetalLB 등을 이용하여 온프레미스 서버의 로드밸런싱을 처리하곤 합니다.
그러면 기존의 nginx-study 서비스 오브젝트를 제거하고, LoadBalancer 타입의 서비스 오브젝트를 배포해 보겠습니다.
apiVersion: v1
kind: Service
metadata:
name: nginx-study
namespace: network-study
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
type: nginx-study
type: LoadBalancer
---
Name: nginx-study
Namespace: network-study
Labels: <none>
Annotations: <none>
Selector: type=nginx-study
Type: LoadBalancer
IP Family Policy: SingleStack
IP Families: IPv4
IP: 172.27.177.151
IPs: 172.27.177.151
LoadBalancer Ingress: 10.108.200.1
Port: <unset> 80/TCP
TargetPort: 80/TCP
NodePort: <unset> 31943/TCP
Endpoints: 192.168.182.7:80,192.168.189.71:80,192.168.189.74:80
Session Affinity: None
External Traffic Policy: Cluster
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal IPAllocated 29s metallb-controller Assigned IP ["10.108.200.1"]
Normal nodeAssigned 29s metallb-speaker announcing from node "worker2" with protocol "layer2"
0 0 KUBE-MARK-MASQ all -- * * 0.0.0.0/0 0.0.0.0/0 /* masquerade traffic for network-study/nginx-study external destinations */
0 0 KUBE-EXT-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 127.0.0.0/8 /* network-study/nginx-study */ nfacct-name localhost_nps_accepted_pkts
0 0 KUBE-EXT-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */
0 0 KUBE-MARK-MASQ all -- * * 192.168.189.71 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.189.71:80
0 0 KUBE-MARK-MASQ all -- * * 192.168.189.74 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.189.74:80
0 0 KUBE-MARK-MASQ all -- * * 192.168.182.7 0.0.0.0/0 /* network-study/nginx-study */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study */ tcp to:192.168.182.7:80
0 0 KUBE-SVC-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 172.27.177.151 /* network-study/nginx-study cluster IP */
0 0 KUBE-EXT-ZTGPJZYBPOKDS2TX tcp -- * * 0.0.0.0/0 10.108.200.1 /* network-study/nginx-study loadbalancer IP */
0 0 KUBE-MARK-MASQ tcp -- * * !192.168.0.0/16 172.27.177.151 /* network-study/nginx-study cluster IP */
0 0 KUBE-SEP-SWBCFBCVJ4JWY6IC all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.182.7:80 */ statistic mode random probability 0.33333333349
0 0 KUBE-SEP-OGDKAREGM7CZ5I7K all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.189.71:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-QFKIBWA6SISBZBHX all -- * * 0.0.0.0/0 0.0.0.0/0 /* network-study/nginx-study -> 192.168.189.74:80 */
iptables 규칙을 확인해 보니, Cluster IP에 추가로 loadbalancer IP에 대해서도 KUBE-EXT-ZTPGJ…를 통해 라우팅 규칙을 추가한 것을 확인할 수 있습니다.
한번 tcpdump 등을 활용해서 External IP에서 발생하는 요청을 확인해 보세요.
그렇다면 이것도…
- Kubernetes를 설치한 뒤 CNI를 배포해야 하는 이유는 무엇인가요?
- CNI 없이 kube proxy만 이용할 때, 쿠버네티스 클러스터 노드 혹은 시스템에서 서비스 오브젝트의 Cluster IP로 접근할 수 있나요? 이유는 무엇인가요?
- Kube Proxy의 iptables 모드를 이용한다면 레플리카 중 하나만 뻗더라도 시스템 장애가 발생할 것으로 예상됩니다. 이를 예방하기 위해 어떤 대책이 있을까요?
- iptables와 nftables의 차이는 무엇인가요?
- 쿠버네티스를 구성중인 노드에서도 FQDN 질의를 통해 서비스에 접근하고 싶습니다. 어떻게 해야 할까요?
- https://github.com/kubernetes/kubernetes/blob/cb93d6ee69b8d4ca8701336e4f7cb278751f34e4/pkg/proxy/iptables/proxier.go#L1626
- https://www.reddit.com/r/kubernetes/comments/1bf8rma/nodeport_kubeproxy_v126_no_longer_has_destination/
- https://github.com/kubernetes/enhancements/blob/master/keps/sig-network/3866-nftables-proxy/README.md#nat-rules
'DevOps > Kubernetes' 카테고리의 다른 글
네트워크로 시작하는 쿠버네티스 - iptables, ipvs, ipip, vxlan (1) | 2024.12.03 |
---|---|
네트워크로 시작하는 쿠버네티스 - 컨테이너 통신, 도커 네트워크 (0) | 2024.11.21 |
네트워크로 시작하는 쿠버네티스 - 컨테이너간 통신, Network Namespace (2) | 2024.11.14 |
네트워크로 시작하는 쿠버네티스 - 내가 데이터를 보낸다면 (2) | 2024.11.14 |
쿠버네티스가 쉬워지는 컨테이너 이야기가 어렵다면 - cgroup, cpu 편 (0) | 2024.11.11 |