09. 쿠버네티스 핵심 개념 (4)
이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크
쿠버네티스 네트워크 (1) Pod - Container 통신
일반적으로 컨테이너 간 통신을 위한 도커 네트워크 구조는 다음과 같다.
각 컨테이너는 veth
라는 가상의 네트워크 인터페이스를 통해서 통신을 한다. 반면에, 쿠버네티스
에서 컨테이너 간 통신 구조는 살짝 다르다.
하나의 veth
가상 네트워크 인터페이스에 여러 컨테이너가 연결되어 있다. 그리고 pause
라는 녀석이 옆에 붙어서 이들 통신을 지원해준다. VM
에 다음을 입력해보자.
$ sudo docker ps | grep pause
7cec7bbcfd41 k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_weave-net-qr5v6_kube-system_52d850d7-c8e4-4b75-93c5-2dd97237b818_8
55878c1c9fbe k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_kube-proxy-hvpz5_kube-system_676e7f6d-825c-43b6-92ea-a1b891092eb4_8
e149257efdfe k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_kube-controller-manager-master_kube-system_683f32b0119799727621446455e8d131_8
ee74476df7aa k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_kube-apiserver-master_kube-system_4497338b493c596567d3c3eb86085559_8
5b7c5e79e66a k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_etcd-master_kube-system_d0eb798391c9389c9721c4631c28dc9a_8
3ec62d452070 k8s.gcr.io/pause:3.4.1 "/pause" 3 minutes ago Up 3 minutes k8s_POD_kube-scheduler-master_kube-system_35ae2ec46407146c0fe6281c2c3292ce_8
수 많은 pause
가 이미 떠 있는 것을 알 수 있다. 이 puase
들은 kube-apiserver
, kube-scheduler-master
등의 컴포넌트에 붙어서 네트워크 통신을 지원한다. 마스터 노드에 있는 pod
들을 한 번 확인해보자.
$ kubectl get pod --all-namespaces -o wide | grep master
kube-system etcd-master 1/1 Running 8 25d 10.0.2.15 master <none> <none>
kube-system kube-apiserver-master 1/1 Running 8 25d 10.0.2.15 master <none> <none>
kube-system kube-controller-manager-master 1/1 Running 8 25d 10.0.2.15 master <none> <none>
kube-system kube-proxy-hvpz5 1/1 Running 8 25d 10.0.2.15 master <none> <none>
kube-system kube-scheduler-master 1/1 Running 8 25d 10.0.2.15 master <none> <none>
kube-system weave-net-qr5v6 2/2 Running 17 25d 10.0.2.15 master <none> <none>
이 중 apiserver
관련만 추출해서 보자.
$ sudo docker ps | grep "apiserver"
58bc8ecf485a 106ff58d4308 "kube-apiserver --ad…" 9 minutes ago Up 9 minutes k8s_kube-apiserver_kube-apiserver-master_kube-system_4497338b493c596567d3c3eb86085559_8
ee74476df7aa k8s.gcr.io/pause:3.4.1 "/pause" 9 minutes ago Up 9 minutes k8s_POD_kube-apiserver-master_kube-system_4497338b493c596567d3c3eb86085559_8
이렇게 각 컴포넌트를 수행하는 pod
과 옆에 붙어서 컨테이너 간 통신을 지원하는 pause pod
이 같이 떠 있는 것을 확인할 수 있다.
쿠버네티스 네트워크 (2) Pod - Pod 통신
쿠버네티스
에서 pod
끼리의 통신은 CNI(Container Network Interface) 플러그인
을 통해서 이루어진다. 다음은 우리가 함께 설치한 Weavenet
의 구조이다.
이를 확인하기 위해서는 VM
에서 다음을 입력한다.
$ sudo netstat -antp | grep weave
tcp 0 0 127.0.0.1:6784 0.0.0.0:* LISTEN 4229/weaver
tcp 0 0 10.0.2.15:43560 10.96.0.1:443 ESTABLISHED 4159/weave-npc
tcp6 0 0 :::6781 :::* LISTEN 4159/weave-npc
tcp6 0 0 :::6782 :::* LISTEN 4229/weaver
tcp6 0 0 :::6783 :::* LISTEN 4229/weaver
tcp6 0 0 10.0.2.15:6783 10.0.2.5:54483 ESTABLISHED 4229/weaver
tcp6 0 0 10.0.2.15:6783 10.0.2.4:37053 ESTABLISHED 4229/weaver
위 명령어는 weavenet
에 통하는 네트워크 입출력을 보여준다. 자세히 보면 master node(10.0.2.15)에서 slave node들(10.0.2.4, 10.0.2.5)로 통신하는 것을 확인할 수 있다. 여기서 weaver
의 노드 ip/port 확인해보자.
$ ps -eaf | grep 4229
root 4229 4090 0 18:48 ? 00:00:01 /home/weave/weaver --port=6783 --datapath=datapath --name=5a:cd:9a:0c:b3:54 --http-addr=127.0.0.1:6784 --metrics-addr=0.0.0.0:6782 --docker-api= --no-dns --db-prefix=/weavedb/weave-net --ipalloc-range=10.32.0.0/12 --nickname=master --ipalloc-init consensus=2 --conn-limit=200 --expect-npc --no-masq-local 10.0.2.4 10.0.2.5
gurumee 13287 4862 0 19:06 pts/0 00:00:00 grep --color=auto 4229
역시 master -> slave 통신을 확인할 수 있다. 한 가지 더 알아둘 것은 이 CNI 플러그인
역시 pod
으로 구성되어 동작한다는 것이다. 터미널에 다음을 입력한다.
$ sudo docker ps | grep weave
21216183cb68 7f92d556d4ff "/usr/bin/launch.sh" 19 minutes ago Up 19 minutes k8s_weave-npc_weave-net-qr5v6_kube-system_52d850d7-c8e4-4b75-93c5-2dd97237b818_8
978cef952fea df29c0a4002c "/home/weave/launch.…" 19 minutes ago Up 19 minutes k8s_weave_weave-net-qr5v6_kube-system_52d850d7-c8e4-4b75-93c5-2dd97237b818_9
7cec7bbcfd41 k8s.gcr.io/pause:3.4.1 "/pause" 19 minutes ago Up 19 minutes k8s_POD_weave-net-qr5v6_kube-system_52d850d7-c8e4-4b75-93c5-2dd97237b818_8
여기서 weave-net
은 daemon-set
이라는 리소스로 구성되어 있다. 자세한 설명을 보려면 다음을 입력해보자.
$ kubectl get ds weave-net -n kube-system -o yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
annotations:
cloud.weave.works/launcher-info: |-
{
"original-request": {
"url": "/k8s/v1.16/net.yaml?k8s-version=Q2xpZW50IFZlcnNpb246IHZlcnNpb24uSW5mb3tNYWpvcjoiMSIsIE1pbm9yOiIyMSIsIEdpdFZlcnNpb246InYxLjIxLjIiLCBHaXRDb21taXQ6IjA5MmZiZmJmNTM0MjdkZTY3Y2FjMWU5ZmE1NGFhYTA5YTI4MzcxZDciLCBHaXRUcmVlU3RhdGU6ImNsZWFuIiwgQnVpbGREYXRlOiIyMDIxLTA2LTE2VDEyOjU5OjExWiIsIEdvVmVyc2lvbjoiZ28xLjE2LjUiLCBDb21waWxlcjoiZ2MiLCBQbGF0Zm9ybToibGludXgvYW1kNjQifQpTZXJ2ZXIgVmVyc2lvbjogdmVyc2lvbi5JbmZve01ham9yOiIxIiwgTWlub3I6IjIxIiwgR2l0VmVyc2lvbjoidjEuMjEuMiIsIEdpdENvbW1pdDoiMDkyZmJmYmY1MzQyN2RlNjdjYWMxZTlmYTU0YWFhMDlhMjgzNzFkNyIsIEdpdFRyZWVTdGF0ZToiY2xlYW4iLCBCdWlsZERhdGU6IjIwMjEtMDYtMTZUMTI6NTM6MTRaIiwgR29WZXJzaW9uOiJnbzEuMTYuNSIsIENvbXBpbGVyOiJnYyIsIFBsYXRmb3JtOiJsaW51eC9hbWQ2NCJ9Cg==",
"date": "Sat Jul 03 2021 09:55:33 GMT+0000 (UTC)"
},
"email-address": "support@weave.works"
}
deprecated.daemonset.template.generation: "1"
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"apps/v1","kind":"DaemonSet","metadata":{"annotations":{"cloud.weave.works/launcher-info":"{\n \"original-request\": {\n \"url\": \"/k8s/v1.16/net.yaml?k8s-version=Q2xpZW50IFZlcnNpb246IHZlcnNpb24uSW5mb3tNYWpvcjoiMSIsIE1pbm9yOiIyMSIsIEdpdFZlcnNpb246InYxLjIxLjIiLCBHaXRDb21taXQ6IjA5MmZiZmJmNTM0MjdkZTY3Y2FjMWU5ZmE1NGFhYTA5YTI4MzcxZDciLCBHaXRUcmVlU3RhdGU6ImNsZWFuIiwgQnVpbGREYXRlOiIyMDIxLTA2LTE2VDEyOjU5OjExWiIsIEdvVmVyc2lvbjoiZ28xLjE2LjUiLCBDb21waWxlcjoiZ2MiLCBQbGF0Zm9ybToibGludXgvYW1kNjQifQpTZXJ2ZXIgVmVyc2lvbjogdmVyc2lvbi5JbmZve01ham9yOiIxIiwgTWlub3I6IjIxIiwgR2l0VmVyc2lvbjoidjEuMjEuMiIsIEdpdENvbW1pdDoiMDkyZmJmYmY1MzQyN2RlNjdjYWMxZTlmYTU0YWFhMDlhMjgzNzFkNyIsIEdpdFRyZWVTdGF0ZToiY2xlYW4iLCBCdWlsZERhdGU6IjIwMjEtMDYtMTZUMTI6NTM6MTRaIiwgR29WZXJzaW9uOiJnbzEuMTYuNSIsIENvbXBpbGVyOiJnYyIsIFBsYXRmb3JtOiJsaW51eC9hbWQ2NCJ9Cg==\",\n \"date\": \"Sat Jul 03 2021 09:55:33 GMT+0000 (UTC)\"\n },\n \"email-address\": \"support@weave.works\"\n}"},"labels":{"name":"weave-net"},"name":"weave-net","namespace":"kube-system"},"spec":{"minReadySeconds":5,"selector":{"matchLabels":{"name":"weave-net"}},"template":{"metadata":{"labels":{"name":"weave-net"}},"spec":{"containers":[{"command":["/home/weave/launch.sh"],"env":[{"name":"HOSTNAME","valueFrom":{"fieldRef":{"apiVersion":"v1","fieldPath":"spec.nodeName"}}},{"name":"INIT_CONTAINER","value":"true"}],"image":"docker.io/weaveworks/weave-kube:2.8.1","name":"weave","readinessProbe":{"httpGet":{"host":"127.0.0.1","path":"/status","port":6784}},"resources":{"requests":{"cpu":"50m","memory":"100Mi"}},"securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/weavedb","name":"weavedb"},{"mountPath":"/host/var/lib/dbus","name":"dbus"},{"mountPath":"/host/etc/machine-id","name":"machine-id","readOnly":true},{"mountPath":"/run/xtables.lock","name":"xtables-lock"}]},{"env":[{"name":"HOSTNAME","valueFrom":{"fieldRef":{"apiVersion":"v1","fieldPath":"spec.nodeName"}}}],"image":"docker.io/weaveworks/weave-npc:2.8.1","name":"weave-npc","resources":{"requests":{"cpu":"50m","memory":"100Mi"}},"securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/run/xtables.lock","name":"xtables-lock"}]}],"dnsPolicy":"ClusterFirstWithHostNet","hostNetwork":true,"initContainers":[{"command":["/home/weave/init.sh"],"image":"docker.io/weaveworks/weave-kube:2.8.1","name":"weave-init","securityContext":{"privileged":true},"volumeMounts":[{"mountPath":"/host/opt","name":"cni-bin"},{"mountPath":"/host/home","name":"cni-bin2"},{"mountPath":"/host/etc","name":"cni-conf"},{"mountPath":"/lib/modules","name":"lib-modules"},{"mountPath":"/run/xtables.lock","name":"xtables-lock"}]}],"priorityClassName":"system-node-critical","restartPolicy":"Always","securityContext":{"seLinuxOptions":{}},"serviceAccountName":"weave-net","tolerations":[{"effect":"NoSchedule","operator":"Exists"},{"effect":"NoExecute","operator":"Exists"}],"volumes":[{"hostPath":{"path":"/var/lib/weave"},"name":"weavedb"},{"hostPath":{"path":"/opt"},"name":"cni-bin"},{"hostPath":{"path":"/home"},"name":"cni-bin2"},{"hostPath":{"path":"/etc"},"name":"cni-conf"},{"hostPath":{"path":"/var/lib/dbus"},"name":"dbus"},{"hostPath":{"path":"/lib/modules"},"name":"lib-modules"},{"hostPath":{"path":"/etc/machine-id","type":"FileOrCreate"},"name":"machine-id"},{"hostPath":{"path":"/run/xtables.lock","type":"FileOrCreate"},"name":"xtables-lock"}]}},"updateStrategy":{"type":"RollingUpdate"}}}
creationTimestamp: "2021-07-03T09:55:33Z"
generation: 1
labels:
name: weave-net
name: weave-net
namespace: kube-system
# ...
쿠버네티스 네트워크 (3) Pod - Svc 통신
아래 그림은 pod
과 service
가 통신을 나타내는 구조이다.
service
생성 시 ClusterIP를 할당 받으면 iptables에 적용한다. 그래서 각 pod
으로 통신을 연결해줄 수 있다. 쿠버네티스
는 netfilter
를 통해 2-7계층까지 네트워크 통신을 지원한다. 한 번 이를 확인해보자. 터미널에 다음을 입력한다.
$ kubectl get svc --all-namespaces
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 3d
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 25d
"CLUSTER-IP" 대역대인 10.96.x.x
은 service
리소스가 사용하는 대역이다. 이 대역대의 iptables를 확인해보자.
$ sudo iptables -S -t nat | grep 10.96
# 53은 DNS 포트
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES -d 10.96.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES -d 10.96.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-A KUBE-SERVICES -d 10.96.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
여러 개가 체인처럼 엮여 있는 것을 볼 수 있는데, 그냥 이는 내부 구현이고 중요한 것은 pod
에서 service
를 호출하게 되면, 쿠버네티스
에 존재하는 내부 DNS 시스템에 의해서 연결된 다른 pod
이 호출된다는 것이다.
쿠버네티스 네트워크 (4) 외부 물리 클라이언트 통신
다음은 외부 클라이언트에서 서비스를 호출했을 때 나타나는 플로우이다.
클라이언트에서 LoadBalancer
를 통해 호출하게 되면 그 안에 route table
에 의해서 node
로, 그 후 안에 네트워크 인터페이스(eth0
)와 netfilter
를 통해서 service
가 호출되고 그 후 pod
이 호출된다. 외부에서는 node와 연결되고 node 내에서는 pod-svc의 플로우가 동작하는 것이다.
쿠버네티스 네트워크 (5) CoreDNS
pod
에서 service
를 호출할 수 있는 이유는 바로 CoreDNS
라는 쿠버네티스
의 DNS 서버 역할을 하는 컴포넌트가 있기 때문이다.
즉 CoreDNS
는 쿠버네티스 클러스터의 DNS 서버 역할을 수행한다. 역시 pod
으로 구성되어서 동작하고 있으며. 각 미들웨어를 통해 로깅, 캐시, 쿠버네티스 질의하는 기능을 수행한다.
일반적으로 service
를 다음과 같이 호출할 수 있다.
<svc_name>.<ns_name>.svc.cluster.local 형식으로 도메인 획득 가능
이에 대한 실습을 진행한다. 먼저 "gurumee"라는 네임스페이스를 생성한다.
src/ch09/k8s/coredns-ns-gurumee.yaml)
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: null
name: gurumee
spec: {}
status: {}
그 후, Deployment
를 생성한다.
src/ch09/k8s/coredns-simple-app-dep.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: simple-app-dep
namespace: gurumee
labels:
app: simple-app
spec:
replicas: 3
selector:
matchLabels:
app: simple-app
template:
metadata:
labels:
app: simple-app
spec:
containers:
- name: simple-app
image: gurumee92/simple-app:v1
ports:
- containerPort: 8080
그 다음 ClusterIP
형태로 Service
를 통해 Deployment
를 연결해준다.
src/ch09/k8s/coredns-simple-app-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: simple-app-svc
namespace: gurumee
spec:
ports:
- port: 8080
targetPort: 8080
selector:
app: simple-app
그 후 nginx
를 기반으로 pod
1개를 실행한다.
$ kubectl run nginx --image=nginx
그 후 그 pod
에서 curl
로 svc_name.ns_name:8080
으로 호출해보자.
$ kubectl exec -it nginx -- curl simple-app-svc.gurumee:8080
Hello World v1
이렇게 떴다면 성공이다.
쿠버네티스 스토리지 (1) 볼륨
"볼륨"은 컨테이너가 외부 스토리지에 액세스하고 공유하는 방법이다. 다음과 같은 방법이 있다.
- 임시 볼륨 : emptyDir (컨테이너 볼륨 공유)
- 로컬 볼륨 : hostPath, local (노드 관리 목적)
- 네트워크 볼륨 : NFS, glusterFS (외부 자원 공유 목적)
- 네트워크 볼륨 (클라우드) : awsEBS, gcePersistentDisk
이외에도 pvc, configMap 등이 있다.
쿠버네티스 스토리지 (2) EmptyDir
볼륨 EmptyDir
을 통해 pod
간 볼륨을 공유하게끔 만들 수 있다. 이제 다음과 같이 스크립트를 하나 작성한다.
#!/bin/sh
trap "exit" SIGINT
mkdir /var/htdocs
SET=$(seq 0 99999)
for i in $SET
do
echo "RUNNING loop seq $i" > /var/htdocs/index.html
sleep 10
done
그리고 build 하기 전에 chmod 777
명령어를 주어서 실행 파일 권한을 주자 Dockerfile
을 다음과 같이 만든다.
FROM busybox:latest
ADD count.sh /bin/count.sh
ENTRYPOINT /bin/count.sh
그 후 자신의 ID로 이미지를 만들어서 push한다. 그 후 다음과 같이 리소스를 생성한다.
src/ch09/k8s/volume-empty-dir.yaml
...
apiVersion: v1
kind: Pod
metadata:
name: volume-empty-dir
spec:
containers:
- image: gurumee92/count
name: html-generator
volumeMounts:
- mountPath: /var/htdocs/
name: html
- image: httpd
name: web-server
volumeMounts:
- mountPath: /usr/local/apache2/htdocs
name: html
readOnly: true
ports:
- containerPort: 80
volumes:
- name: html
emptyDir: {}
그 후 nginx
를 기반으로 pod
1개를 실행한다.
$ kubectl run nginx --image=nginx
그러면 다음의 pod
들이 생성되어진다.
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx 1/1 Running 0 2m55s 10.32.0.3 slave2 <none> <none>
volume-empty-dir 2/2 Running 0 6m34s 10.32.0.2 slave2 <none> <none>
이제 nginx
포드에서 volume-empty-dir
포드로 curl
을 요청해보자.
# 10.32.0.2 = volume-empty-dir IP
$ kubectl exec -it nginx -- curl 10.32.0.2
RUNNING loop seq 48
이제 만들어진지 480초가 지났다는 의미이다. 10초 간격으로 계속 curl
을 요청하면 "seq" 값이 변하는 것을 확인할 수 있다.
쿠버네티스 스토리지 (3) Hostpath
hostPath
볼륨은 노드와 포드간 데이터 공유를 위해서 만들어지는 볼륨이다. 해당 노드의 파일 시스템에 있는 파일 혹은 디렉토리를 지정하기 때문에 노드에 떠 있는 포드들하고만 연결이 가능하다.
주로 노드의 모니터링을 위해서 많이 사용된다. 이제 실습을 진행해보자. 먼저 워커 노드에서 다음을 명령한다.
## sudo 권한 획득
$ sudo -i
## 디렉토리 생성
# mkdir -p /var/htdocs
# echo "$HOSTNAME" > /var/htdocs/index.html
그 후 다음 리소스를 생성한다.
apiVersion: v1
kind: Pod
metadata:
name: volume-hostpath
spec:
containers:
- image: httpd
name: web-server
volumeMounts:
- mountPath: /usr/local/apache2/htdocs
name: html
readOnly: true
ports:
- containerPort: 80
volumes:
- name: html
hostPath:
path: /var/htdocs
type: Directory
그 다음 아래 명령어를 입력하여 포드가 어디 노드에 생성되었는지 확인한다.
$ kubectl get pod -o wide -w
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-hostpath 1/1 Running 0 15s 10.40.0.3 slave1 <none> <none>
워커 노드 1("slave1")에 포드가 위치하고 있다. 이제 이 포드를 접속할 수 있도록 port-forward
로 포트를 개방한다.
$ kubectl port-forward hostpath-http 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80
Handling connection for 8888
이제 새 탭을 열어 다음을 입력한다.
$ curl localhost:8888
slave1
"slave1"이 뜨는 것을 확인할 수 있다.