09. 쿠버네티스 핵심 개념 (4)

09. 쿠버네티스 핵심 개념 (4)

반응형

이 문서는 인프런 강의 "데브옵스를 위한 쿠버네티스 마스터"을 듣고 작성되었습니다. 최대한 요약해서 강의 내용을 최소로 하는데 목표를 두고 있어서, 더 친절하고 정확한 내용을 원하신다면 강의를 구매하시는 것을 추천드립니다. => 강의 링크

쿠버네티스 네트워크 (1) Pod - Container 통신

일반적으로 컨테이너 간 통신을 위한 도커 네트워크 구조는 다음과 같다.

출처: 인프런 강의 Devops를 위한 쿠버네티스 마스터

각 컨테이너는 veth 라는 가상의 네트워크 인터페이스를 통해서 통신을 한다. 반면에, 쿠버네티스 에서 컨테이너 간 통신 구조는 살짝 다르다.

출처: 인프런 강의 Devops를 위한 쿠버네티스 마스터

하나의 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 kube-system kube-apiserver-master 1/1 Running 8 25d 10.0.2.15 master kube-system kube-controller-manager-master 1/1 Running 8 25d 10.0.2.15 master kube-system kube-proxy-hvpz5 1/1 Running 8 25d 10.0.2.15 master kube-system kube-scheduler-master 1/1 Running 8 25d 10.0.2.15 master kube-system weave-net-qr5v6 2/2 Running 17 25d 10.0.2.15 master

이 중 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 의 구조이다.

출처: 인프런 강의 Devops를 위한 쿠버네티스 마스터

이를 확인하기 위해서는 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": "[email protected]" } deprecated.daemonset.template.generation: "1" kubectl.kubernetes.io/last-applied-configuration: | {"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\": \"[email protected]\"

}"},"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 가 통신을 나타내는 구조이다.

출처: 인프런 강의 Devops를 위한 쿠버네티스 마스터

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 443/TCP 3d kube-system kube-dns ClusterIP 10.96.0.10 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) 외부 물리 클라이언트 통신

다음은 외부 클라이언트에서 서비스를 호출했을 때 나타나는 플로우이다.

출처: 인프런 강의 Devops를 위한 쿠버네티스 마스터

클라이언트에서 LoadBalancer 를 통해 호출하게 되면 그 안에 route table 에 의해서 node 로, 그 후 안에 네트워크 인터페이스( eth0 )와 netfilter 를 통해서 service 가 호출되고 그 후 pod 이 호출된다. 외부에서는 node와 연결되고 node 내에서는 pod-svc의 플로우가 동작하는 것이다.

쿠버네티스 네트워크 (5) CoreDNS

pod 에서 service 를 호출할 수 있는 이유는 바로 CoreDNS 라는 쿠버네티스 의 DNS 서버 역할을 하는 컴포넌트가 있기 때문이다.

즉 CoreDNS 는 쿠버네티스 클러스터의 DNS 서버 역할을 수행한다. 역시 pod 으로 구성되어서 동작하고 있으며. 각 미들웨어를 통해 로깅, 캐시, 쿠버네티스 질의하는 기능을 수행한다.

일반적으로 service 를 다음과 같이 호출할 수 있다.

..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 간 볼륨을 공유하게끔 만들 수 있다. 이제 다음과 같이 스크립트를 하나 작성한다.

src/ch09/app/count.sh

#!/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 을 다음과 같이 만든다.

src/ch09/app/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 volume-empty-dir 2/2 Running 0 6m34s 10.32.0.2 slave2

이제 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

그 후 다음 리소스를 생성한다.

src/ch09/volume-hostpath.yaml

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

워커 노드 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"이 뜨는 것을 확인할 수 있다.

from http://gurumee92.tistory.com/280 by ccl(A) rewrite - 2021-08-05 08:00:44